DDD工程代码模型的几种包风格

在团队中,一直在灌输DDD的理念,最近review一些新开发的项目时,发现工程包结构每个人的理解都是不一样的,命名也是各有特色。

因此,觉得有必要把之前整理的工程结构重新梳理下。

而在梳理的过程中,恍惚间,有种看山是山、看山不是山、看山还是山的体会。特别有意思。

传统风格

之前的总结DDD分层,每一层都是明确的。

整个工程的包结构就是这样的:

  • 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实践指南》中有对菱形架构更详细的介绍。

这样解决了上述两种方案的缺点,理解起来也简单。

但后来还是不太喜欢,为啥,因为传统,传统的DDD理论中,repository是领域层,这儿却在acl中,所以一直在寻找别的方式来解决。

六边形风格

  • inputadapter
  • application
  • domain
  • outputadapter

这也是有相当数量受众的架构风格,类似于菱形风格,从外形理解也简单。

facade风格

  • facade
    • query
    • entity
    • appliation
    • adapter

这是在实践中,演变来的一种风格,对外一切都是facade,受CQRS影响

分为query查询与entity单对象的创建、更新操作;

application刚是业务原语的操作,简单理解为一个业务行为,会操作多个单entity;

adapter刚是封装的infrastraction或第三方接口,提供给外部使用。

混合格斗风格

经过一系列的学习,输出一个融合风格。

  • ohs
    • controller
      • pl
    • openapi
      • pl
    • xxljob
    • subscriber
      • mq
      • event
  • application
    • service
  • domain
    • entity
    • vo
    • aggregate
    • repository
  • acl
    • port
    • adapter
  • persistent
  • foundation
  • infrastraction
    • configuration

依赖关系:

ohs -> application

ohs -> infrastraction

请求入口都在ohs,不然是api,还是队列监听。

像队列底层属于infrastraction,但只面向接口编程,由ohs层实现。

application -> domain

domain -> foundation

application是domain的facade

domain -> acl

虽然可以通过供应商模式,其他层都依赖domain,但还有是会出来一些domain的依赖。放在acl中,供所有层使用。

这样也可以把需要主动调用的内容从infrastraction中剥离开,解决掉了以往提到的循环依赖。

回归传统风格

经过以上一系列的变化,可以说是由简到繁的过程。

再回头看经历过的项目现状,想想每次项目初始化,自己内心的纠结,在团队中也需要宣贯,需要解释,需要深化。

不如来得简单明了些,就使用最经典的DDD风格,只要有一点DDD理论知识,大家都看得明白。不会去问ohs是啥。

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,一切都是花架子,形似无神。

公众号:码农戏码
欢迎关注微信公众号『码农戏码』