以下内容为原创,欢迎转载,转载请注明
来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5276587.html
一个干净的架构
原文:https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html
在过去几年中我们能看到的一系列关于系统架构的思想。它们包括:
-
Hexagonal Architecture(也称为
Ports and Adapters
),作者是 Alistair Cockburn,并被 Steve Freeman 和 Nat Pryce 在他们很棒的 Growing Object Oriented Software 这本书中采用。 -
Onion Architecture,作者是 Jeffrey Palermo。
-
Screaming Architecture,来自我去年写的一篇博客。
-
DCI,来自 James Coplien 和 Trygve Reenskaug。
-
BCE,来自 Ivar Jacobson 的一本书 Object Oriented Software Engineering: A Use-Case Driven Approach
尽管这些书在细节上有些不同之处,但是它们是非常相似的。从分离的观点上来讲它们都有着同样的目标。它们都是通过软件分层来实现分离的。每一个都至少有一层是用于业务规则,另外一层用于接口。
这些架构生产系统:
-
框架独立性。架构并不依赖于已经存在的某些库的有负载的特性。这允许你作为工具去使用框架,而不是把你的系统强塞到限制和约束中。
-
可测试性。业务规则可以脱离UI、数据库、web服务器和其它外部元素去进行测试。
-
UI的独立性。UI可以在不修改系统其它地方的情况下很容易地被改变。比如,一个Web UI可以使用console UI替换,而不改变任何业务规则。
-
数据库独立。你可以使用Mongo、BitTable、CouchDB或者其它来置换Oracle或SQL Server。你的业务规则不会被数据库束缚。
-
外部代理的独立性。事实上,你的业务规则根本不知道外面的世界。
这篇文章顶部的图表就是尝试所有这些结构到单个可操作的思想中去。
依赖准则
同心圆表示不同软件的区域。一般情况下,走得越远,软件水平也会变得越高。外面的圆是机制,而内部的圆是政策。
使得这个架构可行的最重要的规则是 依赖准则 。这个准则指 代码的依赖 只能 向内 。没有一个内部圆可以知道外部圆的任何东西。尤其是在内部圆声明的东西名字不能被内部圆中的代码提到。这里包括方法、类、变量、或者其它软件实体的名字。
同样的原因,外部圆中用到的数据格式不能被内部圆使用,尤其是这些格式是被外部圆中的框架生成的。我们不希望任何在外部圆中的东西影响到内部圆。
Entities
实体封装了 企业级 业务规则。一个实体可以是一个有方法的对象,或者有一系列的数据结构和函数。只要这个实体可以被很多不同企业中的应用使用就没关系。
如果你没有一个企业,并且只是编写单个的应用程序,那么这些实体就是应用程序中的业务对象。它封装了大部分一般的和高级别的规则。它们最不可能在外部改变的时候被改变。比如,由于安全你不希望这些对象被导航页面改变而影响。没有特别的可操作的改变可以影响实体层。
用例
软件在这一层包含应用程序指定的业务规则。它封装和实现了这个系统的所有用例。这些用例转化数据流到实体和从实体转化到数据流,然后这些实体直接使用它们企业范围的业务规则来达到用例的目的。
我们不期望改变这一层来影响到实体。我们也不希望这一层被外部的改变,比如数据库、UI、任何常见的框架等影响。在这一层在这的观点上是被孤立的。
然而,我们要做的是希望应用程序操作的改变会影响到软件的用例,所以应该是在这一层。如果用例的细节改变了,那么这一层的某些代码当然也会受到影响。
接口适配器
在软件的这一层是一系列的适配器,通过最方便的用例和实体转换数据格式,最方便的外部代理格式有数据库或者Web等。在这一层,举个例子,将会完整包含 GUI 的 MVC 架构。Presenters、Views、 Controllers 都属于这里。Models有可能仅仅是数据结构,它们从 contrllers 被传入到用例中,然后从用例到 presenters 和 views。
同样的,在这一层,数据会被转换,从最方便的方式如实体和用例,转换到使用的用于持久框架的最方便的方式,即数据库。
这个圆中没有代码可以向内知道任何关于数据库的东西。如果数据库是一个 SQL database,那么所有 SQL 应该在这一层被限制,特别是在这一层需要进行数据库操作的部分。
在这一层也有必要从一些外部形式去转换数据,比如一个外部服务,转化为内部用例和实体使用的形式。
框架和驱动
最外层通常是框架和工具,通常是数据库、Web 框架等的组合。通常你不需要在这一层编写太多的代码,除了一些用于向内圈进行通信的固定代码。
这一层是所有细节走向的地方。Web是一个细节。数据库是一个细节。我们把这些东西放置在外部,这样它们就难以造成伤害。
只有四个圆?
不,这些圆只是简图。你可能会需要多于这四个圆。这里并没有一个规则来让你必须要使用这四个圆。然而,依赖规则 总是适用的。代码依赖总是指向内部。当你向内移动时,抽象级别增加。最外层的圆是最低级别的具体细节。当你向内移动,就会变得更加抽象和更高级别策略的封装。最内部的圆是最通用的。
跨越边界
右下方的图是一个我们怎么去跨越圆的边界的例子。它展示了 Controllers 和 Presenters 通过下一层使用用例进行通信。注意控制流。它在controller中开始,通过用例,然后再Presenter中执行。也要注意代码的依赖。它们每一个都是向内指向用例。
我们通常使用 依赖反转准则 来解决这个明显的矛盾。在像Java的语言中,举个例子,我们使用了interfaces和继承关系,这样代码依赖关系控制权被反转,达到跨越边界的目的。
举个例子,考虑到用例需要调用Presenter。然而,这些调用必须不能是直接的,因为它会违反_依赖准则_:内部圆不能提到外部圆中的任何名字。所以我们这种情况我们会调用在内部圆中的接口(就像这里展示的 Use Case Output Port)),然后在外部圆中的Presenter去实现它。
同样的技术在架构的所有跨越边界的地方被使用到。我们利用动态代理的优势去创建代码依赖来达到控制反转,所以我们可以确保无论什么方向的 依赖准则 控制流都能有效。
什么数据跨越边界。
典型的跨越边界的数据就是简单的数据结构。如果你喜欢你可以使用基本结构或者简单的数据传输对象。或者是方法调用时的简单的参数数据。或者你可以把它放进一个HashMap,或者构建它到一个对象中。重要的是分离的、简单的数据结构跨越过边界。我们不希望去欺骗并传递 Entities 或者数据库的行。我们不希望数据结构有任何的违反 依赖准则 的依赖。
举个例子,很多数据库框架通过一个查询返回一个方便的数据形式的响应。我们可能称它为一个RowStructure。我们不希望这个行结构向内跨越边界。这将违反 依赖准则 因为这将强制一个内部圆去知道外部圆的一些东西。
所以当我们跨越边界传递数据时,它对于内圆来说总是以最方便的形式的。
遵守这些简单的规则并不难,前进的道路上会解决很多头疼的事情。通过分离软件到不同层次,并遵守 依赖准则 ,你将会创建一个本质上可测试的系统,这意味这所有的好处。当任何系统外置的部分变得过时,就像数据库,或者web框架,你可以在最小的改动下替换这些外置元素。