一、DDD的分层
在《领域驱动设计——软件核心复杂性应对之道》一书中Eric Evans将应用架构分为以下层级:
1、用户界面层
负责用户显示信息和解释用户指令;
2、应用层
定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题;
3、领域层
负责表达业务概念,业务状态信息以及业务规则;
领域层是业务软件的核心;
4、基础设施层
为上面各层提供通用的技术能力:为应用层传递消息,为领域层提供持久化机制。
各层的关系,书中也画了个图:
从上往下依次是用户界面层、应用层、领域层、基础设施层。
层与层的依赖关系书中没有说明,只是标了些箭头,没看明白具体什么意思,不过还是可以看到最上面的用户界面层可以直接调用最底下的基础设施层,即上层可以跨层调用下层,即第1层调用第2、3、4层的代码,这种设计不在这里评价好坏,需要每个人根据自己情况做些取舍。
从个人角度来看,看了之后大概明白各层的职责,但没看到具体的例子和代码还是觉得难以落地,所以接下来看几个例子。
二、网上银行的例子
这是书中举的例子,举一个实际场景:转账,时序图如下:
具体步骤如下:
1、用户界面层的transferController接收用户发起的请求;
2、transferController将请求转发给应用层的FundsTransferService;
3、应用层调用基础设施层开启事务,因为这个操作涉及2个账号之间操作,一个加钱,另一个减钱;
4、应用层然后调用领域层的2个账号分别进行加钱、减钱操作;
5、操作完成后调用基础设施层提交事务以进行本次修改。
这里每层的作用如下:
用户界面层:接受用户输入参数,转换成应用层需要的参数;
应用层:事务的发起,提交,领域服务的分派,这里分别调用2个领域对象进行业务处理;
领域层:负责业务逻辑,每个账号能不能加钱,可能存在账号冻结,能不能扣钱,即钱够不够由领域层负责;
基础设施层:事务的管理,数据库等操作,当然如果有Redis之类的缓存操作,也可以放入到这里。
三、真实的代码
网上还有个真实的DDD示例工程,这个工程是一个货物运输系统,主要的功能如下:
1、预约货物发货;
2、跟踪货物的主要处理;
3、当客户到达某个位置时,自动向客户寄送发票。
github地址如下:
https://github.com/citerus/dddsample-core/
代码结构如下:
基本上每一层是一个文件夹。
我们看其中一个场景,预约发货的场景,界面如下:
对应的控制器为:interfaces/booking/CargoAdminController
即入口为用户界面层:
RequestMapping(value = "/register", method = RequestMethod.POST)
public void register(HttpServletRequest request, HttpServletResponse response,
RegistrationCommand command) throws Exception {
Date arrivalDeadline = new SimpleDateFormat("dd/MM/yyyy").parse(command.getArrivalDeadline());
String trackingId = bookingServiceFacade.bookNewCargo(
command.getOriginUnlocode(), command.getDestinationUnlocode(), arrivalDeadline
);
response.sendRedirect("show?trackingId=" + trackingId);
}
调用的bookingServiceFacade还是位于用户界面层:
而bookingServiceFacade依赖的bookingService位于应用层:
应用层会调用基础设施层生成trackingId,查找相应的领域对象,然后在生成新的领域对象,最后调用基础设施保存新的领域对象,这里没有直接调用领域对象太多方法,因为在领域对象的构造函数中就有一些逻辑。
总结如下:
1、用户界面层接受用户输入参数,然后在用户界面层其实又封装了一些服务,由这些服务去调用应用层的代码,而不是直接在控制器中调用;
2、应用层调用基础设施的功能完成领域对象的查找,ID生成等,然后生成领域对象,最后保存领域对象,即应用层完成领域层的编排,由应用层决定分发到相应的领域对象中,这里当然涉及到基础设施层的调用。
另外关于查询的逻辑,有的是直接在用户界面层调用基础设施层的代码就完成了。
另外基础设施层的参数是来自领域层的,即基础设施层依赖领域层,即我们说的反向依赖,而不是领域层依赖基础设施层:
四、总结
1、用户界面层:接受用户输入参数,用户界面层根据代码方便维护又可以分几层,控制器只接受参数,如何调用应用层可以由单独的类去调用,以进一步提高代码的重用;
2、应用层:对领域层的编排,具体包括事务的管理,对象的保存,一个用例需要调用哪几个领域对象,这些都由应用层负责,但具体的业务逻辑它不参与,即这层知道要做什么,但不知道怎么做;
3、领域层:负责具体的业务逻辑和规则;
4、基础设施层:负责对中间件的操作,像数据库等操作,基础设施层依赖于领域层,而不是反过来。
层与层的调用关系书中并没有给予完整的说明,还有代码结构书中也并没有统一定好规则,所有这些都抛给了实现者,这也是好多公司落地DDD比较难的原因,因此真正要落地DDD,代码层面的规范需要自己根据公司的规范再来补充。