整洁架构
整洁架构
在过去几年我们看到关于系统架构的很多想法。这些包括:
每个这些架构产出的系统都是:
- 独立的框架。架构不依赖一些存在类库的特性。这样你可以像工具一样使用这种框架,而不需要让你的系统受到它的约束条件。
- 可测试。业务规则可以脱离
UI ,数据库,web 服务器或其他外部元素进行测试。 - 独立的
UI 。UI 可以很容易的更换,系统的其他部分不需要变更。例如,Web UI 可以被换成控制台UI ,不需要变更业务规则。 - 独立的数据库。你可以交换
Oracle 或SQL Server ,用于Mongo ,BigTable,CouchDB 或其他的东西。你的业务规则不与数据库绑定。 - 独立的外部代理。实际你的业务规则并不知道关于外部世界的任何事情。

简而言之,你会从使用整洁架构中获得以下好处:
- 数据库无关性:核心业务逻辑不用关心使用
Postgres 、MongoDB 还是Neo4J 。 - 客户端接口无关性:核心业务逻辑不关心你是否使用
CLI 、REST API,甚至是gRPC 。 - 框架无关性:使用
vanilla nodeJS 、express、fastify,你的核心业务逻辑也不关心这些。
依赖规则
同心圆表示软件的不同部分。大体上,你走的越远,软件的级别更高。外部的圆是机制,内部的圆是策略。让这个架构工作的覆盖规则是依赖规则。这个规则说明了源代码依赖只能向内。内部圆不能知道任何外部圆的事。实践中,外部圆里一些声明的名字不能被内部圆里的代码提到。这包括,函数,类,变量或其他任何软件实体。
同样的,外部圆使用的数据格式不应该被内部圆使用,尤其是当这些格式是被外部圆使用的框架生成的时候。我们不想让外部圆的东西影响到内部圆。
实体
实体封装企业域范围的业务规则。实体可以是一个有方法的对象,也可以是一组数据结构和函数。只要企业里不同的应用可以使用这些实体就可以。
如果你不是企业级,而只是写一个单体应用,那么这些实体就是应用的业务对象。它们封装了最通用和高层的规则。当外部变化时它们基本不太会变化。例如,你不会认为这些对象会因为页面导航或安全方面的变化而改变。任何特定应用的操作都不应该影响实体层。
用例
这层的软件包含特定应用的业务规则。它封装并实现了系统的所有用例。这些用例组织了实体中的数据流向,并指挥这些实体使用他们的企业域业务规则来完成用例的目标。
我们不期望这层影响实体。我们也不希望这层会在如数据库,UI,或其他常用框架这样的外部变化时被影响。这层隔离了以上关注点。
当然我们期望对于应用操作的变化会影响用例而进一步影响到这层的软件。如果一个用例的细节变化了,那么这层的代码肯定也会被影响。
接口适配器
这层的软件是一组适配器,其将数据转换成从用例和实体最合适的格式,到对于一些类似数据库或网站这种外部设施最合适的格式。在这一层,举个例子,会包含
类似的,数据被转换了,在这层,从对于实体和用例合适的结构,变成对于持久层框架使用的结构。这圈内的代码不应该知道数据库。如果数据库是一个
这层其他适配器也需要将数据从类似外部服务的外部的结构,转换成用例和实体使用的内部结构。
框架与驱动
最外层主要组合了数据库,网络框架这样的框架和工具。在这层你除了写一些与内层环通信的胶水代码,基本不会有其他代码。
这层是所有细节存在的地方。网络是细节。数据库是细节。我们将这些东西放在外部保证它们不会影响其他部分。
只有四个圈?
不是的,圆圈是个示意。你可能发现你需要不止
跨越边界
在图的右下方是我们穿越圆圈边界的示例。它展示了
我们通常使用依赖反转原则解决这个明显的问题。在
例如,用例需要调用
架构里所有的边界穿越都用这个技巧。我们使用动态多态来创建与控制流相反的源码依赖,以便于无论在控制流的任何方向都不会违反依赖规则。
什么样的数据会穿越边界
正常来说穿过边界的数据是简单数据结构。你可以使用基本结构或简单的
例如,很多数据库框架在查询后返回一个方便的数据格式。我们可以叫它
所以当我们在边界传递数据是,要注意其应该是内部圈的格式。