在团队中,一直在灌输DDD的理念,最近review一些新开发的项目时,发现工程包结构每个人的理解都是不一样的,命名也是各有特色。
因此,觉得有必要把之前整理的工程结构重新梳理下。
而在梳理的过程中,恍惚间,有种看山是山、看山不是山、看山还是山的体会。特别有意思。
之前的总结DDD分层[1],每一层都是明确的。
整个工程的包结构就是这样的:
•interface•application•domain•infrastraction
但是在落地时遇到了很多的问题,在DDD系列文章中也提到过:
1、循环依赖:
domain是依赖于infrastraction,但如repository接口是在domain层的,DDD也是这么定义的,但具体的ORM实现是在infrastraction。因此infrastraction又需要依赖domain。形成循环依赖。
2、domain的厚度
以前都是MVC,贫血模型。所以刚开始时,domain是很薄的,以致于没有存在感。很多service都被application干完了。常有application service与domain service区别的讨论。落地时也常搞混。
不知道是不是整洁架构,还是洋葱架构之后或之前吧,依赖倒置成了程序员认知的共识。
为了脱离大泥球,人们注意到整体中各个部分的需求变化速率不同,进而通过关注点分离来降低系统复杂度。这是分层架构的起源。
分层架构对变化传播的控制,是通过层与层之间的依赖关系实现的,因为下层的修改会涉及上层。我们希望通过层来控制变化的传播,只要所有层都单向依赖比自己更稳定的层,更易变依赖不易改变的,那么变化就不会扩散。
倒置的原因,是因为领域层被赋于最稳定层。
1、展现层
逻辑是最容易改变的,新的交互模式以及不同视觉模板。
2、应用层
随着业务流程以及功能点的变化而改变。如流程重组和优化、新功能点引入,都会改变应用层逻辑。
3、领域层
核心领域概念的提取,只要领域概念和核心逻辑不变,基本是不变的。一旦领域层出现重大改变,就意味着重大业务调整,整个系统都被推倒重来。
4、基础设施层
逻辑由所选择的技术栈决定,更改技术组件、替换所使用的框架,都会改变基础设施层的逻辑。因而基础设施层的变化频率跟所用的技术组件有很大关系。越是核心的组件,变化就越缓慢,比如待定数据库系统后,不太可能频繁更换它,不太可能频繁地更换它。而如果是缓存系统,那么变化的频率会快很多。
但基础设施层可能存在不可预知的突变。历数过往诸多思潮,NoSQL、大数据、云计算等等,都为基础设计层带来过未曾预期的突变。
此外,周围系统生态的演化与变更,也会给基础设施层带来不可预知的突变的可能。比如,所依赖的消息通知系统从短信变成微信,支付方式从网银支付变成移动支付,等等。
整个工程的包结构就是这样的:
•infrastraction•interface•application•domain
整体包结构是没有变化的,虽然理论是美好的,落地时问题依旧存在。尤其infrastraction与其它三层的不可调和的关系更浓烈了。
从以往感观,其他三层是必须要依赖infrastraction的,结果现在却在最顶层。
其实在之前文章中就提到,controller是在interface还是infrastraction,角度不同,在哪一层都可以。
而像一些基础的,如mq,应用层要发消息,怎么办呢?依赖结构决定了无法使用。
因此有人提出,基础设施层不是层的结论。每一层都是要依赖基础设施的。
经过了一番学习,发现了菱形架构,解决了之前的很多问题。
OHS:
对外主机服务,提供一切入口服务,分为remote和local.
remote:
提供一切对外服务,来源有传统的web,还是MQ的订阅等等。
local:
本地服务,是application的演变,如果远程服务要访问domain,必须通过local才能到达。
domain:
意义不变,就是domain
acl:
是原先infrastraction,但把范围给扩大了。把所有对外部的依赖都纳入其中,甚至repository。
port是表示接口,而adapter表示具体实现。
在《DDD实践指南》[2]中有对菱形架构更详细的介绍。
这样解决了上述两种方案的缺点,理解起来也简单。
但后来还是不太喜欢,为啥,因为传统,传统的DDD理论中,repository是领域层,这儿却在acl中,所以一直在寻找别的方式来解决。
•inputadapter•application•domain•outputadapter
这也是有相当数量受众的架构风格,类似于菱形风格,从外形理解也简单。
这是在实践中,演变来的一种风格,对外一切都是facade,受CQRS影响
分为query查询与entity单对象的创建、更新操作;
application刚是业务原语的操作,简单理解为一个业务行为,会操作多个单entity;
adapter刚是封装的infrastraction或第三方接口,提供给外部使用。
经过一系列的学习,输出一个融合风格。
依赖关系:
ohs -> application
ohs -> infrastraction
请求入口都在ohs,不管是api,还是队列监听。
像队列底层属于infrastraction,但只面向接口编程,由ohs层实现。
application -> domain
domain -> foundation
application是domain的facade
domain -> acl
虽然可以通过供应商模式,其他层都依赖domain,但还有是会出来一些domain的依赖。放在acl中,供所有层使用。
这样也可以把需要主动调用的内容从infrastraction中剥离开,解决掉了以往提到的循环依赖。
经过以上一系列的变化,可以说是由简到繁的过程。
再回头看经历过的项目现状,想想每次项目初始化,自己内心的纠结,在团队中也需要宣贯,需要解释,需要深化。
不如来得简单明了些,就使用最经典的DDD风格,只要有一点DDD理论知识,大家都看得明白。
interface:有api、dto、assembler三个包,api接受外部请求,有传统的controller,还有rpc,callback,listener来的消息。dto就是传输对象。assembler则是interface->application时,把dto转换成application的command、query。
application: 还是CQRS的思路,分成query、command;还有event,由内部及domain抛出的event。
domain:还是核心概念,entity、vo、aggregate。但没有service,为啥,当有service时,经常会与application service相互干扰,并且会慢慢回到贫血模型。通过强制没有service,可以更加OO。
infrastraction:被拆成不同部分。
基础设施层,不单单是基础设施。得分成两种,一种像是acl,封装第三方接口;另一种像是mq,email等基础设施。
1、我们常见的mq,cache,io,email等等都是基础设施层,domain不是直接依赖他们,而是通过DIP来倒置。表现形式是domain都是接口,而基础设施变成了能力供应商。
2、而依赖的第三方接口,则是直接被domain,application调用。
因此infrastraction被分成两部分,同时解除了循环依赖的困境。
在之前文章中,提到过COLA的持久操作在application,当时很反感,后来感觉好像也对,也是供应商模式的一种体现。
路漫漫其修远兮,吾将上下而求索。对风格的探索反反复复,背后还是因为形神不能合一,形支撑不了神,或者神过于脱离于形。
无论如何,最核心的还是domain的设计,专注修炼OO,没有丰满的domain,一切都是花架子,形似无神。
[1]
DDD分层: https://www.zhuxingsheng.com/blog/ddd-layering.html
[2]
《DDD实践指南》: https://www.zhuxingsheng.com/blog/ddd-tactical-practice-guide.html