【译】Clean Architecure
引入
我们都知道编写一个高质量的软件是困难并且复杂的。它不仅需要满足需求,还要足够健壮,可维护,可测试以及灵活,这样才能够适应产品的不断变化。这时就提出了一个叫做clean architecture的概念,这个架构可以被用来开发任何的软件应用。
CleanArchitecture.jpg好的架构都做了一件事:关注的分离。将代码分成多个层次结构,至少有一层是业务层,其他层次都为接口。
clean architecture的概念很简单,它是生产开发中一系列实践的结晶,具有一下的特性:
-
独立的框架
设计的架构不依赖于现存的类库的功能实现,是要求你把架构设计成一个工具,即插即用,而不是让你的系统充满限制和约束。
-
可测试性
业务规则可以在没有UI、数据库、服务器等一切外部因素的情况下被查测试。
-
独立的UI组件
UI可以在不改变系统其他部分的情况下被轻松改变。例如,在不改变业务规则的情况下,一个Web UI可以被替换成控制台UI。
-
独立的数据库
架构的业务规则不和数据库绑定,可以任意切换数据库。
-
独立的外部代理
架构的业务规则完全不需要知道外面的具体实现。
依赖规则
上图的同心圆表示了软件的不同组成部分。总体来说,越靠近圆心,层级越高。外圈部分是软件的实现机制,内圈部分则是软件的策略。
使得架构能工作的最重要的规则就是依赖规则。此规则表示了源代码的依赖只能指向内部。内圈中的东西是不知道外圈中的任何东西的。尤其是外圈生命的属性是不能在内层出现的,这其中包括了方法、类、变量一起其他一切命名的实体。
实体
实体封装的是企业级的宽泛的业务规则。一个实体可以是一个有实现的对象,或者是一系列的数据结构和方法。关键的是定义的实体可以用在企业中不同的应用里。
如果你不是写一个企业级的架构,而是为了编写单个应用。那么这些实体就变成了该应用的业务对象。它们封装了最通用和最高层次的规则。它们是最不长变动的。任何特定应用程序的操作更改都不应该影响实体层。
用例
该层中的软件包含特定于应用程序的业务规则。它封装并实现了系统的所有用例。这些用例协调数据流到实体的流动,并指导这些实体使用他们的企业范围的业务规则来实现用例的目标。
我们不期望这个层的改变会影响实体。我们也不希望这一层受到外部性的改变的影响,例如数据库、UI或任何公共框架。这一层与这些担忧隔离开来。
然而,我们期望应用程序的操作的改变会影响用例,并因此影响该层中的软件。如果用例的细节发生变化,那么这个层中的某些代码肯定会受到影响。
接口适配器
该层中的软件是一组适配器,它将数据从最适当的用例和实体转换为最适当的一些外部代理(如数据库或Web)的格式。例如,这个层将完全包含GUI的MVC体系结构。Presenter、View和Controller都属于这层。model很可能只是从控制器传递到用例的数据结构,然后从用例返回到演示者和视图。
类似地,在这个层中,数据被从实体和用例最适当的形式转换成最容易使用的任何持久性框架的形式。即数据库。这个圈内的代码完全不知道数据库。如果数据库是SQL数据库,那么所有SQL都应该被限制在这一层上,特别是这个层与数据库相关的部分。
在这个层中,还需要任何其他适配器来将数据从一些外部形式(例如外部服务)转换成用例和实体使用的内部形式。
框架和驱动程序
最外层通常有框架和工具组成,例如数据库,web框架等等。在这层,除了和内层交互的代码外,你不用写太多代码。
这层是实现的细节,例如web 和 数据库的具体实现,将这些细节放在最外层可以减少侵入性。
四层圈的结构是一个普遍设计,具体设计的时候还是要根据具体情况进行改造,但是依赖规则是普遍适用的,最外层是具体细节,越往内层级越高,就越抽象,也就越一般。
跨越边界
在图的右下角是我们如何跨越圆边界的例子。它显示Controller和Presenter与下一层中的用例通信。注意flow of control箭头。它从Controller开始,移动经过用例,然后在Presenter中执行。还要注意源代码的依赖关系。它们中的每一个向内指向用例。
我们通常利用依赖反转原则来解决这个明显的矛盾。像java语言,例如,我们会设置接口和继承关系,这样源代码依赖相对于控制流能在正确的点跨越边界。
例如,考虑这样的用例,它需要调用Presenter,然而并不能直接调用,因为这违背了依赖规则:内圈不能调用外圈的实体。所以我们在内圈de 用例调用一个接口,让外圈的presenter去实现它。
相同的技巧可以被用来跨越架构的所有边界。我们利用动态的多态性来创造相对于控制流的源代码依赖。这样就可以遵循依赖规则而不用关心控制流的流向。
什么样的数据要跨越边界
通常,跨越边界的数据是简单的数据结构。如果你愿意,可以使用基本结构或简单数据传输对象。或者数据可以简单地是函数调用中的参数。或者你可以把它打包成一个哈希表,或者把它构造成一个对象。重要的是孤立的、简单的数据结构可以通过边界。我们不想欺骗和传递实体或数据库的行。我们不希望数据结构具有违反依赖规则的任何依赖性。
例如,许多数据库框架会响应查询返回适当的数据格式。我们可以称之为行结构。我们不想通过边界向内传递该行结构。这将违反依赖规则,因为它会迫使内圈了解外圈的一些东西。所以当我们传递数据跨越边界,数据总是最适合内圈的格式。
总结
设计架构符合依赖规则,将获得最少改动。