name: dotnet-ddd description: 在C#/.NET中实现领域驱动设计战术模式。用于构建实体、值对象、聚合、领域事件、仓库或结构化DDD解决方案。框架无关 — 涵盖使用现代C#的纯领域建模。 license: MIT metadata: version: “1.0.0” domain: 架构 triggers: DDD, 领域驱动设计, 聚合, 值对象, 实体, 领域事件, 仓库模式, 有界上下文, 聚合根, 领域模型, 领域层, 丰富领域模型 role: 架构师 scope: 实现 output-format: 代码 related-skills: 领域分析, 架构模式, C#开发者, .NET核心专家
.NET中的领域驱动设计
在现代C#中实现战术DDD模式 — 使用实体、值对象、聚合、领域事件和仓库构建丰富领域模型。
范围: 此技能涵盖战术DDD(构建块)。对于战略DDD(有界上下文、上下文映射、子域分析),使用domain-analysis技能。
何时使用
- 建模具有复杂业务规则的领域
- 实现实体、值对象或聚合
- 引发和处理领域事件
- 设计仓库接口
- 使用DDD层结构化.NET解决方案
- 应用结果模式、强类型ID或规约模式
不适用于: 简单的CRUD应用、贫血领域模型,或业务逻辑完全在服务中的情况。
关键概念
| 概念 | 是什么 | C#实现 |
|---|---|---|
| 实体 | 具有身份的对象,状态变化中持久存在 | 类带Id,按身份相等 |
| 值对象 | 由其属性定义的不可变对象,无身份 | record或sealed class,结构相等 |
| 聚合 | 实体/值对象的集群,具有单个根和一致性边界 | 根实体守护所有不变量 |
| 聚合根 | 聚合的入口点 — 唯一外部引用的实体 | 公共API,拥有子实体 |
| 领域事件 | 领域中发生的事件,其他部分关心 | record实现IDomainEvent |
| 仓库 | 持久化/检索聚合的抽象 | 领域层接口,基础设施层实现 |
| 领域服务 | 不属于单个实体/值对象的无状态操作 | 静态方法或注入服务 |
| 规约 | 封装的查询/业务规则 | 类带IsSatisfiedBy(T) |
实体基类
public abstract class Entity<TId> : IEquatable<Entity<TId>>
where TId : notnull
{
public TId Id { get; protected init; }
private readonly List<IDomainEvent> _domainEvents = [];
public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents;
protected Entity(TId id) => Id = id;
public void RaiseDomainEvent(IDomainEvent domainEvent) =>
_domainEvents.Add(domainEvent);
public void ClearDomainEvents() => _domainEvents.Clear();
public bool Equals(Entity<TId>? other) =>
other is not null && Id.Equals(other.Id);
public override bool Equals(object? obj) =>
obj is Entity<TId> other && Equals(other);
public override int GetHashCode() => Id.GetHashCode();
public static bool operator ==(Entity<TId>? left, Entity<TId>? right) =>
Equals(left, right);
public static bool operator !=(Entity<TId>? left, Entity<TId>? right) =>
!Equals(left, right);
// 受保护的无参数构造函数供ORM使用
protected Entity() => Id = default!;
}
使用record的值对象
public record Money(decimal Amount, string Currency)
{
public Money Add(Money other)
{
if (Currency != other.Currency)
throw new InvalidOperationException($"Cannot add {Currency} to {other.Currency}");
return this with { Amount = Amount + other.Amount };
}
public static Money Zero(string currency) => new(0, currency);
}
public record Address(string Street, string City, string State, string ZipCode, string Country);
public record DateRange
{
public DateOnly Start { get; init; }
public DateOnly End { get; init; }
public DateRange(DateOnly start, DateOnly end)
{
if (end < start)
throw new ArgumentException("End date must be after start date");
Start = start;
End = end;
}
public bool Overlaps(DateRange other) =>
Start <= other.End && other.Start <= End;
}
聚合示例
public sealed class Order : Entity<OrderId>
{
private readonly List<OrderLine> _lines = [];
public IReadOnlyList<OrderLine> Lines => _lines;
public CustomerId CustomerId { get; private init; }
public OrderStatus Status { get; private set; }
public Money Total => _lines.Aggregate(Money.Zero("USD"), (sum, line) => sum.Add(line.SubTotal));
private Order() { } // ORM
public static Order Create(CustomerId customerId)
{
var order = new Order(OrderId.New())
{
CustomerId = customerId,
Status = OrderStatus.Draft
};
order.RaiseDomainEvent(new OrderCreatedEvent(order.Id));
return order;
}
public void AddLine(ProductId productId, int quantity, Money unitPrice)
{
if (Status != OrderStatus.Draft)
throw new DomainException("Can only add lines to draft orders");
if (quantity <= 0)
throw new DomainException("Quantity must be positive");
var line = new OrderLine(OrderLineId.New(), productId, quantity, unitPrice);
_lines.Add(line);
}
public void Submit()
{
if (_lines.Count == 0)
throw new DomainException("Cannot submit an empty order");
Status = OrderStatus.Submitted;
RaiseDomainEvent(new OrderSubmittedEvent(Id, Total));
}
}
聚合规则:
- 仅通过ID引用其他聚合,绝不通过直接对象引用
- 所有状态更改通过聚合根进行
- 一个事务 = 一个聚合(聚合之间最终一致性)
- 保持聚合小 — 仅包含必须立即一致的内容
强类型ID
public readonly record struct OrderId(Guid Value)
{
public static OrderId New() => new(Guid.NewGuid());
public override string ToString() => Value.ToString();
}
public readonly record struct CustomerId(Guid Value)
{
public static CustomerId New() => new(Guid.NewGuid());
}
领域事件
public interface IDomainEvent
{
DateTime OccurredOn { get; }
}
public abstract record DomainEvent : IDomainEvent
{
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
}
public record OrderCreatedEvent(OrderId OrderId) : DomainEvent;
public record OrderSubmittedEvent(OrderId OrderId, Money Total) : DomainEvent;
仓库接口
// 在领域层定义 — 在基础设施层实现
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(OrderId id, CancellationToken ct = default);
Task AddAsync(Order order, CancellationToken ct = default);
Task UpdateAsync(Order order, CancellationToken ct = default);
}
// 可选:通用基接口
public interface IRepository<T, TId>
where T : Entity<TId>
where TId : notnull
{
Task<T?> GetByIdAsync(TId id, CancellationToken ct = default);
Task AddAsync(T entity, CancellationToken ct = default);
}
结果模式(预期失败时不使用异常)
public sealed class Result<T>
{
public T? Value { get; }
public Error? Error { get; }
public bool IsSuccess => Error is null;
private Result(T value) => Value = value;
private Result(Error error) => Error = error;
public static Result<T> Success(T value) => new(value);
public static Result<T> Failure(Error error) => new(error);
public TOut Match<TOut>(Func<T, TOut> onSuccess, Func<Error, TOut> onFailure) =>
IsSuccess ? onSuccess(Value!) : onFailure(Error!);
}
public record Error(string Code, string Message);
在聚合中使用:
public Result<Order> Submit()
{
if (_lines.Count == 0)
return Result<Order>.Failure(OrderErrors.EmptyOrder);
Status = OrderStatus.Submitted;
RaiseDomainEvent(new OrderSubmittedEvent(Id, Total));
return Result<Order>.Success(this);
}
约束
必须做
- 保持领域层无基础设施依赖(无EF、无HTTP、无日志记录)
- 对无身份的概念使用值对象(如Money、Address、Email)
- 在聚合内部强制执行不变量 — 绝不在外部
- 仅通过ID引用其他聚合
- 对聚合使用工厂方法(如
Create、From)代替公共构造函数 - 为跨聚合边界的副作用引发领域事件
- 在所有异步仓库方法上使用
CancellationToken
禁止做
- 暴露聚合状态的setter(使用行为方法代替)
- 让聚合依赖仓库或服务
- 创建“上帝聚合”包含一切
- 使用领域事件进行聚合内通信
- 将业务逻辑放在应用服务中 — 它属于领域
- 使用贫血领域模型(实体作为数据袋,逻辑在服务中)
额外参考
根据任务加载 — 不要一次性加载所有:
- references/building-blocks.md — 深入探讨实体、值对象、聚合模式及边缘情况
- references/domain-events.md — 事件分发策略、发件箱模式、集成事件
- references/patterns.md — 结果模式、规约、强类型ID、守卫子句
- references/project-structure.md — 解决方案布局、层依赖、项目引用