整洁架构
翻译自Bob大叔的 https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

最近这些年来,我们看到过很多框架设计的想法,这包括:
- 六边形框架(端口和适配器) Alistair Cockburn提出,Steve Freeman和Nat Pryce在他们精彩的《Growing Object Oriented Software》一书种采用。
- 洋葱框架 Jeffrey Palermo
- Screaming Architecture 来自去我去年的博客
- DCI James Coplien, and Trygve Reenskaug
- BCE Ivar Jacobson的《Object Oriented Software Engineering: A Use-Case Driven Approach》书中提到
尽管这些架构在细节上个有不同,但又都很相似。 他们有这同样的目标,即关注分离。它们通过分层的方式,至少拥有一个业规则层和一个接口层,来解决这个问题。
每个架构都有以下特点:
- 框架独立。架构并不依赖那些功能丰富的软件库。我们可以像使用工具一样使用框架,而不是必须被它们约束。
- 容易测试。即使没有数据库、UI、WebServer和其它外部原件,业务规则依然可以测试。
- UI独立。UI可以很容易的变化,比如一个Web UI可以在不变更业务规则的情况下被替换成一个控制台界面UI。
1.数据库独立。你可以将Oracle、SqlServer,更换成其它数据库Mongo、BigTable、CouchDB或者其它。你的业务规则并不依赖数据库。
1.独立于任何存在的形式。事实上,你的业务规则对外界一无所知。
本文顶部的图片试图将所有架构融合到一个可实现的想法中。
依赖原则
每个同心圆代表软件的不同区域。 通常来说,越深的层次,软件的级别越高。 外圆是方法,内圆是规则。
是这个架构正常工作的就是依赖规则。这个规则规定,代码层面只能向内依赖。内圆不能了解外圆的任何事项。特别的是,在外圆种声明的信息,包括函数、类、变量或者任何软件实体,都不能被内圆的代码引用到。
同样,外圆种的数据格式,尤其是那些框架种生成的,也坚决不能被内圆使用到。 我们不希望外圆种的任何事情影响到内圆。
实体
实体封装了业务规则。一个实体可以是一个拥有方法的对象,或者是数据结构和函数的集合,只要是可以被不同的应用使用就可以。
如果没有XX,只有一个单独的应用,那么实体就是这个应用的业务对象。它们封装了一般的和高级的规则。当外部变化时,它们是最不容易变化的。 比如,它们不会因为分页或者安全问题而产生变化。任何特定的应用层变化都不应该影响的实体层。
用例
该层包含应用的业务规则。它概括和实现了整个系统的用例,这些用例编排实体之间的数据流,并让这些实体使用其企业范围的业务规则来实现用例。
我们不期望该层的改动会影响到实体层,我们也不期望该层会由于外部改动比如数据库、UI或者通用框架的改动而影响。该层和这些问题是隔离的。
应用的操作应该要影响到用例层和上面的软件。如果用例的细节改动了,那么该层的代码也会被影响。
接口适配器
该层种的软件是一组适配器,它们将便于用例和实体的数据格式转换成外部(比如数据库和Web)使用的数据。比如这层包含整个GUI的MVC框架,Presenter,View和Controller都在这里。这个模型基本上是,数据从controller流转到用例,然后再从用例返回的presenter和view。
同样,在这一层中,数据从便于实体和用例的格式转换为便于正在使用的持久性框架(即数据库)的格式。这个圆内部的代码都不应该知道关于数据库的任何信息。如果数据库是一个SQL数据库,那么所有的SQL都应该限制在这个层,特别是这个层中与数据库有关的部分。
将外部服务提供的数据格式转换成内部用例和实体使用的数据格式的适配器,也是在这一层种。
框架和驱动
最外层的圆是由像数据库、Web框架等这种框架和工具组成。通常除了将代码粘贴过来向内部传递数据,这一层不会有太多工作。
这一层是所有细节所在,Web是细节,数据库也是细节。我们将这些放在最外层是让它们不会产生什么危害。
只有4个圆?
并不是,图中只是例子,没有规定只能是这4个,实际上肯定需要超过这4个。无论如何,依赖规则总是固定的,代码中外侧代码依赖内侧代码,当你向内移动的时候,抽象的层次就增加了。外层圆的逻辑是内层的实现细节,越靠近圆心,软件越抽象,封装的越完全。最里面的圆就是核心的业务规则。
跨越边界
在图的右下方有个例子,描述了我们如何跨域圆的边界。它演示了controller和view如何与下一层的用例进行通信。请注意这个控制流程,它从controller开始,穿过用例层,在presenter中执行。还要注意代码依赖性,它们中的每一个都指向用例的内部。
我们通常用依赖倒置 来解决这一明显的矛盾。在像Java这样的语言中,我们将使用接口和继承关系,使得源代码依赖性在边界正常的点上控制反转。
例如,假如用例需要调用presenter。但是不能直接调用,因为这将违反依赖关系规则:内部圆不能涉及外部圆中的任何逻辑。因此,我们在内环中有一个用例调用接口(这里显示为用例输出端口),并让外环中的presenter实现它。
架构中涉及到跨边界操作的都使用这个技术,我们利用动态多态性来维护 代码依赖,这样无论控制流的方向如何,是向内还是向外,我们都可以遵循依赖规则。
哪些数据跨域边界
跨越边界的数据都是简单的数据形式,你可以使用基础的数据结构,简单的数据传输对象,封装成hashmap,或者构建城对象。 重点是,分离独立且简单的数据才可以跨界。不能传输实体或者数据库行,我们不希望这些数据结构有任何违反依赖规则的依赖关系。
例如,很多数据库框架对于查询都返回了一个的数据格式,我们通常叫RowStructure。我们不能将RowStructure传到内部因为这将违反依赖规则,它会需要内部的圆了解外部圆的信息,
因此当我们需要将数据穿过边界时,总是使用最适合内部圆的数据形式。
结论
遵循这些简单的规则并不困难,并且可以减少很大一部分头痛的时间:)。通过将软件分则并且遵循依赖规则,你将创造一个可以测试的,并且有很多其它益处的系统。任何系统的外部模块,像数据库或者web框架,废弃时,你都可以几乎无成本的替换它们。





