钓大鱼
123.36M · 2026-04-01
通用语言(Ubiquitous Language)
CREATED / PAID / SHIPPED / COMPLETED,而不是混用 “已下单/已支付/配送中/完成”。限界上下文(Bounded Context)
user-context:用户、权限order-context:订单、订单明细payment-context:支付、退款可以先用文字描述一个简单的订单业务上下文:
概念:
Order、User。Money、Address。代码示例:值对象 Money 和 Address
import java.math.BigDecimal;
import java.util.Objects;
// 值对象:金额
public class Money {
private final BigDecimal amount;
private final String currency; // 货币代码:chy, USD...
public Money(BigDecimal amount, String currency) {
if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("金额不能为负数");
}
if (currency == null || currency.isBlank()) {
throw new IllegalArgumentException("货币不能为空");
}
this.amount = amount;
this.currency = currency;
}
public Money add(Money other) {
checkSameCurrency(other);
return new Money(this.amount.add(other.amount), this.currency);
}
public Money subtract(Money other) {
checkSameCurrency(other);
BigDecimal result = this.amount.subtract(other.amount);
if (result.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("金额不能为负数");
}
return new Money(result, this.currency);
}
private void checkSameCurrency(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("货币类型不一致");
}
}
public BigDecimal getAmount() {
return amount;
}
public String getCurrency() {
return currency;
}
// 值对象必须基于值比较相等性
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money)) return false;
Money money = (Money) o;
return amount.compareTo(money.amount) == 0 &&
currency.equals(money.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount.stripTrailingZeros(), currency);
}
@Override
public String toString() {
return amount + " " + currency;
}
}
// 值对象:收货地址
public class Address {
private final String province;
private final String city;
private final String detail;
public Address(String province, String city, String detail) {
if (province == null || province.isBlank()) {
throw new IllegalArgumentException("省份不能为空");
}
if (city == null || city.isBlank()) {
throw new IllegalArgumentException("城市不能为空");
}
if (detail == null || detail.isBlank()) {
throw new IllegalArgumentException("详细地址不能为空");
}
this.province = province;
this.city = city;
this.detail = detail;
}
public String getProvince() {
return province;
}
public String getCity() {
return city;
}
public String getDetail() {
return detail;
}
@Override
public String toString() {
return province + " " + city + " " + detail;
}
}
订单聚合示例:Order(聚合根) + OrderItem(订单行)。
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Order {
public enum Status {
CREATED, PAID, SHIPPED, COMPLETED, CANCELED
}
private Long id; // 实体 ID
private Long userId; // 下单用户
private List<OrderItem> items = new ArrayList<>(); // 订单行
private Money totalPrice; // 总价(值对象)
private Status status; // 订单状态
private LocalDateTime createdAt;
private LocalDateTime paidAt;
private LocalDateTime completedAt;
// 聚合根创建逻辑封装在构造方法/工厂方法中
public Order(Long userId, List<OrderItem> items) {
if (userId == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
if (items == null || items.isEmpty()) {
throw new IllegalArgumentException("订单行不能为空");
}
this.userId = userId;
this.items.addAll(items);
this.totalPrice = calculateTotalPrice(items);
this.status = Status.CREATED;
this.createdAt = LocalDateTime.now();
}
private Money calculateTotalPrice(List<OrderItem> items) {
Money total = new Money(BigDecimal.ZERO, "chy");
for (OrderItem item : items) {
total = total.add(item.getSubtotal());
}
return total;
}
// 业务行为:支付
public void pay() {
if (status != Status.CREATED) {
throw new IllegalStateException("只有已创建的订单才能支付");
}
this.status = Status.PAID;
this.paidAt = LocalDateTime.now();
}
// 业务行为:确认收货
public void complete() {
if (status != Status.SHIPPED) {
throw new IllegalStateException("只有已发货的订单才能完成");
}
this.status = Status.COMPLETED;
this.completedAt = LocalDateTime.now();
}
// 防止外部直接修改内部集合
public List<OrderItem> getItems() {
return Collections.unmodifiableList(items);
}
public Money getTotalPrice() {
return totalPrice;
}
public Status getStatus() {
return status;
}
public Long getUserId() {
return userId;
}
// 省略 getter/setter ...
}
import java.math.BigDecimal;
// 订单行:属于订单聚合内部实体
public class OrderItem {
private Long productId;
private String productName;
private int quantity;
private Money unitPrice;
public OrderItem(Long productId, String productName, int quantity, Money unitPrice) {
if (quantity <= 0) {
throw new IllegalArgumentException("数量必须大于 0");
}
this.productId = productId;
this.productName = productName;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
public Money getSubtotal() {
return unitPrice.multiply(new BigDecimal(quantity));
}
// 省略 getter ...
}
以 order-context 为例,一个常见的目录结构:
domain 领域层:纯业务逻辑,不依赖具体技术框架
model:实体、值对象、聚合根repository:仓储接口(仅接口)service:领域服务(复杂业务规则)event:领域事件application 应用层:编排用例流程
service:应用服务,调用领域对象、仓储、外部服务command/query:命令和查询对象infrastructure 基础设施层:技术细节实现
repository:JPA/MyBatis 实现仓储接口client:HTTP/RPC 调用其他服务config:Spring 配置interfaces 接口层(或 adapter):Controller、DTO、VO示例包结构(Java):
com.example.order
├── domain
│ ├── model
│ │ ├── Order.java
│ │ └── OrderItem.java
│ ├── repository
│ │ └── OrderRepository.java // 接口
│ ├── service
│ │ └── OrderDomainService.java
│ └── event
│ └── OrderPaidEvent.java
├── application
│ ├── command
│ │ ├── CreateOrderCommand.java
│ │ └── PayOrderCommand.java
│ ├── query
│ │ └── GetOrderDetailQuery.java
│ └── service
│ └── OrderApplicationService.java
├── infrastructure
│ ├── repository
│ │ └── OrderRepositoryJpaImpl.java
│ └── config
│ └── OrderConfig.java
└── interfaces
└── rest
└── OrderController.java
仓储接口(领域层):
package com.example.order.domain.repository;
import com.example.order.domain.model.Order;
import java.util.Optional;
public interface OrderRepository {
Optional<Order> findById(Long id);
void save(Order order);
}
基础设施层实现(举例用 JPA):
package com.example.order.infrastructure.repository;
import com.example.order.domain.model.Order;
import com.example.order.domain.repository.OrderRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public class OrderRepositoryJpaImpl implements OrderRepository {
private final OrderJpaDao jpaDao; // Spring Data JPA 接口
public OrderRepositoryJpaImpl(OrderJpaDao jpaDao) {
this.jpaDao = jpaDao;
}
@Override
public Optional<Order> findById(Long id) {
return jpaDao.findById(id)
.map(this::convertToDomain);
}
@Override
public void save(Order order) {
OrderEntity entity = convertToEntity(order);
jpaDao.save(entity);
}
private Order convertToDomain(OrderEntity entity) {
// 将 JPA 实体转换为领域模型
// 这里省略字段映射细节
return null;
}
private OrderEntity convertToEntity(Order order) {
// 将领域模型转换为 JPA 实体
return null;
}
}
命令对象示例:
// 创建订单命令
public class CreateOrderCommand {
private Long userId;
private List<CreateOrderItem> items;
private String remark;
// 内部类:创建订单行
public static class CreateOrderItem {
private Long productId;
private String productName;
private int quantity;
private BigDecimal unitPrice;
// getter/setter 省略
}
// getter/setter 省略
}
查询对象示例:
// 查询订单详情
public class GetOrderDetailQuery {
private Long orderId;
public GetOrderDetailQuery(Long orderId) {
this.orderId = orderId;
}
public Long getOrderId() {
return orderId;
}
}
可以为每个 Command 定义一个处理器(Handler),方便后续做扩展(如事件总线、拦截器、审计日志)。
// 命令处理接口
public interface CommandHandler<C, R> {
R handle(C command);
}
// 查询处理接口
public interface QueryHandler<Q, R> {
R handle(Q query);
}
创建订单命令处理器:
import com.example.order.domain.model.Order;
import com.example.order.domain.repository.OrderRepository;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
public class CreateOrderCommandHandler implements CommandHandler<CreateOrderCommand, Long> {
private final OrderRepository orderRepository;
public CreateOrderCommandHandler(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Override
public Long handle(CreateOrderCommand command) {
List<OrderItem> items = command.getItems().stream()
.map(i -> new OrderItem(
i.getProductId(),
i.getProductName(),
i.getQuantity(),
new Money(i.getUnitPrice(), "chy")
))
.collect(Collectors.toList());
Order order = new Order(command.getUserId(), items);
// 可以在这里调用领域服务、发布领域事件等
orderRepository.save(order);
return order.getId(); // 假设保存后 ID 已写回
}
}
订单详情查询处理器:
public class GetOrderDetailQueryHandler implements QueryHandler<GetOrderDetailQuery, OrderDetailDTO> {
private final OrderReadModelRepository readModelRepository;
public GetOrderDetailQueryHandler(OrderReadModelRepository readModelRepository) {
this.readModelRepository = readModelRepository;
}
@Override
public OrderDetailDTO handle(GetOrderDetailQuery query) {
// CQRS:查询可以直接走读库/ES/缓存等
return readModelRepository.findDetailById(query.getOrderId())
.orElseThrow(() -> new IllegalArgumentException("订单不存在"));
}
}
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderApplicationService {
private final CreateOrderCommandHandler createOrderHandler;
private final GetOrderDetailQueryHandler getOrderDetailHandler;
public OrderApplicationService(CreateOrderCommandHandler createOrderHandler,
GetOrderDetailQueryHandler getOrderDetailHandler) {
this.createOrderHandler = createOrderHandler;
this.getOrderDetailHandler = getOrderDetailHandler;
}
@Transactional
public Long createOrder(CreateOrderCommand command) {
// 可以在这里做权限校验、幂等等
return createOrderHandler.handle(command);
}
@Transactional(readOnly = true)
public OrderDetailDTO getOrderDetail(GetOrderDetailQuery query) {
return getOrderDetailHandler.handle(query);
}
}
import java.time.LocalDateTime;
// 订单已支付领域事件
public class OrderPaidEvent {
private final Long orderId;
private final Long userId;
private final LocalDateTime paidAt;
public OrderPaidEvent(Long orderId, Long userId, LocalDateTime paidAt) {
this.orderId = orderId;
this.userId = userId;
this.paidAt = paidAt;
}
public Long getOrderId() {
return orderId;
}
public Long getUserId() {
return userId;
}
public LocalDateTime getPaidAt() {
return paidAt;
}
}
在 Order 聚合中支付成功时记录事件:
public class Order {
// ... 之前的字段 ...
private List<Object> domainEvents = new ArrayList<>();
public void pay() {
if (status != Status.CREATED) {
throw new IllegalStateException("只有已创建的订单才能支付");
}
this.status = Status.PAID;
this.paidAt = LocalDateTime.now();
// 产生领域事件
domainEvents.add(new OrderPaidEvent(this.id, this.userId, this.paidAt));
}
public List<Object> getDomainEvents() {
return Collections.unmodifiableList(domainEvents);
}
public void clearDomainEvents() {
domainEvents.clear();
}
}
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@Component
public class DomainEventPublisher {
private final ApplicationEventPublisher applicationEventPublisher;
public DomainEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void publish(Object event) {
applicationEventPublisher.publishEvent(event);
}
}
在仓储保存订单后统一发布事件:
@Repository
public class OrderRepositoryJpaImpl implements OrderRepository {
private final OrderJpaDao jpaDao;
private final DomainEventPublisher eventPublisher;
public OrderRepositoryJpaImpl(OrderJpaDao jpaDao, DomainEventPublisher eventPublisher) {
this.jpaDao = jpaDao;
this.eventPublisher = eventPublisher;
}
@Override
public void save(Order order) {
OrderEntity entity = convertToEntity(order);
jpaDao.save(entity);
// 发布领域事件
order.getDomainEvents().forEach(eventPublisher::publish);
order.clearDomainEvents();
}
}
事件:
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class OrderEventHandler {
@EventListener
public void onOrderPaid(OrderPaidEvent event) {
// 例如:增加积分、发消息、推送通知等
System.out.println("订单已支付,ID = " + event.getOrderId());
}
}
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderApplicationService orderApplicationService;
public OrderController(OrderApplicationService orderApplicationService) {
this.orderApplicationService = orderApplicationService;
}
@PostMapping
public Long createOrder(@RequestBody CreateOrderRequest request) {
CreateOrderCommand command = toCommand(request);
return orderApplicationService.createOrder(command);
}
@GetMapping("/{id}")
public OrderDetailDTO getOrderDetail(@PathVariable Long id) {
GetOrderDetailQuery query = new GetOrderDetailQuery(id);
return orderApplicationService.getOrderDetail(query);
}
private CreateOrderCommand toCommand(CreateOrderRequest request) {
// 将 REST 请求 DTO 转换为 Command
// 这里可以做参数校验、默认值处理等
return null;
}
}