从壹开始微服务 [ DDD ] 之四 ║让你明白DDD的小故事 & EFCore初探

大家好!欢迎来到 《从壹开始微服务 [ DDD ]》 系列的第四篇。在前三篇中,我们已经探讨了微服务基础架构、DDD 核心概念以及分层设计。本篇将通过一个生动的小故事帮助大家深入理解 DDD(领域驱动设计) 的核心思想,并正式引入 Entity Framework Core(EF Core) —— .NET 生态中的 ORM 利器。无论你是刚接触 DDD 的新手,还是寻求落地实践的开发者,本文都将提供清晰的技术路径。


目录#

  1. 📖 理解 DDD 的咖啡店小故事
    • 1.1 故事背景:咖啡店的业务挑战
    • 1.2 构建领域模型
    • 1.3 DDD 核心概念解析
    • 1.4 故事的启示
  2. 🔧 EFCore 初探
    • 2.1 EF Core 是什么?
    • 2.2 为什么在 DDD 中选择 EF Core?
    • 2.3 安装与基本配置
    • 2.4 定义领域实体与值对象
    • 2.5 创建数据库迁移
  3. 🚀 DDD + EF Core 结合实践
    • 3.1 聚合根的持久化设计
    • 3.2 值对象的存储策略
    • 3.3 领域服务与仓储模式
    • 3.4 事务与并发控制
  4. 💡 常见陷阱与最佳实践
    • 4.1 避免 “贫血模型” 反模式
    • 4.2 聚合设计的边界原则
    • 4.3 性能优化建议
  5. 📝 总结与预告
  6. 📚 参考文献

1 理解 DDD 的咖啡店小故事#

1.1 故事背景:咖啡店的业务挑战#

假设你经营一家 “星语咖啡” 连锁店,业务包括:

  • 顾客点单(咖啡、甜点)
  • 库存管理(咖啡豆、牛奶消耗)
  • 会员积分系统

随着分店扩张,系统面临混乱:订单管理混乱库存更新滞后积分规则分散。这正是 DDD 解决的痛点——复杂业务的边界不清晰

1.2 构建领域模型#

☕️ 关键概念映射:  
  - 聚合根:订单(Order)
  - 实体:订单项(OrderItem)、会员(Member)
  - 值对象:金额(Money)、地址(Address)
  - 领域服务:积分计算服务(PointsService)
  - 仓储接口:IOrderRepository

1.3 DDD 核心概念解析#

概念示例特征
聚合根Order全局唯一ID,事务边界
实体OrderItem可变的业务状态(如数量)
值对象Money { Amount, Currency }不可变、无ID、可替换
领域服务PointsService处理跨聚合的业务逻辑

1.4 故事的启示#

DDD 不是技术框架,而是思维方式

  1. 拆分业务边界:订单、库存、会员划分不同子域(Bounded Context)。
  2. 聚焦核心领域:咖啡店的核心是订单处理,而非库存细节(分治策略)。
  3. 代码即设计:领域模型直接映射为代码,如 Order.AddItem() 封装业务规则。

2 EF Core 初探#

2.1 EF Core 是什么?#

Entity Framework Core 是 .NET 平台的轻量级、跨平台 ORM 框架,支持:

  • 将领域模型映射到数据库(Code-First)
  • LINQ 查询与变更跟踪
  • 迁移管理(Migrations)

2.2 为什么在 DDD 中选择 EF Core?#

✅ 无缝整合领域模型与持久化
✅ 支持复杂类型(值对象)
✅ 原生事务与并发控制
✅ 丰富的社区支持与文档

2.3 安装与基本配置#

dotnet add package Microsoft.EntityFrameworkCore.SqlServer
// Startup.cs
services.AddDbContext<CoffeeShopContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("Default")));

2.4 定义领域实体与值对象#

// 值对象:Money
public sealed class Money
{
    public decimal Amount { get; private set; }
    public string Currency { get; private set; }
 
    private Money() { } // EF Core 需要无参构造函数
    public Money(decimal amount, string currency) { ... }
}
 
// 聚合根:Order
public class Order : IAggregateRoot
{
    public int Id { get; private set; }
    private List<OrderItem> _items = new();
    public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
 
    public void AddItem(Product product, int quantity)
    {
        // 业务规则校验
        var item = new OrderItem(product.Id, product.Price, quantity);
        _items.Add(item);
    }
}
 
// 实体:OrderItem
public class OrderItem
{
    public int Id { get; private set; }
    public int ProductId { get; private set; }
    public Money Price { get; private set; }
    public int Quantity { get; private set; }
}

2.5 创建数据库迁移#

dotnet ef migrations add InitOrderModel
dotnet ef database update

生成的数据表结构:

  • Orders (Id)
  • OrderItems (Id, ProductId, Price_Amount, Price_Currency, Quantity, OrderId)

3 DDD + EF Core 结合实践#

3.1 聚合根的持久化设计#

规则:聚合根是唯一通过仓储访问的对象。

// 仓储实现(使用 EF Core)
public class OrderRepository : IOrderRepository
{
    private readonly CoffeeShopContext _context;
    public OrderRepository(CoffeeShopContext context) => _context = context;
 
    public void Add(Order order) => _context.Orders.Add(order);
    public Order GetById(int id) => _context.Orders
        .Include(o => o.Items)  // 自动加载子实体
        .FirstOrDefault(o => o.Id == id);
}

3.2 值对象的存储策略#

EF Core 支持 复杂类型(Owned Types)

// 在 CoffeeShopContext 中配置
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<OrderItem>().OwnsOne(
        o => o.Price,
        p => 
        {
            p.Property(x => x.Amount).HasColumnName("Price_Amount");
            p.Property(x => x.Currency).HasColumnName("Price_Currency");
        });
}

3.3 领域服务与仓储模式#

领域服务处理跨聚合逻辑,不直接依赖 EF Core

public class PointsService
{
    private readonly IOrderRepository _orderRepo;
    public PointsService(IOrderRepository orderRepo) => _orderRepo = orderRepo;
 
    public int CalculatePoints(int memberId)
    {
        var orders = _orderRepo.GetByMember(memberId);
        return orders.Sum(o => o.TotalAmount * 0.1); // 每10元积1分
    }
}

3.4 事务与并发控制#

确保聚合根的原子操作:

// 应用服务层协调
public class OrderApplicationService
{
    public void CompleteOrder(int orderId)
    {
        using var transaction = _context.Database.BeginTransaction();
        var order = _orderRepo.GetById(orderId);
        order.Complete();  // 状态变更业务逻辑
        _inventoryService.UpdateStock(order); // 跨聚合调用
        transaction.Commit();
    }
}

4 常见陷阱与最佳实践#

4.1 避免 “贫血模型” 反模式#

❌ 错误写法:

public class Order
{
    public List<OrderItem> Items { get; set; }  // 公开集合,外部可随意修改
}
public class OrderService
{
    public void AddItem(Order order, OrderItem item)  // 业务逻辑泄漏到服务层
    {
        if (item.Quantity < 0) throw new Exception(...);
        order.Items.Add(item);
    }
}

✅ 正确做法:

public class Order
{
    private List<OrderItem> _items = new();
    public void AddItem(OrderItem item)
    {
        // 业务规则内聚在领域模型内
        if (item.Quantity <= 0) 
            throw new DomainException("数量必须大于0");
        _items.Add(item);
    }
}

4.2 聚合设计的边界原则#

  • 聚合不宜过大:如 Order 不直接包含库存信息,通过 ID 引用。
  • 避免惰性加载:EF Core 的 Include 显式加载所需数据。

4.3 性能优化建议#

  • 为高频查询字段添加索引:
    modelBuilder.Entity<Order>()
        .HasIndex(o => o.MemberId);
  • 批量操作使用 ExecuteUpdate / ExecuteDelete(EF Core 7+)。

5 总结与预告#

本次我们通过 咖啡店故事 具象化 DDD 设计思想,并借助 EF Core 落地领域模型。关键要点:

  1. DDD 的本质是 业务与技术的同构映射
  2. EF Core 的 复杂类型与聚合设计 是 DDD 落地的核心支撑。

下一篇预告:
《从壹开始微服务 [ DDD ] 之五 ║CQRS 读写分离设计与 MediatR 实践》

  • 如何通过 CQRS 分离命令与查询
  • MediatR 实现领域事件的解耦
  • 性能优化的高级技巧

6 参考文献#

  1. Microsoft EF Core Documentation
  2. Eric Evans, 《Domain-Driven Design: Tackling Complexity in the Heart of Software》
  3. Vaughn Vernon, 《Implementing Domain-Driven Design》
  4. eShopOnContainers DDD Reference

如有问题,欢迎留言讨论!我们下期见!
版权声明:本文采用 CC BY-SA 4.0 协议,转载请注明出处。