code-structure

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Service 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 functions
Rule 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 PrincipleDoDon't
API shapeComposable capability blocksOne giant "do everything" method
Inputs/outputsExplicit params, structured returnsHidden global state, reaching into DB
MigrationExtract one block, replace one caller, verify, then migrate restRefactor everything at once
Domain logicKeep auth, policy, error classification in actionsLet service mutate domain state directly
Extraction triggerLogic repeated across 2+ callersLogic 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:
  1. Write the flow in action code first (clear behavior)
  2. Mark repeated operational chunks across callers
  3. Extract only repeated, non-domain chunks to service
  4. Replace one caller → verify → replace remaining callers
  5. Keep domain policy in actions (auth, status transitions, error classification)
  6. Run verification: typecheck, lint, confirm all flows still work
提取共享逻辑时:
  1. 先在动作代码中编写完整流程(明确行为)
  2. 标记出跨调用方重复的操作块
  3. 仅将重复的、非领域相关的块提取到服务层
  4. 替换一个调用方 → 验证 → 再替换剩余调用方
  5. 将领域策略保留在Actions中(权限、状态转换、错误分类)
  6. 执行验证:类型检查、代码扫描,确认所有流程仍正常运行

Anti-Patterns

反模式

Anti-PatternProblem
God serviceOne huge function hides all control flow
Leaky serviceService mutates database tables directly
Inconsistent APIEach function uses different argument styles and error semantics
Over-abstractionExtracting 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 action
Your architecture in one sentence: Actions orchestrate domain rules, while the service layer centralizes reusable operational mechanics with a composable, explicit-input API.
新增功能?→ 先在动作层编写 → 发现重复操作?→ 提取到服务层
                                      → 无重复?→ 保留在动作层
用一句话概括你的架构:Actions编排领域规则,而服务层通过可组合、输入明确的API集中管理可复用的操作机制。