关于 domain-architect
domain-architect 是一项技能,帮助开发者从用户可执行的产品能力出发,划分业务领域并梳理架构纵向切片。它引导追踪 Types、客户端、Repo、Service、Runtime 和 UI,并记录缺失层与边界疑问。
主要功能
- 01从用户能力发现领域
- 02梳理纵向架构分层
- 03识别 Provider 与横切关注点
- 04记录层级缺失和边界疑问
适用场景
- 01梳理现有 Swift 代码库的领域
- 02追踪依赖客户端与 Reducer
- 03与团队审视领域边界
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ pproenca/ dot-skills --skill domain-architect安装后在 Claude Code 中输入/domain-architect确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.jsonreferences/architecture.alsarchitecture.mdprinciples.md详细介绍
领域架构师
通过追踪用户能做什么——也就是产品的功能——并将每项功能映射到架构中的一个垂直切片,来发现业务领域。
你不会从文件夹名称、架构文档或文件数量入手。 你会从产品入手。
你要产出什么
一份领域映射——驱动其他一切的唯一产物:
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 目标、约束规范和迁移计划都会随之自然确定。领域划分错了,后续一切都会出错。
领域如何运作
阅读 references/architecture.md,了解完整的分层规范。
阅读 references/architecture.als,了解可正式验证的模型。
领域是一项用户功能
检验标准:你能用一句话向非工程师描述它吗?
- “安排预约”——领域(Calendar)
- “收取款项”——领域(Payments)
- “浏览可用的治疗项目”——领域(Treatments)
- “处理 HTTP 请求”——不是领域(基础设施)
- “格式化日期”——不是领域(工具)
每个领域都拥有一个垂直切片
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 是构成真正领域的最低要求。
领域之间不会导入彼此的内部实现
跨领域通信通过以下方式进行:
- 将代理操作委托给父级 reducer
- 共享 Types(通用词汇)
- 共享 Config 接口(两个领域使用同一个客户端时)
绝不通过导入其他领域的 Service 或 Repo 来通信。
什么不是领域
| 事物 | 类型 | 所在位置 |
|---|---|---|
| 错误处理 | 横切关注点 | Infra |
| 日志/遥测 | 横切关注点 | Infra |
| 格式化器、常量 | 横切关注点 | Utils |
| Sentry、Stripe SDK、APNS | 提供方(SDK 桥接) | Providers |
| HTTP 传输、持久化引擎 | 共享基础设施 | Repo |
| 设计系统令牌 | 共享 UI | DesignSystem |
| 后台任务调度 | 平台集成 | Runtime |
流程
第 1 步:用户能做什么?
从入口点入手。阅读 @main、根 reducer 和标签页结构。每个子作用域或标签页都可能是一个领域。
grep -r "@main" <project-root> --include="*.swift" -l
阅读根 reducer。追踪其 Scope 和 CombineReducers,找出所有子功能。追踪标签页枚举,找出所有顶层功能。
对于多应用产品(例如患者应用 + 诊所应用),对每个应用都执行此操作。同一个业务领域通常会出现在两个应用中,但动词不同。
第 2 步:有哪些动词?
每个 @DependencyClient 都是一个动词——系统能够执行的一项功能。
grep -r "@DependencyClient" <project-root> --include="*.swift" -l
阅读每个客户端文件。对每个客户端,记录:
- 动词(闭包名称:
fetch、create、cancel、observe) - 名词(流转的类型:
Appointment、Treatment、Patient) - 所属的领域(根据名词推断)
按领域对客户端分组。这就是 Config 层映射。
第 3 步:有哪些名词?
名词位于 Types 层——通用词汇。
find <project-root> -name "Package.swift" -not -path "*/.build/*"
阅读 Package.swift 文件,找到 Types 目标。阅读其源文件,理解领域语言:有哪些实体、哪些 ID 有类型,以及定义了哪些操作。
第 4 步:梳理每个领域的垂直切片
对于在第 1–2 步中发现的每个领域,追踪其完整垂直链路:
| 层 | 问题 | 查找方法 |
|---|---|---|
| Types | 这个领域使用哪些名词? | 在 Types 包中搜索领域名词 |
| Config | 可以请求什么? | 查看第 2 步中的 @DependencyClient 文件 |
| Repo | 如何获取/存储数据? | 搜索 DataService、Repository、Store 和领域名词 |
| Service | 会做出哪些决策? | 搜索 @Reducer 和领域名称 |
| Runtime | 在哪里进行连接? | 搜索 Registration 和领域名称 |
| UI | 用户会看到什么? | 搜索 View 和领域名称 |
每个领域的每一层至少阅读一个文件。 不要凭名称猜测。
第 5 步:识别提供方
提供方会封装外部 SDK。它们不是领域,而是桥接层。
查找:
- 第三方框架导入(Stripe、Firebase、Sentry、Amplitude)
- SDK 初始化代码
- 将外部类型桥接到领域类型的协议实现
grep -r "import Stripe\|import Firebase\|import Sentry\|import Amplitude" <project-root> --include="*.swift" -l
第 6 步:识别横切关注点
领域和提供方之外还剩什么?这些是横切关注点:
- Infra:错误类型、遥测协议、日志抽象
- Utils:纯格式化器、常量、无障碍 ID
- DesignSystem:设计令牌、样式、共享 UI 组件
任何领域都可以导入这些内容,但它们不包含领域知识。
第 7 步:标记模糊之处
有些内容确实存在歧义。将其标记为问题:
- “Profile 是一个领域,还是属于 Auth?”——Profile 有自己的客户端 和 UI,但头像上传通过 Auth 处理。请与团队讨论。
- “Booking 属于 Calendar,还是独立领域?”——它有独立的 客户端,但位于 Calendar 标签页中。取决于复杂程度。
- “Notifications 是一个领域,还是横切关注点?”——它有自己的 UI 和客户端,但 Types 非常精简。界限不明。
将这些内容作为问题提出,而不是做出决定。团队掌握你所没有的背景信息。
输出格式
领域图
针对每个领域:
### [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].
提供方
| Provider | SDK | Used by domains |
|----------|-----|----------------|
| Sentry | Error monitoring | All (Infra) |
| Stripe Terminal | In-person payments | Payments |
横切关注点
| Concern | Layer | Purpose |
|---------|-------|---------|
| AppDomainError | Infra | Error vocabulary |
| Telemetry | Infra | Monitoring |
| AccessibilityId | Utils | UI testing |
问题
1. Is Profile a domain or part of Auth? [evidence for each]
2. Should Booking be extracted from Calendar? [evidence]
避免走捷径的规则
-
从产品出发,而不是从文件出发。 先问“用户可以做什么?” 再问“有哪些文件?”
-
不要按文件夹路径分类。
Services/中的文件可能是 UI 代码。 分类前先阅读。 -
形成自己的判断前,不要阅读架构文档。 如果存在 harness-spec.yml 或 ARCHITECTURE.md,请在梳理完领域后再阅读。 将你的领域图与文档中的内容进行比较——差异是最有价值的发现。
-
不要因为领域看起来相似就跳过。 每个领域都要 单独追踪完整垂直链路。Auth 不等于 Profile。
-
对于超过 200 个文件的代码库,使用子 Agent。 每个领域分配一个 Agent, 分别追踪完整垂直链路。这样可以避免在上下文压力下走捷径。
-
标记模糊之处,而不是自行决定。 你不了解团队的 背景信息。请列出双方的依据,让团队决定。
深度要求
- 100% 客户端发现率——找到每个
@DependencyClient并将其归入某个领域 - 100% Reducer 发现率——找到每个
@Reducer并将其归入某个领域 - 每个领域追踪垂直链路——每个领域的每一层至少阅读一个文件
- 为边界提供依据——引用用于论证每个领域边界的名词/动词
- 明确指出缺口——报告缺失的层(例如:领域有 Service,但没有 Config,这就是一项发现)
- 有疑问就提问,不要假设——不确定时,提问而不是猜测