NET领域驱动设计实现Skill dotnet-ddd

这个技能用于在C#/.NET中实现领域驱动设计(DDD)的战术模式,专注于构建实体、值对象、聚合、领域事件、仓库等核心组件。它帮助开发者建模复杂业务规则,提升软件架构质量,适用于后端开发和架构设计场景。关键词:领域驱动设计、DDD、C#、.NET、实体、值对象、聚合、领域事件、仓库、架构设计、战术模式。

架构设计 0 次安装 0 次浏览 更新于 3/22/2026

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,按身份相等
值对象 由其属性定义的不可变对象,无身份 recordsealed 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));
    }
}

聚合规则:

  1. 仅通过ID引用其他聚合,绝不通过直接对象引用
  2. 所有状态更改通过聚合根进行
  3. 一个事务 = 一个聚合(聚合之间最终一致性)
  4. 保持聚合小 — 仅包含必须立即一致的内容

强类型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引用其他聚合
  • 对聚合使用工厂方法(如CreateFrom)代替公共构造函数
  • 为跨聚合边界的副作用引发领域事件
  • 在所有异步仓库方法上使用CancellationToken

禁止做

  • 暴露聚合状态的setter(使用行为方法代替)
  • 让聚合依赖仓库或服务
  • 创建“上帝聚合”包含一切
  • 使用领域事件进行聚合内通信
  • 将业务逻辑放在应用服务中 — 它属于领域
  • 使用贫血领域模型(实体作为数据袋,逻辑在服务中)

额外参考

根据任务加载 — 不要一次性加载所有

资源