接触过DDD分层架构的同学都会说,分层架构不就是四层:UI层、应用层、领域层、基础设施层吗? 还能有啥秘密啊?
知道分层架构是四层还只是表象,其实分层之后最重要的是知道分层中每层的薄厚(包含业务逻辑的多少)、层内如何再分层、层间/层内依赖关系等。
如果上述任何一点做不好,都很可能把DDD做成了四不像。
要彻底做好DDD分层架构,首先就要深入了解它。
就像要了解一个国家就必须了解他的历史一样;要彻底了解DDD分层架构,就必须了解清楚它的前世今生。
DDD分层架构演进的历史
DDD分层架构来源于EJB的分层架构,EJB分层架构如下:

上图中,controller层负责获取客户端(一般为浏览器)请求,调用service完成业务逻辑,service把请求转换为javabean调用DAO访问数据库,调用的结果再次转换为javabean后返回service,service将结果处理后返回controller,controller最后返回客户端,这一系列的返回完成代表了业务的结束。
这个架构虽然流行了很久,但是ejb架构大家一致认为是不完善的,公认有以下几个pp(痛点):
pp1 贫血
javabean中只有私有数据和get/set方法,属于典型贫血模型,所有代表业务逻辑的行为都在Service层中,应该把数据和它对应的行为放在一起。
pp2 行为耦合
service中逻辑既有跨service的大粒度行为,也有跨javabean中粒度行为,甚至还有javabean内部小粒度行为,这样行为也就成了一个大泥球,其实这些行为可以分为:
2.1贴身行为(封装到javabean内)
2.2协作行为
其中,协作行为又可以分为:
2.2.1、同一个service内跨javabean对象行为
2.2.2、跨service行为等。
pp3 业务和事务耦合
service耦合了业务逻辑和分布式事务
pp4 业务逻辑散落
DAO访问数据库本身也包含了部分业务逻辑,这就造成了业务逻辑散落,不易于维护。
pp5 违规依赖
DAO依赖javabean,service依赖javabean和DAO,造成循环依赖,形成大泥球。
针对ejb架构的不足,演进出了DDD分层架构的第一版,见下图。

DDD分层架构对EBJ架构做了优化。
solution1 充血
原servcie和javabean层合并形成domain层,使代表业务逻辑的行为和数据紧密结合;同时service中2.1 (javabean中的贴身行为)移到javabean中,javabean演进成聚合、实体或值对象。
solution2 事务隔离
原service层2.2.2上升形成application层,做跨service行为和完成分布式事务
solution3 服务独立
原service层2.2.1保留在domain层,演进成domain service
solution4 业务逻辑上浮
原DAO层转演进成Infrastructure层,其中的纯基础设施访问和业务逻辑(基础实施编排)分离,基础设施编排的业务逻辑上浮到domain层中,纯基础实施访问保留Infrastructure层中。
solution5 领域界面清晰
controller层演化为UI(User Interface)层,专门做协议和领域对象的转换。
这样pp1-4都得到了应对。
根据DDD分层架构的依赖规则可以得出,所谓分层,有两点最基本的要求:
1、上层可以依赖下层,下层不能依赖上层
2、上下层两者可以依赖共同的抽象,抽象在上层定义。
下层依赖了上层就形成违规依赖;上下层互相依赖就形成了循环依赖,这些都是架构设计中应该避免的坏味道。
上述DDD分层架构第一版还有两个痛点没有解决:
pp6 循环依赖依旧
原ejb架构中的pp5出现了循环依赖,分层架构第一版应用在复杂系统中时,常常出现infrastructure也会反向依赖domain层中领域对象或数据对象,domain又依赖infrastructure,形成循环依赖。
pp7 基础设施强耦合
application和domain层直接依赖了infrastructure的具体实现,属于业务逻辑和外部依赖的强耦合,不利于domain层独立测试和基础设施层轻量级替换。
在clean architecture发布后,DDD分层架构演进到第二版。

第二版在第一版的基础上,增加了新的解决方案:
solution6 基础设施依赖解耦
infrastructure中基础设施接口RepoInterface上浮到domain层,domain仅依赖RepoInterface,基础设施实现在main或test中通过依赖注入方式注入系统运行态。
这样既解决了循环依赖,又可以使application和domain层隔离对基础设施的具体实现的依赖从而仅包含业务逻辑,可以实现独立测试和基础设施轻量级替换,同时解决pp6-7。
各层的职责和薄厚
UI层
负责外部协议和接口与领域之间的转换。它形成一层切面,切面之上是协议和数据,切面之下是领域对象;
薄薄的一层,仅调用公共支撑的协议栈等进行编解码并生成领域对象,进而调用Application或Service层中业务逻辑。
Application层
完成跨domain service或跨application行为的编排和组合,同时负责跨service、跨application、跨BC的分布式事务;
薄薄的一层。
Domain层
包含且仅包含具体的领域对象和领域逻辑,跨领域对象的行为放置到service中,即service完成领域对象行为编排;领域对象贴身职责归属到领域对象中,对基础设施访问接口RepoInterface也定义在该层中;
最厚的一层。
Infrastructure层
实现domain层中的RepoInterface接口,仅应该包含比较单纯的基础设施访问接口,对接口的编排调用等都属于业务逻辑,不应该放在该层。
比如基层设施中存放了所有用户,现在有个业务功能要给所有用户中满足条件的用户推送一条消息的接口pushAll,这个遍历用户和判断用户是否满足条件都属于业务逻辑,就不应该放在infrastructure层,而是应该上浮到domain,这一功能中,基础设施仅包含推送消息pushMsg接口。
薄薄的一层。
这里的薄不是指接口个数的多少,而是指业务逻辑的多少(特指对接口编排调度的逻辑),这里极容易出错,切记切记。
从组合式设计看,产品功能由业务流程编排业务逻辑实现,两者关系见下图:

对DDD分层架构,从上往下看,分别有多组业务流程和业务逻辑编排关系

当然,大型项目分层架构还可能需要级联,比如服务器管理中每个部件(存储、CPU、NIC等)就是一个个独立的分层架构,既对外提供独立服务,又要提供接口给整机管理功能调用;
整机管理本身又是一个分层架构,但可能只有整机UI、整机Application、整机Domain就够了;整机Domian的service依赖部件UI、Application或service,通过级联形成逻辑上的树状结果,组合成大规模系统应用。

层内分层

层内分层的规则:层->模块->包/文件夹->类/文件。
一般在Domain和Application中会再次分层:
Domain
Domain层内还要分层,按依赖关系从上到下分为服务层(service,对上的北向接口)、接口层(仓储接口,对下的南向接口)、模型层(聚合、实体、值对象)、支撑层(小组件工具功能)。
Application
Application在大型项目中,有时会增加一个行为编排层(比如Transaction DSL),进行业务流程中同步异步调用、定时器等的编排和组合。
规则
层间依赖
上述已有讲述,重要的事情可以多讲几遍,这里再累述下:
-
上层依赖下层,下层不能依赖上层
-
上下层两者可以依赖共同的抽象,抽象在上层定义。

上图为DSM(Dependency Structure Matrix),左下角表示上层依赖下层,为合理依赖;
右上角为下层依赖上层,是违规依赖。从图中看出,只要维护住分层的要求,代码、目录等放置到应该放置的层内,就不会有违规和反向依赖。
具体层间依赖关系为:
-
UI可以依赖Application、Domain、Infrastructure;
Application、Domain、Infrastructure不能依赖UI
-
Application只能依赖Domain;
UI层可以依赖Application;Domain、Infrastructure不能依赖Application
-
Domain不能依赖其他层;
UI、Application、Infrastructure可以依赖Domain
-
Infrastructure只能依赖Domain;
UI可以依赖Infrastructure;Application、Domain不能依赖Infrastructure
层内依赖

Domain层内的服务层、接口层、模型层、支撑层之间也满足从上而下,单向依赖的关系。
从图中DSM矩阵上也可以看出层内分层也要满足层间依赖规则,就不会出现反向依赖和违规依赖。
Tips
除了满足层间依赖外,同一level模块不要有直接依赖,比如同一层次的领域对象间依赖通过service编排;service之间的依赖通过application编排。

当然同一level中的大量的横向依赖极大可能是领域划分不清导致,需要检查领域模型划分是否彻底,并进行解耦治理,不能仅仅依靠上层解决横向依赖问题,否则会极大增加维护成本。
边界规则
类(文件)、包、模块应该出现在期望的位置
小结
DDD分层的出现就是为了解决职责的分配和合理依赖,其本质是高内聚低耦合的集中体现,既要满足分而治之的分层要求,又要满足层内分层和层间层内依赖约束,使得依赖都是单向无网无环,从而使得整个系统既层次分明,条理清晰,又能互相有序协作,形成功能整体。
最重要是大幅增加了系统可理解性,从而使得整个系统易于理解、易于维护,适于长期演进,从而把DDD的优势发挥到极致。
原文始发于微信公众号(丁辉的软件架构说):DDD分层架构的秘密
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/27222.html