过去几年,我们已经看到了一系列关于系统架构的想法,包括:

这些架构有很多共同的点(思想),尽管它们细节上有所不区别,它们都有相同的目标,那就是关注点分离(the speration of concerns), 它们都是通过将软件分层来实现这种分离,每个组件至少有一个用于业务规则的层和另一个用于接口的层。 这些架构中每一个都会产生这样的系统:

  1. 独立的框架:该架构不依赖于某些特性软件库的存,
  2. 可测试的(Testable):可以在不依赖UI、数据库、Web服务器或任何其他外部依赖的情况下进行测试
  3. 独立于UI:UI可以轻松的更改,而无需更改系统的其余部分。例如用控制台UI(终端)代替Web UI,而不需要更改业务规则。
  4. 独立于数据库:你可以切换底层数据库,你的业务业务规则不应该于数据库绑定
  5. 独立于任何外部依赖:你的业务规则根本不了解外部世界

本文顶部的图是尝试将所有这些体系结构集成到一个可行的想法中的尝试。

依赖性规则(The Dependency Rule)

同心圆表示软件的不同的领域。一般来说,随着软件的发展,软件会越来越高级(可以简单理解为分层越多)。越靠近外部的圆代表一种 机制(mechanisms),越内部的圈代表一种政策(policy)。依赖性规则是软件架构很重要规则,该规则定义了源码性依赖(source code dependencies)只能指向内部,内层不需要关注任何外层的逻辑。内层的代码不能够引用外层的代码,包括函数,类,变量等。

实体(Entity)

实体封装了企业范围的业务规则。实体可以是一个具有方法的对象,也可以是一组数据结构或函数,只要实体可以被企业多个应用程序使用,都可以成为实体。 如果你只是编写一个简单的业务程序(不涉及企业),那么该程序的业务对象就是实体。实体封装了最通用和最高级的规则。当某些外部变化时,它们变化的 可能性最小。例如,你不可希望当修改一个前端页面时,需要修改这些实体对象。对任何外层的修改都不会影响到实体层。

用例(Use Cases)

这一层通常是应用程序的业务规则(Application Specific),它封装并实现了应用程序的所有用例,这些用例编排流入和流出实体层的数据,通过这实体所实现 的企业业务规则实现用例业务规则。 我们不希望这一层的改变影响到实体层,同时也不会希望外层如数据库的实现影响到这一层。

接口适配(Interface Adapters)

这一层的软件(或模块)是一组适配器,用来将用例层获取的数据转换为外层依赖(数据库,HTTPServer)的数据格式.例如GUI的MVC结构通常在这一层实现。 实图、演示者和控制器都属于这一层。 同样,这一层也会发生数据转换,在这一层中数据会从用例层的结构装换持久性框架的结构比如数据库,这一层不需要关注任何数据库实现的逻辑

框架和驱动程序(Frameworks and Drivers)

最外层通常是由框架和工具组成,如数据库、Web框架,通常情况下这一层不需要有太多的代码,只是一些粘合下层的代码。这一层是所有细节所在,WEB是一个 细节,数据库是一个细节,我们将这些放在外面,方便后续修改而不需要改动底层。

是不是只有四层?

当然不是,四层只是理想下的软件分层模型,实际情况根据需求调整。依赖性原则始终适用,源码依赖性始终是从外层指向内层的,越往内层移动,抽象级别越高。

跨越边界(Crossing boundaries)

上图的右下方是我们如何越过圆边界的示例。它显示了控制器和演示者在下一层与用例进行通信。注意控制流程。它从控制器开始,遍历 用例,然后在演示者中结束执行。另请注意源代码依赖性。他们每个人都指向用例。我们通常通过使用依赖倒置原则解决这种明显的矛盾。 例如,在Java之类的语言中,我们将安排接口和继承关系,以使源代码依赖项在跨边界的正确点处反对控制流。例如,考虑用例需要呼叫 演示者。但是,此调用不能直接进行,因为这会违反“依赖关系规则”:外层中的任何定义都不能由内层提及。 因此,我们在内层中有一个 用例调用接口(在此处显示为用例输出端口),并在内层中有一个演示者来实现它。使用相同的技术来跨越架构中的所有边界。 我们利用 动态多态性来创建与控制流相反的源代码依赖项,以便无论控制流的方向如何,我们都可以遵守“依赖关系规则”。

什么数据需要跨越边界

通常,跨越边界的数据是简单的数据结构。如果愿意,可以使用基本结构或简单的数据传输对象。或者,数据可以只是函数调用中的参数。 或者,您可以将其打包到一个哈希图中,或将其构造到一个对象中。重要的是,隔离的,简单的数据结构跨边界传递。我们不想欺骗并传 递实体或数据库行。我们不希望数据结构具有任何违反《依赖性规则》的依赖性。例如,许多数据库框架都响应查询返回方便的数据格式。 我们可以称其为RowStructure。 我们不想将该行结构向内跨边界传递。 这将违反“依赖关系规则”,因为它会迫使一个内圈知道一些 关于外圈的信息。因此,当我们跨边界传递数据时,数据总是以最方便内层形式出现。

总结


遵循这些简单规则并不难,并且可以避免许多麻烦。通过将软件划分为多个层,并遵循“依赖关系规则”,您将创建一个具有内在可测试性 的系统,并具有其所隐含的所有优势。当系统的任何外部部分(例如数据库或Web框架)过时时,您都可以以最少的麻烦替换那些过时的元素。

原文

The Clean Architecture