code-structure
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseService Layer Architecture
Service Layer Architecture
Overview
概述
Two-layer separation: Actions orchestrate domain rules (the "why/when"), while a service layer centralizes reusable operational mechanics (the "how").
This prevents duplicated code, inconsistent behavior, and bugs fixed in one path but not others.
两层分离:Actions编排领域规则(即"原因/时机"),而服务层集中管理可复用的操作机制(即"实现方式")。
这可以避免代码重复、行为不一致以及仅修复某一条路径但遗漏其他路径的问题。
When to Use
适用场景
- Multiple callers need the same low-level operation (sandbox creation, email sending, payment processing)
- You're copy-pasting operational logic between action files
- A bug fix in one workflow doesn't propagate to others doing the same thing
- Adding a new feature that shares mechanics with existing flows
Don't use when: Logic is truly domain-specific and used by only one caller.
- 多个调用方需要执行相同的底层操作(如沙箱创建、邮件发送、支付处理)
- 你需要在多个动作文件之间复制粘贴操作逻辑
- 修复某一工作流中的Bug后,无法同步到执行相同操作的其他工作流
- 添加与现有功能共享操作机制的新功能
不适用场景:逻辑完全是特定领域专属,且仅被一个调用方使用。
Core Pattern
核心模式
Orchestration Layer (Actions) Service Layer (Shared Mechanics)
├── owns business rules ├── owns reusable operations
├── owns state transitions ├── owns provider/SDK interactions
├── owns auth/ownership checks ├── owns command execution details
├── owns failure classification ├── owns health checks / readiness
├── owns retries / user-facing errors └── returns structured results
└── calls service functionsRule of thumb:
- "What this product flow means" → keep in actions
- "How to do this operation reliably" → move to service layer
编排层(Actions) 服务层(共享机制)
├── 负责业务规则 ├── 负责可复用操作
├── 负责状态转换 ├── 负责与提供商/SDK交互
├── 负责权限/归属校验 ├── 负责命令执行细节
├── 负责故障分类 ├── 负责健康检查/就绪状态
├── 负责重试/面向用户的错误处理 └── 返回结构化结果
└── 调用服务函数经验法则:
- "该产品流程的意义是什么" → 保留在Actions中
- "如何可靠地执行该操作" → 迁移到服务层
Quick Reference
快速参考
| Design Principle | Do | Don't |
|---|---|---|
| API shape | Composable capability blocks | One giant "do everything" method |
| Inputs/outputs | Explicit params, structured returns | Hidden global state, reaching into DB |
| Migration | Extract one block, replace one caller, verify, then migrate rest | Refactor everything at once |
| Domain logic | Keep auth, policy, error classification in actions | Let service mutate domain state directly |
| Extraction trigger | Logic repeated across 2+ callers | Logic used once (over-abstraction) |
| 设计原则 | 建议 | 禁忌 |
|---|---|---|
| API形态 | 采用可组合的能力模块 | 不要设计一个“包办一切”的巨型方法 |
| 输入/输出 | 使用明确参数、结构化返回 | 不要依赖隐藏全局状态、直接访问数据库 |
| 迁移方式 | 先提取一个模块,替换一个调用方,验证后再迁移剩余部分 | 不要一次性重构所有内容 |
| 领域逻辑 | 将权限、策略、错误分类保留在Actions中 | 不要让服务直接修改领域状态 |
| 提取触发条件 | 逻辑被2个及以上调用方重复使用 | 不要提取仅被使用一次的逻辑(过度抽象) |
Designing Service Functions
服务函数设计
Design as capability blocks, not monoliths:
ts
// Good: composable, each caller chooses what to use
createManagedSandbox(...)
prepareRepo(...)
detectPackageManager(...)
installDependencies(...)
runBuildCommand(...)
startSandboxRuntime(...)Each function should:
- Accept all required data as explicit parameters
- Return structured outputs (e.g., )
{ ready, previewUrl, proxyPort } - Never reach into database/state directly
- Make failure explicit (structured results, not swallowed errors)
This lets callers choose strict vs relaxed behavior per flow.
设计为能力模块,而非单体:
ts
// Good: composable, each caller chooses what to use
createManagedSandbox(...)
prepareRepo(...)
detectPackageManager(...)
installDependencies(...)
runBuildCommand(...)
startSandboxRuntime(...)每个函数应满足:
- 所有必要数据通过明确参数传入
- 返回结构化输出(例如:)
{ ready, previewUrl, proxyPort } - 绝不直接访问数据库/状态
- 明确暴露故障(返回结构化结果,而非隐藏错误)
这样调用方可以根据不同流程选择严格或宽松的行为模式。
Migration Checklist
迁移 Checklist
When extracting shared logic:
- Write the flow in action code first (clear behavior)
- Mark repeated operational chunks across callers
- Extract only repeated, non-domain chunks to service
- Replace one caller → verify → replace remaining callers
- Keep domain policy in actions (auth, status transitions, error classification)
- Run verification: typecheck, lint, confirm all flows still work
提取共享逻辑时:
- 先在动作代码中编写完整流程(明确行为)
- 标记出跨调用方重复的操作块
- 仅将重复的、非领域相关的块提取到服务层
- 替换一个调用方 → 验证 → 再替换剩余调用方
- 将领域策略保留在Actions中(权限、状态转换、错误分类)
- 执行验证:类型检查、代码扫描,确认所有流程仍正常运行
Anti-Patterns
反模式
| Anti-Pattern | Problem |
|---|---|
| God service | One huge function hides all control flow |
| Leaky service | Service mutates database tables directly |
| Inconsistent API | Each function uses different argument styles and error semantics |
| Over-abstraction | Extracting logic used by only one caller |
| 反模式 | 问题 |
|---|---|
| 上帝服务 | 单个巨型函数隐藏了所有控制流 |
| 泄漏服务 | 服务直接修改数据库表 |
| 不一致API | 每个函数使用不同的参数风格和错误语义 |
| 过度抽象 | 提取仅被一个调用方使用的逻辑 |
Example: Email Service (Simple)
示例:邮件服务(简易版)
ts
// emailService.ts — shared mechanics
export async function sendWelcomeEmail(params: { to: string; name: string }) {
const html = `<h1>Welcome ${params.name}</h1>`;
await emailProvider.send(params.to, "Welcome", html);
}
// userSignup.ts — orchestration (owns WHEN to send)
if (user.marketingOptIn) {
await sendWelcomeEmail({ to: user.email, name: user.name });
}
// adminInvite.ts — orchestration (different business rule, same mechanic)
await sendWelcomeEmail({ to: invitee.email, name: invitee.name });ts
// emailService.ts — 共享机制
export async function sendWelcomeEmail(params: { to: string; name: string }) {
const html = `<h1>Welcome ${params.name}</h1>`;
await emailProvider.send(params.to, "Welcome", html);
}
// userSignup.ts — 编排层(负责决定发送时机)
if (user.marketingOptIn) {
await sendWelcomeEmail({ to: user.email, name: user.name });
}
// adminInvite.ts — 编排层(不同业务规则,相同操作机制)
await sendWelcomeEmail({ to: invitee.email, name: invitee.name });Mental Model
思维模型
New feature? → Write in action first → See repeated ops? → Extract to service
→ No repetition? → Keep in actionYour architecture in one sentence: Actions orchestrate domain rules, while the service layer centralizes reusable operational mechanics with a composable, explicit-input API.
新增功能?→ 先在动作层编写 → 发现重复操作?→ 提取到服务层
→ 无重复?→ 保留在动作层用一句话概括你的架构:Actions编排领域规则,而服务层通过可组合、输入明确的API集中管理可复用的操作机制。