文章目录
前言
充血模型是领域驱动设计(DDD)中的一种重要模式,它将业务逻辑封装在实体内部,使实体成为包含行为和数据的完整领域对象。
一、核心概念
- 充血模型:实体类不仅包含数据,还包含业务逻辑。
- 贫血模型:实体类仅包含数据,业务逻辑由服务层处理。
- 领域驱动设计(DDD):强调领域模型是系统的核心,通过聚合根、值对象等模式构建复杂业务系统。
二、实现步骤
1.创建领域对象(含行为)
- 代码如下(示例):
public class Order { // 内部状态(私有set) public int Id { get; private set; } public OrderStatus Status { get; private set; } = OrderStatus.Pending; public decimal TotalAmount { get; private set; } private readonly List<OrderItem> _items = new(); public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly(); // 核心业务行为 public void AddItem(Product product, int quantity) { // 业务规则校验 if (Status != OrderStatus.Pending) throw new InvalidOperationException("无法修改已发货订单"); if (quantity <= 0) throw new ArgumentException("数量必须为正数"); // 领域逻辑计算 var item = new OrderItem(product, quantity); _items.Add(item); UpdateTotalAmount(); } public void Ship() { if (Status != OrderStatus.Pending) throw new InvalidOperationException("订单已发货"); if (_items.Count == 0) throw new InvalidOperationException("无法发货空订单"); Status = OrderStatus.Shipped; } private void UpdateTotalAmount() => TotalAmount = _items.Sum(i => i.Subtotal); } public class OrderItem { internal OrderItem(Product product, int quantity) { Product = product; Quantity = quantity; } public Product Product { get; } public int Quantity { get; } public decimal Subtotal => Product.Price * Quantity; }
2.工厂方法(控制创建)
- 代码如下(示例):
public class Order { // 私有构造函数,防止外部直接实例化 private Order() { } // 工厂方法创建订单 public static Order Create(string customerName) { if (string.IsNullOrWhiteSpace(customerName)) throw new ArgumentNullException(nameof(customerName), "客户名称不能为空"); return new Order { Id = Guid.NewGuid(), OrderDate = DateTime.UtcNow, CustomerName = customerName, Status = OrderStatus.Created }; } }
3.值对象封装业务概念
- 示例代码
public record Address { public string Street { get; init; } public string City { get; init; } // 业务行为 public bool IsDomestic() => Country == "USA"; }
4.仓储接口(持久化抽象)
- 示例代码
public interface IOrderRepository { Order GetById(int id); void Save(Order order); } // 实现(EF Core) public class OrderRepository : IOrderRepository { private readonly MyDBContext _context; public OrderRepository(MyDBContext context) => _context = context; public Order GetById(int id) => _context.Orders .Include(o => o.Items) .FirstOrDefault(o => o.Id == id); public void Save(Order order) { if (order.Id == 0) _context.Orders.Add(order); _context.SaveChanges(); } }
5.应用服务(协调领域对象)
代码示例:
public class OrderService { private readonly IOrderRepository _orderRepository; public OrderService(IOrderRepository orderRepository) => _orderRepository = orderRepository; public void ProcessOrder(int orderId) { var order = _orderRepository.GetById(orderId); order.Ship(); // 调用领域行为 _orderRepository.Save(order); } }
三、关键实践
1.禁用公共setter
使用私有set或init-only属性
public decimal TotalAmount { get; private set; }
2.领域事件处理
- 实现领域事件解耦业务逻辑
public class Order { public event Action<Order> Shipped; public void Ship() { // ... 发货逻辑 Shipped?.Invoke(this); } }
3.EF Core配置
- 处理私有字段映射
builder.Property(r => r.TotalAmount).HasField("totalAmount");
4.业务规则验证
在方法内部实现原子校验
public void AddItem(Product product, int quantity) { if (product.IsDiscontinued) throw new DomainException("无法添加已停产的产品"); // ... }
四、对比贫血模型
充血模型 | 贫血模型 |
---|---|
order.Ship() | orderService.Ship(order) |
业务逻辑内聚在领域对象 | 业务逻辑分散在Service类 |
对象自洽保证一致性 | 需额外校验对象状态 |
符合面向对象设计 | 过程式编程风格 |
五、常见陷阱
领域服务过度膨胀 → 将核心逻辑移回实体
暴露内部状态 → 通过方法控制状态变更
忽略聚合根边界 → 通过根实体修改子实体
依赖基础设施 → 领域对象不直接访问数据库
总结
领域对象方法应仅使用自己的属性/参数进行计算,避免依赖外部服务(可通过方法参数注入依赖)。
通过充血模型,可显著提升代码可维护性和业务表达能力,特别适合复杂业务系统的领域驱动设计(DDD)实现。