参考:https://insights.thoughtworks.cn/backend-development-ddd/ 后端开发实践系列——领域驱动设计(DDD)编码实践
战略设计更偏向于软件架构,那么战术设计便更偏向于编码实现。DDD战术设计的目的是使得业务能够从技术中分离并突显出来,让代码直接表达业务的本身,其中包含了聚合根、应用服务、资源库、工厂等概念。
源码参考: https://github.com/e-commerce-sample/ecommerce-order-service
实现业务的3种方式对比
1、service+贫血模型:存在一个贫血的“领域对象”,业务逻辑通过一个Service类实现,然后通过setter方法更新领域对象,最后通过DAO(多数情况下可能使用诸如Hibernate之类的ORM框架)保存到数据库中。
职责划分模糊不清,使本应该内聚在Order
中的业务逻辑泄露到了其他地方(OrderService
),导致Order
成为一个只是充当数据容器的贫血模型(Anemic Model),而非真正意义上的领域模型。在项目持续演进的过程中,这些业务逻辑会分散在不同的Service类中,最终的结果是代码变得越来越难以理解进而逐渐丧失扩展能力。
@Transactional public void changeProductCount(String id, ChangeProductCountCommand command) { Order order = DAO.findById(id); if (order.getStatus() == PAID) { throw new OrderCannotBeModifiedException(id); } OrderItem orderItem = order.getOrderItem(command.getProductId()); orderItem.setCount(command.getCount()); order.setTotalPrice(calculateTotalPrice(order)); DAO.saveOrUpdate(order); }
2、事务脚本:在不使用ORM的情况下,领域对象甚至都没有必要存在。于是,此时的代码实现便退化成了事务脚本(Transaction Script),也就是直接将Service类中计算出的结果直接保存到数据库(或者有时都没有Service类,直接通过SQL实现业务逻辑):
@Transactional public void changeProductCount(String id, ChangeProductCountCommand command) { OrderStatus orderStatus = DAO.getOrderStatus(id); if (orderStatus == PAID) { throw new OrderCannotBeModifiedException(id); } DAO.updateProductCount(id, command.getProductId(), command.getCount()); DAO.updateTotalPrice(id); }
3、DDD: 核心的业务逻辑被内聚在行为饱满的领域对象
class Order { public void changeProductCount(ProductId productId, int count) { if (this.status == PAID) { throw new OrderCannotBeModifiedException(this.id); } OrderItem orderItem = retrieveItem(productId); orderItem.updateCount(count); } } // Controller @PostMapping("/order/{id}/products") public void changeProductCount(@PathVariable(name = "id") String id, @RequestBody @Valid ChangeProductCountCommand command) { Order order = DAO.byId(orderId(id)); order.changeProductCount(ProductId.productId(command.getProductId()), command.getCount()); order.updateTotalPrice(); DAO.saveOrUpdate(order); }
目录划分
基于聚合根进行顶层包的划分:“内聚性”原则的典型代表,在各自的顶层包下再根据代码结构的复杂程度划分子包
如果子包下只有单个文件,足够简单,可以不创建子包
├── order
│ ├── OrderApplicationService.java
│ ├── OrderController.java
│ ├── OrderPaymentProxy.java
│ ├── OrderPaymentService.java
│ ├── OrderRepository.java
│ ├── command
│ │ ├── ChangeAddressDetailCommand.java
│ │ ├── CreateOrderCommand.java
│ │ ├── OrderItemCommand.java
│ │ ├── PayOrderCommand.java
│ │ └── UpdateProductCountCommand.java
│ ├── exception
│ │ ├── OrderCannotBeModifiedException.java
│ │ ├── OrderNotFoundException.java
│ │ ├── PaidPriceNotSameWithOrderPriceException.java
│ │ └── ProductNotInOrderException.java
│ ├── model
│ │ ├── Order.java
│ │ ├── OrderFactory.java
│ │ ├── OrderId.java
│ │ ├── OrderIdGenerator.java
│ │ ├── OrderItem.java
│ │ └── OrderStatus.java
│ └── representation
│ ├── OrderItemRepresentation.java
│ ├── OrderRepresentation.java
│ └── OrderRepresentationService.java
除上述之外,可创建单独的common类(下含util/log/xxx)等