.NET Core充血模型
文章目录
- 前言
- 一、核心概念
- 二、实现步骤
- 1.创建领域对象(含行为)
- 2.工厂方法(控制创建)
- 3.值对象封装业务概念
- 4.仓储接口(持久化抽象)
- 5.应用服务(协调领域对象)
- 三、关键实践
- 1.禁用公共setter
- 2.领域事件处理
- 3.EF Core配置
- 4.业务规则验证
- 四、对比贫血模型
- 五、常见陷阱
- 总结
前言
充血模型是领域驱动设计(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)实现。