从壹开始微服务 [ DDD ] 之四 ║让你明白DDD的小故事 & EFCore初探
大家好!欢迎来到 《从壹开始微服务 [ DDD ]》 系列的第四篇。在前三篇中,我们已经探讨了微服务基础架构、DDD 核心概念以及分层设计。本篇将通过一个生动的小故事帮助大家深入理解 DDD(领域驱动设计) 的核心思想,并正式引入 Entity Framework Core(EF Core) —— .NET 生态中的 ORM 利器。无论你是刚接触 DDD 的新手,还是寻求落地实践的开发者,本文都将提供清晰的技术路径。
目录#
- 📖 理解 DDD 的咖啡店小故事
- 1.1 故事背景:咖啡店的业务挑战
- 1.2 构建领域模型
- 1.3 DDD 核心概念解析
- 1.4 故事的启示
- 🔧 EFCore 初探
- 2.1 EF Core 是什么?
- 2.2 为什么在 DDD 中选择 EF Core?
- 2.3 安装与基本配置
- 2.4 定义领域实体与值对象
- 2.5 创建数据库迁移
- 🚀 DDD + EF Core 结合实践
- 3.1 聚合根的持久化设计
- 3.2 值对象的存储策略
- 3.3 领域服务与仓储模式
- 3.4 事务与并发控制
- 💡 常见陷阱与最佳实践
- 4.1 避免 “贫血模型” 反模式
- 4.2 聚合设计的边界原则
- 4.3 性能优化建议
- 📝 总结与预告
- 📚 参考文献
1 理解 DDD 的咖啡店小故事#
1.1 故事背景:咖啡店的业务挑战#
假设你经营一家 “星语咖啡” 连锁店,业务包括:
- 顾客点单(咖啡、甜点)
- 库存管理(咖啡豆、牛奶消耗)
- 会员积分系统
随着分店扩张,系统面临混乱:订单管理混乱、库存更新滞后、积分规则分散。这正是 DDD 解决的痛点——复杂业务的边界不清晰。
1.2 构建领域模型#
☕️ 关键概念映射:
- 聚合根:订单(Order)
- 实体:订单项(OrderItem)、会员(Member)
- 值对象:金额(Money)、地址(Address)
- 领域服务:积分计算服务(PointsService)
- 仓储接口:IOrderRepository1.3 DDD 核心概念解析#
| 概念 | 示例 | 特征 |
|---|---|---|
| 聚合根 | Order | 全局唯一ID,事务边界 |
| 实体 | OrderItem | 可变的业务状态(如数量) |
| 值对象 | Money { Amount, Currency } | 不可变、无ID、可替换 |
| 领域服务 | PointsService | 处理跨聚合的业务逻辑 |
1.4 故事的启示#
DDD 不是技术框架,而是思维方式:
- 拆分业务边界:订单、库存、会员划分不同子域(Bounded Context)。
- 聚焦核心领域:咖啡店的核心是订单处理,而非库存细节(分治策略)。
- 代码即设计:领域模型直接映射为代码,如
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 落地领域模型。关键要点:
- DDD 的本质是 业务与技术的同构映射。
- EF Core 的 复杂类型与聚合设计 是 DDD 落地的核心支撑。
下一篇预告:
⚡ 《从壹开始微服务 [ DDD ] 之五 ║CQRS 读写分离设计与 MediatR 实践》
- 如何通过 CQRS 分离命令与查询
- MediatR 实现领域事件的解耦
- 性能优化的高级技巧
6 参考文献#
- Microsoft EF Core Documentation
- Eric Evans, 《Domain-Driven Design: Tackling Complexity in the Heart of Software》
- Vaughn Vernon, 《Implementing Domain-Driven Design》
- eShopOnContainers DDD Reference
如有问题,欢迎留言讨论!我们下期见!
版权声明:本文采用 CC BY-SA 4.0 协议,转载请注明出处。