领域驱动设计与工程构建规范与脚手架

3,279 阅读9分钟

[TOC]

在领域驱动的理念基础下,定义工程命名规范和包路径规范,与大家交流命名规范。 在此使用java实现工程,运行生成初始开发工程,免去每次创建工程所需要手动建立包和工程的操作。 本次的内容会比较精简,比较白话,一些领域概念上的讲解可能会比较少,适合贫血模式的开发者、希望工程长远发展的开发者、规范定义的开发者阅读。

目录

背景

目的

方案

包路径层级划分

工程划分

ddd-structure


背景

刚接触开发的时候大家都会使用贫血式模式的开发,只有三层controller请求接口处,service业务逻辑层,dao数据访问层、entity数据模型实例的代码运行,而到后来业务发展越快,逻辑越来越多的时候,代码就会非常臃肿。

目的

解决业务耦合,定义开发工程命名规范、工程层级规范、包路径规范。 开发一个开源java工程,实现运行代码,则能生成初始开发工程,免去每次创建工程所需要手动建立包和工程的操作。

方案

我们希望在项目前期,接到需求时有时间的话做好分析,对业务进行归类,则可以划分出多个业务领域,以防日后单个领域被拆出来做单独的一个服务。日后,我们就可以很方便地把之前对业务进行分类好的其中一个领域(也就是一个文件夹下的所有问题件)拆出来放到新工程中,这个是我认为领域驱动设计的一个好处。

场景样例

如之前做过的银行账务体系中,如大家了解到银行账务有个人的余额、借款金额、应还本金利息,这些都是***“个人账务的业务领域”***,而资金在放款、还款、投资时,每次都会有一笔流水,只关心金额和流水是否成功,这种发生的就是***“资金扭转的业务领域”***。 可能一开始都在一个工程中,但是如果没有划分好领域就会导致所有的代码都在一个service业务层下,到时候想找出哪些类是以前写过的***“个人账务业务领域”***就比较麻烦了,想拆业务出另外的系统就比较麻烦。

总体目录划分样例

其实最下面的service应该叫server

领域驱动工程目录.png
其实service应该叫server

领域划分与工程内包路径层级划分

主要讲一个工程下的包路径使用规范。

image.png

一、 infrastrusture

infrastrusture,基础设施,一般指基础架构,spring、spring-cloud、分库分表技术、定时任务调度框架等基础架构,如在技术中心部门下会有单独的基础架构组同时去改造、推广或发现框架缺陷。

但是放在在业务系统中infrastrusture指的是包括entity数据模型层、dao数据访问层(或者叫mapper)、repository仓储是用来管理实体的集合层共三层。如上图中的infrastrusture。

  1. entity 实体一般和主要的业务/领域对象有一个直接的关系,一种是指orm层意义上的实体,与数据模型映射,所以又命名为model,会比较传统贫血模式任务只是数据库表的模型。但是另一种意思指不是orm层的实体,也是有业务逻辑的构造对象,这种可能不被持久化,但是也是有逻辑类型,如rabbitMq中接受mq消息的需要创建queue类,构造时会注入ip端口、队列名等信息

  2. dao 数据访问层,又被命名为mapper,对数据库进行sql的映射,供上层访问。

  3. repository 仓储是用来管理实体的集合。 仓储里面存放的对象一定是聚合,原因是domain是以聚合的概念来划分边界的,会做业务层里得到dto转换成dao的所需的入参,再去调用dao层的方法;上层service服务层或者business业务层不再直接访问dao,而是访问repository。

我们的代码中,一个Dao类下可能会有很多的方法,每个方法又需要去mapper.xml里面编写sql语句,所以每次做一次sql操作都需要改动到dao和mapper.xml就比较麻烦。 我们可以做一个

List<A> selectByEntity(A a);

方法,或者使用mybatis-plus的同样的selectByEntity,xml中对应的映射sql将会把每个字段做下述的语句。

select * from table where 1=1 
<if 成员!=null>
and 字段 = # {成员}
</if>

这样我们用相同的dao方法,但是在repository实现类中,把查询条件作为成员set进A类的各个成员中

List<A> selectByIdAndName(Long id,String name){
  A a=   new A();
  a.setId(id);
  a.setName(name);
  return this.dao.selectByEntity(a);
}

这样我们就可以把一些简单的查询,写一次的dao和mapper.xml,调试一次的sql就好了,就不用每新建一个sql测试一次。

二、 domain

领域层,包括business业务逻辑层、facade防腐层(或者叫聚合层)、infrastrusture基础设施。 domain就是之前提及到的,划分业务领域,如资金扭转领域、用户账务领域,就不再像以前一个service业务层包含所有的领域的代码。 只要划分好领域,日后,我们就可以很方便地其中一个领域的代码拆出来放到新工程中,如 总体目录划分 图下的domain1、domain2.

  1. business 此时service层不再是我们的以前写业务逻辑层,业务逻辑改名为business层,而service将会作为服务层后面讲解。

  2. facade facade是防腐层,相当于一些文章中的聚合及聚合根(aggregate、aggregate root),主要是处理service服务层或者controller请求接口层给的request对象,或者是把domain2的接口返回的对象转换为domain1所需要的对象。

为什么需要把service服务层或者controller请求接口层转换呢? 传统的贫血模式下确实没有多大的意义,但是如果你的服务是rpc形式,如spring-cloud的feign、dubbo idl生成的service,这些rpc接口类、方法和方法的入参和出参,在开发过程中一般都是先定义好的,就相当于先定义好与前端交互的json输入和输出参数,前端同事就可以先开发,同样提供feign给其他系统调用时,其他系统的java开发同事也可以根据你的feign来开发。 这样别人就不用等我自己逻辑实现完成了,此时面临一个问题,rpc定义的类和数据orm层的entity不一样怎么办,这个时候facade或者叫aggregate包路径下就可以做一个类来实现把其他领域的类来和我们领域的类转换了。

3. service或者叫server

service层是服务层,也有叫application。对应服务接口实现类和应用的启动层,service本身是服务的意思,只是以前贫血模式没那么划分服务和业务的边界,所以以前叫controller请求接口层,然而springcloud或者dubbo提供的rpc接口是面向服务对外提供服务,所以叫service。

service层的resources,会做启动所需要的配置,如spring.xml,springboot的application.properties

service层的java代码,主要实现定义好的rpc的interface接口的实现类,相当于先定义好feign再实现,请求接口的逻辑,会做一些如请求接口的redis分布式锁防并发,如果没有facade层,也可以在service层代码中把请求参数做转换为domain层的dto。

后续讲工程划分的时候讲讲这里做转换的好处

某某系统名.application-|
                     |->service包
                     |->controller包
                     |->App springboot启动类

或者

某某系统名-|
             |->service包
             |->App springboot启动类

工程划分

image.png

1.service/server工程

业务系统的服务启动层,负责启动我们的系统和提供外部系访问接口的实现类。

2.domain工程

真正的业务逻辑处理工程。

service服务层(或者叫请求接口层、服务启动层)代码和domain层代码划分在不同的两个工程中,如上图,这样有两个好处:

  • 可以把service层放在一个工程下,把domain层划分在另一个单独工程,domain工程做单元测试的时候只需要测domain层代码,domain单元测试用容器方式启动的时,就只会使用spring框架的内容,打jar包就无须加载service层rpc所需要的spring-cloud框架的依赖了。
  • 另外一个情况就是,如果你现在是用传统springMVC做请求接口层,如果要换成dubbo框架,你只需要更改service工程的内容,domain层代码可以保持原状,因为domain层方法入参是domain层下的dto类,而不是service工程定义请求返回类vo、request等类,因为service要进去domain都需要转换。日后换成spring-cloud也是会比较方便了。
  • 还可以把domain层代码提供给定时任务shedule工程使用,这样shedule工程就无须引用更多的依赖了。

3.shedule工程

主要做定时任务执行类和定时任务系统的启动运行类。 我们把定时任务的一部分逻辑,写在这个工程,并以一个单独系统来启动,这样定时任务在跑的过程中,若service工程需要重新构建,也不会影响定时任务的执行。 例如:一个定时任务需要跑一个小时,但是发现线上运行的主要业务有bug并需要上线修复,这个时候改好代码并随时上线,就不需要像包含定时任务和主业务的单服务情况,一上线就把定时任务影响了。反之亦然。

4. contract工程

就是放rpc框架,如利用spring-cloud的feign的自定义接口interface类、方法、请求、返回参数,做好后deploy上去公司私服,这样别的系统就可以不等我们系统的实现来先开发逻辑了。


ddd-structure

这个是我开发的工程规范构建工程,层级图在上文,。欢迎大家讨论你们喜欢的层级目录命名规范。

gitee.com/kelvin-cai/…

gitee中包含在readme中有使用说明

2019-09-21 gitee:地藏Kelvin 简书:地藏Kelvin 公众号:地藏思维