# 📊 领域驱动设计(DDD)
# 一、DDD概述
领域驱动设计(Domain-Driven Design,DDD)是一种专注于业务领域的软件开发方法论,由Eric Evans在其2003年出版的《Domain-Driven Design: Tackling Complexity in the Heart of Software》一书中首次提出。DDD强调将软件系统的设计与业务领域的核心概念紧密结合,通过深入理解业务领域来指导软件设计。
# 1.1 DDD的核心思想
- 以领域为中心:将业务领域作为软件设计的核心,而不是技术实现
- 协作设计:业务专家与技术专家紧密协作,共同构建领域模型
- 分层架构:通过分层架构将领域逻辑与其他关注点分离
- 模型驱动:通过领域模型来指导软件的设计和实现
- 渐进式完善:领域模型是一个渐进式完善的过程,随着对领域的理解深入而不断优化
# 1.2 DDD的应用场景
DDD特别适合复杂的业务系统开发,尤其是以下场景:
- 业务逻辑复杂:具有丰富业务规则和复杂业务流程的系统
- 团队协作开发:需要业务专家和技术专家紧密协作的大型项目
- 长期维护演进:需要持续维护和演进的系统
- 领域知识密集:包含大量专业领域知识的系统,如金融、医疗、法律等领域
# 二、DDD核心概念
# 2.1 领域(Domain)与子领域(Subdomain)
- 领域:指企业所涉及的业务范围,包含业务规则、概念和活动
- 子领域:将一个大的领域划分为多个较小的、更具体的子领域,每个子领域专注于特定的业务功能
- 核心域:企业的核心竞争力所在的子领域,是业务的核心价值所在
- 支撑域:支持核心域运作的子领域,但不构成核心竞争力
- 通用域:可以被多个系统或组织复用的通用功能子领域
示例:电商系统领域划分
电商系统
├── 核心域:订单管理、商品管理
├── 支撑域:支付处理、物流管理
└── 通用域:用户认证、日志管理
# 2.2 实体(Entity)
实体是具有唯一标识的领域对象,其标识在整个生命周期中保持不变,即使其属性发生变化。
特征:
- 具有唯一标识符
- 可以修改其状态
- 其相等性基于标识符,而不是属性
- 具有生命周期
实现示例(Java):
public class Order {
private OrderId id; // 使用值对象作为唯一标识
private Money totalAmount;
private OrderStatus status;
private List<OrderLine> orderLines;
private LocalDateTime createTime;
private LocalDateTime updateTime;
// 构造函数应该确保对象创建时的完整性
public Order(OrderId id, List<OrderLine> orderLines) {
this.id = id;
this.orderLines = new ArrayList<>(orderLines);
this.status = OrderStatus.CREATED;
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
calculateTotalAmount();
}
// 业务方法,体现领域逻辑
public void confirm() {
if (status != OrderStatus.CREATED) {
throw new IllegalStateException("Order cannot be confirmed");
}
this.status = OrderStatus.CONFIRMED;
this.updateTime = LocalDateTime.now();
}
public void ship() {
if (status != OrderStatus.CONFIRMED) {
throw new IllegalStateException("Order cannot be shipped");
}
this.status = OrderStatus.SHIPPED;
this.updateTime = LocalDateTime.now();
}
public void cancel() {
if (status != OrderStatus.CREATED && status != OrderStatus.CONFIRMED) {
throw new IllegalStateException("Order cannot be canceled");
}
this.status = OrderStatus.CANCELED;
this.updateTime = LocalDateTime.now();
}
// 供仓储层使用的方法,设置订单状态
public void setStatus(OrderStatus status) {
this.status = status;
}
// 获取订单状态
public OrderStatus getStatus() {
return status;
}
private void calculateTotalAmount() {
this.totalAmount = orderLines.stream()
.map(line -> line.getSubtotal())
.reduce(Money.ZERO, Money::add);
}
// Getters (通常只有getter,setter应尽量少用,通过业务方法修改状态)
public OrderId getId() {
return id;
}
public Money getTotalAmount() {
return totalAmount;
}
// 重写equals和hashCode,基于id进行比较
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Order order = (Order) o;
return Objects.equals(id, order.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
// 订单ID值对象
public class OrderId {
private final String value;
public OrderId(String value) {
if (value == null || value.isEmpty()) {
throw new IllegalArgumentException("Order ID cannot be empty");
}
this.value = value;
}
public String getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OrderId orderId = (OrderId) o;
return Objects.equals(value, orderId.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
# 2.3 值对象(Value Object)
值对象是没有唯一标识的领域对象,其相等性基于属性值,而不是标识。值对象是不可变的,一旦创建就不能修改。
特征:
- 没有唯一标识符
- 不可变
- 其相等性基于属性值
- 可以作为其他对象的属性
实现示例(Java):
public class Money {
public static final Money ZERO = new Money(BigDecimal.ZERO, Currency.USD);
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
if (amount == null) {
throw new IllegalArgumentException("Amount cannot be null");
}
if (currency == null) {
throw new IllegalArgumentException("Currency cannot be null");
}
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
// 工厂方法
public static Money of(BigDecimal amount, Currency currency) {
return new Money(amount, currency);
}
public static Money of(double amount, Currency currency) {
return new Money(BigDecimal.valueOf(amount), currency);
}
// 行为方法 - 不修改原对象,返回新对象
public Money add(Money other) {
checkCurrencyMatch(other);
return new Money(this.amount.add(other.amount), this.currency);
}
public Money subtract(Money other) {
checkCurrencyMatch(other);
BigDecimal result = this.amount.subtract(other.amount);
if (result.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalStateException("Result cannot be negative");
}
return new Money(result, this.currency);
}
public Money multiply(int factor) {
if (factor < 0) {
throw new IllegalArgumentException("Factor cannot be negative");
}
return new Money(this.amount.multiply(BigDecimal.valueOf(factor)), this.currency);
}
private void checkCurrencyMatch(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currencies do not match");
}
}
// Getters
public BigDecimal getAmount() {
return amount;
}
public Currency getCurrency() {
return currency;
}
// 重写equals和hashCode
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return Objects.equals(amount, money.amount) &&
Objects.equals(currency, money.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
@Override
public String toString() {
return amount + " " + currency.getCurrencyCode();
}
}
# 2.4 聚合(Aggregate)与聚合根(Aggregate Root)
聚合是一组相关的实体和值对象的集合,它们作为一个整体被视为数据修改的单元。聚合根是聚合中作为外部访问入口的实体。
聚合的规则:
- 外部对象只能引用聚合根,不能直接引用聚合内的其他实体
- 聚合内的实体可以引用其他聚合的聚合根
- 数据修改必须通过聚合根进行,确保聚合的一致性
- 聚合之间通过领域事件进行通信
实现示例(Java):
// 订单聚合根
public class Order {
private OrderId id;
private CustomerId customerId;
private List<OrderLine> orderLines = new ArrayList<>();
private Money totalAmount;
private OrderStatus status;
// 构造函数确保订单创建时的完整性
public Order(OrderId id, CustomerId customerId) {
this.id = id;
this.customerId = customerId;
this.status = OrderStatus.CREATED;
this.totalAmount = Money.ZERO;
}
// 添加订单项 - 通过聚合根管理聚合内的实体
public void addOrderLine(ProductId productId, int quantity, Money unitPrice) {
if (status != OrderStatus.CREATED) {
throw new IllegalStateException("Cannot add order line to confirmed or shipped order");
}
OrderLine line = new OrderLine(productId, quantity, unitPrice);
orderLines.add(line);
recalculateTotalAmount();
}
// 移除订单项
public void removeOrderLine(ProductId productId) {
if (status != OrderStatus.CREATED) {
throw new IllegalStateException("Cannot remove order line from confirmed or shipped order");
}
orderLines.removeIf(line -> line.getProductId().equals(productId));
recalculateTotalAmount();
}
// 确认订单 - 业务方法,确保订单状态的一致性
public void confirm() {
if (status != OrderStatus.CREATED) {
throw new IllegalStateException("Order cannot be confirmed");
}
if (orderLines.isEmpty()) {
throw new IllegalStateException("Cannot confirm order with no order lines");
}
this.status = OrderStatus.CONFIRMED;
}
private void recalculateTotalAmount() {
this.totalAmount = orderLines.stream()
.map(OrderLine::getSubtotal)
.reduce(Money.ZERO, Money::add);
}
// Getters
public OrderId getId() {
return id;
}
public CustomerId getCustomerId() {
return customerId;
}
public List<OrderLine> getOrderLines() {
return Collections.unmodifiableList(orderLines);
}
public Money getTotalAmount() {
return totalAmount;
}
public OrderStatus getStatus() {
return status;
}
}
// 订单项实体(聚合内的实体)
class OrderLine {
private ProductId productId;
private int quantity;
private Money unitPrice;
private Money subtotal;
public OrderLine(ProductId productId, int quantity, Money unitPrice) {
if (quantity <= 0) {
throw new IllegalArgumentException("Quantity must be positive");
}
this.productId = productId;
this.quantity = quantity;
this.unitPrice = unitPrice;
this.subtotal = unitPrice.multiply(quantity);
}
// Getters
public ProductId getProductId() {
return productId;
}
public int getQuantity() {
return quantity;
}
public Money getUnitPrice() {
return unitPrice;
}
public Money getSubtotal() {
return subtotal;
}
}
# 2.5 领域服务(Domain Service)
领域服务是封装不属于单个实体或值对象的业务逻辑的组件。当一个操作涉及多个实体或值对象时,通常将其实现为领域服务。
特征:
- 实现特定的领域操作
- 操作涉及多个实体或值对象
- 无状态或有很少的状态
- 定义明确的接口
实现示例(Java):
public interface OrderService {
Order createOrder(CustomerId customerId, List<OrderItemRequest> items);
void cancelOrder(OrderId orderId);
void confirmOrder(OrderId orderId);
Order findOrder(OrderId orderId);
}
@Service
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
private final CustomerRepository customerRepository;
private final InventoryService inventoryService;
private final DomainEventPublisher eventPublisher;
@Autowired
public OrderServiceImpl(OrderRepository orderRepository,
ProductRepository productRepository,
CustomerRepository customerRepository,
InventoryService inventoryService,
DomainEventPublisher eventPublisher) {
this.orderRepository = orderRepository;
this.productRepository = productRepository;
this.customerRepository = customerRepository;
this.inventoryService = inventoryService;
this.eventPublisher = eventPublisher;
}
@Override
@Transactional
public Order createOrder(CustomerId customerId, List<OrderItemRequest> items) {
// 1. 验证客户是否存在
Customer customer = customerRepository.findById(customerId)
.orElseThrow(() -> new CustomerNotFoundException("Customer not found: " + customerId));
// 2. 创建订单
OrderId orderId = new OrderId(UUID.randomUUID().toString());
Order order = new Order(orderId, customerId);
// 3. 添加订单项并检查库存
for (OrderItemRequest item : items) {
ProductId productId = new ProductId(item.getProductId());
Product product = productRepository.findById(productId)
.orElseThrow(() -> new ProductNotFoundException("Product not found: " + productId));
// 检查库存
if (!inventoryService.checkInventory(productId, item.getQuantity())) {
throw new InsufficientInventoryException("Insufficient inventory for product: " + productId);
}
// 添加订单项
order.addOrderLine(productId, item.getQuantity(), product.getPrice());
}
// 4. 保存订单
orderRepository.save(order);
// 5. 发布订单创建事件
eventPublisher.publish(new OrderCreatedEvent(orderId, customerId));
return order;
}
@Override
@Transactional
public void cancelOrder(OrderId orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException("Order not found: " + orderId));
// 检查订单状态是否允许取消
if (order.getStatus() != OrderStatus.CREATED && order.getStatus() != OrderStatus.CONFIRMED) {
throw new IllegalStateException("Order cannot be canceled");
}
// 执行取消逻辑(例如恢复库存等)
for (OrderLine line : order.getOrderLines()) {
inventoryService.restoreInventory(line.getProductId(), line.getQuantity());
}
// 更新订单状态
order.cancel();
orderRepository.save(order);
// 发布订单取消事件
eventPublisher.publish(new OrderCanceledEvent(orderId));
}
@Override
@Transactional
public void confirmOrder(OrderId orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException("Order not found: " + orderId));
// 确认订单
order.confirm();
orderRepository.save(order);
// 扣减库存
for (OrderLine line : order.getOrderLines()) {
inventoryService.reduceInventory(line.getProductId(), line.getQuantity());
}
// 发布订单确认事件
eventPublisher.publish(new OrderConfirmedEvent(orderId));
}
@Override
public Order findOrder(OrderId orderId) {
return orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException("Order not found: " + orderId));
}
}
# 2.6 仓储(Repository)
仓储是用于访问聚合根的接口,它提供了查找、添加、删除聚合根的方法,隐藏了数据存储的细节。
特征:
- 针对聚合根设计
- 提供持久化和检索聚合根的方法
- 隐藏数据存储的实现细节
- 可以实现缓存、查询优化等
实现示例(Java):
// 订单仓储接口
public interface OrderRepository {
void save(Order order);
Order findById(OrderId id);
List<Order> findByCustomerId(CustomerId customerId);
void delete(OrderId id);
List<Order> findByStatus(OrderStatus status);
}
// JPA实现
@Repository
public class JpaOrderRepository implements OrderRepository {
@Autowired
private OrderJpaRepository orderJpaRepository;
@Autowired
private OrderLineJpaRepository orderLineJpaRepository;
@Override
@Transactional
public void save(Order order) {
// 将领域模型转换为JPA实体
OrderEntity orderEntity = convertToEntity(order);
// 保存订单和订单项
OrderEntity savedOrder = orderJpaRepository.save(orderEntity);
// 处理订单项
List<OrderLineEntity> lineEntities = order.getOrderLines().stream()
.map(line -> convertToEntity(line, savedOrder))
.collect(Collectors.toList());
orderLineJpaRepository.saveAll(lineEntities);
}
@Override
@Transactional(readOnly = true)
public Order findById(OrderId id) {
return orderJpaRepository.findById(id.getValue())
.map(this::convertToDomain)
.orElse(null);
}
@Override
@Transactional(readOnly = true)
public List<Order> findByCustomerId(CustomerId customerId) {
return orderJpaRepository.findByCustomerId(customerId.getValue()).stream()
.map(this::convertToDomain)
.collect(Collectors.toList());
}
@Override
@Transactional
public void delete(OrderId id) {
orderLineJpaRepository.deleteByOrderId(id.getValue());
orderJpaRepository.deleteById(id.getValue());
}
@Override
@Transactional(readOnly = true)
public List<Order> findByStatus(OrderStatus status) {
return orderJpaRepository.findByStatus(status.name()).stream()
.map(this::convertToDomain)
.collect(Collectors.toList());
}
// 领域模型转换为JPA实体
private OrderEntity convertToEntity(Order order) {
OrderEntity entity = new OrderEntity();
entity.setId(order.getId().getValue());
entity.setCustomerId(order.getCustomerId().getValue());
entity.setStatus(order.getStatus().name());
entity.setTotalAmount(order.getTotalAmount().getAmount());
entity.setCurrency(order.getTotalAmount().getCurrency().getCurrencyCode());
// 设置其他属性
return entity;
}
// JPA实体转换为领域模型
private Order convertToDomain(OrderEntity entity) {
OrderId id = new OrderId(entity.getId());
CustomerId customerId = new CustomerId(entity.getCustomerId());
Order order = new Order(id, customerId);
// 加载订单项
List<OrderLineEntity> lineEntities = orderLineJpaRepository.findByOrderId(entity.getId());
for (OrderLineEntity lineEntity : lineEntities) {
ProductId productId = new ProductId(lineEntity.getProductId());
Money unitPrice = Money.of(lineEntity.getUnitPrice(),
Currency.getInstance(lineEntity.getCurrency()));
order.addOrderLine(productId, lineEntity.getQuantity(), unitPrice);
}
// 设置订单状态
order.setStatus(OrderStatus.valueOf(entity.getStatus()));
return order;
}
// 订单项领域模型转换为JPA实体
private OrderLineEntity convertToEntity(OrderLine line, OrderEntity orderEntity) {
OrderLineEntity entity = new OrderLineEntity();
entity.setOrderId(orderEntity.getId());
entity.setProductId(line.getProductId().getValue());
entity.setQuantity(line.getQuantity());
entity.setUnitPrice(line.getUnitPrice().getAmount());
entity.setCurrency(line.getUnitPrice().getCurrency().getCurrencyCode());
entity.setSubtotal(line.getSubtotal().getAmount());
return entity;
}
}
// Spring Data JPA接口
public interface OrderJpaRepository extends JpaRepository<OrderEntity, String> {
List<OrderEntity> findByCustomerId(String customerId);
List<OrderEntity> findByStatus(String status);
}
public interface OrderLineJpaRepository extends JpaRepository<OrderLineEntity, Long> {
List<OrderLineEntity> findByOrderId(String orderId);
void deleteByOrderId(String orderId);
}
# 2.7 工厂(Factory)
工厂负责创建复杂的领域对象,尤其是聚合根。工厂隐藏了对象创建的复杂性,确保创建的对象是有效的。
特征:
- 封装对象创建的复杂性
- 确保创建的对象是有效的(符合业务规则)
- 可以是独立的类,也可以是实体或值对象的静态方法
实现示例(Java):
public class OrderFactory {
private final ProductRepository productRepository;
public OrderFactory(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public Order createOrder(CustomerId customerId, List<OrderItemData> items) {
// 创建订单ID
OrderId orderId = new OrderId(UUID.randomUUID().toString());
// 创建订单对象
Order order = new Order(orderId, customerId);
// 添加订单项
for (OrderItemData item : items) {
ProductId productId = new ProductId(item.getProductId());
Product product = productRepository.findById(productId)
.orElseThrow(() -> new ProductNotFoundException("Product not found: " + productId));
// 验证订单项数据
if (item.getQuantity() <= 0) {
throw new IllegalArgumentException("Quantity must be positive");
}
order.addOrderLine(productId, item.getQuantity(), product.getPrice());
}
// 验证订单
if (order.getOrderLines().isEmpty()) {
throw new IllegalStateException("Order cannot be empty");
}
return order;
}
}
// 使用静态工厂方法的实体
public class Money {
// 私有构造函数
private Money(BigDecimal amount, Currency currency) {
// 验证逻辑
this.amount = amount;
this.currency = currency;
}
// 静态工厂方法
public static Money of(BigDecimal amount, Currency currency) {
return new Money(amount, currency);
}
public static Money of(double amount, Currency currency) {
return new Money(BigDecimal.valueOf(amount), currency);
}
public static Money zero(Currency currency) {
return new Money(BigDecimal.ZERO, currency);
}
}
# 2.8 领域事件(Domain Event)
领域事件是表示领域中发生的重要事件的对象。领域事件用于在聚合之间或限界上下文之间进行通信,实现最终一致性。
特征:
- 表示领域中发生的重要事件
- 包含事件发生的时间、相关实体的标识等信息
- 不可变
- 可以被多个组件订阅和处理
实现示例(Java):
// 领域事件接口
public interface DomainEvent {
LocalDateTime getOccurredAt();
String getEventId();
}
// 订单创建事件
public class OrderCreatedEvent implements DomainEvent {
private final String eventId;
private final OrderId orderId;
private final CustomerId customerId;
private final LocalDateTime occurredAt;
public OrderCreatedEvent(OrderId orderId, CustomerId customerId) {
this.eventId = UUID.randomUUID().toString();
this.orderId = orderId;
this.customerId = customerId;
this.occurredAt = LocalDateTime.now();
}
@Override
public LocalDateTime getOccurredAt() {
return occurredAt;
}
@Override
public String getEventId() {
return eventId;
}
public OrderId getOrderId() {
return orderId;
}
public CustomerId getCustomerId() {
return customerId;
}
}
// 领域事件发布者接口
public interface DomainEventPublisher {
void publish(DomainEvent event);
}
// Kafka实现的领域事件发布者
@Service
public class KafkaDomainEventPublisher implements DomainEventPublisher {
private final KafkaTemplate<String, DomainEvent> kafkaTemplate;
@Autowired
public KafkaDomainEventPublisher(KafkaTemplate<String, DomainEvent> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
@Override
public void publish(DomainEvent event) {
// 根据事件类型发送到不同的主题
String topic = getTopicForEvent(event);
kafkaTemplate.send(topic, event.getEventId(), event);
}
private String getTopicForEvent(DomainEvent event) {
// 根据事件类型返回对应的主题名
if (event instanceof OrderCreatedEvent) {
return "order-created-events";
} else if (event instanceof OrderConfirmedEvent) {
return "order-confirmed-events";
} else if (event instanceof OrderCanceledEvent) {
return "order-canceled-events";
}
// 其他事件类型...
throw new IllegalArgumentException("Unknown event type: " + event.getClass().getName());
}
}
// 领域事件处理器
@Service
public class OrderEventHandlers {
private static final Logger log = LoggerFactory.getLogger(OrderEventHandlers.class);
private final InventoryService inventoryService;
private final NotificationService notificationService;
@Autowired
public OrderEventHandlers(InventoryService inventoryService, NotificationService notificationService) {
this.inventoryService = inventoryService;
this.notificationService = notificationService;
}
// 处理订单创建事件
@KafkaListener(topics = "order-created-events", groupId = "inventory-group")
public void handleOrderCreatedEvent(OrderCreatedEvent event) {
// 记录日志
log.info("Received order created event: {}", event.getOrderId().getValue());
// 这里可以实现相应的业务逻辑
// 例如:发送订单确认邮件、更新统计信息等
notificationService.sendOrderConfirmationEmail(event.getCustomerId(), event.getOrderId());
}
// 处理订单确认事件
@KafkaListener(topics = "order-confirmed-events", groupId = "inventory-group")
public void handleOrderConfirmedEvent(OrderConfirmedEvent event) {
// 记录日志
log.info("Received order confirmed event: {}", event.getOrderId().getValue());
// 这里可以实现相应的业务逻辑
// 例如:扣减库存、安排发货等
// 注意:在实际应用中,库存扣减通常在订单确认的事务中完成
// 这里只是为了演示事件处理
}
// 处理订单取消事件
@KafkaListener(topics = "order-canceled-events", groupId = "inventory-group")
public void handleOrderCanceledEvent(OrderCanceledEvent event) {
// 记录日志
log.info("Received order canceled event: {}", event.getOrderId().getValue());
// 这里可以实现相应的业务逻辑
// 例如:恢复库存、发送取消通知等
// 注意:在实际应用中,库存恢复通常在订单取消的事务中完成
// 这里只是为了演示事件处理
}
}
# 三、DDD战略设计
# 3.1 限界上下文(Bounded Context)
限界上下文是一个明确的边界,在这个边界内,领域模型具有特定的含义和一致性。不同的限界上下文可以有相同名称的概念,但这些概念可能有不同的含义和实现。
特征:
- 定义了模型的适用范围
- 内部模型保持一致
- 与其他限界上下文通过明确的接口进行通信
- 可以映射到一个或多个微服务
示例:电商系统的限界上下文划分
电商系统
├── 订单上下文(Order Context)
│ ├── 订单(Order)
│ ├── 订单项(OrderLine)
│ ├── 订单状态(OrderStatus)
│ └── 订单服务(OrderService)
├── 商品上下文(Product Context)
│ ├── 商品(Product)
│ ├── 商品分类(Category)
│ └── 商品服务(ProductService)
├── 用户上下文(User Context)
│ ├── 用户(User)
│ ├── 角色(Role)
│ └── 用户服务(UserService)
├── 支付上下文(Payment Context)
│ ├── 支付(Payment)
│ ├── 支付方式(PaymentMethod)
│ └── 支付服务(PaymentService)
└── 库存上下文(Inventory Context)
├── 库存(Inventory)
├── 库存移动(InventoryMovement)
└── 库存服务(InventoryService)
# 3.2 上下文映射(Context Map)
上下文映射是表示限界上下文之间关系的图。它描述了限界上下文之间如何通信、如何共享数据以及如何保持一致性。
常见的上下文映射关系:
- 合作关系(Partnership):两个限界上下文相互依赖,共同完成业务目标
- 客户-供应商关系(Customer-Supplier):一个上下文(客户)依赖另一个上下文(供应商)提供的服务
- 遵奉者关系(Conformist):一个上下文(遵奉者)完全遵循另一个上下文(上游)的模型
- 防腐层(Anticorruption Layer):在两个上下文之间添加一层,将上游上下文的模型转换为下游上下文的模型
- 开放主机服务(Open Host Service):一个上下文提供标准化的服务接口供其他上下文使用
- 发布-订阅(Published Language):使用共享的语言(如领域事件)在上下文之间通信
- 共享内核(Shared Kernel):两个上下文共享一部分核心模型
- 大泥球(Big Ball of Mud):上下文之间没有明确的边界和关系,模型混乱
实现示例(防腐层):
// 上游系统的模型(可能来自第三方或遗留系统)
public class LegacyOrder {
private String orderNumber;
private String customerCode;
private List<LegacyOrderLine> lines;
private double totalPrice;
private String orderStatus;
private Date orderDate;
// getters and setters
}
public class LegacyOrderLine {
private String productCode;
private int quantity;
private double unitPrice;
// getters and setters
}
// 防腐层 - 将上游模型转换为下游领域模型
@Service
public class OrderAnticorruptionService {
private final LegacyOrderService legacyOrderService;
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
@Autowired
public OrderAnticorruptionService(LegacyOrderService legacyOrderService,
OrderRepository orderRepository,
ProductRepository productRepository) {
this.legacyOrderService = legacyOrderService;
this.orderRepository = orderRepository;
this.productRepository = productRepository;
}
public Order importOrderFromLegacySystem(String legacyOrderNumber) {
// 调用上游系统的服务
LegacyOrder legacyOrder = legacyOrderService.getOrderByNumber(legacyOrderNumber);
if (legacyOrder == null) {
throw new OrderNotFoundException("Legacy order not found: " + legacyOrderNumber);
}
// 创建领域模型
OrderId orderId = new OrderId(UUID.randomUUID().toString());
CustomerId customerId = new CustomerId(legacyOrder.getCustomerCode());
Order order = new Order(orderId, customerId);
// 转换订单项
for (LegacyOrderLine legacyLine : legacyOrder.getLines()) {
ProductId productId = new ProductId(legacyLine.getProductCode());
// 查找或创建产品
Product product = productRepository.findById(productId)
.orElseGet(() -> createProductFromLegacy(legacyLine));
Money unitPrice = Money.of(legacyLine.getUnitPrice(), Currency.getInstance("CNY"));
order.addOrderLine(productId, legacyLine.getQuantity(), unitPrice);
}
// 转换订单状态
OrderStatus status = mapOrderStatus(legacyOrder.getOrderStatus());
order.setStatus(status);
// 保存订单
orderRepository.save(order);
return order;
}
private Product createProductFromLegacy(LegacyOrderLine legacyLine) {
ProductId productId = new ProductId(legacyLine.getProductCode());
Product product = new Product(productId, "Product " + legacyLine.getProductCode(),
Money.of(legacyLine.getUnitPrice(), Currency.getInstance("CNY")));
productRepository.save(product);
return product;
}
private OrderStatus mapOrderStatus(String legacyStatus) {
switch (legacyStatus) {
case "CREATED":
return OrderStatus.CREATED;
case "CONFIRMED":
return OrderStatus.CONFIRMED;
case "SHIPPED":
return OrderStatus.SHIPPED;
case "DELIVERED":
return OrderStatus.DELIVERED;
case "CANCELED":
return OrderStatus.CANCELED;
default:
throw new IllegalArgumentException("Unknown order status: " + legacyStatus);
}
}
}
# 四、DDD与微服务
# 4.1 DDD与微服务的关系
DDD和微服务架构是天然的盟友,它们有很多共同的理念:
- 关注业务:都强调以业务为中心进行系统设计
- 模块化:都强调将系统划分为更小的、内聚的模块
- 边界明确:都强调明确的边界和接口
- 松耦合:都强调模块之间的松耦合
在微服务架构中,限界上下文可以映射到一个或多个微服务,领域模型可以作为微服务内部的核心业务逻辑。
# 4.2 基于DDD的微服务拆分策略
基于DDD的微服务拆分应该遵循以下策略:
- 根据限界上下文拆分:每个限界上下文对应一个或多个微服务
- 根据聚合拆分:在限界上下文内部,每个聚合可以作为一个独立的组件
- 保持业务逻辑的内聚性:确保每个微服务包含完整的业务逻辑,而不是简单的CRUD操作
- 考虑团队结构:参考康威定律,微服务的划分应该考虑团队的结构和沟通方式
- 考虑技术因素:如数据存储、性能要求、部署环境等
# 4.3 DDD在微服务架构中的实践
在微服务架构中应用DDD时,需要注意以下几点:
- 微服务边界与限界上下文:微服务的边界应该与限界上下文的边界尽可能一致
- API设计:微服务的API应该反映领域模型的概念和操作
- 数据一致性:采用最终一致性策略,通过领域事件确保数据最终一致
- 服务通信:优先使用异步通信(如消息队列),减少服务之间的耦合
- 测试策略:针对领域模型进行单元测试,针对聚合和领域服务进行集成测试,针对限界上下文进行端到端测试
# 五、DDD实践指南
# 5.1 DDD实施步骤
实施DDD的一般步骤如下:
- 领域探索:与业务专家合作,理解业务领域,识别核心概念和业务流程
- 限界上下文划分:根据业务领域的边界,划分限界上下文
- 上下文映射:描述限界上下文之间的关系
- 领域建模:在每个限界上下文中,识别实体、值对象、聚合、领域服务等
- 模型实现:将领域模型转换为代码
- 持续迭代:随着对领域的理解深入,不断优化和完善领域模型
# 5.2 常见误区与挑战
在DDD实施过程中,常见的误区和挑战包括:
- 过度设计:为简单的业务系统引入复杂的DDD概念
- 技术驱动:过多关注技术实现,而忽视业务领域
- 模型与代码不一致:领域模型与实际代码实现脱节
- 团队协作问题:业务专家与技术专家之间的沟通障碍
- 领域知识缺失:团队缺乏对业务领域的深入理解
# 5.3 成功实施DDD的关键因素
成功实施DDD需要注意以下关键因素:
- 领域专家的参与:业务专家应该全程参与DDD的实施过程
- 团队协作:业务专家、架构师、开发人员等应该紧密协作
- 渐进式实施:从核心域开始,逐步扩展到其他领域
- 持续学习:团队成员应该不断学习和理解业务领域
- 工具支持:使用合适的工具支持领域建模和代码实现
# 六、总结
领域驱动设计(DDD)是一种强大的软件开发方法论,它强调将软件系统的设计与业务领域的核心概念紧密结合。通过DDD,我们可以构建出更符合业务需求、更易于理解和维护的复杂系统。
DDD的核心价值在于:
- 以领域为中心:将业务领域作为软件设计的核心,确保软件真正解决业务问题
- 清晰的边界:通过限界上下文和聚合等概念,明确系统的边界和组件关系
- 丰富的领域模型:通过实体、值对象、领域服务等概念,构建丰富、表达力强的领域模型
- 团队协作:促进业务专家与技术专家之间的有效沟通和协作
- 可维护性和可扩展性:构建的系统更易于维护和扩展,能够更好地适应业务变化
在当今快速变化的业务环境中,DDD为我们提供了一种应对复杂业务系统开发的有效方法。通过深入理解业务领域,构建清晰的领域模型,我们可以开发出更有价值、更具竞争力的软件系统。