clean-architecture
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseClean Architecture & DDD
Clean Architecture与DDD
Architectural patterns and tactical design techniques for building systems where business logic is isolated, testable, and independent of frameworks, databases, and delivery mechanisms. Rooted in the work of Robert C. Martin, Alistair Cockburn, and Eric Evans.
本文介绍用于构建业务逻辑独立、可测试且与框架、数据库及交付机制无关的系统的架构模式与战术设计技巧,其理论源于Robert C. Martin、Alistair Cockburn和Eric Evans的研究成果。
When to Use
适用场景
- The domain has meaningful business rules that deserve explicit modeling
- The system must survive framework upgrades, database migrations, or delivery mechanism changes
- Multiple entry points (API, CLI, message consumer, scheduled jobs) share the same business logic
- Long-lived product where maintenance cost outweighs initial development speed
- Team size or turnover demands clear boundaries and enforceable conventions
- Testability is a priority — business rules must be verifiable without infrastructure
- 业务领域具备有意义的业务规则,需要进行显式建模
- 系统需要在框架升级、数据库迁移或交付机制变更时仍能正常运行
- 多个入口(API、CLI、消息消费者、定时任务)共享相同的业务逻辑
- 长期维护的产品,维护成本高于初始开发速度
- 团队规模较大或人员流动频繁,需要清晰的边界和可执行的规范
- 可测试性为优先需求——业务规则无需依赖基础设施即可验证
When NOT to Use
不适用场景
Not every system benefits from this level of architectural rigor. Avoid over-engineering when:
- The application is a simple CRUD wrapper with no meaningful business logic
- The project is a short-lived prototype or proof of concept
- The team is very small and the domain is well understood with few rules
- The overhead of layer separation exceeds the complexity of the problem
- A simple framework-centric approach (e.g., MVC with active records) adequately serves the need
The key question: Does the domain have enough complexity to justify the investment? If the answer is no, a simpler architecture is the right choice. You can always introduce boundaries later as complexity grows.
并非所有系统都能从这种严格的架构设计中获益。在以下情况避免过度设计:
- 应用仅为简单的CRUD包装器,无实际业务逻辑
- 项目为短期原型或概念验证
- 团队规模极小,且业务领域规则少、已被充分理解
- 分层带来的开销超过问题本身的复杂度
- 简单的框架中心化方案(如结合活动记录的MVC)已能满足需求
核心问题:**业务领域的复杂度是否足以证明投入的合理性?**如果答案是否定的,选择更简单的架构即可。随着复杂度提升,后续可再引入边界设计。
Architecture Styles
架构风格
Three well-known styles share the same core principle: dependencies point inward, and the domain sits at the center.
| Style | Origin | Key Metaphor | Distinguishing Idea |
|---|---|---|---|
| Clean Architecture | Robert C. Martin (2012) | Concentric circles | Explicit layer names: Entity, Use Case, Interface Adapter, Framework |
| Hexagonal (Ports & Adapters) | Alistair Cockburn (2005) | Hexagon with ports | Symmetry between driving (primary) and driven (secondary) adapters |
| Onion Architecture | Jeffrey Palermo (2008) | Onion layers | Emphasis on domain model at the core, infrastructure at the outer ring |
What they share:
- Domain logic at the center, free of external dependencies
- The Dependency Rule: source code dependencies always point inward
- Infrastructure and delivery concerns live in outer layers
- Communication crosses boundaries through abstractions (interfaces/ports)
For practical purposes, the differences are naming conventions. The principles below apply to all three.
三种知名的架构风格共享同一核心原则:依赖指向内部,领域层位于中心位置。
| 风格 | 起源 | 核心隐喻 | 独特理念 |
|---|---|---|---|
| Clean Architecture | Robert C. Martin(2012) | 同心圆 | 明确的分层命名:实体层、用例层、接口适配层、框架层 |
| 六边形架构(Ports & Adapters) | Alistair Cockburn(2005) | 带端口的六边形 | 驱动(主)适配器与被驱动(次)适配器的对称性 |
| 洋葱架构 | Jeffrey Palermo(2008) | 洋葱分层 | 强调领域模型为核心,基础设施位于最外层 |
共同特性:
- 领域逻辑位于中心,无外部依赖
- 依赖规则:源代码依赖始终指向内部
- 基础设施与交付相关逻辑位于外层
- 通过抽象(接口/端口)实现跨边界通信
从实践角度看,三者的差异仅在于命名规范。以下原则适用于所有三种架构。
Ports & Adapters Concept
端口与适配器概念
The Hexagonal Architecture introduces a useful vocabulary that applies across all three styles:
- Ports are interfaces defined by the application. They describe what the application needs from the outside world (driven/secondary ports) or what the outside world can ask of the application (driving/primary ports).
- Adapters are implementations that connect a port to a specific technology. A database adapter implements a repository port. An HTTP controller is an adapter for a driving port.
- Driving (primary) adapters initiate interaction with the application: HTTP controllers, CLI commands, message consumers, scheduled jobs.
- Driven (secondary) adapters are called by the application: database repositories, email senders, payment gateways, file storage.
This symmetry is powerful: the application does not know whether it is being driven by a REST API or a CLI command, and it does not know whether it is persisting to a relational database or an in-memory store.
六边形架构引入的术语体系适用于所有三种风格:
- Ports(端口):由应用定义的接口,描述应用对外界的需求(被驱动/次级端口)或外界可向应用发起的请求(驱动/初级端口)。
- Adapters(适配器):将端口连接到特定技术的实现。数据库适配器实现仓储端口,HTTP控制器是驱动端口的适配器。
- 驱动(初级)适配器:发起与应用的交互:HTTP控制器、CLI命令、消息消费者、定时任务。
- 被驱动(次级)适配器:被应用调用:数据库仓储、邮件发送器、支付网关、文件存储。
这种对称性的优势在于:应用无需知晓是被REST API还是CLI命令驱动,也无需知晓数据是持久化到关系型数据库还是内存存储。
The Dependency Rule
依赖规则
Source code dependencies must point inward. Nothing in an inner layer can know anything about an outer layer — no names, types, interfaces, or concepts.
This is the single most important rule. Every other guideline flows from it.
What "inward" means:
- Inner layers define interfaces (ports); outer layers implement them
- Data crosses boundaries as simple structures (DTOs, primitives), never as framework objects
- The domain layer never imports from infrastructure, presentation, or application layers
- The application layer never imports from infrastructure or presentation layers
Why it matters:
- The domain can be tested without a database, HTTP server, or message broker
- Frameworks become replaceable implementation details
- Business rules are readable without understanding deployment topology
How to enforce it:
- Use static analysis or architecture testing tools to verify import directions
- Organize code into modules or packages that reflect layers, making violations visible
- Code reviews should flag any inner-layer import of outer-layer types
- The Composition Root (application entry point) is the only place that wires all layers together — it is the exception that knows about everything
源代码依赖必须指向内部。内层的任何内容都不得知晓外层的任何信息——包括名称、类型、接口或概念。
这是最重要的规则,所有其他准则均由此衍生。
“内部”的含义:
- 内层定义接口(端口),外层实现接口
- 跨边界传递的数据为简单结构(DTO、基本类型),绝不能是框架对象
- 领域层从不导入基础设施层、表示层或应用层的代码
- 应用层从不导入基础设施层或表示层的代码
重要性:
- 领域层无需数据库、HTTP服务器或消息队列即可测试
- 框架可被替换为实现细节
- 无需理解部署拓扑即可读懂业务规则
如何强制执行:
- 使用静态分析或架构测试工具验证导入方向
- 按分层组织代码模块或包,使违规行为一目了然
- 代码评审时标记任何内层导入外层类型的情况
- 组合根(应用入口点)是唯一连接所有层的地方——它是唯一知晓所有内容的例外
Layer Reference
分层参考
| Layer | Direction | Responsibility | What Belongs Here | What Does NOT Belong |
|---|---|---|---|---|
| Domain (innermost) | Depends on nothing | Business rules, invariants | Entities, Value Objects, Aggregates, Domain Services, Domain Events, Repository interfaces | Framework imports, database queries, HTTP concepts |
| Application | Depends on Domain | Use case orchestration | Application Services, Command/Query objects, DTOs, Port interfaces | Business logic, direct infrastructure calls |
| Infrastructure | Depends on Application + Domain | Technical capabilities | Repository implementations, API clients, message queue adapters, file system access | Business rules, use case orchestration |
| Presentation (outermost) | Depends on Application | User/system interaction | Controllers, CLI commands, API endpoints, View Models | Business logic, direct database access |
See Layer Details for in-depth guidance on each layer.
| 分层 | 依赖方向 | 职责 | 包含内容 | 不包含内容 |
|---|---|---|---|---|
| Domain(领域层)(最内层) | 无任何依赖 | 业务规则、不变量 | 实体、值对象、聚合、领域服务、领域事件、仓储接口 | 框架导入、数据库查询、HTTP相关概念 |
| Application(应用层) | 依赖领域层 | 用例编排 | 应用服务、命令/查询对象、DTO、端口接口 | 业务逻辑、直接调用基础设施 |
| Infrastructure(基础设施层) | 依赖应用层+领域层 | 技术能力实现 | 仓储实现、API客户端、消息队列适配器、文件系统访问 | 业务规则、用例编排 |
| Presentation(表示层)(最外层) | 依赖应用层 | 用户/系统交互 | 控制器、CLI命令、API端点、视图模型 | 业务逻辑、直接数据库访问 |
详见分层细节获取各层的深入指导。
DDD Tactical Patterns
DDD战术模式
Tactical patterns give structure to the domain layer. Each pattern has a specific role and placement within the architecture.
| Pattern | Purpose | Layer | Key Rule |
|---|---|---|---|
| Entity | Object with identity and lifecycle | Domain | Identity determines equality |
| Value Object | Immutable, identity-less concept | Domain | Attribute equality, no side effects |
| Aggregate | Consistency boundary | Domain | One aggregate per transaction, reference others by ID |
| Repository | Collection-like access to aggregates | Interface in Domain, implementation in Infrastructure | One repository per aggregate root |
| Domain Service | Stateless cross-entity logic | Domain | Only when logic doesn't belong to a single entity |
| Domain Event | Record of something that happened | Domain | Named in past tense, immutable payload |
| Application Service | Use case orchestrator | Application | No business logic — delegates to domain objects |
| Factory | Complex object creation | Domain | Encapsulates construction invariants |
See DDD Tactical Patterns for detailed guidance on each pattern.
战术模式为领域层提供结构,每种模式在架构中都有特定的角色和位置。
| 模式 | 用途 | 分层 | 核心规则 |
|---|---|---|---|
| Entity(实体) | 带标识和生命周期的对象 | 领域层 | 标识决定相等性 |
| Value Object(值对象) | 不可变、无标识的概念 | 领域层 | 属性相等、无副作用 |
| Aggregate(聚合) | 一致性边界 | 领域层 | 每个事务对应一个聚合,通过ID引用其他聚合 |
| Repository(仓储) | 类集合的聚合访问方式 | 接口定义在领域层,实现位于基础设施层 | 每个聚合根对应一个仓储 |
| Domain Service(领域服务) | 无状态的跨实体逻辑 | 领域层 | 仅当逻辑不属于单个实体时使用 |
| Domain Event(领域事件) | 记录已发生的事件 | 领域层 | 采用过去式命名,负载不可变 |
| Application Service(应用服务) | 用例编排器 | 应用层 | 无业务逻辑——委托给领域对象 |
| Factory(工厂) | 复杂对象创建 | 领域层 | 封装构造不变量 |
详见DDD战术模式获取各模式的详细指导。
DDD Strategic Patterns
DDD战略模式
Strategic patterns address the large-scale structure of a system — how to divide a complex domain into manageable parts and how those parts interact.
战略模式解决系统的大规模结构问题——如何将复杂领域划分为可管理的部分,以及这些部分如何交互。
Bounded Context
Bounded Context(限界上下文)
A Bounded Context is a boundary within which a particular domain model is defined and consistent. Each context has its own Ubiquitous Language — the same word can mean different things in different contexts.
Identifying boundaries:
- Different teams or departments often indicate different contexts
- When the same term (e.g., "Account") means different things to different stakeholders, they belong in separate contexts
- A context should be small enough for one team to own
- Align contexts with business capabilities, not technical layers
Bounded Context是特定领域模型定义且保持一致的边界。每个上下文都有自己的Ubiquitous Language(通用语言)——同一词汇在不同上下文中可能含义不同。
识别边界:
- 不同团队或部门通常对应不同的上下文
- 当同一术语(如“账户”)对不同利益相关者含义不同时,它们属于不同的上下文
- 上下文应小到足以由单个团队负责
- 上下文与业务能力对齐,而非技术分层
Ubiquitous Language
Ubiquitous Language(通用语言)
Within each Bounded Context, the team (developers and domain experts) shares a precise vocabulary. Code should use this language directly — class names, method names, and variable names reflect domain concepts exactly as the domain expert would describe them.
Practical tips for maintaining Ubiquitous Language:
- If a developer cannot explain a class name to a domain expert, the name is wrong
- Refactor code when the language evolves — renaming is not cosmetic, it is a design activity
- Avoid technical jargon in domain layer code — use business terms, not implementation terms
- Document the language in a glossary shared between developers and domain experts
在每个Bounded Context中,团队(开发人员和领域专家)共享精确的词汇表。代码应直接使用这些词汇——类名、方法名和变量名应与领域专家描述的业务概念完全一致。
维护通用语言的实用技巧:
- 如果开发人员无法向领域专家解释类名,那么这个名称是错误的
- 当语言演进时重构代码——重命名不是表面工作,而是设计活动
- 领域层代码避免使用技术术语——使用业务术语而非实现术语
- 在开发人员和领域专家共享的术语表中记录通用语言
Context Map
Context Map(上下文映射)
A Context Map documents the relationships between Bounded Contexts. It makes integration strategies explicit rather than accidental.
| Pattern | Relationship | When to Use |
|---|---|---|
| Shared Kernel | Two contexts share a subset of the model | Closely collaborating teams willing to coordinate changes |
| Customer-Supplier | Upstream context serves downstream context | Downstream can negotiate with upstream |
| Conformist | Downstream conforms to upstream model | No negotiating power; upstream won't change |
| Anti-Corruption Layer | Translation layer protects downstream | Integrating with legacy or external systems |
| Open Host Service | Upstream provides a well-defined protocol | Multiple consumers need stable access |
| Published Language | Shared interchange format | Cross-context communication via standard schemas |
See Context Mapping for integration patterns and boundary decisions.
Context Map记录Bounded Context之间的关系,使集成策略显式化而非偶然形成。
| 模式 | 关系 | 适用场景 |
|---|---|---|
| Shared Kernel(共享内核) | 两个上下文共享模型的子集 | 密切协作的团队愿意协调变更 |
| Customer-Supplier(客户-供应商) | 上游上下文为下游上下文提供服务 | 下游可与上游协商 |
| Conformist(追随者) | 下游上下文遵循上游模型 | 无协商能力;上游不会变更 |
| Anti-Corruption Layer(防腐层) | 转换层保护下游上下文 | 与遗留系统或外部系统集成 |
| Open Host Service(开放主机服务) | 上游提供定义良好的协议 | 多个消费者需要稳定访问 |
| Published Language(发布语言) | 共享的交互格式 | 通过标准 schema 进行跨上下文通信 |
详见上下文映射获取集成模式和边界决策的指导。
Anti-Corruption Layer
Anti-Corruption Layer(防腐层)
When integrating with external or legacy systems, an Anti-Corruption Layer (ACL) translates between the external model and the internal domain model. The ACL prevents foreign concepts from leaking into the domain.
Structure: External system -> ACL (adapter + translator) -> Domain model
The ACL belongs in the infrastructure layer and implements a port defined by the application or domain layer.
与外部或遗留系统集成时,防腐层(ACL)在外部模型与内部领域模型之间进行转换,防止外来概念渗透到领域层。
结构: 外部系统 -> ACL(适配器+转换器)-> 领域模型
防腐层属于基础设施层,实现由应用层或领域层定义的端口。
CQRS — Command Query Responsibility Segregation
CQRS——命令查询职责分离
CQRS is a natural companion to Clean Architecture and DDD. It separates the write model (commands) from the read model (queries), allowing each to be optimized independently.
How it fits the architecture:
- Commands flow through the application layer, modify aggregates, and persist through repositories
- Queries can bypass the domain layer entirely and read from optimized projections or views
- The write side enforces business rules through the domain model
- The read side is optimized for the consumer's needs — no need to reconstruct full aggregates for display purposes
When to consider CQRS:
- Read and write patterns are significantly different (e.g., complex writes but simple reads, or vice versa)
- Performance requirements differ between reads and writes
- The read model needs denormalized views that do not map to the domain model
- Event sourcing is in use (CQRS is nearly always paired with event sourcing)
When to avoid CQRS:
- Simple CRUD applications where reads and writes are symmetric
- The added complexity is not justified by the domain's needs
CQRS是Clean Architecture和DDD的天然配套模式,它将写模型(命令)与读模型(查询)分离,允许两者独立优化。
与架构的契合方式:
- 命令流经应用层,修改聚合,并通过仓储持久化
- 查询可完全绕过领域层,直接从优化的投影或视图读取数据
- 写侧通过领域模型强制执行业务规则
- 读侧针对消费者需求进行优化——无需为显示目的重构完整聚合
考虑使用CQRS的场景:
- 读和写模式差异显著(如复杂写但简单读,或反之)
- 读和写的性能要求不同
- 读模型需要非规范化视图,与领域模型不匹配
- 使用事件溯源(CQRS几乎总是与事件溯源配对使用)
避免使用CQRS的场景:
- 简单CRUD应用,读和写对称
- 增加的复杂度无法通过领域需求证明其合理性
Event Sourcing — Brief Overview
Event Sourcing——简介
Event Sourcing stores the state of an aggregate as a sequence of domain events rather than a current-state snapshot. The current state is derived by replaying events.
Relationship to Clean Architecture:
- Events are part of the domain layer
- The event store is an infrastructure concern (a specialized repository)
- Projections (read models) are built from events in the infrastructure or application layer
- The domain model does not depend on the event store implementation
When to consider: audit requirements, complex state transitions, temporal queries ("what was the state at time X"), or when domain events are already central to the design.
Event Sourcing将聚合的状态存储为一系列领域事件,而非当前状态快照。当前状态通过重放事件推导得出。
与Clean Architecture的关系:
- 事件属于领域层
- 事件存储是基础设施相关的关注点(一种特殊的仓储)
- 投影(读模型)由基础设施层或应用层从事件构建
- 领域模型不依赖事件存储的实现
考虑使用的场景: 审计需求、复杂状态转换、时间查询(“X时间点的状态是什么”),或领域事件已成为设计核心时。
Common Misconceptions
常见误解
- "Clean Architecture means lots of boilerplate" — The layers add some structural code, but the trade-off is explicit boundaries. If the boilerplate is overwhelming, the domain may not be complex enough to justify the approach.
- "Every application needs all four layers" — Small applications may collapse the application and presentation layers. The critical boundary is between domain and infrastructure.
- "DTOs everywhere means Clean Architecture" — DTOs are a mechanism for crossing boundaries, not the architecture itself. The architecture is about dependency direction.
- "The domain model must mirror the database schema" — The opposite is true. The domain model reflects business concepts. The infrastructure layer maps between the domain model and the storage schema.
- "Hexagonal and Clean Architecture are different things" — They are different descriptions of the same core idea. Pick the vocabulary that resonates with your team.
- "DDD requires Clean Architecture" — DDD tactical patterns can be used without strict layer separation, though they work best together. Conversely, Clean Architecture does not require DDD patterns.
- "One bounded context = one microservice" — A bounded context is a logical boundary. It may map to a microservice, a module within a monolith, or any other deployment unit.
- “Clean Architecture意味着大量样板代码”——分层会增加一些结构性代码,但换来的是明确的边界。如果样板代码过于繁琐,说明领域复杂度可能不足以证明这种方法的合理性。
- “每个应用都需要四层结构”——小型应用可以合并应用层和表示层。关键边界是领域层与基础设施层之间的边界。
- “到处使用DTO就是Clean Architecture”——DTO是跨边界的机制,而非架构本身。架构的核心是依赖方向。
- “领域模型必须与数据库模式镜像”——恰恰相反。领域模型反映业务概念,基础设施层负责领域模型与存储模式之间的映射。
- “六边形架构和Clean Architecture是不同的东西”——它们是同一核心思想的不同表述。选择团队更容易接受的术语即可。
- “DDD需要Clean Architecture”——DDD战术模式可以在没有严格分层的情况下使用,尽管两者配合使用效果最佳。反之,Clean Architecture也不需要DDD模式。
- “一个限界上下文对应一个微服务”——限界上下文是逻辑边界。它可以映射到微服务、单体应用中的模块或任何其他部署单元。
Common Violations
常见违规情况
| Violation | Symptoms | Fix |
|---|---|---|
| Domain depends on framework | Domain classes import HTTP, ORM, or framework annotations | Move infrastructure concerns to outer layers; use plain objects in domain |
| Business logic in controller | Controllers contain conditional logic, validation rules, calculations | Extract logic into domain objects or application services |
| Anemic domain model | Entities are data bags with only getters/setters; all logic lives in services | Push behavior into entities and value objects; enforce invariants inside aggregates |
| Application service contains business rules | Application layer has complex conditionals, domain calculations | Move rules into domain services or entity methods |
| Infrastructure leaking inward | Domain layer references database column names, API response shapes | Introduce mapping in infrastructure; keep domain model pure |
| God aggregate | One aggregate handles too many concerns, grows without limit | Split into smaller aggregates; use domain events for cross-aggregate communication |
| Shared database between contexts | Multiple bounded contexts read/write the same tables | Separate storage per context; integrate through events or APIs |
| Circular dependencies between layers | Inner layer imports from outer layer; compile errors or runtime coupling | Re-examine which layer owns the interface; invert the dependency |
| DTO reuse across boundaries | Same data structure used in API response, use case input, and domain logic | Create separate DTOs per boundary; map between them |
| Missing Anti-Corruption Layer | External system's model pollutes the domain vocabulary | Add a translation layer in infrastructure |
| 违规行为 | 症状 | 修复方案 |
|---|---|---|
| 领域层依赖框架 | 领域类导入HTTP、ORM或框架注解 | 将基础设施相关代码移到外层;领域层使用普通对象 |
| 业务逻辑存在于控制器中 | 控制器包含条件逻辑、验证规则、计算 | 将逻辑提取到领域对象或应用服务中 |
| 贫血领域模型 | 实体仅为数据容器,只有getter/setter;所有逻辑都在服务中 | 将行为推送到实体和值对象中;在聚合内部强制执行不变量 |
| 应用层包含业务规则 | 应用层有复杂的条件判断、领域计算 | 将规则移到领域服务或实体方法中 |
| 基础设施层代码渗透到内层 | 领域层引用数据库列名、API响应结构 | 在基础设施层引入映射;保持领域模型纯净 |
| 上帝聚合 | 一个聚合处理过多关注点,无限制膨胀 | 拆分为更小的聚合;使用领域事件进行跨聚合通信 |
| 上下文之间共享数据库 | 多个限界上下文读写相同的表 | 为每个上下文分离存储;通过事件或API进行集成 |
| 层之间存在循环依赖 | 内层导入外层代码;编译错误或运行时耦合 | 重新审视哪个层拥有接口;反转依赖 |
| 跨边界复用DTO | 同一数据结构用于API响应、用例输入和领域逻辑 | 为每个边界创建独立的DTO;在它们之间进行映射 |
| 缺少防腐层 | 外部系统的模型污染领域词汇 | 在基础设施层添加转换层 |
Testing Strategy
测试策略
The layered architecture enables a clear testing strategy where each layer has a distinct testing approach:
| Layer | Test Type | Dependencies | What to Verify |
|---|---|---|---|
| Domain | Unit tests | None — pure logic | Business rules, invariants, value object equality, aggregate consistency |
| Application | Unit tests with mocked ports | Domain layer + mocked infrastructure ports | Use case orchestration, correct domain method calls, transaction boundaries |
| Infrastructure | Integration tests | Real external systems (database, API, queue) | Correct mapping, query behavior, adapter fidelity |
| Presentation | Functional / acceptance tests | Full stack or mocked application layer | Request parsing, response formatting, error handling, status codes |
Key principle: The majority of tests should be fast domain unit tests. Infrastructure integration tests are fewer but ensure real systems behave as expected. End-to-end tests cover critical paths but should be minimal.
分层架构支持清晰的测试策略,每个层都有独特的测试方法:
| 分层 | 测试类型 | 依赖 | 验证内容 |
|---|---|---|---|
| 领域层 | 单元测试 | 无——纯逻辑 | 业务规则、不变量、值对象相等性、聚合一致性 |
| 应用层 | 带模拟端口的单元测试 | 领域层 + 模拟的基础设施端口 | 用例编排、领域方法调用正确性、事务边界 |
| 基础设施层 | 集成测试 | 真实外部系统(数据库、API、队列) | 映射正确性、查询行为、适配器保真度 |
| 表示层 | 功能/验收测试 | 全栈或模拟的应用层 | 请求解析、响应格式化、错误处理、状态码 |
核心原则: 大多数测试应为快速的领域层单元测试。基础设施集成测试数量较少,但可确保真实系统行为符合预期。端到端测试仅覆盖关键路径,应尽量精简。
Quality Checklist
质量检查清单
Use this checklist to evaluate whether the architecture is properly structured:
Dependency Rule
- Domain layer has zero imports from application, infrastructure, or presentation layers
- Application layer has zero imports from infrastructure or presentation layers
- All cross-boundary communication uses interfaces/ports defined by the inner layer
Domain Layer
- Entities enforce their own invariants — invalid state is unrepresentable
- Value Objects are immutable
- Aggregates are the unit of consistency — one aggregate per transaction
- Repository interfaces are defined in the domain, not in infrastructure
- Domain events capture meaningful state changes
Application Layer
- Application services orchestrate but contain no business logic
- Use cases are explicit — each has a clear input, output, and single responsibility
- DTOs are used to cross boundaries — domain objects do not leak to outer layers
Infrastructure Layer
- Repository implementations live here, not in the domain
- External service integrations use adapters that implement ports
- Framework-specific code is isolated and replaceable
Presentation Layer
- Controllers are thin — they delegate to application services immediately
- No business logic in controllers, views, or CLI commands
- Input validation (format, type) happens here; business validation happens in the domain
Bounded Contexts
- Each context has its own ubiquitous language
- Contexts communicate through well-defined interfaces (events, APIs), not shared databases
- Anti-Corruption Layers protect against external model pollution
Testing
- Domain logic is testable without any infrastructure
- Application services are testable with mocked ports
- Infrastructure adapters have integration tests against real dependencies
- No test requires a running framework to verify business rules
使用此清单评估架构是否结构合理:
依赖规则
- 领域层无任何来自应用层、基础设施层或表示层的导入
- 应用层无任何来自基础设施层或表示层的导入
- 所有跨边界通信使用内层定义的接口/端口
领域层
- 实体强制执行自身的不变量——无法表示无效状态
- 值对象是不可变的
- 聚合是一致性单元——每个事务对应一个聚合
- 仓储接口定义在领域层,而非基础设施层
- 领域事件捕获有意义的状态变更
应用层
- 应用服务仅负责编排,不包含业务逻辑
- 用例明确——每个用例有清晰的输入、输出和单一职责
- 使用DTO跨边界——领域对象不会渗透到外层
基础设施层
- 仓储实现位于此处,而非领域层
- 外部服务集成使用实现端口的适配器
- 框架特定代码被隔离且可替换
表示层
- 控制器很薄——立即委托给应用服务
- 控制器、视图或CLI命令中无业务逻辑
- 输入验证(格式、类型)在此处进行;业务验证在领域层进行
限界上下文
- 每个上下文有自己的通用语言
- 上下文通过定义良好的接口(事件、API)通信,而非共享数据库
- 防腐层防止外部模型污染
测试
- 领域逻辑无需任何基础设施即可测试
- 应用服务可通过模拟端口进行测试
- 基础设施适配器针对真实依赖有集成测试
- 验证业务规则无需运行框架
Best Practices
最佳实践
- Start with the domain — model business rules first, add infrastructure later
- Name things after domain concepts, not technical patterns (avoid SomethingManager, SomethingHelper)
- Keep aggregates small — large aggregates are a design smell indicating misplaced boundaries
- Prefer composition over inheritance across all layers
- Use domain events to communicate between aggregates rather than direct coupling
- Make invalid state unrepresentable — push validation into value objects and entity constructors
- Keep the application layer thin — if it contains business logic, the domain layer is anemic
- Refactor toward deeper insight — the first domain model is rarely the best one; evolve it as understanding grows
- Apply architecture testing to enforce dependency rules automatically
- Introduce Clean Architecture incrementally — start with the most complex subdomain and expand
- 从领域开始——先建模业务规则,再添加基础设施
- 以领域概念命名,而非技术模式(避免使用SomethingManager、SomethingHelper这类名称)
- 保持聚合小型化——大型聚合是边界设计不当的信号
- 在所有层优先使用组合而非继承
- 使用领域事件进行聚合间通信,而非直接耦合
- 使无效状态无法表示——将验证推送到值对象和实体构造函数中
- 保持应用层轻薄——如果它包含业务逻辑,说明领域层是贫血的
- 重构以获得更深入的理解——第一个领域模型很少是最佳的;随着理解加深不断演进
- 应用架构测试自动强制执行依赖规则
- 逐步引入Clean Architecture——从最复杂的子领域开始,逐步扩展