.NET Core充血模型

发布于:2025-06-26 ⋅ 阅读:(14) ⋅ 点赞:(0)


前言

充血模型领域驱动设计DDD)中的一种重要模式,它将业务逻辑封装在实体内部,使实体成为包含行为和数据的完整领域对象。

一、核心概念

  • 充血模型:实体类不仅包含数据,还包含业务逻辑。
  • 贫血模型:实体类仅包含数据,业务逻辑由服务层处理。
  • 领域驱动设计DDD):强调领域模型是系统的核心,通过聚合根、值对象等模式构建复杂业务系统。

二、实现步骤

1.创建领域对象(含行为)

  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.工厂方法(控制创建)

  1. 代码如下(示例):
    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.值对象封装业务概念

  1. 示例代码
    public record Address
    {
        public string Street { get; init; }
        public string City { get; init; }
        
        // 业务行为
        public bool IsDomestic() => Country == "USA";
    }
    

4.仓储接口(持久化抽象)

  1. 示例代码
    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.应用服务(协调领域对象)

  1. 代码示例:

    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)实现。


网站公告

今日签到

点亮在社区的每一天
去签到