DDD分层架构深度解析:电商系统实战与核心代码设计

张开发
2026/4/15 10:06:18 15 分钟阅读

分享文章

DDD分层架构深度解析:电商系统实战与核心代码设计
1. DDD分层架构的本质与电商场景适配第一次接触DDD分层架构时我盯着那个领域层不依赖任何框架的原则发愣——这跟传统三层架构有什么区别直到重构电商订单系统时库存超卖的问题才让我真正明白分层不是目的保护业务逻辑的纯粹性才是核心。想象一下当你修改支付方式时不应该被迫调整数据库表结构当你切换缓存方案时也不该影响订单状态机流转。这就是DDD分层要解决的问题。电商系统的复杂性在于多上下文协作。订单要调用库存、支付、物流等多个服务传统架构容易变成大泥球。通过DDD分层我们明确划分出四个关键层级用户接口层像快递柜一样处理多种投递方式。HTTP API、消息队列、RPC接口在这里统一转换为应用层理解的参数。我曾用一周时间将Kafka消息接入改造为与HTTP接口共用同一套应用服务验证了这层的灵活性应用层类似电商平台的运营人员不直接操作商品但协调各个部门完成订单生命周期。这里要警惕变成第二个Service层——我曾在这个坑里挣扎过把优惠券计算逻辑写在应用服务里导致修改促销策略要全量回归测试领域层真正的商品仓库存放订单、库存等核心业务实体。关键要识别聚合根比如订单聚合根要管控所有订单项的状态变更。有次促销活动暴露的问题让我记忆犹新因为没通过聚合根修改配送地址导致部分订单项地址不一致基础设施层像物流车队默默支持业务运转。这里有个实用技巧利用Spring的Profile实现不同环境的多套仓储实现本地开发用H2测试环境切MySQL// 典型电商分层依赖示例 order-interfaces (WEB) ↓ order-application ↓ ↘ order-domain ← order-infrastructure2. 电商核心聚合的充血模型设计订单聚合根的设计是电商系统的灵魂。早期我犯过典型错误——把订单做成贫血模型所有逻辑都在Service里// 反面教材贫血模型 public class Order { private Long id; private String status; // 只有getter/setter } public class OrderService { public void pay(Long orderId) { Order order dao.findById(orderId); if(!CREATED.equals(order.getStatus())) { throw new Exception(状态异常); } order.setStatus(PAID); dao.update(order); } }充血模型的改造关键在于让聚合根掌握业务规则// 正确姿势充血模型 public class Order implements AggregateRootLong { private Long id; private OrderStatus status; private ListOrderItem items; // 核心业务方法 public void pay(Payment payment) { if (!status.canPay()) { throw new DomainException(当前状态不可支付); } if (!payment.validate()) { throw new DomainException(支付校验失败); } status OrderStatus.PAID; addDomainEvent(new OrderPaidEvent(this.id)); } // 管控订单项变更 public void addItem(Product product, int quantity) { if (status ! OrderStatus.DRAFT) { throw new DomainException(仅草稿订单可修改); } items.add(new OrderItem(product, quantity)); } }库存聚合的设计陷阱更值得注意。我们曾因库存扣减后释放的逻辑分散在多个Service中导致超卖。最终方案是用Inventory聚合根统一管理public class Inventory { private String sku; private int stock; private int lockedStock; public void lock(int quantity) { if (stock - lockedStock quantity) { throw new DomainException(库存不足); } lockedStock quantity; } public void deduct(int quantity) { if (lockedStock quantity) { throw new DomainException(未锁定足够库存); } lockedStock - quantity; stock - quantity; } public void release(int quantity) { lockedStock Math.max(0, lockedStock - quantity); } }3. 仓储实现的精妙平衡仓储接口属于领域层实现却属于基础设施层这种分裂设计曾让我非常困惑。直到需要支持多数据库时才体会到其价值。仓储的核心是扮演领域对象和持久化机制之间的翻译// 领域层定义接口 public interface OrderRepository { Order findById(OrderId id); void save(Order order); ListOrder findOversdueOrders(DateTime deadline); } // 基础设施层JPA实现 Repository public class OrderRepositoryImpl implements OrderRepository { Autowired private OrderJpaRepository jpaRepository; Override public Order findById(OrderId id) { OrderEntity entity jpaRepository.findById(id.value()) .orElseThrow(OrderNotFoundException::new); return OrderMapper.toDomain(entity); } Override public void save(Order order) { OrderEntity entity OrderMapper.toEntity(order); jpaRepository.save(entity); // 处理领域事件 order.domainEvents().forEach(event - eventPublisher.publish(event)); order.clearEvents(); } }值对象持久化的三种策略需要根据场景选择JSON序列化适合复杂嵌套结构Embeddable public class Address { private String province; private String city; // getters... } Entity public class OrderEntity { Column(columnDefinition JSON) private String addressJson; }Embeddable注解适合简单值对象Embeddable public class Money { private BigDecimal amount; private String currency; } Entity public class OrderItemEntity { Embedded private Money price; }多字段平铺适合需要索引查询的字段Entity public class OrderEntity { private String province; private String city; private String district; }4. 事件驱动的上下文协作电商中最棘手的分布式事务问题我们最终用领域事件最终一致性解决。当订单创建时// 领域层事件定义 public class OrderCreatedEvent implements DomainEvent { private OrderId orderId; private ListOrderItem items; // 事件数据要包含全部必要信息 } // 应用层处理 public class OrderAppService { Transactional public void createOrder(CreateOrderCommand cmd) { Order order OrderFactory.create(cmd); orderRepository.save(order); // 发布领域事件 eventPublisher.publish(new OrderCreatedEvent(order)); } } // 库存上下文监听 KafkaListener(topics order-events) public void handleOrderCreated(OrderCreatedEvent event) { inventoryService.batchLockStock(event.getItems()); }事件设计的三个要点事件要包含完整的业务语义避免接收方再查数据事件命名用过去时态表示已发生的事实考虑幂等处理比如在事件中添加唯一ID我们在实践中总结出事件版本控制方案public class OrderCreatedEventV2 implements DomainEvent { private String eventId UUID.randomUUID().toString(); private int version 2; private OrderId orderId; private Address shippingAddress; // V2新增字段 }5. 实战中的分层陷阱与应对陷阱1应用层变成上帝类症状单个AppService超过2000行代码解决按业务能力拆分比如OrderPaymentAppService、OrderDeliveryAppService陷阱2领域层引入技术依赖反例在领域实体上添加Entity注解正确做法通过转换器隔离public class OrderMapper { public static Order toDomain(OrderEntity entity) { return Order.builder() .id(new OrderId(entity.getId())) .status(OrderStatus.valueOf(entity.getStatus())) .build(); } }陷阱3基础设施层污染业务逻辑反例在Repository实现中写SQL包含业务规则正确做法将复杂查询拆分为Specification模式public interface OrderSpecification { Predicate toPredicate(RootOrderEntity root, CriteriaQuery? query); } public class OverdueOrderSpec implements OrderSpecification { private DateTime deadline; Override public Predicate toPredicate(RootOrderEntity root, CriteriaQuery? query) { return builder.lessThan(root.get(createTime), deadline); } }性能优化方面我们通过CQRS模式解决查询瓶颈// 命令端 Transactional public void confirmOrder(Long orderId) { Order order orderRepository.findById(orderId); order.confirm(); // 同步更新读模型 orderReadModelUpdater.update(order); } // 查询端 public class OrderReadModel { private String orderNumber; private String customerName; // 关联查询字段 private String productNames; // 聚合展示字段 }6. 代码组织结构的最佳实践经过多个电商项目迭代我们总结出可扩展的包结构src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ ├── order/ │ │ │ ├── interfaces/ │ │ │ │ ├── web/ │ │ │ │ │ └── OrderController.java │ │ │ │ └── mq/ │ │ │ │ └── OrderMessageListener.java │ │ │ ├── application/ │ │ │ │ ├── command/ │ │ │ │ │ └── PlaceOrderCommand.java │ │ │ │ └── event/ │ │ │ │ └── OrderEventHandler.java │ │ │ ├── domain/ │ │ │ │ ├── model/ │ │ │ │ │ ├── Order.java │ │ │ │ │ └── OrderItem.java │ │ │ │ ├── service/ │ │ │ │ │ └── PricingService.java │ │ │ │ └── repository/ │ │ │ │ └── OrderRepository.java │ │ │ └── infrastructure/ │ │ │ ├── persistence/ │ │ │ │ └── OrderRepositoryImpl.java │ │ │ └── client/ │ │ │ └── InventoryClient.java │ │ └── inventory/ │ │ └── ... │ └── resources/ │ ├── application.yml │ └── db/ │ └── migration/ │ └── V1__init_order.sql关键目录规范每个限界上下文独立模块领域层按聚合根分目录应用层按命令/查询分离基础设施实现与接口同包名不同模块对于复杂查询的处理我们采用参数化查询对象public class OrderQuery { private String orderNo; private DateRange createTimeRange; private OrderStatus status; public SpecificationOrderEntity toSpec() { return (root, query, cb) - { ListPredicate predicates new ArrayList(); if (StringUtils.isNotBlank(orderNo)) { predicates.add(cb.equal(root.get(orderNo), orderNo)); } // 其他条件... return cb.and(predicates.toArray(new Predicate[0])); }; } }7. 测试策略的层级映射DDD分层架构需要配套的测试金字塔领域层纯单元测试class OrderTest { Test void should_throw_when_pay_cancelled_order() { Order order new Order(); order.cancel(); assertThrows(DomainException.class, () - order.pay(new Payment())); } }应用层集成测试带Spring容器SpringBootTest class OrderAppServiceTest { Autowired private OrderAppService service; Test Transactional void should_create_order() { CreateOrderCommand cmd new CreateOrderCommand(...); OrderResult result service.createOrder(cmd); assertNotNull(result.getOrderId()); } }接口层MockMVC测试WebMvcTest(OrderController.class) class OrderControllerTest { MockBean private OrderAppService appService; Test void should_return_400_when_param_invalid() throws Exception { mockMvc.perform(post(/orders) .contentType(MediaType.APPLICATION_JSON) .content({})) .andExpect(status().isBadRequest()); } }基础设施层测试容器集成Testcontainers class OrderRepositoryIT { Container static PostgreSQLContainer? postgres new PostgreSQLContainer(postgres:13); DynamicPropertySource static void configure(DynamicPropertyRegistry registry) { registry.add(spring.datasource.url, postgres::getJdbcUrl); } Test void should_save_and_load_order() { // 测试真实数据库操作 } }8. 演进式架构的实践心得在电商系统迭代过程中我们总结出渐进式改造的方法新功能严格分层从新模块开始实践完整DDD分层老功能绞杀者模式逐步替换旧系统组件双写过渡方案// 过渡期间的双写逻辑 Deprecated public class OldOrderService { Transactional public void createOrder(OldOrderDTO dto) { // 旧逻辑 oldDao.insert(dto); // 新逻辑 NewOrderCommand cmd convert(dto); newOrderAppService.create(cmd); } }性能与模型的权衡当遇到性能瓶颈时我们采用逃逸通道设计public class Order { // 常规业务方法... Transactional public void fastUpdateStatus(OrderStatus status) { // 紧急情况下绕过业务规则 this.status status; // 标记为特殊操作 addDomainEvent(new OrderEmergencyUpdatedEvent(this.id)); } }监控方面我们通过分层埋点追踪问题// 应用层统一AOP监控 Aspect Component public class AppLayerMonitor { Around(within(org.springframework.stereotype.Service)) public Object monitor(ProceedingJoinPoint pjp) throws Throwable { String method pjp.getSignature().getName(); Timer timer Metrics.timer(app.service, method, method); return timer.record(() - pjp.proceed()); } }最后关于团队协作的建议建立分层代码审查清单领域层检查是否包含业务逻辑是否有框架依赖聚合根是否管控子实体应用层检查是否只是流程编排事务注解是否正确使用是否处理了跨聚合协调接口层检查参数校验是否完整返回DTO是否与领域对象隔离异常转换是否合理基础设施层检查是否实现领域接口转换器是否处理了所有字段外部调用是否有熔断机制

更多文章