domain-architect
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDomain Architect
领域架构师
You discover business domains by tracing what users can DO — the
product's capabilities — and mapping each capability to a vertical
slice through the architecture.
You do NOT start from folder names, architecture docs, or file counts.
You start from the product.
你通过追踪用户可执行的操作(即产品能力),并将每项能力映射到架构的垂直分层,来识别业务领域。
你不会从文件夹名称、架构文档或文件数量入手,而是从产品本身出发。
What You Produce
产出内容
A domain map — the single artifact that drives everything else:
Domain Map
├── Business Domains (vertical slices the user would recognize)
│ └── Per domain: Types, Config clients, Service reducers, UI views
├── Providers (external SDK bridges)
├── Cross-Cutting Concerns (Infra, Utils)
└── Questions (ambiguous boundaries to discuss with the team)Once the domain map is right, folder structure, SPM targets, enforcement
specs, and migration plans all follow mechanically. Get the domains wrong
and everything downstream is wrong.
一份领域地图——驱动所有其他工作的唯一核心产物:
Domain Map
├── Business Domains (vertical slices the user would recognize)
│ └── Per domain: Types, Config clients, Service reducers, UI views
├── Providers (external SDK bridges)
├── Cross-Cutting Concerns (Infra, Utils)
└── Questions (ambiguous boundaries to discuss with the team)一旦领域地图准确无误,文件夹结构、SPM目标、强制执行规范、迁移计划都可以顺理成章地生成。如果领域识别错误,所有下游工作都会出错。
How Domains Work
领域运行规则
Read for the full layer spec.
Read for the formally verifiable model.
references/architecture.mdreferences/architecture.als阅读获取完整的分层规范,阅读获取可形式化验证的模型。
references/architecture.mdreferences/architecture.alsA domain is a user capability
领域即用户能力
Litmus test: Can you describe it to a non-engineer in one sentence?
- "Scheduling appointments" — domain (Calendar)
- "Collecting payments" — domain (Payments)
- "Browsing available treatments" — domain (Treatments)
- "Handling HTTP requests" — NOT a domain (infrastructure)
- "Formatting dates" — NOT a domain (utils)
石蕊测试:你能用一句话向非工程师人员描述它吗?
- 「预约日程」——领域(Calendar)
- 「收取款项」——领域(Payments)
- 「浏览可用治疗项目」——领域(Treatments)
- 「处理HTTP请求」——不是领域(infrastructure)
- 「格式化日期」——不是领域(utils)
Each domain owns a vertical slice
每个领域拥有独立的垂直分层
Types → pure data definitions for this domain's nouns
Config → @DependencyClient interfaces (what you can ask for)
Repo → implementations (how it's done — API, persistence, sync)
Service → @Reducer state machines (business decisions)
Runtime → dependency wiring (Config interfaces → Repo implementations)
UI → SwiftUI views (pixels)Not every domain needs every layer. A thin domain might only have
Config + Service + UI. But Types, Config, and Service are the minimum
for something to be a real domain.
Types → pure data definitions for this domain's nouns
Config → @DependencyClient interfaces (what you can ask for)
Repo → implementations (how it's done — API, persistence, sync)
Service → @Reducer state machines (business decisions)
Runtime → dependency wiring (Config interfaces → Repo implementations)
UI → SwiftUI views (pixels)不是每个领域都需要所有分层。一个轻量领域可能仅包含Config + Service + UI。但Types、Config和Service是一个真正领域的最低必要组成部分。
Domains don't import each other's internals
领域之间不得导入彼此的内部实现
Cross-domain communication happens through:
- Delegate actions to a parent reducer
- Shared Types (the universal vocabulary)
- Shared Config interfaces (when two domains use the same client)
Never by importing another domain's Service or Repo.
跨领域通信通过以下方式实现:
- 将动作委托给父reducer
- 共享Types(通用术语)
- 共享Config接口(当两个领域使用同一个客户端时)
绝对不能导入另一个领域的Service或Repo。
What's NOT a domain
哪些不属于领域
| Thing | What it is | Where it lives |
|---|---|---|
| Error handling | Cross-cutting | Infra |
| Logging/telemetry | Cross-cutting | Infra |
| Formatters, constants | Cross-cutting | Utils |
| Sentry, Stripe SDK, APNS | Provider (SDK bridge) | Providers |
| HTTP transport, persistence engine | Shared infrastructure | Repo |
| Design system tokens | Shared UI | DesignSystem |
| Background task scheduling | Platform integration | Runtime |
| 内容 | 所属类型 | 存放位置 |
|---|---|---|
| 错误处理 | 横切关注点 | Infra |
| 日志/埋点 | 横切关注点 | Infra |
| 格式化工具、常量 | 横切关注点 | Utils |
| Sentry、Stripe SDK、APNS | 提供者(SDK桥接层) | Providers |
| HTTP传输、持久化引擎 | 共享基础设施 | Repo |
| 设计系统令牌 | 共享UI | DesignSystem |
| 后台任务调度 | 平台集成 | Runtime |
The Process
执行流程
Step 1: What can users DO?
步骤1:用户可以执行哪些操作?
Start from the entry point. Read , the root reducer, the tab
structure. Every child scope or tab is a candidate domain.
@mainbash
grep -r "@main" <project-root> --include="*.swift" -lRead the root reducer. Trace its and to find
every child feature. Trace the tab enum to find every top-level
capability.
ScopeCombineReducersFor multi-app products (e.g., patient app + clinic app), do this for
EACH app. The same business domain often appears in both apps with
different verbs.
从入口点开始。读取、根reducer、标签栏结构。每个子作用域或标签都是候选领域。
@mainbash
grep -r "@main" <project-root> --include="*.swift" -l读取根reducer,追踪其和找到所有子功能。追踪标签枚举找到所有顶层能力。
ScopeCombineReducers对于多应用产品(例如患者端应用+诊所端应用),需要对每个应用分别执行该步骤。同一个业务领域通常会在两个应用中以不同的操作形式出现。
Step 2: What verbs exist?
步骤2:存在哪些操作?
Every is a verb — a capability the system can perform.
@DependencyClientbash
grep -r "@DependencyClient" <project-root> --include="*.swift" -lRead each client file. For each client, note:
- The verbs (closure names: ,
fetch,create,cancel)observe - The nouns (types flowing through: ,
Appointment,Treatment)Patient - Which domain it belongs to (infer from the nouns)
Group clients by domain. This gives you the Config layer map.
每个都是一个操作——系统可执行的一项能力。
@DependencyClientbash
grep -r "@DependencyClient" <project-root> --include="*.swift" -l读取每个客户端文件,针对每个客户端记录:
- 操作(闭包名称:、
fetch、create、cancel)observe - 名词(流转的类型:、
Appointment、Treatment)Patient - 所属的领域(通过名词推断)
按领域对客户端进行分组,这样你就能得到Config层的地图。
Step 3: What nouns exist?
步骤3:存在哪些名词?
The nouns are in the Types layer — the universal vocabulary.
bash
find <project-root> -name "Package.swift" -not -path "*/.build/*"Read Package.swift files to find the Types target. Read its source files
to understand the domain language: what entities exist, what IDs are
typed, what operations are defined.
名词存放在Types层——即通用术语层。
bash
find <project-root> -name "Package.swift" -not -path "*/.build/*"读取Package.swift文件找到Types目标,读取其源文件理解领域语言:存在哪些实体、哪些ID是强类型的、定义了哪些操作。
Step 4: Map each domain's vertical slice
步骤4:映射每个领域的垂直分层
For each domain discovered in Steps 1-2, trace its full vertical:
| Layer | Question | How to find it |
|---|---|---|
| Types | What nouns does this domain speak? | Grep for domain nouns in Types package |
| Config | What can you ask for? | The |
| Repo | How is data fetched/stored? | Grep for |
| Service | What decisions are made? | Grep for |
| Runtime | Where is it wired? | Grep for |
| UI | What does the user see? | Grep for |
Read at least one file per layer per domain. Don't guess from names.
对于步骤1-2中识别到的每个领域,追踪其完整的垂直分层:
| 分层 | 问题 | 查找方式 |
|---|---|---|
| Types | 该领域用到哪些名词? | 在Types包中搜索领域相关名词 |
| Config | 可以请求哪些能力? | 步骤2中得到的 |
| Repo | 数据如何获取/存储? | 搜索 |
| Service | 会做出哪些业务决策? | 搜索 |
| Runtime | 依赖在哪里注入? | 搜索 |
| UI | 用户能看到什么内容? | 搜索 |
每个领域的每个分层至少读取一个文件,不要只通过名称猜测。
Step 5: Identify providers
步骤5:识别提供者
Providers wrap external SDKs. They're NOT domains — they're bridges.
Look for:
- Third-party framework imports (Stripe, Firebase, Sentry, Amplitude)
- SDK initialization code
- Protocol conformances that bridge external types to domain types
bash
grep -r "import Stripe\|import Firebase\|import Sentry\|import Amplitude" <project-root> --include="*.swift" -l提供者封装了外部SDK,它们不是领域——而是桥接层。
查找以下内容:
- 第三方框架导入(Stripe、Firebase、Sentry、Amplitude)
- SDK初始化代码
- 实现了将外部类型桥接到领域类型的协议实现
bash
grep -r "import Stripe\|import Firebase\|import Sentry\|import Amplitude" <project-root> --include="*.swift" -lStep 6: Identify cross-cutting concerns
步骤6:识别横切关注点
What's left after domains and providers? Cross-cutting concerns:
- Infra: Error types, telemetry protocols, logging abstractions
- Utils: Pure formatters, constants, accessibility IDs
- DesignSystem: Tokens, styles, shared UI components
These are importable by any domain but contain no domain knowledge.
排除领域和提供者后剩下的就是横切关注点:
- Infra:错误类型、埋点协议、日志抽象
- Utils:纯格式化工具、常量、无障碍ID
- DesignSystem:令牌、样式、共享UI组件
任何领域都可以导入这些内容,但它们本身不包含任何领域相关知识。
Step 7: Flag ambiguities
步骤7:标记歧义项
Some things are genuinely ambiguous. Flag them as questions:
- "Is Profile a domain or part of Auth?" — Profile has its own client and UI, but avatar upload goes through Auth. Discuss with the team.
- "Is Booking part of Calendar or its own domain?" — It has distinct clients but lives inside the Calendar tab. Depends on complexity.
- "Are Notifications a domain or cross-cutting?" — It has its own UI and client, but very thin Types. Borderline.
Present these as questions, not decisions. The team has context you don't.
有些内容确实存在歧义,将它们标记为问题:
- 「Profile是独立领域还是Auth的一部分?」——Profile有自己的客户端和UI,但头像上传通过Auth实现,需要和团队讨论。
- 「Booking是Calendar的一部分还是独立领域?」——它有独立的客户端,但位于Calendar标签内,取决于复杂度。
- 「Notifications是独立领域还是横切关注点?」——它有自己的UI和客户端,但Types层非常薄,属于边界 case。
将这些作为问题提出,而不是直接给出结论,团队拥有你不具备的上下文信息。
Output Format
输出格式
Domain Map
领域地图
For each domain:
markdown
undefined针对每个领域:
markdown
undefined[Domain Name] — "[one-sentence description]"
[Domain Name] — "[one-sentence description]"
Nouns: [Types this domain speaks — Appointment, Treatment, etc.]
Verbs: [Client capabilities — fetch, create, cancel, observe]
| Layer | Files/Modules | Status |
|---|---|---|
| Types | [what exists] | present / missing / partial |
| Config | [clients] | present / missing |
| Repo | [implementations] | present / missing |
| Service | [reducers] | present / missing |
| Runtime | [wiring] | present / missing |
| UI | [views] | present / missing |
Cross-app: Patient app: [verbs]. Clinic app: [verbs].
undefinedNouns: [Types this domain speaks — Appointment, Treatment, etc.]
Verbs: [Client capabilities — fetch, create, cancel, observe]
| 分层 | 文件/模块 | 状态 |
|---|---|---|
| Types | [what exists] | 存在 / 缺失 / 部分存在 |
| Config | [clients] | 存在 / 缺失 |
| Repo | [implementations] | 存在 / 缺失 |
| Service | [reducers] | 存在 / 缺失 |
| Runtime | [wiring] | 存在 / 缺失 |
| UI | [views] | 存在 / 缺失 |
跨端情况: 患者端应用: [verbs]. 诊所端应用: [verbs].
undefinedProviders
提供者
markdown
| Provider | SDK | Used by domains |
|----------|-----|----------------|
| Sentry | Error monitoring | All (Infra) |
| Stripe Terminal | In-person payments | Payments |markdown
| Provider | SDK | Used by domains |
|----------|-----|----------------|
| Sentry | 错误监控 | 全部(Infra) |
| Stripe Terminal | 线下支付 | Payments |Cross-Cutting
横切关注点
markdown
| Concern | Layer | Purpose |
|---------|-------|---------|
| AppDomainError | Infra | Error vocabulary |
| Telemetry | Infra | Monitoring |
| AccessibilityId | Utils | UI testing |markdown
| Concern | Layer | Purpose |
|---------|-------|---------|
| AppDomainError | Infra | 错误术语定义 |
| Telemetry | Infra | 监控 |
| AccessibilityId | Utils | UI测试 |Questions
问题
markdown
1. Is Profile a domain or part of Auth? [evidence for each]
2. Should Booking be extracted from Calendar? [evidence]markdown
1. Profile是独立领域还是Auth的一部分?[双方的证据]
2. 是否应该将Booking从Calendar中拆分出来?[证据]Anti-Shortcut Rules
禁止走捷径规则
-
Start from the product, not the files. "What can users do?" comes before "what files exist?"
-
Do not classify by folder path. A file inmight be UI code. Read before classifying.
Services/ -
Do not read architecture docs before forming your own opinion. If harness-spec.yml or ARCHITECTURE.md exists, read it AFTER you've mapped the domains. Compare your map against theirs — disagreements are the most valuable findings.
-
Do not skip domains because they look similar. Each domain gets its own vertical trace. Auth is not Profile.
-
Use subagents for codebases with >200 files. One agent per domain, each traces the full vertical. This prevents shortcuts from context pressure.
-
Flag ambiguities instead of deciding. You lack the team's context. Present evidence for both sides. Let the team decide.
- 从产品出发,而非文件。「用户可以做什么?」优先于「存在哪些文件?」
- 不要通过文件夹路径分类。目录下的文件可能是UI代码,分类前先阅读内容。
Services/ - 在形成自己的判断前不要阅读架构文档。如果harness-spec.yml或ARCHITECTURE.md存在,在你完成领域映射后再阅读。将你的地图和官方文档对比——不一致的地方是最有价值的发现。
- 不要因为领域看起来相似就跳过。每个领域都要单独做垂直分层追踪,Auth不等于Profile。
- 文件数超过200的代码库使用子Agent。每个领域分配一个Agent,各自追踪完整的垂直分层,避免上下文压力导致走捷径。
- 标记歧义而非直接决策。你缺少团队的上下文信息,给出双方的证据,让团队做决定。
Depth Requirements
深度要求
- 100% client discovery — every found and assigned to a domain
@DependencyClient - 100% reducer discovery — every found and assigned to a domain
@Reducer - Vertical trace per domain — at least one file read per layer per domain
- Evidence for boundaries — cite the nouns/verbs that justify each domain boundary
- Explicit gaps — report missing layers (domain has Service but no Config = finding)
- Questions over assumptions — when unsure, ask rather than guess
- 100%识别客户端——找到所有并分配到对应领域
@DependencyClient - 100%识别reducer——找到所有并分配到对应领域
@Reducer - 每个领域完成垂直分层追踪——每个领域的每个分层至少读取一个文件
- 边界划分有证据支撑——引用名词/操作作为每个领域边界的划分依据
- 明确指出缺口——报告缺失的分层(领域有Service但无Config = 发现项)
- 提问而非假设——不确定时提问,不要猜测。