清洁架构系列 - 第 3 部分
焦虑源于无法看清全局。如果你感到焦虑,但又不确定原因,那就试着整理一下你的东西。——近藤麻理惠
清洁架构🧹
我们已经了解了六边形架构和洋葱架构是什么,因此下一步是回顾一下清洁架构的含义。
您可以在这里查看有关六边形和洋葱架构的前两篇文章:
什么是清洁架构?
罗伯特·C·马丁(鲍勃大叔)于 2012 年提出的架构模式,试图在架构模式中更进一步,实现代码的独立性、可维护性、可测试性、可扩展性、可演化性以及编写良好的代码。鲍勃大叔遵循与六边形和洋葱类似的原则,用以下图表展示了他的架构:

该架构的主要目标是关注点分离。Bob大叔提到,他并不想创建另一个与六边形和洋葱架构完全不同的全新架构。事实上,他希望将所有这些优秀的架构整合成一个可操作的单一理念。
该架构的关键原则是:
- 这实际上是关于关注点分离
- 应该独立于框架
- 它们应该是可测试的
- 它们应该独立于 UI
- 它们应该独立于数据库
- 清晰架构图— 最 内层:“企业/关键业务规则”(实体) — 下一层:“应用程序业务规则”(用例) — 下一层:“接口适配器”(网关、控制器、演示器) — 外层:“框架和驱动程序”(设备、Web、UI、外部接口、DB)
- 最内圈是最通用/最高级别的
- 内部圈子是政策
- 外圈是机制
- 内圈不能依赖外圈
- 外圈不能影响内圈
是的,这些原理来自六边形和洋葱架构,正如您现在一定想知道的那样。
此外,所有这些原则都遵循相同的规则,即……
依赖规则
同心圆代表软件的不同领域。通常,越往内层,软件的级别就越高。外圈代表机制,内圈代表策略。
这意味着什么?
内圈中的任何事物都无法知晓外圈中的任何事物。具体来说,外圈中声明的事物的名称不得在内圈的代码中提及。这包括函数、类、变量或任何其他命名的软件实体。
例如:
位于实体圈(企业业务规则)中的元素不应引用其外部的任何元素(例如应用程序业务规则、接口适配器、框架和驱动程序)。
外圈使用的数据格式绝对不能被内圈使用,尤其是由外圈框架生成的数据格式。清晰架构 (Clean Architecture) 可以防止外圈中的任何内容影响内圈,如下图所示:
考虑到这一点,让我们看一下图中定义的圆圈
清洁架构圈

实体
- 实体封装了企业范围的业务规则。
- 实体可以是具有方法的对象,也可以是一组数据结构和函数。只要实体能够被企业中的许多不同应用程序使用,那么实体的类型就无关紧要。
- 如果您没有企业,而只是在编写单个应用程序,那么这些实体就是该应用程序的业务对象。它们封装了最通用、最高级的规则。
用例
- 用例是特定于应用程序的业务规则。变更不应影响实体。变更不应受到数据库等基础设施的影响。
- 用例协调实体的数据流进/出,并指导实体使用其关键业务规则来实现用例。
接口适配器
- 将数据从数据层转换到用例层或实体层。Presenter、视图和控制器都属于此类。
- 进一步的代码(用例、实体)不应该具有任何数据库知识。
框架和驱动程序
- 这些是将各个层连接起来的胶水。
- 基础设施详细信息在此处。
- 您没有编写太多此类代码,即我们使用 SQL Server 驱动程序,但我们没有编写它。
您可以根据需要添加任意数量的圆圈。这些只是示意图。正如 Bob 叔叔提到的,您可以添加所需的所有圆圈/图层,但始终要遵循依赖规则。
所以问题是,我们有这些圈子,我们遵循依赖规则,但我该如何跨越这些圈子的界限呢?
如何跨越圆界?
这个小截图是鲍勃叔叔提出的原始图表的一部分,展示了控制器和演示者如何与下一个圈/层中的用例进行通信的示例。
控制流从控制器开始,经过用例,然后结束执行演示者。
如果你还记得的话,依赖规则规定,外圈中的任何名称都不能被内圈提及。而按照控制流,用例需要调用演示者。那么,如何在不违反依赖规则的情况下解决这个问题呢?有两种处理方法
- 通过使用 SOLID 原则中的依赖倒置原则,我们可以在内圈定义一个端口(接口)并在外圈中实现它。
- 通过使用 Mediator 这个很棒的设计模式,我们可以定义一个对象来封装一组对象如何交互。
从我的角度来看,我更喜欢使用中介者模式。它有两大优点,并且与清晰架构 (Clean Architecture) 非常契合:
- 对象将其交互委托给中介对象,而不是直接相互交互。
- 应该可以独立地改变一组对象之间的交互。
主持人
这是我们在控制流图中看到的另一个概念。Presenter的目的是将用例与 UI 的格式解耦。
它们是为了解耦 Interactor 和视图而创建的,因此如果我们使用它们,就可以遵循依赖规则。如果我们从 Interactor 返回 API 的响应模型,我们就会将内层与外层耦合,这样就不符合依赖规则了。
简洁的六边形洋葱架构
在上一个关于如何处理控制器和用例之间通信的示例中,我们已经了解了整洁架构如何使用端口和适配器模式。此外,如果我们回顾洋葱架构的关键原则,我们必须记住它是一个由用例驱动的架构。因此,通过这些用例,我们将介绍洋葱架构原则的用法。
这再次印证了我在本文开头提到的内容。清洁架构、六边形架构和洋葱架构并非相互竞争,看谁更酷或更好。 它们都试图找到一种方法来分离关注点,而清洁架构则专注于将先前架构模式中的原则与更多原则(例如稳定依赖原则、 稳定抽象原则、SOLID 等)相结合。
那么,我应该如何构建我的代码呢?
首先,这不是教条。它的好处是众所周知的,并且应该 100% 适用于许多业务规则不断变化且功能不断增加的企业项目。但对于简单的后台流程服务或无服务器功能,你或许不需要这样做。这取决于你。
但如果你真的想尝试,那就去做吧。试着把所有东西都落实到位,理解所有概念,以及为什么应该把东西放在那个圆圈/层里。尝试之后,把你的样板与 GitHub或其他网站上其他干净架构的项目进行比较。
下一章将重点介绍如何按照此架构构建 API,但现在我想分享该 API 的样板:
├── src | |
│ ├── Api # API layer | |
| | ├── UseCases # API business rules in use cases | |
| | | ├── GetTodos # Use Case to get all the todo tasks | |
| | | | ├── TodoController.cs # Todo Controller for the Get All | |
| | | | ├── GetTodosPresenter.cs # Presenter | |
│ ├── Application # Application layer | |
| | ├── Boundaries # Input and output ports helping us to cross boundaries | |
| | ├── Services # Application services to handle application business logic | |
| | ├── UseCases # Use cases interactors | |
│ ├── Domain | |
| | ├── Aggregate root folders # Domain layer following DDD | |
| | ├── (entities, domain services and repositories interfaces per aggreagate root) | |
│ ├── Infrastructure # Infrastructure layer | |
| | ├── Repositories # Implementation of our repositories | |
| | ├── Clients # i.e. implementation of some http clients to retrieve data | |
└── ... |
这是 API 层级和文件夹结构的简化版本,有时你需要使用更多架构模式,有时则需要更少。这类事情的关键在于理解规则,并每天学习新知识。
结论
我认为,每天都有越来越多的开发人员尝试了解如何遵循最佳实践来编写高质量的代码,以便添加更多功能、在测试中获得良好的代码覆盖率、轻松扩展代码规模,并保持代码无 bug 和糟糕的“意大利面条式代码异味”。
我见过数十个遗留项目几乎无法维护,大量的 bug 和不良实践导致了性能问题、永无止境的功能开发以及开发人员的倦怠。

在过去的几年中,清洁架构模式在 Android 开发中变得越来越流行,我真的希望它在后端架构甚至前端变得更加流行。
想象一下每天创造伟大的东西、为复杂的业务场景创建精心设计的解决方案并确保您可以添加新功能而不会浪费大量时间和心理健康的满足感。
当然,有些开发人员不喜欢这种创建干净的代码/架构的想法,或者他们会说“这看起来过度设计了”。
我不喜欢回复这种评论。但近藤麻理惠会:
我们无法放手的原因有两个:对过去的执着或对未来的恐惧。
记得整理好你的代码,她正在看着你。
本系列的下一篇文章将更加实用,使用.NET Core 构建一个简单的 API 并访问真实数据。
此外,如果您喜欢这篇文章,请随意分享并表达您的喜爱!
如果您已经读完了这篇文章,非常感谢。欢迎在领英上与我联系!
遵循清洁架构的存储库
- https://github.com/android/architecture-samples/tree/todo-mvp-clean
- https://github.com/ivanpaulovich/clean-architecture-manga
- https://github.com/bufferapp/android-clean-architecture-boilerplate
- https://github.com/eduardomoroni/react-clean-architecture
- https://github.com/jasontaylordev/CleanArchitecture
- https://github.com/ardalis/CleanArchitecture
- https://github.com/carlphilipp/clean-architecture-example
参考
- https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
- http://xurxodev.com/por-que-utilizo-clean-architecture-en-mis-proyectos/
- http://jmperezramos.net/desarrollo-en-android/visteme-con-clean-architecture-que-tengo-prisas/
- https://github.com/ivanpaulovich/clean-architecture-manga/wiki/Flow-of-Control
- https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/
- https://groups.google.com/forum/#!topic/clean-code-discussion/Ehwo7yIHUPQ
- https://vrgsoft.net/blog/clean-architecture-for-android/