编辑
2022-05-07
开发
00
请注意,本文编写于 1111 天前,最后修改于 966 天前,其中某些信息可能已经过时。

目录

依赖规则
实体
用例
接口适配器
框架和驱动
只有四层?
跨越边界
跨越边界数据格式
结论
结合现有项目的思考

attach_16ecc0608296b8eb.jpg

过去的几年,我们已经见过很多系统架构的设想。它们是:

  • 由阿利斯泰尔·科伯恩设计的六边形架构(别名:端口和适配器结构),斯蒂夫·弗里曼和奈特·皮亚斯采纳了这种架构,并把这种架构写在他们的《Growing Object Oriented Software》书中。
  • 杰弗瑞·巴勒莫的洋葱架构
  • 我去年在博客中提到的尖叫架构
  • 詹姆斯·科普林和瑞恩·斯科格的DCI
  • 伊瓦尔·雅各布森在他的著作《Object Oriented Software Engineering: A Use-Case Driven Approach》中提到的BCE

虽然这些架构的细节方面有很些差异,但是它们是很相似的。它们都有同样的目标,那就是业务的分隔。它们都通过把软件分层实现了这种分隔。这些架构都至少有一个业务层和一个接口层。

这些架构都按照下面的方式开发系统:

  1. 框架的独立性。整个架构不是建立在提供具有完备特性的软件库上。这样可以像使用工具一样使用整个框架,而不是把自己的系统强塞进那些有很多约束的框架中。
  2. 可测试性。业务层可以抛开UI、数据库、web服务或者其他的外部组件单独测试。
  3. UI的独立性。UI可以在不更改系统其他模块的情况下方便地进行替换。比如,可以在不更改业务层的情况下从Web界面替换到控制台界面。
  4. 数据库的独立性。业务代码和数据库分离,可以将数据库任意地替换成Oracle、SQL Server、Mongo、BigTable、CouchDB等。
  5. 外部依赖的独立性。事实上,外层逻辑或组件对业务层是透明的。

最上面的图表试图将这些架构的想法整合成一个可行的设计。

依赖规则

上面的同心圆表示了软件的不同层级。一般来说,越往外软件的层级越高。外层的圆是机制,内层的圆是规则。

本架构的首要规则就是依赖规则,即代码的依赖只能指向内部。任何外圈的层级对内圈来讲都是透明的。特别地,内圈的代码不能访问任何外圈声明的对象,包括:方法、类、变量或者其他的软件实例。

同样的,外圈代码使用的数据格式也不能在内圈使用,特别是那些由外圈的框架生成的格式。我们不想让外圈的任何东西影响内圈。

实体

实体封装了企业范围内的业务规则。一个实体可以是具有方法的对象,或者是一组数据结构和方法。实体可以被企业内任何应用共享。

如果你不是为企业开发软件,只是想实现一个单应用。这些实体就是应用的业务对象。这些实体定义了最通用和最高级别的业务规则,它们不会因为外部的变化而变化,或者只是产生较小的变化。比如,我们不希望它们因为分页导航或者安全模块的更改而更改。任何对应用执行的变更都不应该影响实体层。

用例

在软件的这一层,包含了应用相关的业务规则。这一层封装和实现了系统的所有用例。这些用例组织了实体数据的流入和流出,并且引导实体在企业范围的业务规则内实现用例的目标。

我们不希望在这一层的变更会影响实体。我们也不希望这一层被其他的外层模块,比如数据库、UI或者其他的框架影响。这一层和上述的模块是隔离的。

但是,我们期望对应用的操作会影响这一层的用例和软件。如果用例的细节发生了变更,这一层的代码也会相应的做出变更。

接口适配器

软件的这一层是一些适配器,用以将适于用例和实体的数据格式,转换成适于外部组件比如数据库或者web使用的格式。这就是这一层的作用,例如,这一层会包含整个MVC架构的用户界面。展示器、视图和控制器都属于这一层。MVC中的模型是由控制器传给用例,再由用例返回给展示器和视图。

同样地,在这一层数据会从适于用例和实体的格式,转换成适用于任何其他的存储框架,比如数据库。任何这一层的内部代码都不能和数据库打交道。如果应用使用的是SQL数据库,sql语句在这一层就限制使用,特别是和数据库交互的部分。

这一层同样也会有些其他的适配器,用以将来自外部的数据格式转换成用例和实体使用的格式。

框架和驱动

最外层是一些框架、工具的组合,如数据库,web框架等。通常这一层不会有很多代码,主要的代码都是用来与下一层进行交互的。

这一层是框架的细节部分。Web是细节,数据库也是细节。我们把这些东西放在外层,是为了避免它们对整个框架的破坏。

只有四层?

上面只是一个简略图,并不代表整个框架只有四层。框架并没有约束只能有四层,但是依赖规则是必须要遵守的。代码永远只能向内依赖。越往内层,抽象等级越高。最外层是低级的具体细节。越往内层软件变得越抽象,封装越高级的规则。最内层是最通用的。

跨越边界

图的右下角演示了跨越边界的例子。这里演示了控制器、展示器和下一层的用例交互的例子。注意其中的控制流。流动开始于控制器,流经用例,然后流入展示层运行。同样注意一下代码依赖,它们都向内依赖用例。

我们通常使用依赖倒置原则解决这种矛盾。Java语言中,我们需要安排好接口和继承关系,这样才能使控制流在正确的地方跨越边界。

举个例子,如果用例需要调用展示器。但这是不被允许的,因为这违反了依赖原则,内层不可以访问外层对象。这样我们可以在内层用例中调用一个接口,然后在展示器中实现它。

同样的技术也可以用在框架中的其他边界的跨越上。我们利用多态来编写代码依赖,使得控制流在遵循依赖原则的前提下流动,不管控制流向哪个方向流动。

跨越边界数据格式

通常跨越边界的数据都是简单的数据格式。可以采用基本的结构体或者简单的数据传输对象。或者是通过调用方法传参。或者把它打包成一个hashmap,或构造成一个对象。最重要的是使用隔离的,简单的数据进行边界间的传输。我们不想要直接传输实体或者数据库的查询行。我们不想数据结构有任何违反依赖原则的依赖。

比如,很多数据库在查询的时候会返回方便使用的数据格式,可以称之为行结构体。我们不想将行结构体直接跨过边界传入内层,这会违反依赖原则。因为这样做就强制内层需要了解外层的信息。

所以跨边界传输的数据,必须是适于内层的格式。

结论

遵循这些简单的规则不难,可以解决你很多头疼的问题。通过将软件分层,并且遵循依赖规则,你可以开发出真正可测试的,以及框架蕴含的其他优势的系统。当任何系统的外层部分被弃用,比如数据库、或者web框架,都可以花很小的代价替换它们。


以上是整洁架构的全文翻译,下面是结合现有项目的一些思考


结合现有项目的思考

通过梳理文章中提到的其他架构,对其他架构有了一些了解。 六边形架构或者叫端口适配器架构大的方面分为三层:应用层,领域层和外设层,其中领域层属于内层,应用层和外设层属于外层。领域层实现核心业务逻辑并提供端口供外层调用。应用层和外设层通过领域层进行数据交互,并分别实现各自的适配器进行本层数据格式和领域层数据格式直接的转换。

洋葱架构在六边形架构的基础上对领域层又进行了划分,将领域层分为领域模型和领域服务。从里到外依次是领域模型、领域服务、应用服务、用户接口和外设。洋葱架构和六边形架构都约定了依赖向内,整个结构和整洁架构相似度较高。

根据整洁结构,现有的项目分层api、service和dao层应该处于架构的外层,service层和dao层用来处理外部数据的转换,将用户数据或者数据库数据转换成用例和实体使用的数据格式。api层相当于用户界面。

所以整个项目应该还需要一个用例层,完整的层次应该是 api, service, usecase, dao/repositry。应该service层仅处理数据的转换,也可以将service层作为用例层,再添加一个适配器adapter。

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:谭三皮

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!