Domain 是业务概念的载体,所有层的依赖基础
@Entity
public class Order {
@Id
private Long id;
private Long userId;
private BigDecimal totalAmount;
private OrderStatus status;
private LocalDateTime createdAt;
// 只有 getter/setter,没有业务逻辑
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
// ... 其他 getter/setter
}
@Service
public class OrderService {
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow();
// 贫血模型:业务逻辑散落在 Service 里
if (order.getStatus() == OrderStatus.SHIPPED) {
throw new IllegalStateException("已发货订单不能取消");
}
if (order.getStatus() == OrderStatus.CANCELLED) {
throw new IllegalStateException("订单已取消");
}
// 修改状态
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order);
}
}
@Entity
public class Order {
@Id
private Long id;
private Long userId;
private BigDecimal totalAmount;
private OrderStatus status;
private LocalDateTime createdAt;
// 🎯 业务行为封装在实体里
/**
* 取消订单
*/
public void cancel() {
// 状态校验内聚在方法里
if (this.status == OrderStatus.SHIPPED) {
throw new IllegalStateException("已发货订单不能取消");
}
if (this.status == OrderStatus.CANCELLED) {
throw new IllegalStateException("订单已取消");
}
this.status = OrderStatus.CANCELLED;
// 可以触发领域事件
registerEvent(new OrderCancelledEvent(this.id));
}
/**
* 支付订单
*/
public void pay(Payment payment) {
if (this.status != OrderStatus.PENDING_PAYMENT) {
throw new IllegalStateException("订单状态不正确");
}
if (!payment.getAmount().equals(this.totalAmount)) {
throw new IllegalArgumentException("支付金额不匹配");
}
this.status = OrderStatus.PAID;
this.paymentTime = LocalDateTime.now();
}
// ... 其他业务方法
}
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final DomainEventPublisher eventPublisher;
@Transactional
public void cancelOrder(Long orderId) {
// 1. 加载聚合根
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
// 2. 💡 调用领域对象的业务方法
// 业务规则封装在 Order 里,Service 只做协调
order.cancel();
// 3. 保存变更
orderRepository.save(order);
// 4. 发布领域事件
order.getDomainEvents().forEach(eventPublisher::publish);
order.clearDomainEvents();
}
}
值对象是没有唯一标识、不可变的对象,它描述了某种特征或属性。两个值对象如果所有属性相等,就被认为是同一个对象。
// 值对象:不可变、无 ID
public record Address(
String province, // 省
String city, // 市
String district, // 区
String street, // 街道
String zipCode // 邮编
) {
// 值对象的方法通常是转换或计算
public String toDisplayString() {
return String.format("%s%s%s%s",
province, city, district, street);
}
// 校验逻辑
public boolean isValid() {
return StringUtils.isNotBlank(province)
&& StringUtils.isNotBlank(city);
}
}
// 使用:地址相等只要属性相同
Address addr1 = new Address("广东", "深圳", "南山", "科技园", "518000");
Address addr2 = new Address("广东", "深圳", "南山", "科技园", "518000");
System.out.println(addr1.equals(addr2)); // true - 值对象比较的是值
// 金钱是经典的值对象
public record Money(
BigDecimal amount,
Currency currency
) {
// 工厂方法
public static Money of(BigDecimal amount, String currencyCode) {
return new Money(amount, Currency.getInstance(currencyCode));
}
public static Money yuan(BigDecimal amount) {
return new Money(amount, Currency.getInstance("CNY"));
}
// 值对象的核心:运算返回新的值对象
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Cannot add different currencies");
}
return new Money(this.amount.add(other.amount), this.currency);
}
public Money multiply(int factor) {
return new Money(this.amount.multiply(BigDecimal.valueOf(factor)), currency);
}
public boolean isGreaterThan(Money other) {
return this.amount.compareTo(other.amount) > 0;
}
// 格式化显示
public String toDisplayString() {
return currency.getSymbol() + amount.setScale(2, RoundingMode.HALF_UP);
}
}
// 使用示例
Money price = Money.yuan(new BigDecimal("199.99"));
Money shipping = Money.yuan(new BigDecimal("10.00"));
Money discount = Money.yuan(new BigDecimal("20.00"));
Money total = price.add(shipping).add(discount.negate());
System.out.println(total.toDisplayString()); // ¥189.99