← 返回首页
🎯

DDD数据模型

📂 architecture ⏱ 2 min 397 words

DDD数据模型

CQRS模式

CQRS(Command Query Responsibility Segregation)将读写操作分离到不同的模型中。

CQRS 架构:

写入端(Command)                    读取端(Query)
┌─────────────────┐                 ┌─────────────────┐
│   Command       │                 │   Query         │
│   Handler       │                 │   Handler       │
└────────┬────────┘                 └────────┬────────┘
         │                                   │
         ▼                                   ▼
┌─────────────────┐                 ┌─────────────────┐
│   领域模型       │                 │   读模型         │
│   (写优化)       │                 │   (读优化)       │
└────────┬────────┘                 └────────┬────────┘
         │                                   │
         ▼                                   ▼
┌─────────────────┐                 ┌─────────────────┐
│   写数据库       │──── 同步 ────►│   读数据库       │
│   (范式化)       │                 │   (反范式化)     │
└─────────────────┘                 └─────────────────┘
// Command 端
@CommandHandler
public class CreateOrderCommandHandler {
    
    private final OrderRepository orderRepository;
    private final EventPublisher eventPublisher;
    
    @Transactional
    public OrderId handle(CreateOrderCommand command) {
        Order order = Order.create(
            command.getCustomerId(),
            command.getItems()
        );
        
        orderRepository.save(order);
        
        // 发布事件同步到读端
        eventPublisher.publish(new OrderCreatedEvent(order));
        
        return order.getId();
    }
}

// Query 端
@RestController
@RequestMapping("/api/orders")
public class OrderQueryController {
    
    private final OrderQueryService queryService;
    
    @GetMapping
    public Page<OrderListItem> listOrders(
            @RequestParam Long customerId,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        return queryService.findByCustomerId(customerId, page, size);
    }
    
    @GetMapping("/{orderId}")
    public OrderDetail getOrderDetail(@PathVariable Long orderId) {
        return queryService.findById(orderId);
    }
}

// 读模型
@Entity
@Table(name = "order_list_view")
public class OrderListItem {
    @Id
    private Long id;
    private String orderNo;
    private String customerName;
    private BigDecimal totalAmount;
    private String status;
    private LocalDateTime createdAt;
}

@Entity
@Table(name = "order_detail_view")
public class OrderDetail {
    @Id
    private Long id;
    private String orderNo;
    private CustomerInfo customer;
    private List<OrderItemInfo> items;
    private BigDecimal totalAmount;
    private String status;
    private PaymentInfo payment;
    private ShippingInfo shipping;
}

事件存储

事件存储将领域事件持久化,支持事件溯源和审计。

// 事件存储实体
@Entity
@Table(name = "event_store")
public class EventEntity {
    @Id
    private Long id;
    private String aggregateId;
    private String aggregateType;
    private String eventType;
    private String eventData;  // JSON 格式
    private LocalDateTime occurredAt;
    private Long version;
}

// 事件存储 Repository
@Repository
public class EventStoreRepository {
    
    private final EventStoreJpaRepository repository;
    private final ObjectMapper objectMapper;
    
    public void append(DomainEvent event, String aggregateId, Long version) {
        EventEntity entity = new EventEntity();
        entity.setAggregateId(aggregateId);
        entity.setAggregateType(aggregate.getClass().getSimpleName());
        entity.setEventType(event.getClass().getSimpleName());
        entity.setEventData(objectMapper.writeValueAsString(event));
        entity.setOccurredAt(LocalDateTime.now());
        entity.setVersion(version);
        
        repository.save(entity);
    }
    
    public List<DomainEvent> getEvents(String aggregateId) {
        return repository.findByAggregateIdOrderByVersion(aggregateId)
            .stream()
            .map(this::toDomainEvent)
            .collect(Collectors.toList());
    }
    
    private DomainEvent toDomainEvent(EventEntity entity) {
        try {
            Class<?> eventType = Class.forName(
                "com.example.order.domain.event." + entity.getEventType());
            return (DomainEvent) objectMapper.readValue(
                entity.getEventData(), eventType);
        } catch (Exception e) {
            throw new EventDeserializationException(e);
        }
    }
}

// 事件溯源聚合
public class Order {
    
    public static Order reconstitute(List<DomainEvent> events) {
        Order order = new Order();
        for (DomainEvent event : events) {
            order.apply(event);
        }
        return order;
    }
    
    private void apply(DomainEvent event) {
        if (event instanceof OrderCreatedEvent) {
            applyCreated((OrderCreatedEvent) event);
        } else if (event instanceof OrderPaidEvent) {
            applyPaid((OrderPaidEvent) event);
        }
        // 记录已应用的事件
        changes.add(event);
    }
}

读写分离的优势

CQRS 适合读写比例差异大、查询复杂的场景。