DDD领域驱动设计
大约 12 分钟约 3674 字
DDD领域驱动设计
简介
Domain-Driven Design
DDD领域驱动设计,是一种以业务领域为核心的软件设计方法。它强调开发团队与业务专家紧密协作,通过统一语言(Ubiquitous Language)来建立领域模型,从而让代码真实反映业务。
特点
DDD 战略设计 — 限界上下文
什么是限界上下文(Bounded Context)
限界上下文是 DDD 中最重要的概念,它定义了一个明确的边界,在边界内部,领域模型有且只有一种明确的含义。
电商系统
|
+-- 订单上下文: Order, OrderItem, Payment
| 统一语言: "下单"、"支付"
|
+-- 商品上下文: Product, Category, Inventory
| 统一语言: "上架"、"库存"
|
+-- 用户上下文: User, Address, MemberLevel
统一语言: "注册"、"认证"限界上下文示例
/// <summary>
/// 同一个"商品"在不同上下文中有不同的含义
/// </summary>
// 商品上下文中的 Product — 关注商品本身的属性
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public string Specifications { get; set; } // 详细规格参数
}
// 订单上下文中的 OrderProduct — 只关注订单需要的信息
public class OrderProduct
{
public int ProductId { get; set; }
public string ProductName { get; set; } // 下单时的快照
public decimal UnitPrice { get; set; } // 下单时的价格快照
public int Quantity { get; set; }
}
// 库存上下文中的 InventoryItem — 关注库存信息
public class InventoryItem
{
public int ProductId { get; set; }
public int AvailableStock { get; set; }
public int ReservedStock { get; set; }
public string WarehouseLocation { get; set; }
}DDD 战术设计 — 核心构建块
分层架构
接口层 (API/UI) Controller, DTO
|
应用层 (Application) ApplicationService, Handler
|
领域层 (Domain) Entity, ValueObject, Aggregate
|
基础设施层 (Infra) Repository, 外部服务1. 实体(Entity)
有唯一标识的对象,即使属性相同,ID不同就是不同实体。
/// <summary>
/// 实体基类 — 提供唯一标识
/// </summary>
public abstract class Entity
{
public Guid Id { get; protected set; }
public override bool Equals(object obj)
{
if (obj is not Entity other)
return false;
if (GetType() != other.GetType())
return false;
return Id == other.Id;
}
public override int GetHashCode() => Id.GetHashCode();
}2. 值对象(Value Object)
没有唯一标识,通过属性值判断相等性。值对象不可变。
/// <summary>
/// 值对象基类
/// </summary>
public abstract class ValueObject
{
protected abstract IEnumerable<object> GetEqualityComponents();
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != GetType())
return false;
var other = (ValueObject)obj;
return GetEqualityComponents()
.SequenceEqual(other.GetEqualityComponents());
}
public override int GetHashCode()
{
return GetEqualityComponents()
.Select(x => x?.GetHashCode() ?? 0)
.Aggregate((x, y) => x ^ y);
}
}
/// <summary>
/// 地址 — 典型的值对象
/// 只要省市区街道一样,就是同一个地址
/// </summary>
public class Address : ValueObject
{
public string Province { get; }
public string City { get; }
public string District { get; }
public string Street { get; }
public string Detail { get; }
public Address(string province, string city, string district,
string street, string detail)
{
Province = province;
City = city;
District = district;
Street = street;
Detail = detail;
}
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Province;
yield return City;
yield return District;
yield return Street;
yield return Detail;
}
}
/// <summary>
/// 金额 — 值对象示例
/// </summary>
public class Money : ValueObject
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency = "CNY")
{
if (amount < 0)
throw new ArgumentException("金额不能为负数");
Amount = amount;
Currency = currency;
}
public Money Add(Money other)
{
if (Currency != other.Currency)
throw new InvalidOperationException("币种不同,不能相加");
return new Money(Amount + other.Amount, Currency);
}
public Money Multiply(int factor) => new Money(Amount * factor, Currency);
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Amount;
yield return Currency;
}
}3. 聚合根(Aggregate Root)
聚合是一组相关对象的集合,聚合根是聚合的入口。外部只能通过聚合根操作聚合内的对象。
/// <summary>
/// 订单聚合根 — 管理订单内的所有实体和值对象
/// 外部只能通过 Order 来操作 OrderItem
/// </summary>
public class Order : Entity, IAggregateRoot
{
// 聚合根内包含的实体
private readonly List<OrderItem> _items = new List<OrderItem>();
public IReadOnlyList<OrderItem> Items => _items.AsReadOnly();
// 值对象
public Address ShippingAddress { get; private set; }
// 基本属性
public string OrderNo { get; private set; }
public Guid UserId { get; private set; }
public OrderStatus Status { get; private set; }
public DateTime CreatedTime { get; private set; }
public string CancelReason { get; private set; }
// 领域事件
private readonly List<IDomainEvent> _domainEvents = new List<IDomainEvent>();
public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
// 计算属性 — 业务规则封装在聚合内
public Money TotalAmount => _items.Aggregate(
new Money(0),
(sum, item) => sum.Add(item.GetSubtotal())
);
// 工厂方法 — 保证创建的聚合始终是合法的
public static Order Create(Guid userId, Address shippingAddress)
{
var order = new Order
{
Id = Guid.NewGuid(),
OrderNo = GenerateOrderNo(),
UserId = userId,
ShippingAddress = shippingAddress ?? throw new ArgumentNullException(nameof(shippingAddress)),
Status = OrderStatus.Pending,
CreatedTime = DateTime.Now
};
order._domainEvents.Add(new OrderCreatedEvent(order.Id, userId));
return order;
}
// 业务方法 — 所有操作都通过聚合根
public void AddItem(Guid productId, string productName, Money unitPrice, int quantity)
{
if (Status != OrderStatus.Pending)
throw new DomainException("只有待支付订单才能添加商品");
var existingItem = _items.FirstOrDefault(i => i.ProductId == productId);
if (existingItem != null)
{
existingItem.IncreaseQuantity(quantity);
}
else
{
_items.Add(new OrderItem(productId, productName, unitPrice, quantity));
}
}
public void RemoveItem(Guid productId)
{
if (Status != OrderStatus.Pending)
throw new DomainException("只有待支付订单才能移除商品");
var item = _items.FirstOrDefault(i => i.ProductId == productId)
?? throw new DomainException("商品不存在");
_items.Remove(item);
}
public void Pay()
{
if (Status != OrderStatus.Pending)
throw new DomainException("订单状态不正确");
if (!_items.Any())
throw new DomainException("订单不能为空");
Status = OrderStatus.Paid;
_domainEvents.Add(new OrderPaidEvent(Id, TotalAmount));
}
public void Cancel(string reason)
{
if (Status == OrderStatus.Shipped || Status == OrderStatus.Completed)
throw new DomainException("已发货/已完成的订单不能取消");
Status = OrderStatus.Cancelled;
CancelReason = reason;
_domainEvents.Add(new OrderCancelledEvent(Id, reason));
}
public void Ship()
{
if (Status != OrderStatus.Paid)
throw new DomainException("只有已支付的订单才能发货");
Status = OrderStatus.Shipped;
}
private static string GenerateOrderNo()
{
return $"ORD{DateTime.Now:yyyyMMddHHmmss}{Random.Shared.Next(1000, 9999)}";
}
}
// 订单项 — 聚合内的实体(没有独立的 Repository)
public class OrderItem : Entity
{
public Guid ProductId { get; private set; }
public string ProductName { get; private set; }
public Money UnitPrice { get; private set; }
public int Quantity { get; private set; }
internal OrderItem(Guid productId, string productName, Money unitPrice, int quantity)
{
Id = Guid.NewGuid();
ProductId = productId;
ProductName = productName;
UnitPrice = unitPrice ?? throw new ArgumentNullException(nameof(unitPrice));
SetQuantity(quantity);
}
public Money GetSubtotal() => UnitPrice.Multiply(Quantity);
internal void IncreaseQuantity(int amount)
{
SetQuantity(Quantity + amount);
}
private void SetQuantity(int quantity)
{
if (quantity <= 0)
throw new DomainException("商品数量必须大于0");
if (quantity > 99)
throw new DomainException("单个商品数量不能超过99");
Quantity = quantity;
}
}
public enum OrderStatus
{
Pending, Paid, Shipped, Completed, Cancelled
}
public interface IAggregateRoot { }
public class DomainException : Exception
{
public DomainException(string message) : base(message) { }
}4. 领域事件(Domain Event)
记录领域中发生的重要事情,实现聚合之间的解耦通信。
/// <summary>
/// 领域事件基类
/// </summary>
public interface IDomainEvent
{
DateTime OccurredOn { get; }
}
public abstract class DomainEvent : IDomainEvent
{
public DateTime OccurredOn { get; } = DateTime.Now;
}
// 具体的领域事件
public class OrderCreatedEvent : DomainEvent
{
public Guid OrderId { get; }
public Guid UserId { get; }
public OrderCreatedEvent(Guid orderId, Guid userId)
{
OrderId = orderId;
UserId = userId;
}
}
public class OrderPaidEvent : DomainEvent
{
public Guid OrderId { get; }
public Money Amount { get; }
public OrderPaidEvent(Guid orderId, Money amount)
{
OrderId = orderId;
Amount = amount;
}
}
public class OrderCancelledEvent : DomainEvent
{
public Guid OrderId { get; }
public string Reason { get; }
public OrderCancelledEvent(Guid orderId, string reason)
{
OrderId = orderId;
Reason = reason;
}
}
/// <summary>
/// 事件处理器 — 不同上下文的响应
/// </summary>
public class OrderCreatedEventHandler : INotificationHandler<OrderCreatedEvent>
{
private readonly IInventoryRepository _inventoryRepo;
public OrderCreatedEventHandler(IInventoryRepository inventoryRepo)
{
_inventoryRepo = inventoryRepo;
}
public async Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken)
{
// 库存上下文响应订单创建事件 — 预留库存
Console.WriteLine($"[库存服务] 收到订单创建事件,预留库存 OrderId={notification.OrderId}");
}
}
public class OrderPaidEventHandler : INotificationHandler<OrderPaidEvent>
{
public async Task Handle(OrderPaidEvent notification, CancellationToken cancellationToken)
{
// 物流上下文响应订单支付事件 — 准备发货
Console.WriteLine($"[物流服务] 收到订单支付事件,准备发货 OrderId={notification.OrderId}");
}
}5. 仓储(Repository)
聚合的持久化接口,隔离领域层与基础设施层。
/// <summary>
/// 仓储接口 — 定义在领域层
/// </summary>
public interface IOrderRepository
{
Task<Order> GetByIdAsync(Guid id);
Task<List<Order>> GetByUserIdAsync(Guid userId);
Task AddAsync(Order order);
Task UpdateAsync(Order order);
Task DeleteAsync(Guid id);
}
/// <summary>
/// 仓储实现 — 定义在基础设施层
/// </summary>
public class OrderRepository : IOrderRepository
{
private readonly OrderDbContext _context;
public OrderRepository(OrderDbContext context)
{
_context = context;
}
public async Task<Order> GetByIdAsync(Guid id)
{
return await _context.Orders
.Include(o => o.Items)
.FirstOrDefaultAsync(o => o.Id == id);
}
public async Task<List<Order>> GetByUserIdAsync(Guid userId)
{
return await _context.Orders
.Include(o => o.Items)
.Where(o => o.UserId == userId)
.OrderByDescending(o => o.CreatedTime)
.ToListAsync();
}
public async Task AddAsync(Order order)
{
await _context.Orders.AddAsync(order);
await _context.SaveChangesAsync();
}
public async Task UpdateAsync(Order order)
{
_context.Orders.Update(order);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(Guid id)
{
var order = await GetByIdAsync(id);
if (order != null)
{
_context.Orders.Remove(order);
await _context.SaveChangesAsync();
}
}
}6. 应用服务(Application Service)
协调领域对象完成业务用例,本身不包含业务逻辑。
/// <summary>
/// 应用服务 — 协调领域对象,不包含业务逻辑
/// </summary>
public class OrderApplicationService
{
private readonly IOrderRepository _orderRepository;
private readonly IProductRepository _productRepository;
private readonly IMediator _mediator; // 用于发布领域事件
public OrderApplicationService(
IOrderRepository orderRepository,
IProductRepository productRepository,
IMediator mediator)
{
_orderRepository = orderRepository;
_productRepository = productRepository;
_mediator = mediator;
}
// 创建订单用例
public async Task<OrderDto> CreateOrderAsync(CreateOrderCommand command)
{
// 1. 创建值对象
var address = new Address(
command.Province, command.City, command.District,
command.Street, command.Detail
);
// 2. 通过工厂方法创建聚合根
var order = Order.Create(command.UserId, address);
// 3. 添加商品
foreach (var item in command.Items)
{
var product = await _productRepository.GetByIdAsync(item.ProductId);
var unitPrice = new Money(product.Price);
order.AddItem(product.Id, product.Name, unitPrice, item.Quantity);
}
// 4. 持久化
await _orderRepository.AddAsync(order);
// 5. 发布领域事件
foreach (var domainEvent in order.DomainEvents)
{
await _mediator.Publish(domainEvent);
}
// 6. 返回 DTO
return MapToDto(order);
}
// 支付订单用例
public async Task PayOrderAsync(Guid orderId)
{
var order = await _orderRepository.GetByIdAsync(orderId)
?? throw new NotFoundException("订单不存在");
order.Pay(); // 业务规则在聚合根内部
await _orderRepository.UpdateAsync(order);
foreach (var domainEvent in order.DomainEvents)
{
await _mediator.Publish(domainEvent);
}
}
private OrderDto MapToDto(Order order)
{
return new OrderDto
{
OrderId = order.Id,
OrderNo = order.OrderNo,
Status = order.Status.ToString(),
TotalAmount = order.TotalAmount.Amount,
Items = order.Items.Select(i => new OrderItemDto
{
ProductName = i.ProductName,
Quantity = i.Quantity,
Subtotal = i.GetSubtotal().Amount
}).ToList()
};
}
}DDD 项目结构示例
OrderService/
├── OrderService.API/ # 接口层
│ ├── Controllers/
│ │ └── OrderController.cs
│ ├── DTOs/
│ │ ├── OrderDto.cs
│ │ └── CreateOrderCommand.cs
│ └── Program.cs
│
├── OrderService.Application/ # 应用层
│ ├── Services/
│ │ └── OrderApplicationService.cs
│ ├── Handlers/
│ │ └── OrderCreatedEventHandler.cs
│ └── Interfaces/
│ └── IOrderRepository.cs
│
├── OrderService.Domain/ # 领域层(核心,无外部依赖)
│ ├── Entities/
│ │ ├── Order.cs # 聚合根
│ │ └── OrderItem.cs # 实体
│ ├── ValueObjects/
│ │ ├── Address.cs
│ │ ├── Money.cs
│ │ └── Entity.cs
│ ├── Events/
│ │ ├── OrderCreatedEvent.cs
│ │ └── OrderPaidEvent.cs
│ └── Exceptions/
│ └── DomainException.cs
│
└── OrderService.Infrastructure/ # 基础设施层
├── Repositories/
│ └── OrderRepository.cs
├── Persistence/
│ └── OrderDbContext.cs
└── Configurations/
└── OrderConfiguration.cs贫血模型 vs 充血模型
/// <summary>
/// 贫血模型 — 数据和行为分离(传统三层架构)
/// Order 只是数据容器,业务逻辑散落在 Service 层
/// </summary>
public class AnemicOrder // 贫血的
{
public Guid Id { get; set; }
public List<OrderItem> Items { get; set; }
public OrderStatus Status { get; set; }
// 没有行为!只是一个数据容器
}
public class AnemicOrderService
{
// 业务逻辑在 Service 中,不在实体中
public void PayOrder(AnemicOrder order)
{
if (order.Status != OrderStatus.Pending)
throw new Exception("订单状态不正确");
if (!order.Items.Any())
throw new Exception("订单为空");
order.Status = OrderStatus.Paid;
// 保存、发通知...
}
}
// ============================================
/// <summary>
/// 充血模型 — DDD推荐的方式
/// 行为和数据封装在一起,实体自己负责业务规则
/// </summary>
public class RichOrder // 充血的
{
private readonly List<OrderItem> _items = new List<OrderItem>();
public void Pay()
{
if (Status != OrderStatus.Pending)
throw new DomainException("订单状态不正确");
if (!_items.Any())
throw new DomainException("订单为空");
Status = OrderStatus.Paid;
}
public void AddItem(...) { /* 业务规则在这里 */ }
public void Cancel(string reason) { /* 业务规则在这里 */ }
}优点
缺点
DDD 适用场景判断
| 场景 | 是否适合DDD | 建议 |
|---|---|---|
| 复杂业务系统(电商、金融、ERP) | 适合 | 核心领域用DDD,非核心用传统方式 |
| 微服务架构 | 适合 | 限界上下文天然对应微服务边界 |
| 简单CRUD系统 | 不适合 | 用传统三层架构更高效 |
| 数据报表/BI系统 | 不适合 | 直接SQL更适合 |
| 团队DDD经验不足 | 谨慎 | 先从小模块试点,逐步推广 |
总结
DDD 的核心价值不是技术实现,而是设计思维 — 用业务的语言建模,让代码成为业务的真实反映。掌握实体、值对象、聚合根、领域事件、仓储这些构建块,结合限界上下文的战略设计,才能真正驾驭复杂业务系统。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来“高级”而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《DDD领域驱动设计》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕“为了模式而模式”,尤其是在简单业务里。
复盘问题
- 如果把《DDD领域驱动设计》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《DDD领域驱动设计》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《DDD领域驱动设计》最大的收益和代价分别是什么?
