关于DDD战略架构(划分子域,创建聚合模型等)在此不再讨论,这里探讨一下在DDD的战术设计上的一些重点和难点。
我的项目地址
我想很多人在看完DDD的设计原则后曾经自己也想过采用DDD去构建一个系统,但是在构建过程中却无从下手,不知道DDD的层级是怎样的,不知道它的各层之间的依赖关系等等,下面一一说明。
准确来说,我的项目之前是三层架构,而现在将转换成DDD架构,那么我要做哪些?
DDD中最核心的层:领域层,而领域层是通过应用层来进行协调的,还有基础层,接口层,那么我们可以简单的这么类比:
图片引用自:中台架构与实现:基于DDD和微服务 (欧创新 邓頓)
那么我的项目架构为:
接口层(其实就是熟知的controller,以及在其中的入参合法校验)
——>应用层(用于服务间事件发布和处理,DTO解析和创建等)
——>领域层(核心层,这里是领域对象的业务逻辑)
——>基础层(包括持久层,第三方工具,CLI,SMS等在内的一些业务无关的基础设施)
接下来再来看项目的组织结构:
对于系统的各个层级,他们各担当了项目中的什么角色,实现了什么功能?
图片引用自:中台架构与实现:基于DDD和微服务 (欧创新 邓頓)
图片引用自:中台架构与实现:基于DDD和微服务 (欧创新 邓頓)
显而易见我们系统中包含了DTO,DO,PO,注意:VO是前端页面对象,我们系统中最外层对象是DTO。
另外有一点是有争议的:关于DO转DTO到底是应该在应用层还是应该在用户接口层?我的想法是,应用层作为系统内服务间的通讯的最小粒度,那么我就该在应用层转换,同样的,DTO转DO也应该在应用层转换。其实只要你说的有道理,都是可行的,只要你符合高内聚,低耦合原则。
在使用了DDD设计原则之后,我们就迎来了一个问题:对于持久层操作,一个聚合是最小粒度,这就带来一个问题:
为什么会有这个问题?举个例子,我们现在有一个订单聚合
订单聚合{
订单信息{
下单时间,付款总额},
购买人信息{
购买人姓名},
订单详情{
包含的商品}
}
我现在只要查询该订单的订单详情,但是我不得不查询出整个订单聚合,然后根据订单聚合来得到订单详情,这样在持久层来说(我可能采用了关系型数据库),可能为了查询出整个聚合我要进行多次表关联,查询出很多实际上无意义的数据,这就带来了性能上的消耗。
这个是网上较多的解决方案,通过Command(持久层写)与Query(持久层读)分离的思想,而如果有Query的需求,我可以直接跳过聚合对象,直接查询我需要的某些对象或是某些属性(映射到数据库就是某些字段)。
先看一下我们的数据流:
我们从dao查询到PO之后,再将其转化成DO,期间可能会去对DO的一些属性做修改操作,最后再由DO转化成DTO,传输给前端页面。
那么我们举个例子,同样是订单聚合对象:
订单聚合{
订单信息{
下单时间,付款总额},
购买人信息{
购买人姓名},
订单详情{
包含的商品}
}
我们的写法应该是从dao查询到PO(如果是关系型数据库可能使用到表关联,这是主要的性能消耗),然后可能对DO做出处理(也就是说会执行某个或某些属性的getter),最后转换成DTO对象传输(一定会执行某个或某些属性的getter),所以说,我们实际需要用到的属性其实就是这些被调用了getter的属性。那么方案就来了,我可不可以在知道真正需要哪些属性(即数据库字段)后,我再去查询数据库呢?
完全是可以的!就拿上面的例子来说,我要在api层查询订单详情中包含的商品,我可以在dao层调用数据库查询时伪加载,即PO的属性都为初值,再将其转换成DO对象,在service层调用DO或是api层租状DTO时直接或间接使用到PO的某些属性的时候,再去数据库查询相应的字段,就可以大大减小数据库的性能消耗。
实现
具体的实现我采用了数据库查询框架的延迟加载功能,同时将PO的所有属性都设置为需要延迟加载,并且使用cglib代理实现了延迟加载的传递,将PO的延迟加载传递到DO中去(即是生成一个代理后的DO对象,并拦截其getter方法并对其做增强)。
DDD 大粒度读与写性能优化
优点
缺点
说完了读我们再来看看写,下面讲一个场景,同样是订单聚合:
订单聚合{
订单信息{
下单时间,付款总额},
购买人信息{
购买人姓名},
订单详情{
包含的商品}
}
如果我现在要修改订单详情中某个商品的数量,在遵循DDD规范的前提下,我应该是这样的:
这就带来了一个问题:无意义的写。我们会想:我只修改了订单详情中的某一个属性,但是却要保存整个聚合对象,须知:我的持久层为了保存这一个对象可能要保存很多张表(我们这里仅讨论关系型数据库,对于nosql我们不予探讨),这样性能又怎么保证呢?
看一下数据流更加清晰:
从dao查询到PO,接着转化成DO后在domain层进行更新修改成DO’,接着再转换成PO’,使用dao进行持久化操作。
看了这个图就很清晰了,我能不能这么玩:
直接通过判断PO和PO‘的差别,然后比对修改的属性,并将其持久化到数据库,这将大大减小数据库保存带来的性能消耗!
最后再引流一波,关于DDD的具体落地实现参照开源项目 Mall-Vue branch:Mall-CI 。目前后端尚处于开发状态,欢迎star&fork。