8月上了一个项目,on boarding 的时候TL和Coach就说我们项目使用的是DDD架构让我们先了解一下。

什么是DDD?

“DDD”全称叫Domain Driven Design,在wikipedia中有这样的解释

Domain-driven design (DDD) is a software design approach [1] focusing on modelling software to match a domain according to input from that domain’s experts.

这样解释的云里雾里的,初看时根本不知所云。什么是domain?他和我们熟知的 Module 有没有区别联系?domain expert 又是什么鬼,使我更加难以理解。

于是又从网上查阅了一些资料,结合原作者Eric Evans的《Domain-Driven Design: Tackling Complexity in the Heart of Software》书中的描述,才敢提笔勉强写一点粗浅的认识,还望诸位笑看。

贫血模型

抛开一上来就给一个概念下定义的思路,我们可以先想象一个场景。

假如你现在手里的任务是生成一个订单页面,这个订单页面包含了商品、价格、状态信息。用户可以在下单的时候是未付款状态,而在付款后显示已付款。并且在生成订单的时候就要计算总价。

熟悉三层架构的同学此时已经迫不及待的开始摩拳擦掌要准备开干了,于是飞快的写出了下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Order {
private List<Product> products;
private BigDecimal totalPrice;
private STATUS status;
// Setter Getter ...
}

class OrderController{...}

class OrderService {

public Page<OrderResponse> create(OrderRequestDTO dto){
if(dto.getStatus == PAY){
throw new BusinessException("message");
}
Order order = new Order();
order.setProducts(dto.getProducts());
order.setStatus(NO_PAY);

BigDecimal totalPrice = calculateTotalPrice(order.getProductes);
order.setTotalPrice(totalPrice);
return OrderRepository.save(order);
}

private BigDecimal calculateTotalPrice (List<Product> products){...}

}
class OrderRepository{...}

Well,似乎看上去没有什么大问题,程序写完了,测试也能通过。

但是,一周后,TL同学笑嘻嘻的找到你说,我们引入了第三方的订单系统,创建订单的话直接向第三方发请求就好了。

你把之前写的订单的逻辑改一下吧。

此时你的内心:

宝宝心里苦,但我不想说

因为,虽然移除Order这个需求听起来很简单,但是你自己知道,不是简单把它删除的问题,而是你需要在你的service层级,乃至其他层级去查找是否引用过Order,并且把他们改成新的逻辑。

更要命的是如果你的同事也写了一些对订单的操作,你也一并要去修改。

上述情景,就是经典的Service+贫血模型的在不断的迭代中的变化,其缺点在于:

  1. 虽然我们定义了Order但是我们仅仅只把他当作传值的对象的话,那么和他相关的业务逻辑就会散落四处。无论是将来重构或者对原有功能进行迭代升级时都会遭遇问题。并且违背了也SOLID原则中的单一职责原则。
  2. 上层对下层有依赖关系。

领域对象模型

铺垫了这么多,大家一定好奇,听你这么说领域对象是不是就能解决这个问题呢?

我们再回到上面的例子。如果一开始使用领域对象建模的话,他的代码应该是长这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Order {
private List<Product> products;
private BigDecimal totalPrice;
private STATUS status;
// Setter Getter ...

public Order create(OrderRequestDTO dto){
Order order = new Order();
order.setProducts(dto.getProducts());
order.setStatus(NO_PAY);
return order;
}

public void calculateTotalPrice(){
this.totalPrice = calculateTotalPrice(products);
}
private BigDecimal calculateTotalPrice (List<Product> products){...}
}

class OrderController{...}

class OrderService {

public Page<OrderResponse> create(OrderRequestDTO dto){
if(dto.getStatus == PAY){
throw new BusinessException("message");
}
Order order = Order.create(dto);
order.calculateTotalPrice();
return OrderRepository.save(order);
}

}
class OrderRepository{...}

有的小伙伴可能会有疑惑,这不就是把原本Servie里的有关order的操作移动到Order中了么,和之前的相比有什么进步呢?

  1. 划清了职责。例如Order的创建,修改等操作放在了Order内部,使得外部service只需要调用即可,而无需关心其实现。
  2. 通过依赖反转实现解耦。方便今后的迭代更新。

此时,即使重新写一个Order或者Order本身增删一些功能,都不会对其他层造成影响。对比缺血模型变化如图:

同时,其依赖关系也由原来的上层对下层的依赖,转变为上层提供接口,下层负责实现。

层与层之间的数据传递也互不影响,也方便进行微服务替换:

项目中实际的DDD

然而上述只是理论上的DDD过程,从我接触的这个项目来说,是一种DDD和传统的结合方式。

比如,简单查询和存储走的是Domain层连接JPA进行查询,而复杂查询则绕开了Domain层,从Repository中使用Mybatis这样的ORM框架进行的查询。

总之DDD在实践中落地还是和理论上有着一定的差距。

总结

听我上面说了这么多之后,相信大家对DDD有了比较直观的一个感受。

从战略层面上来说DDD关注的不再是具体的技术实现或者技术架构,它更想让我们把注意力关注在业务逻辑

围绕业务本身来进行切分,划定清晰的业务上下文边界。从而让复杂软件开发能够更加顺利以及可维护

不过,本人水平有限,也是刚接触DDD,后续还需要在项目中继续体会深造,下面也贴出来相关资料供大家参观学习。

参考资料:

  1. 《Domain-Driven Design: Tackling Complexity in the Heart of Software》
  2. 白话讲解领域驱动知识
  3. 后端开发实践系列——领域驱动设计(DDD)编码实践 - Thoughtworks中国的文章 - 知乎

最后

感谢项目组TL黄成、Coach林文军老师平日里的耐心帮助,以及地球组其他小伙伴们的支持。比心❤️