mcpField
C
clean-architecture

clean-architecture

github.com/krzysztofsurdy开发3656 次安装1 周前更新
代码仓库

关于 clean-architecture

介绍 Clean Architecture、Hexagonal Architecture、Onion Architecture 和 DDD 模式的架构参考资料。供开发者和团队用于隔离业务逻辑、划分系统边界,并保持领域规则可测试。

主要功能

  1. 01说明依赖向内的原则与分层职责
  2. 02介绍端口、适配器和架构边界
  3. 03讲解 DDD 战术与战略模式
  4. 04指导评估 CQRS 与事件溯源
  5. 05提供判断何时避免额外复杂度的依据

适用场景

  1. 01规划领域逻辑复杂应用的边界
  2. 02使业务规则脱离基础设施进行测试
  3. 03构建具有多个入口的系统
  4. 04评估 CQRS 是否适合读写需求

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/krzysztofsurdy/code-virtuoso --skill clean-architecture

安装后在 Claude Code 中输入/clean-architecture确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
context-mapping.md
ddd-patterns.md
layers.md

详细介绍

整洁架构与 DDD

用于构建系统的架构模式和战术设计技术。这类系统将业务逻辑隔离出来,使其可测试,并且独立于框架、数据库和交付机制。其理论源自 Robert C. Martin、Alistair Cockburn 和 Eric Evans 的研究。

适用场景

  • 领域包含值得明确建模的重要业务规则
  • 系统必须能够适应框架升级、数据库迁移或交付机制变更
  • 多个入口(API、CLI、消息使用者、定时任务)共用相同的业务逻辑
  • 产品生命周期长,维护成本高于初期开发速度
  • 团队规模或人员流动要求划分清晰边界并执行约定
  • 可测试性是优先事项——业务规则必须能够在不依赖基础设施的情况下验证

不适用场景

并非所有系统都能从这种程度的架构严谨性中受益。遇到以下情况时,应避免过度设计:

  • 应用只是简单的 CRUD 封装,没有重要的业务逻辑
  • 项目是短期原型或概念验证
  • 团队规模很小,且对领域已有充分了解,规则也很少
  • 分层带来的开销超过了问题本身的复杂度
  • 简单的以框架为中心的方案(例如带 Active Record 的 MVC)足以满足需求

关键问题是:领域是否足够复杂,值得投入这番功夫? 如果答案是否定的,那么选择更简单的架构才是正确做法。随着复杂度增加,你随时可以再引入边界。

架构风格

三种知名风格遵循相同的核心原则:依赖指向内侧,领域位于中心。

风格起源关键比喻区别性理念
整洁架构Robert C. Martin(2012)同心圆明确的层名称:实体、用例、接口适配器、框架
六边形架构(端口与适配器)Alistair Cockburn(2005)带端口的六边形驱动(主)适配器与被驱动(次)适配器之间的对称性
洋葱架构Jeffrey Palermo(2008)洋葱层强调以领域模型为核心,基础设施位于最外层

它们的共同点:

  • 领域逻辑位于中心,不依赖外部组件
  • 依赖规则:源代码依赖始终指向内侧
  • 基础设施和交付相关事项位于外层
  • 通信通过抽象(接口/端口)跨越边界

在实际应用中,它们的区别只是命名约定。以下原则适用于这三种风格。

端口与适配器的概念

六边形架构引入了一套实用的术语,适用于这三种风格:

  • 端口是由应用定义的接口。它描述应用需要外部世界提供什么(被驱动/次端口),或外部世界可以要求应用做什么(驱动/主端口)。
  • 适配器是将端口连接到特定技术的实现。数据库适配器实现存储库端口。HTTP 控制器是驱动端口的适配器。
  • 驱动(主)适配器发起与应用的交互:HTTP 控制器、CLI 命令、消息使用者、定时任务。
  • 被驱动(次)适配器由应用调用:数据库存储库、电子邮件发送器、支付网关、文件存储。

这种对称性非常强大:应用无需知道驱动它的是 REST API 还是 CLI 命令,也无需知道它持久化到的是关系型数据库还是内存存储。

依赖规则

源代码依赖必须指向内层。内层的任何内容都不能了解外层的任何内容——包括名称、类型、接口或概念。

这是最重要的一条规则。其他所有准则都由此而来。

“指向内层”的含义:

  • 内层定义接口(端口);外层负责实现
  • 数据以简单结构(DTO、原始类型)的形式跨越边界,绝不使用框架对象
  • 领域层绝不导入基础设施层、表示层或应用层的内容
  • 应用层绝不导入基础设施层或表示层的内容

为什么这很重要:

  • 无需数据库、HTTP 服务器或消息代理,也能测试领域层
  • 框架可替换,只是实现细节
  • 无需了解部署拓扑,也能读懂业务规则

如何落实:

  • 使用静态分析或架构测试工具验证导入方向
  • 将代码组织成体现各层的模块或包,让违规情况一目了然
  • 代码审查应标记任何内层对外层类型的导入
  • 组合根(应用程序入口点)是唯一负责连接所有层的地方——它是了解所有内容的例外

层参考

层依赖方向职责应包含的内容不应包含的内容
领域层(最内层)不依赖任何内容业务规则、不变量实体、值对象、聚合、领域服务、领域事件、仓储接口框架导入、数据库查询、HTTP 概念
应用层依赖领域层用例编排应用服务、命令/查询对象、DTO、端口接口业务逻辑、直接调用基础设施
基础设施层依赖应用层 + 领域层技术能力仓储实现、API 客户端、消息队列适配器、文件系统访问业务规则、用例编排
表示层(最外层)依赖应用层用户/系统交互控制器、CLI 命令、API 端点、视图模型业务逻辑、直接访问数据库

有关各层的深入指南,请参阅 层级详解。

DDD 战术模式

战术模式为领域层提供结构。每种模式在架构中都有特定职责和位置。

模式目的层核心规则
实体具有身份和生命周期的对象领域层身份决定相等性
值对象不可变、无身份的概念领域层按属性判断相等,不产生副作用
聚合一致性边界领域层每个事务处理一个聚合,其他聚合通过 ID 引用
仓储以类似集合的方式访问聚合领域层中的接口,基础设施层中的实现每个聚合根对应一个仓储
领域服务跨实体的无状态逻辑领域层仅当逻辑不属于单个实体时使用
领域事件对已发生事项的记录领域层以过去时命名,载荷不可变
应用服务用例编排者应用层不包含业务逻辑——委托给领域对象处理
工厂创建复杂对象领域层封装构造不变量

有关各模式的详细指南,请参阅 DDD 战术模式。

DDD 战略模式

战略模式关注系统的大规模结构——如何将复杂领域划分为易于管理的部分,以及这些部分如何交互。

有界上下文

有界上下文是定义并保持特定领域模型一致性的边界。每个上下文都有自己的通用语言——同一个词在不同上下文中可能有不同的含义。

识别边界:

  • 不同团队或部门通常意味着不同的上下文
  • 当同一个术语(例如“Account”)对不同利益相关者有不同含义时,它们应属于不同的上下文
  • 上下文应足够小,便于一个团队负责
  • 根据业务能力划分上下文,而不是根据技术层
通用语言

在每个有界上下文中,团队(开发人员和领域专家)都使用精确的词汇。代码应直接使用这种语言——类名、方法名和变量名应准确反映领域概念,符合领域专家的描述方式。

维护通用语言的实用建议:

  • 如果开发人员无法向领域专家解释某个类名,那么这个名字就不恰当
  • 语言演变时要重构代码——重命名不只是表面工作,而是一项设计活动
  • 避免在领域层代码中使用技术术语——使用业务术语,而不是实现术语
  • 将这套语言记录在开发人员和领域专家共享的术语表中
上下文映射

上下文映射记录有界上下文之间的关系。它明确集成策略,避免集成方式由偶然因素决定。

模式关系适用场景
共享内核两个上下文共享模型的一个子集团队紧密协作,并愿意协调变更
客户-供应商上游上下文服务于下游上下文下游能够与上游协商
遵从者下游遵从上游模型没有协商能力;上游不会改变
防腐层转换层保护下游与遗留系统或外部系统集成
开放主机服务上游提供定义清晰的协议多个使用方需要稳定的访问方式
发布语言共享的交换格式通过标准模式进行跨上下文通信

有关集成模式和边界决策,请参阅上下文映射。

防腐层

与外部系统或遗留系统集成时,防腐层(ACL)会在外部模型与内部领域模型之间进行转换,防止外部概念渗入领域模型。

结构: 外部系统 -> ACL(适配器 + 转换器)-> 领域模型

ACL 属于基础设施层,并实现由应用层或领域层定义的端口。

CQRS — 命令查询职责分离

CQRS 是整洁架构和 DDD 的天然搭档。它将写模型(命令)与读模型(查询)分离,使两者能够分别优化。

它如何融入架构:

  • 命令经由应用层流转,修改聚合,并通过仓储持久化
  • 查询可以完全绕过领域层,读取经过优化的投影或视图
  • 写侧通过领域模型强制执行业务规则
  • 读侧针对使用方的需求进行优化——用于展示时,无需重建完整聚合

适合考虑使用 CQRS 的情况:

  • 读写模式差异显著(例如写入复杂但读取简单,或反之)
  • 读写的性能要求不同
  • 读模型需要无法映射到领域模型的反规范化视图
  • 正在使用事件溯源(CQRS 几乎总是与事件溯源配套使用)

不适合使用 CQRS 的情况:

  • 读写对称的简单 CRUD 应用
  • 增加的复杂度与领域需求不相称

事件溯源 — 简要概述

事件溯源将聚合的状态存储为一系列领域事件,而非当前状态快照。当前状态通过重放事件得出。

与整洁架构的关系:

  • 事件属于领域层
  • 事件存储属于基础设施关注点(专用仓储)
  • 投影(读取模型)基于事件,在基础设施层或应用层构建
  • 领域模型不依赖事件存储的实现

适用场景: 需要审计、状态转换复杂、需要进行时间查询(“时间 X 时的状态是什么”),或者领域事件已在设计中居于核心地位。

常见误解

  • “整洁架构意味着大量样板代码” — 分层会增加一些结构性代码,但换来的是明确的边界。如果样板代码多到难以承受,说明领域可能还不够复杂,不值得采用这种架构。
  • “每个应用都需要全部四层” — 小型应用可以合并应用层和表示层。关键边界在领域层与基础设施层之间。
  • “到处使用 DTO 就是整洁架构” — DTO 是跨越边界的一种机制,并非架构本身。架构关注的是依赖方向。
  • “领域模型必须映射数据库架构” — 实际情况恰恰相反。领域模型反映业务概念,基础设施层负责在领域模型与存储架构之间进行映射。
  • “六边形架构和整洁架构是两回事” — 它们是对同一核心理念的不同描述。选择团队更认同的说法即可。
  • “DDD 要求使用整洁架构” — DDD 战术模式可以在不严格分层的情况下使用,不过二者搭配效果最佳。反过来,整洁架构也不要求使用 DDD 模式。
  • “一个限界上下文 = 一个微服务” — 限界上下文是逻辑边界。它可以对应一个微服务、单体应用中的一个模块,或任何其他部署单元。

常见违规情况

违规情况症状修复方法
领域层依赖框架领域类导入 HTTP、ORM 或框架注解将基础设施关注点移至外层;在领域层使用普通对象
控制器中包含业务逻辑控制器包含条件逻辑、验证规则或计算将逻辑提取到领域对象或应用服务中
贫血领域模型实体只是包含数据的容器,只有 getter/setter;所有逻辑都位于服务中将行为移入实体和值对象;在聚合内部强制执行不变量
应用服务包含业务规则应用层包含复杂条件逻辑和领域计算将规则移至领域服务或实体方法中
基础设施关注点渗入内层领域层引用数据库列名或 API 响应结构在基础设施层引入映射;保持领域模型纯粹
上帝聚合一个聚合处理过多关注点,不断膨胀拆分为更小的聚合;使用领域事件进行跨聚合通信
不同上下文共用数据库多个限界上下文读写相同的表为每个上下文分离存储;通过事件或 API 集成
层之间存在循环依赖内层导入外层,导致编译错误或运行时耦合重新审视接口应由哪一层负责;反转依赖方向
跨边界复用 DTOAPI 响应、用例输入和领域逻辑使用相同的数据结构为每个边界创建独立的 DTO;在它们之间进行映射
缺少防腐层外部系统的模型污染领域词汇在基础设施层添加转换层

测试策略

分层架构有助于制定清晰的测试策略,每一层都有独特的测试方法:

层测试类型依赖项验证内容
领域层单元测试无 — 纯逻辑业务规则、不变量、值对象相等性、聚合一致性
应用层使用模拟端口的单元测试领域层 + 模拟的基础设施端口用例编排、是否正确调用领域方法、事务边界
基础设施层集成测试真实外部系统(数据库、API、队列)映射是否正确、查询行为、适配器保真度
表现层功能测试 / 验收测试完整技术栈或模拟的应用层请求解析、响应格式化、错误处理、状态码

核心原则: 大多数测试应当是快速的领域单元测试。基础设施集成测试数量较少,但可确保真实系统按预期运行。端到端测试覆盖关键路径,但应尽量精简。

质量检查清单

使用此检查清单评估架构的结构是否合理:

依赖规则

  • 领域层不导入应用层、基础设施层或表现层的任何内容
  • 应用层不导入基础设施层或表现层的任何内容
  • 所有跨边界通信均使用内层定义的接口/端口

领域层

  • 实体维护自身的不变量 — 无效状态无法表示
  • 值对象不可变
  • 聚合是保持一致性的单位 — 每个事务处理一个聚合
  • 仓储接口定义在领域层,而非基础设施层
  • 领域事件记录有意义的状态变化

应用层

  • 应用服务负责编排,但不包含业务逻辑
  • 用例定义明确 — 每个用例都有清晰的输入、输出和单一职责
  • 使用 DTO 跨边界传递数据 — 领域对象不会泄漏到外层

基础设施层

  • 仓储实现位于此层,而非领域层
  • 外部服务集成使用实现端口的适配器
  • 框架特定代码彼此隔离且可替换

表现层

  • 控制器保持精简 — 立即将任务委托给应用服务
  • 控制器、视图或 CLI 命令中不包含业务逻辑
  • 输入校验(格式、类型)在此处进行;业务校验在领域层进行

限界上下文

  • 每个上下文都有自己的通用语言
  • 上下文通过定义明确的接口(事件、API)通信,而非共享数据库
  • 防腐层可防止外部模型污染

测试

  • 无需任何基础设施即可测试领域逻辑
  • 使用模拟端口即可测试应用服务
  • 基础设施适配器有针对真实依赖项的集成测试
  • 验证业务规则的测试无需启动框架

最佳实践

  • 从领域入手 — 先对业务规则建模,再添加基础设施
  • 根据领域概念命名,而非技术模式(避免使用 SomethingManager、SomethingHelper)
  • 保持聚合小巧 — 大型聚合是设计异味,表明边界划分不当
  • 在所有层中优先使用组合,而非继承
  • 使用领域事件在聚合之间通信,而非直接耦合
  • 让无效状态无法表示 — 将校验逻辑放入值对象和实体构造函数
  • 保持应用层精简 — 如果其中包含业务逻辑,说明领域层过于贫血
  • 通过重构不断加深理解 — 第一个领域模型很少是最佳模型;随着理解加深,持续演进模型
  • 使用架构测试自动强制执行依赖规则
  • 逐步引入整洁架构 — 从最复杂的子域开始,再逐步扩展

评论

支持 Markdown