整洁软件架构

原文地址: http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

这篇文章是Bob大叔在2012年8月分写的,现在读来依然带感。首先,大叔说自己这几年来看过各种各样的系统架构的想法,从中学到了很多东西,这就是他写这篇文章的原始素材。这些架构思想包括,但不限于:

  • Alistair Cockburn提出的六角架构,Steve Freeman和Nat Pryce的《Growing Object Oriented Software》一书中就使用了这种架构。
  • Jefferey Palermo的洋葱环形架构
  • Bob大叔自己2011年提出的"Screaming Architecture"
  • James Coplien和Trygve Reenskaug提出的DCI(Data, Context and Interaction)架构
  • Ivar Jacobson在《面向对象工程》一书提出的BCE(Boundary Control Entity)架构

所有的这些架构在细节上虽然各有不同,但是它们在大方向上却有很多相似性。它们诞生的目的都是一样的,就是隔离出不同的关注点;它们的解决方法也一致,就是通过分层架构;它们都至少有一层表示业务逻辑,一层表示接口。基于这些架构的系统有如下的特点:

  • 独立于技术框架之外,技术框架只是支撑架构的工具,而不是让架构去适应技术框架的各种限制。
  • 可测试性高,业务逻辑可以在没有UI,DB,服务器等任何外部元素下进行测试。
  • UI隔离性高,UI的变化不会影响到系统的其他部分。
  • DB隔离,可以很容易的在各种DB之间切换。
  • 业务逻辑独立,不受任何外在世界变化的影响。

把所有这些观点都揉进一张图里的话,大概就是下面这个样子:


依赖规则
上图中,每一个圆环代表软件系统的一个方面,总的来说,外环越多,软件越复杂。外环是软件的实现方式和展现形式。内环则是基础的业务逻辑策略。

维持整个圆环的最重要的规则就是依赖原则;这个原则的要求是所有的源代码依赖只能从外向内,内环的代码不能依赖任何的外环的代码。这其中包括外环的类,方法,参数,甚至是数据格式。总之,任何外环的代码改变都不太应该影响到内环的代码。

实体层

实体层封装了基础的业务逻辑,基本上就是定义实体,实现实体定义上应该提供的方法。实体层可以是一堆对象,甚至就一个对象,也可以是一堆数据和方法。这一层是最少受外在改变影响的。比方说,在页面导航方式或者说安全验证方式上的改动,就不应该影响到实体层。事实上,任何操作方式的变化都不应该影响到实体层的代码。

用例层
这一层主要封装的针对应用的业务逻辑,它覆盖实现了当前软件系统的所有用例。这一层主要做的事情就是组织整个系统的业务流,通过调用实体层中各个实体的方法来实现用例需求。对于用例层来说,其依赖于实体层提供的方法,但是不希望用例层的改动不应该影响到实体层,应该只是调用不同的实体层方法,或者按不同的顺序调用。 同样的,用例层应该也不应受外在UI,框架的变化影响。只有当用例或者实体层发生变化时,才需要改动用例层。

适口适配层
这一层的主要作用就是提供一系列的接口适配器,把数据从用例层和实体层最习惯的格式转换为外部用户(DB,Web页面)最方便使用的格式。大叔甚至说,整个MVC架构都可以放到这一层里面,View和Controller本来就属于这一层,而Model层则只是用于和用例层传递数据。因为这一层的作用是数据结构的转换,但是需要注意是的这一层是不对外环的对象做任何依赖的,比方说,适配层需要把用例的数据持久化到数据库,那么该层就会承担起把用例的数据转化为最方便持久化的格式,比方说SQL,但是本层不会去关心具体存储到那个数据库中,本层的任何代码都不应该对某个特定的数据库产生依赖。当然,除了内环转外环,还可能有外环转内环的接口适配。

框架和驱动层
这一层主要就是包括所有支撑系统的框架和工具了。比方说,DB,Web框架等等。这一层除了把框架装配到接口上,基本上只需要写很少的代码,但是这一层会涉及到很多的细节,Web是细节,具体使用哪个数据是细节 ,当然也是改动最为频繁的一层。

只能有四层?
当然不是,上面的洋葱图只是一个简图。没有规定只能有四层,或者只能用这四层。但是,我们必须遵守依赖原则。系统越往内走,抽象层次越高,越往外抽象层次越低,会涉及更多的细节的东西。

跨越圆环边界
在洋葱图的右下角,大叔画了一幅跨越圆环边界的示例图,图中的控制流程,发起于Controller,然后在用例层转了一圈,最后在Presenter中结束。在这个示例中,用例层需要调用Presenter以完成这个流程,但是用例层不能直接调用Presenter的方法,因为这样会破坏依赖原则。对于这样的矛盾,常用的解决方案是依赖倒转,像在Java世界,就可以通过在用例层定义接口,Controller,Presenter去具体实现这些接口,这样用例层就不用知道外环的状况,而外环则根据自己的需要去实现响应的接口就行了。如例图中的Use Case Output Port.

数据跨越边界
通常情况下,跨越边界的数据就是一些简单的数据结构,你可以视情况使用最简单的数据结构或者简单的DTO。不管怎样都好,但是当数据需要跨越边界时,必须注意不要破坏依赖原则,不要直接传递外层实体,不要传递数据库表记录,不要传递任何和外环有关联的数据结构。因此,数据跨越边界传输时,需要有2个原则,一是以最方便内层使用的方式,第二就是不要使用对外环有任何依赖的格式。

结论
遵从上面提到的原则并不困难,但是却会为你避免很多头疼的问题。通过把软件架构分层,并遵从依赖原则,那么你构建的系统将会非常容易测试。同时,当系统的外环构件已经过时时,系统可以以最小的成本对其进行技术栈升级,这样可以避免很多遗留系统技术栈无法升级的弊病。

猜你喜欢

转载自ningandjiao.iteye.com/blog/1897114