对于复杂业务,DDD 绝对是一把神器,由于它过于复杂,很多人望而却步。因为太过严谨,形成了很多设计模式、规范化流程,这些爆炸的信息已经成为 DDD 落地的重大阻力。
但,如果我们将这些规范化的流程封装到框架,仅把核心业务逻辑暴露给开发人员,又会是什么样子?
在尝试使用 DDD 处理复杂业务之后,就难以回到 CRUD 的世界。相对于 CRUD 来说,DDD 具备一套完整的理论基础,提供了一组业务模式和规范用以应对复杂的业务流程。但,由于其概念繁多,通常还过于抽象,存在一定的门槛;加上过于规范,业务流程被拆分多个组件,大大增加了理解成本,也加大了开发人员的代码量。
好在,由于规范所以产生了大量的最佳实践,日常开发中的众多业务场景均可完成抽象化、模板化甚至清单化,开发人员只需照“猫画虎”便可以完成DDD落地。而这些最佳实践,最好能够以“基础设施”的方式进行支持,降低入门门槛,提升开发效率。
在设计上,CommandService 借鉴了 Spring Data 核心理念,在使用上也与 Spring Data 保存一致,以降低使用门槛。
首先,在项目中引入 lego-starter,具体如下:
com.geekhalo.lego lego-starter 0.1.10-command_service-SNAPSHOT
然后,依次引入 validation 和 spring data jpa 支持
org.springframework.boot spring-boot-starter-validation
org.springframework.boot spring-boot-starter-data-jpa
在 application 文件中添加 Datasource 配置:
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/legousername: rootpassword: rootjpa:hibernate:ddl-auto: updateshow-sql: true
新增
SpringDataJpaConfiguration 配置类,完成对 spring data jpa 的配置,具体如下:
@Configuration
@EnableJpaRepositories(basePackages = {"com.geekhalo.lego.command"})
public class SpringDataJpaConfiguration {
}
新增
CommandServiceConfiguration 配置类,完成对 CommandService 的配置,具体如下:
@Configuration
@EnableCommandService(basePackages = "com.geekhalo.lego.command")
public class CommandServiceConfiguration {
}
其中,@EnableCommandService 开启 CommandService 自动扫描,扫描路径为:com.geekhalo.lego.command
新建 Order、OrderAddress、OrderItem、PayRecord 实体对象,并以 Order 为聚合根管理其他关联对象,Order 定义如下:
@Data
@Entity(name = "CommandOrder")
@Table(name = "command_order")
@Setter(AccessLevel.PRIVATE)
public class Order implements AggRoot {@Transientprivate final List events = Lists.newArrayList();@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(name = "user_id")private Long userId;@Column(name = "status")@Enumerated(EnumType.STRING)private OrderStatus status;@Column(name = "price")private int price;@OneToOne(cascade = CascadeType.ALL)@JoinColumn(name = "user_address_id")private OrderAddress address;@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)@JoinColumn(name = "order_id")private List items = Lists.newArrayList();@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)@JoinColumn(name = "order_id")private List payRecords = Lists.newArrayList();
}
创建 OrderRepository,用于完成 Order 聚合根的持久化,具体如下:
@Repository("orderRepositoryForCommand")
public interface OrderRepository extends JpaRepository,CommandRepository {
}
@CommandServiceDefinition(domainClass = Order.class,idClass = Long.class,repositoryClass = OrderRepository.class)
public interface OrderCommandServiceProxy{
}
定义 OrderCommandServiceProxy 接口,使用 @CommandServiceDefinition 将其声明为 CommandService,具体配置如下:
框架将自动创建 OrderCommandServiceProxy 的代理对象,并实现核心业务逻辑。
创建订单的核心逻辑由 Order 聚合根的静态 create 方法承载,具体如下:
public static Order create(CreateOrderContext contextProxy) {Order order = new Order();order.setUserId(contextProxy.getCommand().getUserId());Address address = contextProxy.getAddress();OrderAddress orderAddress = new OrderAddress();orderAddress.setDetail(address.getDetail());order.setAddress(orderAddress);List products = contextProxy.getProducts();Map productMap = products.stream().collect(Collectors.toMap(Product::getId, Function.identity()));List productForBuys = contextProxy.getCommand().getProducts();productForBuys.stream().map(productForBuy -> {Product product = productMap.get(productForBuy.getProductId());return OrderItem.create(product, productForBuy.getAmount());}).forEach(orderItem -> order.addOrderItem(orderItem));order.init();OrderCreatedEvent event = new OrderCreatedEvent(order);order.events.add(event);return order;
}
其核心逻辑包括:
创建订单的业务流程位于 OrderCommandServiceImpl 的 create 方法,具体代码如下:
@Override
public Long create(CreateOrderCommand command) {CreateOrderContext context = new CreateOrderContext(command);CreateOrderContext contextProxy = this.lazyLoadProxyFactory.createProxyFor(context);validateService.validate(contextProxy);Order order = Order.create(contextProxy);this.orderRepository.save(order);order.consumeAndClearEvent(event -> eventPublisher.publishEvent(event));return order.getId();
}
核心逻辑包括:
核心业务操作随业务变化而变,而业务流程各个步骤基本不变,这些不变部分应该由框架来完成。
相比手工实现业务流程,使用 CommandService 只需在接口中增加 create 方法,将由 CommandService 框架为其生成代理实现,具体如下:
@CommandServiceDefinition(domainClass = Order.class,idClass = Long.class,repositoryClass = OrderRepository.class)
public interface OrderCommandServiceProxy{Long create(CreateOrderCommand command);
}
无需编写实现,只需定义接口
支付成功的核心业务操作位于 order 对象的 paySuccess 方法,具体如下:
public void paySuccess(PaySuccessCommand paySuccessCommand){PayRecord payRecord = PayRecord.create(paySuccessCommand.getChanel(), paySuccessCommand.getPrice());this.payRecords.add(payRecord);this.setStatus(OrderStatus.PAID);OrderPaySuccessEvent event = new OrderPaySuccessEvent(this);this.events.add(event);
}
核心操作包括:
有了 paySuccess 核心业务操作,业务流程也变得非常简单,具体如下:
public void paySuccess(PaySuccessCommand command) {Order order = this.orderRepository.findById(command.getOrderId()).orElseThrow(() -> new AggNotFoundException(command.getOrderId()));order.paySuccess(command);this.orderRepository.save(order);order.consumeAndClearEvent(event -> eventPublisher.publishEvent(event));
}
核心操作如下:
聚合根更新操作的业务流程基本不变,这些不变部分应该由框架来完成。
相比手工实现业务流程,使用 CommandService 只需在接口中增加 paySuccess 方法,将由 CommandService 框架为其生成代理实现,具体如下:
@CommandServiceDefinition(domainClass = Order.class,idClass = Long.class,repositoryClass = OrderRepository.class)
public interface OrderCommandServiceProxy{Long create(CreateOrderCommand command);void paySuccess(PaySuccessCommand command);
}
如果业务逻辑并不是简单的创建和更新,而是更为复杂的定制化流程,这时便可以使用自定义逻辑进行扩展。
首先,我们需要创建一个自定义接口,具体如下:
public interface CustomOrderCommandService {void cancel(Long orderId);
}
然后,按照业务需求,在
CustomOrderCommandServiceImpl 中实现业务逻辑。
@Service
public class CustomOrderCommandServiceImpl implements CustomOrderCommandService{@Autowiredprivate OrderRepository orderRepository;@Overridepublic void cancel(Long orderId) {Order order = this.orderRepository.findById(orderId).orElseThrow(() -> new AggNotFoundException(orderId));order.cancel();this.orderRepository.save(order);}
}
最后,我们需要将自定义接口与 OrderCommandServiceProxy 进行集成。
只需让 OrderCommandServiceProxy 接口继承 CustomOrderCommandService 即可。
@CommandServiceDefinition(domainClass = Order.class,idClass = Long.class,repositoryClass = OrderRepository.class)
public interface OrderCommandServiceProxy extends CustomOrderCommandService{Long create(CreateOrderCommand command);void paySuccess(PaySuccessCommand command);
}
在调用 cancel 方法时,proxy 会将请求转发给
CustomOrderCommandServiceImpl 的 cancel 方法。
为 CommandService 自动实现的 Proxy 结构如下:

image
Proxy 实现 自定义的CommandService 接口,并将方法调用分发给不同的实现,核心拦截器包括:
以下是整个框架的初始化流程:

image
通过 @EnableCommandService 注解开启 CommandService 支持后,将向 Spring 容器注册
CommandServiceBeanDefinitionRegistrar,由该组件完成 CommandService 的装配: