订单服务(Ordering.API)之领域层

news/2025/12/4 2:47:13/文章来源:https://www.cnblogs.com/pengboke/p/19304607

前言

领域层(Ordering.Domain)

1.领域层职责

职责 说明 示例
1️⃣ 表达领域模型 通过实体(Entity)、值对象(Value Object)建模业务对象及其行为。 OrderOrderItemAddress
2️⃣ 封装业务规则 在模型中定义业务不变量(Invariants),通过方法约束状态变化。 Order.AddOrderItem() 校验重复商品
3️⃣ 保持领域一致性 通过聚合(Aggregate)维护内部数据一致性,防止跨聚合直接修改。 Order 聚合根负责管理 OrderItem
4️⃣ 实现业务行为 在实体或领域服务中实现核心业务逻辑(非应用逻辑)。 计算价格、验证库存、状态变更
5️⃣ 处理领域事件 通过发布领域事件(Domain Event)表达领域内的状态变化。 OrderStartedDomainEvent
6️⃣ 提供领域服务 当业务逻辑无法归属于单个实体时,用领域服务表达业务操作。 PaymentDomainService
7️⃣ 抽象仓储接口 定义持久化抽象接口,不涉及具体数据库操作。 IOrderRepositoryIBuyerRepository
8️⃣ 独立于技术实现 不依赖 ORM、框架、API、UI、消息队列等外部设施。 仅依赖纯 C# 对象与接口定义

2.领域层的设计原则(Design Principles)

领域层的设计原则(Design Principles)

原则 说明
1️⃣ 高内聚、低耦合 聚合内部封装完整业务逻辑,对外只暴露必要接口,避免跨聚合直接依赖。
2️⃣ 面向领域建模,不面向数据库 模型应表达业务含义,而非表结构,数据库只是持久化手段。
3️⃣ 保持纯净性(Pure Domain Model) 不依赖基础设施、框架或 ORM,让领域模型只关注业务。
4️⃣ 使用统一语言(Ubiquitous Language) 类名、方法名、属性名与业务术语一致,开发与领域专家使用同一套语言。
5️⃣ 领域内封装业务规则(Encapsulation of Invariants) 所有业务约束、验证逻辑必须在领域层内部维护,而非散落在 Controller 或 Service 中。
6️⃣ 以聚合为一致性边界(Aggregate Boundary) 聚合是事务和一致性的最小单元,不跨聚合修改数据。
7️⃣ 通过领域事件实现解耦(Domain Event Decoupling) 用事件驱动上下文间的通信,降低模块耦合度。
8️⃣ 保持无状态的领域服务(Stateless Services) 领域服务只封装行为逻辑,不保存状态或依赖外部资源。
9️⃣ 设计领域层与持久化无关(Persistence Ignorance) Repository 接口定义在领域层,实现由基础设施层提供。
🔟 以业务能力为中心,而非技术分层 设计模型要体现领域行为(动词),而不仅是数据属性(名词)。

3.领域层的组成结构(Core Components)

组成 作用 示例
1️⃣ 实体(Entity) 具有唯一标识、可变状态的业务对象,封装核心属性与行为。 Order, Buyer
2️⃣ 值对象(Value Object) 无唯一标识、以值区分的不可变对象,常用于描述属性。 Address
3️⃣ 聚合与聚合根(Aggregate & Root) 定义一致性边界,聚合根是唯一访问入口,保证内部规则完整。 Order 聚合内含 OrderItemBuyer 聚合内含 PaymentMethod
4️⃣ 领域服务(Domain Service) 实现无法归属单一实体的业务逻辑;保持无状态。(跨聚合/不属于任何实体的业务逻辑) 聚合根 + 领域事件 + 事件处理器 = 聚合自带的行为 + 事件驱动的跨聚合协调
5️⃣ 领域事件(Domain Event) 表示领域中发生的有意义的事件,用于解耦与异步处理。 OrderCancelledDomainEventOrderStartedDomainEvent
6️⃣ 仓储接口(Repository Interface) 提供聚合根的持久化访问抽象;领域层只定义接口。 IBuyerRepositoryIOrderRepository
(可选)工厂(Factory) 封装复杂对象或聚合的创建逻辑。 Order.NewDraft

4.订单服务领域层(Ordering.Domain)

这些基本的概念一定要明白

订单服务领域层 (Ordering.Domain) 中,主要包括以下内容:

  • 实体(Entity)OrderOrderItemBuyerPaymentMethod
  • 聚合根(Aggregate Root)
    • Order:订单聚合根
      • 聚合(Entity):订单项 OrderItem
      • 值对象(Value Object):地址 Address
    • Buyer:买家聚合根
      • 聚合(Entity):支付方式 PaymentMethod
  • 领域事件(Domain Event)OrderCancelledDomainEventOrderShippedDomainEventOrderStartedDomainEvent
  • 仓储接口(Repository Interface)IBuyerRepositoryIOrderRepository
  • 领域服务(Domain Service):通过 聚合根 + 领域事件 + 事件处理器 实现聚合内部行为及跨聚合业务协调
  • 工厂(Factory)Order.NewDraft,用于封装订单聚合的创建逻辑

image-20251107171622291

5.实体

5.1基本概念

实体是指具有唯一标识(Identity)的对象,其生命周期内可以发生状态变化,但其身份保持不变:

  • 唯一标识(ID):每个实体都有一个唯一标识来区分它与其他实体。
  • 可变状态(State):
    • 实体的属性可以在生命周期中变化,但它的身份不变。
    • 例如:订单状态从 SubmittedAwaitingValidationStockConfirmedPaidShippedCancelled(已提交→等待验证→库存确认→已支付→已发货→已取消)。
    • 业务行为:实体不仅仅是数据结构,还封装业务逻辑。

总结

  • 实体 = 唯一标识 + 可变状态 + 封装行为
  • 是 DDD 的核心构建块,聚合根也是实体的一种特殊形式
  • 实体主要职责是维护自身的一致性和生命周期内的业务规则

5.2贫血模型(Anemic Model)

概念

  • 实体仅包含数据(属性),没有业务行为。

  • 所有业务逻辑都写在服务(Service)层中。

  • 数据和行为分离。

优点

  • 简单易懂,符合 CRUD 风格
  • 数据操作直接

缺点

  • 聚合不变式难保证
  • 业务逻辑分散在服务层,容易重复
  • 不符合 DDD 思想
// 实体:仅数据
public class Order
{public int Id { get; set; }public decimal Total { get; set; }public OrderStatus Status { get; set; }public List<OrderItem> Items { get; set; } = new List<OrderItem>();
}public class OrderItem
{public int ProductId { get; set; }public int Units { get; set; }public decimal UnitPrice { get; set; }
}// 服务层处理业务逻辑
public class OrderService
{public void AddOrder(Order order, OrderItem item){// 添加订单...}public void DeleteOrder(int orderId){// 删除订单...}public void GetOrder(int orderId){// 删除订单...}
}

5.3充血模型(Rich/Full Model)

概念

  • 实体不仅存数据,还封装业务行为和规则

  • 聚合根内部保证聚合的一致性

  • 跨聚合业务逻辑可以通过领域服务或事件实现

优点

  • 聚合内部不变式由实体自己保证
  • 业务逻辑集中在实体,符合 DDD 精神
  • 易于与领域事件、聚合服务结合

缺点

  • 初期实现复杂
  • 对开发人员要求高

Order 不仅是数据模型,还封装了完整的业务逻辑(如添加订单项、设置订单状态等操作),并通过发布领域事件(Domain Events)实现跨聚合或跨系统的业务通知和状态同步,从而保证聚合内部一致性和领域行为的完整性。

image-20251107193359243

5.4实体基类(Entity)

订单服务领域层(Ordering.Domain)封装了实体基类(Entity),实体基类(Entity)作用是:

  • 1.统一 Id 管理:保证每个实体都有唯一标识。
  • 2.封装领域事件:聚合根可以触发事件并集中管理。
  • 3.统一相等性比较:比较实体对象时只看 Id,不看其他属性。
  • 4.支持DDD 基础设施:如 EF Core 映射、事件分发。

_Id:实体的唯一标识,保护级别,通过 protected set 避免外部随意修改。

image-20251107194355843

领域事件管理:

  • 只读访问领域事件集合(DomainEvents)
  • 添加领域事件(AddDomainEvent)
  • 移除已添加的领域事件(RemoveDomainEvent)
  • 清空所有领域事件(ClearDomainEvents)

为什么把事件逻辑放在 Entity?

聚合根继承 Entity 自动拥有事件管理能力。

所有实体(聚合根或子实体)都可以使用这个功能,不必每个类都写一遍。

聚合根的方法才是真正业务触发事件的地方,保证聚合一致性。

image-20251107194518727

统一相等性比较

image-20251107194948358

OrderEF Core 数据库映射配置中:领域事件(DomainEvents)被 EF Core 忽略,说明它 只存在内存,不持久化到数据库

image-20251107195616071

5.5领域层(Ordering.Domain)中的实体

OrderOrderItemBuyerPaymentMethod 都继承自 Entity,因此它们都是 实体(Entity),具有唯一标识(Id)和领域事件管理能力,并承载业务逻辑和状态,是 DDD 聚合根或聚合内部的核心组成部分。

image-20251107200127595

4.6实体的实现

订单服务(Ordering.API)封装了实体基类(Entity),所有实体继承(Entity)即可:

  • 唯一标识(ID)管理

    • 每个实体都有唯一标识 Id
    • Id 由实体内部或子类设置(protected set),外部不可修改。
    • 提供 IsTransient() 方法判断实体是否为瞬态(未持久化)。
  • 领域事件管理

    • _domainEvents 保存实体触发的领域事件(实现 INotification)。
    • 提供方法 AddDomainEvent() / RemoveDomainEvent() / ClearDomainEvents() 管理事件。
    • DomainEvents 对外只读,保证聚合内部封装。
  • 实体相等性和哈希码

    • Equals() 基于 类型 + Id 判断实体是否相等。
    • 瞬态实体不与其他实体相等。
    • GetHashCode() 使用 Id 生成哈希,并缓存已持久化实体的哈希值。
    • 重载 ==!= 运算符,确保实体比较一致。
  • 性能优化

    • _requestedHashCode 缓存已持久化实体的哈希码,避免重复计算。

image-20251108214733705

6.值对象

6.1基本概念

值对象是 DDD 中的一种建模概念,用于表示 没有唯一身份标识的对象,它完全由自己的属性值定义。

  • 与实体(Entity)不同:实体有唯一标识(Id),值对象没有。
  • 例子:地址(Address)、货币(Money)、时间段(DateRange)等。
  • 核心思想值相等即对象相等,不关心对象引用,只关心属性值是否一致。

值对象的特点

特点 说明
无唯一标识 不需要 Id,值相等则视为同一对象
不可变性 通常设计为 immutable(属性只读,构造时赋值)
自包含业务逻辑 可以包含计算、验证等逻辑(如地址格式验证、货币加减法)
生命周期依附实体 生命周期与聚合根绑定,单独存在没有意义

总结:值对象是 无标识、不可变的对象,完全由属性值定义。它承载业务规则,与聚合根绑定,增强模型表达能力,避免数据散乱,是 DDD 中不可或缺的建模工具。

6.2值对象基类(ValueObject)

无标识性:没有 Id,相等性由值决定。

不可变性:通过只读属性或拷贝方法保证。

image-20251107201706516

统一比较逻辑

  • EqualsGetHashCode 基于 GetEqualityComponents()
  • 子类只需实现 GetEqualityComponents()

image-20251107201918623

操作符重载支持EqualOperator / NotEqualOperator 支持 ==!=

image-20251107201952198

领域模型表达力

  • 例如 Address 等值对象在聚合根中使用时更具语义化。
  • 聚合根的行为可以安全依赖不可变的值对象。

Address 继承ValueObject代表Address是值对象

image-20251107202410110

6.3值对象(Address)的实现

AddressOrder 聚合根中的值对象,用于表示订单的收货地址。

image-20251107202434051

查看 OrderEF Core 数据库映射配置,以理解 EF Core 中值对象的实现方式

OwnsOne:表示实体拥有一个“被拥有对象(Owned Entity)”,常用于 值对象(Value Object)

OwnsOne 的作用:

  • 告诉 EF Core,AddressOrder 的值对象(Owned Entity),而不是独立的实体。
  • 值对象没有自己的 Id,生命周期依赖于拥有它的聚合根(这里是 Order)。
  • EF Core 会把 Address 的各个属性 映射到 orders 表中,而不会单独创建 Address 表。

image-20251107202840450

Address 内的每个属性都会被展开为 orders 表中的列:

  • StreetAddress_Street
  • CityAddress_City
  • StateAddress_State
  • CountryAddress_Country
  • ZipCodeAddress_ZipCode

image-20251107203143372

OrderStatusOrder 聚合根的内部属性,用于表示订单状态。它是一个枚举类型,用来限定状态值,但不是值对象。

image-20251107203548219

查看 EF Core 中配置 Order 实体的枚举属性 OrderStatus

  • Property(o => o.OrderStatus):告诉 EF Core 需要对 Order 实体的 OrderStatus 属性进行配置。
  • HasConversion<string>()
    • EF Core 默认会将枚举存储为整数(int)。
    • 使用 HasConversion<string>() 可以把枚举值转换为 字符串 存储,例如 "Submitted" 而不是 1
    • 优点:数据库可读性强,直接看到 "Submitted" 而不是数字 1。便于调试和报表查询。
    • 缺点:占用的存储比整数稍大。如果枚举值非常多,字符串长度可能成为问题。
  • HasMaxLength(30):指定数据库列最大长度为 30 个字符,防止插入过长的枚举字符串导致报错。

image-20251107204146046

OrderStatus在数据库映射类型为varchar(30)

image-20251107204634373

7.聚合根

7.1基本概念

聚合根(Aggregate Root)聚合(Aggregate)的唯一入口和控制者,它管理聚合内部所有对象的一致性和生命周期,并封装业务规则。

核心思想:外部世界只能通过聚合根访问聚合内部对象,保证聚合内的一致性。

换句话说:

  • 聚合根是一个实体(Entity);
  • 聚合根代表一个业务整体(如订单、购物车、商品等);
  • 外部系统 不能直接访问聚合内部的其他实体或值对象,只能通过聚合根提供的行为(方法)操作。

聚合根的设计原则

原则 说明
唯一访问入口 聚合内部对象只能通过聚合根访问,外部不可直接操作
封装内部状态 内部实体、值对象私有化,通过方法操作
保证业务一致性 聚合根控制规则和状态变化
行为优先于属性 聚合根提供方法表达业务行为,而不是让外部直接修改属性
聚合根可持久化 通常通过 Repository 访问和持久化聚合根
发布领域事件 聚合根在状态变化时触发事件,用于解耦

7.2聚合根标记接口(IAggregateRoot)

这是一个空接口(marker/marker-interface),通常称为 聚合根标记接口(Aggregate Root Marker Interface)。它本身不声明任何成员(方法或属性),仅用于在类型系统中标识某个实体是聚合根

IAggregateRoot 的主要目的就是:

  • 语义化: 明确哪个实体是聚合根(如 Order : Entity, IAggregateRoot)。
  • 静态约束: 在泛型接口/类中使用约束,例如:IRepository<T> where T : IAggregateRoot,让仓储只能用于聚合根。
  • 代码可读性 & 约定: 团队成员一眼就能看出哪些类是聚合根,有助于模型一致性。

image-20251107225253564

7.3订单聚合根(Order:IAggregateRoot)

OrderOrder 聚合的聚合根,负责封装订单的行为与一致性规则(添加明细、状态流转、触发领域事件、持有地址和值对象等),外部只能通过它来变更订单相关状态或数据。

Order继承了EntityIAggregateRoot,代表Order即是实体也是聚合根:

  • Entity:实体基类,封装了唯一标识和领域事件管理
  • IAggregateRoot:表示Order是一个聚合根

image-20251107225941725

唯一访问入口Order 作为聚合根封装聚合内部所有实体和值对象,所有属性和集合均为 private set,外部只能通过聚合根提供的构造函数或领域方法修改状态,从而保证业务规则和聚合一致性。

image-20251107230600364

封装内部状态 & 保证业务一致性Order 聚合根将订单状态 (OrderStatus) 封装为不可写属性,外部无法直接修改,所有状态变更必须通过聚合根提供的专用方法完成,包括 SetAwaitingValidationStatus(等待验证)、SetStockConfirmedStatus(库存确认)、SetPaidStatus(已支付)、SetShippedStatus(已发货)和 SetCancelledStatus(已取消)。通过这种方式,聚合根在方法内部可以统一执行业务规则检查和异常处理,确保订单在整个生命周期中始终保持合法和一致的状态。

image-20251107231206582

行为优先于属性Order 聚合根强调通过行为来操作聚合内部状态,而不是让外部直接修改属性。除了提供用于更新订单状态 (OrderStatus) 的方法外,它还提供了 AddOrderItem 方法来添加订单明细,订单明细集合 _orderItems 对外只读 (OrderItems),外部无法直接修改或添加明细,所有对订单明细的操作必须通过聚合根的方法AddOrderItem进行,从而确保业务规则和聚合一致性得到统一维护。

image-20251107232545322

聚合根可持久化:在 eShop 系统中,订单聚合根通过领域层定义的仓储接口 IOrderRepository 提供持久化能力,基础设施层通过实现该接口完成数据库操作。按照 DDD 原则,通常只有聚合根才拥有对应的仓储实现,以保证整个聚合的一致性和完整性。作用在于:所有对聚合的增删改操作都通过仓储统一管理,避免外部直接操作聚合内部实体导致数据不一致。好处是:保持聚合边界完整性,便于实现事务控制和领域事件发布,同时简化代码维护和单元测试。

image-20251107234428450

订单领域层(Ordering.Domain)这里对仓储接口(IRepository<T>)基进行约束(Where T : IAggregateRoot)

只有聚合根才能继承该接口,只有订单仓储(IOrderRepository)和买家仓储(IBuyerRepository)继承实现仓储

image-20251108220652282

Order 聚合根中,共发布了以下领域事件:

  • OrderStartedDomainEvent:订单开始事件
  • OrderStatusChangedToAwaitingValidationDomainEvent:订单状态(OrderStatus)从 Submitted 变为 AwaitingValidation事件
  • OrderStatusChangedToStockConfirmedDomainEvent:订单状态从 AwaitingValidation 变为 StockConfirmed(库存确认),通知后续可以进行支付或配送
  • OrderStatusChangedToPaidDomainEvent:订单状态从 StockConfirmed 变为 Paid(已支付),通知发货系统可以处理配送
  • OrderShippedDomainEvent:订单状态为 Shipped(已发货),通知用户和其他系统订单已发货
  • OrderCancelledDomainEvent:订单状态被取消,通知库存、支付等系统撤销相关操作

发布领域事件:通过聚合根发布领域事件,保证了业务行为由聚合根统一控制,状态变化有明确入口,同时实现系统解耦。外部系统可以订阅事件响应,而无需直接依赖聚合内部逻辑,从而增强了扩展性、可维护性和业务一致性。

image-20251107235352142

聚合根Order有2个构造函数

构造函数 调用方 作用
protected Order() EF Core / 内部 用于 ORM 反射创建实体,初始化私有集合
public Order(...) 应用层 / 业务逻辑 创建新订单,保证聚合不变式,触发领域事件

无参构造函数 : ORM 使用,受保护,不能直接被业务层用

带参数构造函数 : 业务层使用,保证创建聚合时业务规则一致性

两个构造函数配合,既满足 DDD 聚合根封装原则,又满足 EF Core ORM 映射要求

image-20251108194422187

总结

设计思想与好处:通过聚合根封装属性和行为、控制状态变化、发布领域事件,Order 聚合根实现了业务规则集中管理、聚合内部一致性、系统解耦和可维护性。外部系统只能通过事件和仓储接口与聚合根交互,保证了系统可扩展性和业务安全性,同时遵循了 DDD 的聚合根设计原则。

7.4买家聚合根(Buyer:IAggregateRoot)

BuyerBuyer 聚合的聚合根,负责管理买家的核心信息及支付方式,封装聚合内部的一致性规则(如验证或添加支付方式、触发领域事件等)。外部对象只能通过 Buyer 聚合根的方法来修改其内部状态或数据,保证聚合的一致性和完整性。

Buyer继承了EntityIAggregateRoot,代表Order即是实体也是聚合根:

  • Entity:实体基类,封装了唯一标识和领域事件管理
  • IAggregateRoot:表示Buyer是一个聚合根

image-20251108003435472

唯一访问入口Buyer 作为 Buyer 聚合 的聚合根,封装了 IdentityGuidName 等核心属性,均为 private set,外部无法直接修改。所有操作必须通过聚合根提供的方法(如 VerifyOrAddPaymentMethod)执行。从而保证聚合的一致性、业务规则的正确性,以及支付方式的验证与管理流程完整。

image-20251108003635724

封装内部状态并保证业务一致性Buyer 作为聚合根,将支付方式集合 _paymentMethods 封装为只读集合 PaymentMethods,外部无法直接访问或修改集合中的数据。所有对支付方式的新增、验证或管理操作必须通过聚合根提供的 VerifyOrAddPaymentMethod 方法执行,从而确保支付方式的一致性、聚合内部状态的完整性以及相关业务规则(如去重验证、触发领域事件)得到严格遵守。

image-20251108004154416

聚合根可持久化Buyer 作为聚合根,在领域层定义了对应的仓储接口 IBuyerRepository,用于抽象买家的持久化操作。接口的具体实现由基础设施层提供(如 BuyerRepository),保证了领域模型与数据访问的解耦,同时遵循依赖倒置原则,实现领域模型与数据访问解耦。

image-20251108005841561

订单领域层(Ordering.Domain)这里对仓储接口(IRepository<T>)基进行约束(Where T : IAggregateRoot)

只有聚合根才能继承该接口,只有订单仓储(IOrderRepository)和买家仓储(IBuyerRepository)继承实现仓储

image-20251108220652282

领域事件发布:在 Buyer 聚合根中,每当执行支付方式验证或新增操作时,会触发 BuyerAndPaymentMethodVerifiedDomainEvent 领域事件,从而通知系统其他组件该买家及其支付方式已被验证或新增,实现聚合内外部状态变更的解耦与事件驱动处理。

image-20251108010119342

聚合根Buyer有2个构造函数

构造函数 使用者 作用
protected Buyer() EF Core / DDD 内部 ORM 反射创建实体,初始化私有集合 _paymentMethods
public Buyer(string identity, string name) 应用层 / 业务逻辑 创建新买家聚合根,保证必填字段和聚合不变式,初始化集合,复用内部逻辑

受保护的无参构造函数:ORM/框架使用

带参数的公共构造函数:业务层使用,保证创建对象符合业务规则

两者配合可以同时满足 DDD 聚合封装原则EF Core ORM 映射需求

image-20251108194730653

7.5聚合根的实现

聚合根就是一个领域模型的“守护者”
它负责管理聚合内所有实体和值对象的生命周期,封装业务行为和规则,保证内部一致性,通过领域事件与外部或其他聚合解耦通信。

选择聚合根实体

  • Order 聚合根:主实体为 Order,继承EntityIAggregateRoot
  • Buyer 聚合根:主实体为 Buyer,继承EntityIAggregateRoot

image-20251108215607637

管理内部子实体和值对象

  • 定义私有集合 _orderItems_paymentMethods
  • 只暴露只读集合属性 OrderItems / PaymentMethods
  • 通过聚合根方法添加或更新子实体
  • 对值对象(如 Address)直接封装为只读属性,确保不可变

image-20251108215724897

封装业务行为

  • 聚合根提供方法完成业务操作,而不是直接操作属性:
    • Order:AddOrderItemSetPaidStatusSetCancelledStatus
    • Buyer:VerifyOrAddPaymentMethod
  • 聚合根内部保证业务规则和一致性

image-20251108215845724

触发领域事件

  • 聚合根内部状态改变时,触发领域事件,而不是直接调用其他聚合
  • 事件携带必要信息,由事件处理器异步处理跨聚合逻辑

image-20251108215945016

受保护的构造函数

  • 提供受保护的无参构造函数,便于 ORM(如 EF Core)创建聚合实例
  • 公共构造函数用于创建新聚合实例并初始化状态

image-20251108220003594

外部引用与聚合边界

  • 聚合根之间不直接操作彼此内部状态,只通过 ID 或领域事件解耦
  • 例如 Order 聚合持有 BuyerIdPaymentId,而不是直接修改 Buyer 的支付方式
  • 这样可以保持聚合边界清晰,避免跨聚合直接依赖导致一致性问题

image-20251108220113807

聚合根与持久化

  • 聚合根是唯一持久化入口,ORM 或数据库操作通过聚合根进行
  • Order 聚合根负责保存自身及内部的 OrderItem,定义了自己的仓储接口IOrderRepository
  • Buyer 聚合根负责保存自身及 PaymentMethod,定义了自己的仓储接口IBuyerRepository

image-20251108221040020

7.6总结

Buyer 聚合根(买家聚合根)负责 买家身份识别支付方式管理,是买家领域的核心:

  • 买家标识与账户管理:管理买家的唯一身份(如 BuyerId、用户账号)。保证买家信息的一致性(如姓名、邮箱等)。
  • 支付方式管理:维护买家绑定的支付方式集合,校验支付方式合法性(例如是否已过期或未激活),添加、修改或删除支付方式时,保持内部数据一致性。
  • 与订单的关联:当买家下单时,为订单提供支付方式信息。通过发布领域事件(如 BuyerAndPaymentMethodVerifiedDomainEvent)与 Order 聚合根解耦通信,确保订单创建前支付方式已验证。
  • 一致性与完整性:保证买家与其支付方式的聚合内一致性规则。封装对外访问,外部服务不能直接修改支付方式。

**Order 聚合根(订单聚合根) **负责 订单生命周期管理订单状态流转,是订单领域的核心:

  • 订单创建与标识:创建订单并生成唯一订单号。管理订单与买家、支付方式、地址等的关联。
  • 订单项管理:管理订单中包含的多个商品项(OrderItems)。校验每个商品项的价格、数量、库存、折扣等合法性。
  • 订单状态流转:控制订单从 “已提交 → 等待验证 → 库存已确认 → 已支付 → 已发货 → 已取消” 的业务流转。保证状态变更遵守业务规则(如未支付订单不能发货)。
  • 事件驱动的解耦通信:当订单状态变化时,发布领域事件(如 OrderStartedDomainEvent, OrderStatusChangedToPaidDomainEvent)。与其他领域(商品、购物车)通过事件总线异步通信,保持低耦合。
  • 一致性控制:保证订单内所有订单项及状态的一致性。通过事务性聚合边界防止部分更新。

在 DDD(领域驱动设计)架构中:

  • Buyer 聚合根代表买家领域的核心对象,负责买家的身份标识与支付方式的管理与验证,确保买家支付信息的一致性与唯一性;
  • Order 聚合根代表订单领域的核心对象,负责订单的完整生命周期管理(创建、验证、库存、支付、发货、取消),并通过封装行为与领域事件维持聚合内部一致性。

两个聚合根之间通过 领域事件(Domain Events) 实现解耦通信,避免直接依赖,确保跨聚合的最终一致性与业务扩展性,构成了电商系统中买家与订单协作的核心模型。

8.聚合

8.1基本概念

聚合(Aggregate):是一组相关对象(实体 Entity、值对象 Value Object)的集合,这些对象围绕一个聚合根(Aggregate Root)组织在一起,作为一致性边界(consistency boundary)。聚合定义了事务一致性单元:在同一事务中应该保证聚合内部的一致性。

聚合根(Aggregate Root):聚合中的主实体,对外暴露操作和不变式(invariants)。外部对象只能通过聚合根访问或修改聚合内部的其它成员(实体、值对象)。聚合根负责维护聚合的不变性规则并发布领域事件等。

关键概念

  • 一致性边界(transactional boundary):对聚合的修改应该在一个事务内完成。
  • 导航约束:外部只能直接引用聚合根,不应该直接引用聚合内部的实体。
  • 最小化聚合:聚合应尽量小以减少锁争用和分布式一致性的复杂度。
  • 聚合内保持强一致性:聚合间通过领域事件/异步方式保持最终一致性。

设计原则

  • 仅将需要在同一事务中保持一致的对象放入同一聚合
  • 聚合应尽可能小(规则:如果你不得不在并发场景下频繁锁定/更新它,就考虑拆分)。
  • 聚合根是唯一对外的入口:外部只能通过聚合根的方法修改状态。
  • 禁止跨聚合直接引用内部实体:跨聚合引用应使用聚合根的 ID(标识符)。
  • 尽量以行为(行为导向)建模而非数据结构:聚合根提供业务语义方法( Order.AddItem(...)),而不是暴露一堆 setter。
  • 领域事件用于跨聚合副作用:若修改需要影响其它聚合,发布事件并异步处理。
  • 避免把读取模型也当成聚合:CQRS 可把读模型与写模型分离,写模型才使用聚合规则。

8.2订单聚合(Order)

订单聚合 = Order(聚合根)+ 订单明细 OrderItem + 收货地址 Address + 相关外部引用(BuyerId / PaymentId),形成一个完整的一致性边界。

Order 聚合(Order Aggregate)
│
├── 聚合根:Order(订单主实体)
│     ├── 值对象:Address(收货地址)
│     ├── 实体集合:List<OrderItem>(订单项)
│     ├── 外部引用:BuyerId(买家聚合)
│     └── 外部引用:PaymentId(支付方式聚合)

8.2.1聚合根(Order)

核心标识与状态

  • Entity:继承自 Entity,唯一标识订单和领域事件管理
  • IAggregateRoot:继承聚合根接口
  • OrderStatus:订单状态,控制订单生命周期(Submitted、AwaitingValidation、StockConfirmed、Paid、Shipped、Cancelled)
  • Description:订单状态或处理描述信息

image-20251108222617436

聚合内部字段

  • _orderItems:存储订单明细的私有集合(子实体)
  • Address:订单配送地址(值对象)
  • BuyerId / PaymentId:外部聚合引用,关联买家和支付方式
  • _isDraft:标记订单是否为草稿

image-20251108222649521

外部访问接口

  • OrderItems:只读集合属性,供外部读取订单明细信息
  • GetTotal():计算订单总金额

image-20251108222747276

核心行为方法

  • AddOrderItem(...):添加或更新订单明细(子实体),封装折扣和数量逻辑
  • SetPaymentMethodVerified(...):设置已验证的支付方式
  • SetAwaitingValidationStatus()SetStockConfirmedStatus()SetPaidStatus()SetShippedStatus()SetCancelledStatus():订单状态变更方法,触发对应领域事件
  • SetCancelledStatusWhenStockIsRejected(...):处理库存不足时的订单取消

image-20251108222809382

EF Core 映射关系

  • _orderItems 通过 OrderId 外键关联父订单
  • Address 使用 Owned Entity 方式持久化
  • 每个 Order 对应一个 PaymentMethod(单向导航)
  • 一个Order属于一个 Buyer(导航属性)
  • 聚合根控制生命周期,删除订单时可级联删除子订单明细

image-20251108223055312

设计意义

  • 聚合根控制一致性:所有内部状态变更必须通过聚合根方法完成,保证业务规则统一执行
  • 封装子实体和值对象:外部无法直接修改 _orderItemsAddress,保证聚合完整性
  • 行为优先于属性:操作通过方法执行,而不是直接修改字段
  • 领域事件触发:状态变更时触发领域事件,解耦其他聚合或系统模块
  • 聚合边界明确:Order 聚合包括订单明细、地址、状态和外部引用,形成完整一致性边界

8.2.2值对象(Address)

  • 字段与属性
    • 包含收货地址相关信息,例如街道(Street)、城市(City)、邮编(ZipCode)、省份(State)、国家(Country)等
    • 所有字段通常为只读({ get; private set; }),创建后不可修改

image-20251108222952879

  • 在聚合中的角色
    • 属于 Order 聚合根内部的一部分
    • 没有独立标识,生命周期依赖 Order
    • 外部无法单独存在或修改,只能通过聚合根改变(如修改订单地址)

image-20251108223010128

  • EF Core 映射关系
    • 使用 Owned Entity(值对象持久化方式)
    • 存储在 Order 表中或单独表中,但依赖 OrderId
    • 与订单一同保存或删除

image-20251108223114480

  • 设计意义
    • 封装不可变属性:地址数据本身不具有身份,保证值对象不可变性
    • 简化聚合一致性:地址与订单生命周期绑定,修改订单即修改地址,保证一致性
    • 行为最小化:值对象主要封装属性和简单验证(如邮编格式),不包含复杂业务逻辑
    • 增强可读性与可维护性:将地址抽象为值对象,业务层逻辑清晰、易于复用

8.2.3子实体(OrderItem)

  • _orderItems:订单聚合根内部维护的私有集合字段,存储所有订单明细对象,外部无法直接修改。
  • OrderItems:只读集合属性,暴露 _orderItems 的只读视图,供外部读取订单明细信息。
  • 聚合根方法 AddOrderItem(...):向订单添加或更新订单明细,包含业务逻辑:
    • 如果已有同一产品,更新折扣和数量;
    • 如果是新产品,创建新的 OrderItem 并加入集合。

image-20251108223216121

  • EF Core 映射关系:
    • OrderItem 表通过 OrderId 外键关联父订单;
    • 生命周期由聚合根控制,删除订单时可以级联删除子订单明细(可配置)。

image-20251108223234480

  • 设计意义:
    • 封装内部状态:防止外部直接修改订单明细,保证聚合完整性;
    • 行为优先于属性:订单明细操作通过聚合根方法完成,业务规则统一处理;
    • 保证业务一致性:订单明细与订单状态同步,避免逻辑不一致;
    • 聚合间解耦:Order 聚合根控制明细行为,外部不直接依赖明细实现。

Order 聚合根有一个订单项集合 _orderItems,但 OrderItem 实体中:没有 OrderId 外键字段、没有导航属性、没有 DataAnnotation 或 Fluent API 显式配置关系,它们是如何与 Order 关联的?为什么 EF Core 能加载它们?
OrderItemOrder 的关联依赖 EF Core 的 影子外键 OrderId + _orderItems 集合,实现了 DDD 聚合封装,同时在数据库层有完整外键关系,显式加载或 Include 都能正确查询关联数据。
订单仓储(OrderRepository)我们看过这个问题

image-20251108192424032

8.2.4外部聚合引用(Buyer)

  • BuyerId:订单对应买家的外键,可为空,表示订单可能尚未绑定买家。
  • Buyer:导航属性,表示订单所属的买家对象,可访问买家的基本信息(如姓名、联系方式等)。
  • EF Core 映射关系:
    • HasOne(o => o.Buyer):订单对买家是单向多对一关系。
    • WithMany():一个买家可以拥有多个订单。
    • HasForeignKey(o => o.BuyerId):外键列 BuyerId 将订单关联到买家。OrderPaymentId关联PaymentMethodId
  • 设计意义:订单聚合通过外键引用买家聚合而不直接操作其内部状态,保持聚合边界的独立性,并确保业务逻辑中买家信息可用。

image-20251108001028600

8.2.5外部聚合引用(PaymentMethod)

  • PaymentId:订单对应支付方式的外键,可为空,表示订单可能尚未绑定支付方式。
  • PaymentMethod:在聚合根中未声明直接导航属性,但通过外键在数据库中关联支付方式表。
  • EF Core 映射关系:
    • HasOne<PaymentMethod>():每个订单对应一个支付方式(单向导航)。
    • WithMany():一个支付方式可以被多个订单使用。
    • HasForeignKey(o => o.PaymentId):外键列 PaymentId 将订单关联到支付方式。OrderPaymentId关联PaymentMethodId
    • OnDelete(DeleteBehavior.Restrict):删除支付方式时禁止级联删除订单,保证历史订单数据完整性。
  • 设计意义:订单通过外键引用支付方式聚合,实现聚合间解耦,同时由聚合根方法控制支付绑定逻辑,保证订单状态与支付信息的一致性。

image-20251108001028600

8.3买家聚合(Buyer)

8.3.1聚合根(Buyer)

  • 聚合根职责
    • 表示系统中的买家实体。
    • 管理买家的支付方式及相关业务行为。
    • 保证聚合内部的一致性和封装性。

image-20251108223343253

  • 主要属性
    • IdentityGuid:买家唯一标识,必填,用于跨系统或前端唯一识别用户。
    • Name:买家姓名,可选。
    • _paymentMethods:私有字段,存储买家的支付方式集合,外部不能直接访问。
    • PaymentMethods:只读集合属性,暴露 _paymentMethods 的只读视图,外部只能读取,不能修改。

image-20251108223410651

  • 构造函数
    • 受保护无参构造函数 protected Buyer()
      • EF Core 或 DDD 内部使用,ORM 在加载实体时调用。
      • 初始化 _paymentMethods 集合,避免空引用。
    • 公共构造函数 public Buyer(string identity, string name)
      • 应用层或服务层创建买家聚合根的入口。
      • 保证必填字段 IdentityGuidName 不为空。
      • 通过 : this() 调用无参构造函数,复用集合初始化逻辑。

image-20251108223428695

  • 核心方法
    • VerifyOrAddPaymentMethod(...):验证或新增买家的支付方式。
      • 如果支付方式已存在(通过卡类型、卡号、过期时间判断),直接返回。
      • 如果支付方式不存在,创建新的 PaymentMethod 并加入 _paymentMethods
      • 触发领域事件 BuyerAndPaymentMethodVerifiedDomainEvent,通知系统支付方式已验证或新增。

image-20251108223444327

  • 设计意义
    • 封装内部状态:买家支付方式集合只能通过聚合根方法操作,外部无法直接修改。
    • 行为优先于属性:支付方式的添加和验证通过方法完成,确保业务逻辑一致。
    • 保证业务一致性:聚合根控制支付方式生命周期和验证流程。
    • 聚合间解耦:外部系统不依赖支付方式具体实现,聚合根负责内部管理。

image-20251108200406878

8.3.2子实体集合 (_paymentMethods)

  • 字段
    • _paymentMethodsBuyer 聚合根内部维护的私有集合字段,存储买家的所有支付方式对象。
    • PaymentMethods:只读集合属性,暴露 _paymentMethods 的只读视图,供外部读取,不允许直接修改。

image-20251108223536482

  • 聚合根方法操作
    • VerifyOrAddPaymentMethod(...)
      • 聚合根控制支付方式行为和验证逻辑。
      • 已存在支付方式
        • 通过卡类型、卡号和过期时间判断。
        • 如果存在,返回已有支付方式并触发领域事件。
      • 新增支付方式
        • 如果不存在,创建新的 PaymentMethod 并加入 _paymentMethods 集合。
        • 触发领域事件 BuyerAndPaymentMethodVerifiedDomainEvent

image-20251108223444327

  • EF Core 映射关系
    • PaymentMethod 表通过 BuyerId 外键关联父买家。
    • 生命周期由聚合根控制,删除 Buyer 时可以级联删除支付方式(可配置)。

image-20251108200406878

  • 设计意义
    • 封装内部状态:外部不能直接修改 _paymentMethods 集合,保证聚合完整性。
    • 行为优先于属性:支付方式的添加、验证通过聚合根方法完成,业务规则统一处理。
    • 保证业务一致性:所有支付方式的生命周期和验证流程由聚合根管理。
    • 聚合间解耦:外部系统不直接操作支付方式,实现聚合内部封装和职责单一。

8.4聚合的实现

订单聚合通过 Order 聚合根封装内部状态(订单明细 OrderItem、值对象 Address),控制聚合生命周期和业务行为,触发领域事件,保证内部一致性,同时通过外部引用(BuyerIdPaymentId)与其他聚合解耦。

上面写的很细了,这里只是总结

特性 实现方式 说明
聚合根 Order 控制内部状态,提供业务方法和事件触发
子实体 OrderItem 私有集合 _orderItems,生命周期受聚合根控制
值对象 Address 不可变对象,嵌入聚合根,属性相等判断
外部引用 BuyerIdPaymentId 只存 Id,不直接引用外部聚合对象
业务逻辑封装 聚合根方法 添加/修改明细、变更状态、支付验证等
事件机制 DomainEvent 聚合根触发领域事件,实现聚合间解耦
一致性保证 聚合内部 聚合根控制所有子实体和值对象的一致性,外部只能通过聚合根操作
生命周期管理 聚合根 聚合根负责创建、修改、删除子实体,保证聚合完整性
访问控制 私有字段/只读属性 外部无法直接修改聚合内部状态,只能通过聚合根方法操作
可扩展性 聚合根行为与事件 新增业务行为或事件可扩展功能而不破坏聚合边界
ORM映射 EF Core Owned/外键映射 子实体通过外键关联,值对象嵌入聚合根,实现数据库持久化

8.5总结

订单聚合(Order Aggregate):以 Order 为聚合根,封装订单明细(OrderItem)、配送地址(值对象 Address)以及外部聚合引用(BuyerIdPaymentId),负责管理订单生命周期、订单项的添加与更新、订单状态流转和支付方式验证,通过聚合根方法保证聚合内部的一致性和业务规则执行,并通过领域事件解耦系统其他模块。

买家聚合(Buyer Aggregate):以 Buyer 为聚合根,封装买家的支付方式(PaymentMethod)和核心身份信息(IdentityGuidName),负责验证或添加支付方式,保证支付信息一致性,所有修改必须通过聚合根方法执行,并通过领域事件通知订单或其他聚合,实现聚合间的解耦和一致性维护。

聚合 聚合根 子实体 值对象 外部引用 核心职责
订单聚合 Order OrderItem Address BuyerId / PaymentId 管理订单生命周期、明细、支付方式和状态流转,保证订单一致性
买家聚合 Buyer PaymentMethod 管理买家身份和支付方式,保证支付信息一致性,提供支付验证行为

9.领域事件

9.1基本概念

定义

领域事件是指在业务领域中发生的重要事件,它通常:

  • 已经发生(过去式)

  • 与业务逻辑相关

  • 对系统其他部分可能产生影响

特点

  • 不可变:一旦事件发生,事件内容不可更改。

  • 业务驱动:事件源于领域行为,不是技术或基础设施触发。

  • 解耦:事件消费者与事件发布者互相独立,通过事件通知完成逻辑协作。

  • 历史可追溯:记录事件可以追踪业务流程和状态变化。

作用

  • 解耦聚合:聚合之间不直接调用彼此方法,而是通过事件通信。例如:
    • Order 聚合支付成功后触发 OrderPaidDomainEvent
    • Buyer 聚合可以订阅事件进行其他业务操作。
  • 封装业务规则:聚合内部触发事件时,业务逻辑已经完成,事件是对行为的通知,而不是控制行为。
  • 支持异步处理:事件可以异步发布给其他处理器,避免聚合根直接依赖其他服务。
  • 历史记录与审计:保存领域事件可以重建聚合状态,适合 Event Sourcing(事件溯源)。

image-20251108000346008

先回忆下每个聚合根都是实体,所以都继承了实体基类Entity,实体基类Entity除了提供唯一标识,还有一个很重要的功能—领域事件管理:

  • DomainEvents:只读访问领域事件集合

  • AddDomainEvent:添加领域事件

  • RemoveDomainEvent:移除已添加的领域事件

  • ClearDomainEvents:清空所有领域事件

image-20251108225323967

9.2订单聚合根中领域事件(Order)

9.2.1订单状态为等待验证(OrderStatusChangedToAwaitingValidationDomainEvent)

当订单为 已提交(Submitted) 时,SetAwaitingValidationStatus() 会将一个包含订单 Id 与订单明细快照的 OrderStatusChangedToAwaitingValidationDomainEvent 领域事件添加到聚合的领域事件列表,然后将订单状态设置为等待验证(AwaitingValidation),以在受控的聚合内完成合法的状态转换并通知下游(如商品库存/校验)异步处理后续逻辑,保证不变式与最终一致性。

image-20251108230229339

OrderStatusChangedToAwaitingValidationDomainEvent 是一个用于表示订单从“已提交”成功进入“待验证”阶段的领域事件,它携带订单 ID 与订单明细快照,用于通知订单、校验等下游服务执行异步验证逻辑,从而实现聚合间的解耦与业务一致性的最终保障。

OrderStatusChangedToAwaitingValidationDomainEvent 继承了INotification,我们之前学习过MediatR,这个接口用于发布/订阅,最后在一起看一下这个接口

image-20251108230418095

9.2.2订单状态为库存已确认(OrderStatusChangedToStockConfirmedDomainEvent)

当订单状态为等待验证(AwaitingValidation) 时,SetStockConfirmedStatus() 会发布包含订单 Id 的 OrderStatusChangedToStockConfirmedDomainEvent 领域事件,并将状态更新为库存已确认(StockConfirmed),表示所有订单项库存已确认可用,从而驱动后续发货或支付流程。

image-20251108231145577

OrderStatusChangedToStockConfirmedDomainEvent 是在订单所有商品库存确认可用后发布的领域事件,用于通知下游服务(如支付或发货)订单已通过库存校验,可继续后续业务流程。

OrderStatusChangedToStockConfirmedDomainEvent 继承了INotification

image-20251108231333201

9.2.3订单状态为已支付(OrderStatusChangedToPaidDomainEvent)

当订单状态为库存已确认(StockConfirmed)时,SetPaidStatus() 会发布包含订单 Id 与订单明细的 OrderStatusChangedToPaidDomainEvent 领域事件,并将订单状态更新为已支付(Paid),表示支付已完成,可触发发货或其他后续业务。

image-20251108231438681

OrderStatusChangedToPaidDomainEvent 是一个领域事件类,用于在订单状态变更为“已支付(Paid)”时发布。它包含订单的唯一标识 OrderId 和订单明细集合快照 OrderItems,用于向下游系统或服务传递支付完成的事实。该事件通常触发后续操作,如库存最终扣减、发货安排、会计处理或对账流程,同时通过事件机制实现聚合间解耦和业务流程异步驱动,确保支付完成后的订单状态一致性和业务完整性。

image-20251108232608879

9.2.4订单状态为已发货(OrderShippedDomainEvent)

当订单状态为已支付(Paid)时,SetShippedStatus() 会发布包含整个订单对象的 OrderShippedDomainEvent 领域事件,并将订单状态更新为已发货(Shipped),表示订单已进入物流配送阶段;若状态非法则抛出异常防止错误操作。

image-20251108232236883

OrderShippedDomainEvent 是一个领域事件类,用于在订单进入发货流程或完成发货时发布。它封装了已发货的 Order 聚合根实例,事件处理器可以通过该事件获取订单 ID、收货地址、订单明细等关键信息,从而触发下游业务逻辑,如物流处理、用户通知(邮件/短信)、审计或日志记录等。此事件实现了 INotification 接口,支持在应用中通过 MediatR 等机制进行异步发布和订阅,实现聚合间解耦并保证业务流程的连续性与一致性。

image-20251108232805052

9.2.5订单状态为已取消(OrderCancelledDomainEvent)

当订单未支付且未发货时,SetCancelledStatus() 会发布包含整个订单对象的 OrderCancelledDomainEvent 领域事件,并将订单状态更新为已取消(Cancelled),表示订单被用户或系统取消;若订单已支付或已发货则抛出异常。

image-20251108232327565

OrderCancelledDomainEvent 是一个领域事件类,用于在订单被取消(Order 状态变为 Cancelled)时发布。它封装了被取消的 Order 聚合根实例,事件处理器可以读取订单信息(如订单 ID、明细、买家信息等)以执行后续业务操作,例如释放库存、触发退款流程或发送取消通知等。此事件实现了 INotification 接口,支持异步发布与订阅,遵循领域事件模式,强调事件仅作为“事实”的载体,不直接修改聚合状态,从而实现聚合间解耦并保证业务一致性。

image-20251108233023962

9.2.6创建订单(OrderStartedDomainEvent)

在创建订单时,AddOrderStartedDomainEvent() 会发布包含订单对象、用户信息及支付信息的 OrderStartedDomainEvent 领域事件,表示订单已生成,可触发库存检查、支付准备及其他后续流程,实现聚合根对业务流程的控制与解耦。

image-20251108232513571

OrderStartedDomainEvent 是一个领域事件类(以 record 定义,天然不可变),用于在订单创建(下单)时发布。它封装了触发事件的 Order 聚合根实例,以及发起下单的用户信息(UserIdUserName)和支付信息(卡类型、卡号、CVV、持卡人姓名、过期时间),用于支持跨聚合或外部系统的后续处理。发布该事件通常用于触发订单验证、库存检查、支付授权、发送确认通知等后置业务流程,同时通过事件机制实现聚合间解耦和业务流程的异步驱动,确保订单创建的完整性与一致性。

record class

C# 9.0 引入的一种特殊类型,用来创建 不可变(immutable)且结构化的对象,用来简洁地定义“只承载数据”的对象。

在 DDD 中,它非常适合表示 领域事件(Domain Event)值对象(Value Object)

概念 解释 举例
不可变(Immutable) 创建后不能修改 record 对象的属性是只读的
结构化(Structured) 有清晰字段和业务含义 UserRecord(Name, Age) 代表完整的用户信息
// 普通类
public class UserClass
{
public string Name { get; set; }
public int Age { get; set; }public UserClass(string name, int age)
{Name = name;Age = age;
}public override string ToString() => $"Name: {Name}, Age: {Age}";
}// record 类(不可变)
public record class UserRecord(string Name, int Age);// 不可变
var user = new UserRecord("Alice", 18);
// user.Age = 20; //编译错误,属性是只读的// 如果要“修改”,要创建一个新对象
var newUser = user with { Age = 20 }; 

image-20251108233135047

9.3买家聚合根中领域事件(Buyer)

9.3.1添加和验证买家支付方式(BuyerAndPaymentMethodVerifiedDomainEvent)

VerifyOrAddPaymentMethod 方法在 Buyer 聚合根中负责验证是否存在指定支付方式,如存在则返回并触发 BuyerAndPaymentMethodVerifiedDomainEvent,如不存在则创建新支付方式加入聚合然后也发布BuyerAndPaymentMethodVerifiedDomainEvent事件,以封装业务逻辑并通知下游系统处理订单支付相关流程。

image-20251108233559546

BuyerAndPaymentMethodVerifiedDomainEvent 是当买家的支付方式验证通过(已存在)或新增成功时发布的领域事件,携带买家、支付方式摘要及关联订单 Id,用于通知下游服务继续订单处理,同时保持聚合边界和业务一致性。

image-20251108234319838

9.4发布领域事件(MediatR/Publish)

IUnitOfWork 在 DDD 中的核心职责是:在一次业务操作中协调多个仓储的持久化操作,确保领域模型的一致性,并在事务提交后触发领域事件或集成事件,实现数据与事件的一致提交。

eshop的工作单元(IUnitOfWork)提供了2个接口,由OrderingContext继承实现:

  • SaveChangesAsync:由OrderingContext继承的数据库上下文DbContext隐式实现。用来保存实体。
  • SaveEntitiesAsync:发布领域事件后调用SaveChangesAsync保存实体。

image-20251109183103260

eshop中,OrderingContext 作为工作单元实现类, SaveEntitiesAsync() 先分发领域事件再调用 SaveChangesAsync() 持久化实体,从而确保聚合内外状态更新与事件副作用在同一事务中保持一致。`

image-20251109183824431

发布领域事件方法具体实现(DispatchDomainEventsAsync):

  • 获取OrderingContext中所有继承Entity的实体
    • Entity是实体基类,主要功能提供实体的唯一标识和领域事件管理(领域事件集合、添加领域事件、删除领域事件、清空领域事件集合)
  • 获取每个实体的领域事件集合(DomainEvents)收集到一个列表中
  • 每个继承Entity的实体调用ClearDomainEvents清空集合
  • 遍历所有领域事件集合调用mediator.Publish发布领域事件(所有的领域事件都继承了INotification

image-20251109184007762

OrderingContext.SaveEntitiesAsync 中,领域事件首先被发布,然后实体的状态被持久化。而且OrderingContext.SaveEntitiesAsync都是命令处理器(Handler)调用的。命令处理器通过 管道事务(TransactionBehavior 保证:事件的发布与实体的保存作为同一事务的一部分提交,从而确保数据与事件的一致性。先发布事件可以保证事件反映最新业务状态,同时配合事务机制避免外部处理器读取到不一致的数据。

image-20251109185002762

这里再回忆下商品服务如何处理数据变更和事件日志事务保存`?(这个和领域事件无关,只是想到了,觉得也挺重要的)

  • 使用 EF Core 的执行策略 (ResilientTransaction): 在数据库操作中启用了 EF Core 提供的弹性事务执行策略ResilientTransaction),用于在出现临时性错误(如网络抖动、连接超时、瞬时数据库不可用)时自动重试,从而提高系统的事务提交可靠性容错性
  • 数据变更与事件日志共享同一 CatalogContext 实例:在执行数据持久化与记录集成事件日志时,这两部分操作使用同一个 EF Core 上下文实例(即同一数据库事务范围)。这样可确保领域数据的变更与事件日志的写入要么同时成功,要么同时回滚
    实现业务数据与事件消息的事务一致性(原子性)

感觉跟这里很相似,可能这个技术点可能也用得上

image-20251109190134670

9.5订阅领域事件(INotification/INotificationHandler)

9.5.1领域事件处理器(INotificationHandler)

在领域层 (Ordering.Domain) 中,所有领域事件都实现了 INotification 接口。
这意味着每个领域事件都是 可发布的通知,可以通过 MediatR 的管道分发。 当事件发布时,系统会根据事件类型查找所有实现了 INotificationHandler<TEvent> 的处理器(订阅者),并依次调用它们,从而实现事件驱动的异步处理和解耦逻辑

在本项目中,领域层的所有领域事件除了实现 INotification 接口外,其命名均遵循以 DomainEvent 结尾的规范。通过这种命名规则可以快速识别一个类型是否为领域事件。
注意:领域事件本身没有单独的抽象类或接口封装,完全依赖 INotification 接口和命名约定来标识其语义。

image-20251109191309878

全局搜索一下INotificationHandler,发现所有事件处理器都在接口层/表现层(Ordering.API)

领域事件处理器必须实现 INotificationHandler<TEvent> 接口,其中 TEvent 为对应的领域事件类型。
此外,处理器的命名均遵循以 DomainEventHandler 结尾的规范,以便快速识别它是处理领域事件的组件。

同样地,领域事件处理器没有单独的抽象类或接口封装,完全依赖命名规则与接口约束实现事件订阅与处理。

之前领域事件看完了,然后就是对应的事件处理器

image-20251109205030932

9.5.2高性能结构化日志(OrderingApiTrace)

领域事件处理器中统一封装了日志OrderingApiTrace

  • internal:表示该类只能在当前程序集内使用,外部程序集无法访问。
  • static:该类不允许实例化,只包含静态成员。
  • partial:允许类定义拆分在多个文件中,便于日志方法集中管理和扩展。
  • 作用:集中定义 订单微服务 的日志记录方法,尤其是高频调用的 结构化日志,目的是 提升性能和统一日志规范
internal static partial class OrderingApiTrace

image-20251110201654622

9.5.2.1预编译日志消息机制(LoggerMessage)

LoggerMessageMicrosoft.Extensions.Logging 命名空间下提供的一种机制,它允许你 预定义日志模板和委托,避免每次记录日志时都进行字符串拼接或格式化,从而提高性能。

LoggerMessage 是 .NET 5+ 提供的 源生成特性,特点:

属性 含义
EventId 日志事件唯一 Id,便于监控和聚合
EventName 日志事件名称
Level 日志级别(Trace/Debug/Information/Warning/Error/Critical)
Message 日志模板,使用 {PropertyName} 占位符,支持结构化日志

image-20251110202242891

LoggerMessage 就是 .NET 提供的一种 预编译日志消息机制,用来提升性能、支持结构化日志,并可统一管理日志事件。

“预编译日志消息机制” 就是指在 编译时(不是运行时),.NET 会生成高性能的日志代码
避免在运行时做字符串拼接、模板解析等耗时操作。

优点 说明
高性能 避免每次记录日志都进行字符串模板解析
结构化日志 支持 {ParamName} 字段查询
事件 ID 管理 可以统一管理日志事件,方便监控和分析
编译时生成 Source generator 特性方式让代码更简洁、易维护

日志代码已被提前生成

image-20251110204820484

9.5.2.2记录订单状态更新操作(LogOrderStatusUpdated)

当订单状态(OrderStatus)发生变化时,使用 logger 记录订单 Id(orderId)及新状态的结构化日志,便于跟踪订单生命周期和状态流转:

  • ILogger logger:日志实例。
  • int orderId:订单编号。
  • OrderStatus status:新的订单状态。
[LoggerMessage(EventId = 1, EventName = "OrderStatusUpdated", Level = LogLevel.Trace, Message = "Order with Id: {OrderId} has been successfully updated to status {Status}")]
public static partial void LogOrderStatusUpdated(ILogger logger, int orderId, OrderStatus status);

image-20251110202032528

查看自动生成后的LogOrderStatusUpdated方法:

  • 1.检查日志级别
  • 2.线程局部状态对象(ThreadLocalState)存储日志参数,保证线程安全和性能
  • 3.state.ReserveTagSpace(3)为日志占用空间(3个字段)
  • 4.调用 ILogger.Log
    • LogLevel.Trace:日志级别。
    • new(1, "OrderStatusUpdated"):EventId 和 EventName。
    • state:存放日志参数。
    • null:异常(这里没有)。
    • static string(s, _) => ...:格式化函数,将参数渲染为文本。
  • 5.state.Clear()清空 TagArray,为下一次日志复用。避免对象频繁创建,提升性能。

image-20251110204016268

9.5.2.3记录订单支付方式更新操作(LogOrderPaymentMethodUpdated)

当订单支付方式被添加或更新时,使用 logger 记录订单 Id(orderId)、支付方式名称(paymentMethod)及支付方式 Id(id)的结构化日志,便于审计支付方式变更历史:

  • orderId:订单编号。
  • paymentMethod:支付方式名称,如 VisaMasterCard
  • id:支付方式的唯一编号。

image-20251110203620180

查看自动生成后的LogOrderPaymentMethodUpdated方法:

  • 1.检查日志级别。
  • 2.线程局部状态对象(ThreadLocalState)存储日志参数,保证线程安全和性能
  • 3.使用 state.ReserveTagSpace(4),为日志占用空间(4个字段)因为有 4 个字段:
    • 原始模板 {OriginalFormat}
    • OrderId
    • PaymentMethod
    • Id
  • 4.调用 logger.Log,使用 静态委托生成文本
  • 5.清空state,避免对象频繁创建,提升性能。

image-20251110204907296

9.5.2.4记录买家信息及支付方式验证或更新(LogOrderBuyerAndPaymentValidatedOrUpdated)

在订单创建或支付方式更新后验证或更新买家信息时,使用 logger 记录买家 Id(buyerId)和订单 Id(orderId)的结构化日志,便于追踪买家与订单支付方式的关联操作:

  • buyerId:买家编号。
  • orderId:关联订单编号。

image-20251110203631510

生成模版都是一致的

查看自动生成后的LogOrderBuyerAndPaymentValidatedOrUpdated方法:

  • 1.检查日志级别。
  • 2.线程局部状态对象(ThreadLocalState)存储日志参数,保证线程安全和性能
  • 3.使用 state.ReserveTagSpace(3)为日志占用空间(3个字段)
  • 4.调用 logger.Log,使用 静态委托生成文本
  • 5.清空state,避免对象频繁创建,提升性能。

image-20251110205559276

9.5.2.5总结

这套日志体系分为 两部分代码

部分 文件来源 作用 特点
手写类:OrderingApiTrace 开发者编写 定义日志模板与结构化字段 无方法体,仅声明
生成类(auto-generated) 编译器自动生成 实现真正的日志输出逻辑 Microsoft.Gen.Logging 源生成器生成

预编译消息机制[LoggerMessage]

  • EventId = 1:日志编号
  • EventName = "OrderStatusUpdated":事件名称
  • Message:日志模板(支持结构化字段 {OrderId}{Status}

编译生成的日志类:

  • 1.判断日志级别
  • 2.获取线程本地缓存对象(ThreadLocalState)
  • 3.使用 state.ReserveTagSpace(3)为日志占用空间
  • 4.调用 logger.Log() 输出日志
  • 5.清理缓存

9.5.3订单取消领域事件处理器(OrderCancelledDomainEventHandler)

OrderCancelledDomainEventHandler 是一个领域事件处理器,当 OrderCancelledDomainEvent 发生时,它负责记录日志、读取订单与买家信息、构建并持久化“订单已取消”的集成事件,从而通知其他微服务做后续一致性动作。

image-20251110212126325

这段 做了三件事:记录日志 → 读取订单与买家 → 持久化集成事件

image-20251110212215563

回忆下事务管道(TransactionBehavior):命令处理与集成事件的记录在同一事务中执行。事务成功提交后,事件调度器(IOrderingIntegrationEventService)从集成事件日志表中读取待发布事件,并将其推送至 RabbitMQ,实现跨服务的一致性消息发布。

image-20251110212357340

9.5.4订单已发货领域事件处理器(OrderShippedDomainEventHandler)

OrderShippedDomainEventHandler 在订单进入 已发货 (Shipped) 状态时触发:记录日志 → 读取订单与买家信息 → 构建“订单已发货”集成事件并持久化(Outbox),由后台异步发布到消息总线(RabbitMQ)。

image-20251110212858966

订单已发货事件处理器(OrderShippedDomainEventHandler.Handle):

  • 1.记录订单状态变更日志
  • 2.通过订单仓储和买家仓储获取订单及买家信息
  • 3.构建“订单已发货”集成事件并持久化到事件日志表(Outbox)
  • 4.事务管道(TransactionBehavior)在提交命令事务时同时保存业务数据和事件日志,随后统一读取事件日志表,发布待发送的集成事件

image-20251110213041042

9.5.5订单等待验证领域事件处理器(OrderStatusChangedToAwaitingValidationDomainEventHandler)

当订单进入等待验证(AwaitingValidation )状态时,处理器记录日志 → 读取订单与买家 → 根据订单项构建库存清单 → 将“订单等待验证”集成事件写入事件表(Outbox),由事务管道/后台投递器统一发布。

image-20251110213903246

订单等待验证事件处理器(OrderStatusChangedToAwaitingValidationDomainEventHandler.Handle):

  • 1.记录订单状态日志
  • 2.通过仓储查询订单和买家信息
  • 3.构建库存列表:检查每个订单项的库存是否足够。(订单等待验证和订单已支付都需要库存,但是职责不同)
  • 4.构建订单等待验证集成事件并持久化到事件日志表(Outbox)
  • 5.事务管道(TransactionBehavior)在事务保存业务数据和集成事件后,从事件日志表(Outbox)读取状态为未发布的集成事件发布到RabbitMQ

image-20251110214424762

9.5.6订单已支付领域事件处理器(OrderStatusChangedToPaidDomainEventHandler)

当订单状态变为 Paid(已支付) 时:记录日志 → 读取订单与买家信息 → 构建订单库存明细 → 将“订单已支付”集成事件写入事件表(Outbox),由后台统一发布到消息总线,驱动下游服务(库存、通知、发货等)。

image-20251110214638474

订单已支付事件处理器(OrderStatusChangedToPaidDomainEventHandler.Handle):

  • 1.记录订单状态日志
  • 2.使用仓储查询订单和买家信息
  • 3.构建库存列表:通知库存服务扣减库存和发货。(订单等待验证和订单已支付都需要库存,但是职责不同)
  • 4.构建订单已支付集成事件并持久化到事件日志表(Outbox)
  • 5.事务管道(TransactionBehavior)在事务保存业务数据和集成事件后,从事件日志表(Outbox)读取状态为未发布的集成事件发布到RabbitMQ

image-20251110214656529

9.5.7订单库存已确认领域事件处理器(OrderStatusChangedToStockConfirmedDomainEventHandler)

该处理器在订单库存确认后触发:记录日志、读取订单与买家信息,并将“订单已确认库存”作为集成事件写入(通常为 outbox),供其他微服务异步消费。

image-20251110215642267

订单库存已确认事件处理器(OrderStatusChangedToStockConfirmedDomainEventHandler):

  • 1.记录订单状态日志
  • 2.通过仓储获取订单和买家信息
  • 3.构建订单库存已确认集成事件并持久化到事件日志表(Outbox)
  • 4.事务管道(TransactionBehavior)在事务保存业务数据和集成事件后,从事件日志表(Outbox)读取状态为未发布的集成事件发布到RabbitMQ

“等待验证”状态就是触发库存验证的阶段,库存服务处理完毕后才将订单状态更新为“库存已确认”,所以验证发生在等待验证阶段,而不是库存已确认阶段。

image-20251110220522505

9.5.8订单买家或者支付方式变更领域事件处理器(UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler)

``UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler` 是一个领域事件处理器,用于在买家及其支付方式被创建或验证后,将订单聚合根的 BuyerId 和 PaymentMethodId 更新为对应外键,并记录高性能结构化日志,实现跨聚合副作用的安全、幂等处理。

DDD(领域驱动设计) 或软件架构里,“副作用”(Side Effect) 指的是一个操作除了产生主要结果之外,还会对系统的其他部分产生额外的影响或变化

image-20251110220545658

买家和支付方式创建或验证领域事件处理器(UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.Handle):

  • 1.仓储模式查询订单
  • 2.更新订单买家Id和支付方式Id
  • 3.记录订单支付方式变更日志

image-20251110220951473

为什么这里没有持久化保存(只调用了领域方式修改订单的支付方式)?

ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler中验证了支付方式
image-20251110223108966

在验证过程中发布了领域事件,并返回了PaymentMethod,可能会对PaymentMethod进行修改
image-20251110223127884

ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler已经对买家和支付方式的修改进行保存

image-20251110230201227

UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler订阅了BuyerAndPaymentMethodVerifiedDomainEvent领域事件,对订单的BuyerId、PaymentId进行了修改,但是没有保存,我觉得这是一个bug
这个问题我们后面验证下,这里没有提交保存:INotificationHandler<BuyerAndPaymentMethodVerifiedDomainEvent>领域事件处理器是发布/订阅模式,不会经过MediatR配置的事务管道,就不会持久化到数据库

image-20251110230427573

9.5.9订单绑定买家和支付方式领域事件(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler)

当订单创建时它会验证或新增 Buyer 聚合、为买家添加/验证支付方式、保存买家变更、持久化一个用于通知其它微服务的集成事件,并记录日志。

image-20251110231546127

订单绑定买家和支付方式领域事件(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler)

  • 1.确定支付卡类型
  • 2.查询买家,不存在,则创建
  • 3.创建或验证买家支付方式
  • 4.买家和支付方式持久化到数据库
  • 5.构建订单已提交领域事件
  • 6.记录订单和买家日志

image-20251110231730148

9.5.10总结

提交订单 / 验证买家

  • 领域事件:OrderStartedDomainEvent : INotification
  • 领域事件处理器:ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler : INotificationHandler<OrderStartedDomainEvent>
  • 业务逻辑:检查买家是否存在,不存在则创建新的买家聚合;验证或添加支付方式;保存买家信息
  • 订单已提交集成事件:OrderStatusChangedToSubmittedIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid)

等待验证

  • 领域事件:OrderStatusChangedToAwaitingValidationDomainEvent : INotification
  • 领域事件处理器:OrderStatusChangedToAwaitingValidationDomainEventHandler : INotificationHandler<OrderStatusChangedToAwaitingValidationDomainEvent>
  • 业务逻辑:记录日志、查询订单与买家信息、构建库存列表
  • 订单等待验证集成事件:OrderStatusChangedToAwaitingValidationIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid, orderStockList)

库存已确认

  • 领域事件:OrderStatusChangedToStockConfirmedDomainEvent : INotification
  • 领域事件处理器:OrderStatusChangedToStockConfirmedDomainEventHandler: INotificationHandler<OrderStatusChangedToStockConfirmedDomainEvent>
  • 业务逻辑:记录日志、查询订单与买家信息
  • 库存已确认集成事件:OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid)

订单已支付

  • 领域事件:OrderStatusChangedToPaidDomainEvent : INotification
  • 领域事件处理器:OrderStatusChangedToPaidDomainEventHandler : INotificationHandler<OrderStatusChangedToPaidDomainEvent>
  • 业务逻辑:记录日志、查询订单与买家信息、构建订单库存列表
  • 订单已支付集成事件:OrderStatusChangedToPaidIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid, orderStockList)

订单已发货

  • 领域事件:OrderShippedDomainEvent : INotification
  • 领域事件处理器:OrderShippedDomainEventHandler : INotificationHandler<OrderShippedDomainEvent>
  • 业务逻辑:记录日志、查询订单与买家信息
  • 订单已发货集成事件:OrderStatusChangedToShippedIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid)

订单取消

  • 领域事件:OrderCancelledDomainEvent: INotification
  • 领域事件处理器:OrderCancelledDomainEventHandler : INotificationHandler<OrderCancelledDomainEvent>
  • 业务逻辑:负责记录日志、读取订单与买家信息、构建并持久化“订单已取消”的集成事件,从而通知其他微服务做后续一致性动作。
  • 集成事件:订单已取消集成事件(OrderStatusChangedToCancelledIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid))

订单买家或者支付方式变更

  • 领域事件:BuyerAndPaymentMethodVerifiedDomainEvent : INotification
  • 领域事件处理器:UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler: INotificationHandler<BuyerAndPaymentMethodVerifiedDomainEvent>
  • 业务逻辑:用于在买家及其支付方式被创建或验证后,将订单聚合根的 BuyerId 和 PaymentMethodId 更新为对应外键
  • 集成事件:无

订单服务的领域事件其实都是围绕订单的生命周器,并且每个订单周期也发布了集成事件:提交订单→等待验证→库存已确认→已支付→已发货→已取消

其实领域事件比较简单,只是我看的稍微细点。领域事件继承INotification,领域事件处理器继承INotificationHandler,通过MediatR调用Publish发布即可

10.领域服务

10.1基本概念

定义:领域服务是 DDD 中的一个概念,用来封装不属于某个实体或聚合根的业务逻辑,特别是跨聚合的业务逻辑或无法归属单一实体的操作。

  • 他是 纯业务逻辑,不持久化状态(不存数据库)。
  • 他操作实体和聚合,但本身不是实体。
  • 通常用于复杂业务操作,或者跨多个聚合的规则实现。

核心特征

特征 说明
无状态 领域服务不保存业务数据,它只提供逻辑方法。
聚焦业务规则 实现复杂业务逻辑,而非数据访问。
跨聚合操作 处理需要多个聚合参与的业务场景。
可被实体/应用层调用 实体自身业务逻辑属于自身,跨聚合或复杂操作用服务封装,应用层可直接调用。

领域服务的典型职责

  • 跨聚合业务逻辑:例如订单需要验证库存和支付方式,但库存属于产品聚合,支付方式属于买家聚合。实体自身无法访问其他聚合,领域服务就是桥梁。

  • 复杂算法或规则:比如价格计算、折扣计算、信用评估、积分计算等。

  • 无法归属单一实体的操作:有些业务操作涉及多个实体或聚合,没有自然的“归属”对象。

领域服务 vs 实体 vs 应用服务

类型 作用 数据持有 调用场景
实体 聚合内部一致性和规则 内部状态 聚合根方法内
领域服务 跨聚合逻辑,复杂业务规则 无状态 实体内部或应用层调用
应用服务 协调多个聚合和领域服务,实现业务流程 无状态 应用层(调用领域对象和服务,处理事务/事件)

在 eShop 的订单服务中为什么没有使用领域服务(Domain Service)?

eShop 中大部分业务逻辑都被聚合根自身封装,跨聚合操作由事件驱动异步处理(领域事件),因此没有大量使用领域服务,既保持了聚合内一致性,又简化了系统设计。

对比维度 领域事件 领域服务
耦合性 低耦合 高耦合
执行方式 被动响应 / 异步 主动调用 / 同步
适用场景 跨聚合通知、微服务事件驱动 跨聚合业务逻辑、复杂操作封装
优势 解耦、异步、可追踪 封装复杂逻辑、可复用、可控
劣势 调试难、需处理幂等性、最终一致性 容易耦合、同步调用难异步化、历史追踪需额外设计

10.2实例

场景:用户下单时,需要验证库存、支付方式和优惠券等信息。

问题:订单聚合根不能直接访问库存(属于商品聚合)、支付方式(属于买家聚合)和优惠券(属于营销聚合)。

领域服务:跨聚合业务逻辑封装

eShop 中没有显式使用领域服务。为了理解领域服务的概念,可以参考示例:领域服务是一个独立的类,通常注入多个仓储或其他依赖,用于封装跨聚合的复杂业务逻辑。应用层通过调用领域服务来协调不同聚合间的操作,同时保持聚合本身的边界和一致性规则。

public interface IOrderValidationService
{Task<bool> ValidateOrderAsync(Order order, Buyer buyer, CancellationToken cancellationToken);
}public class OrderValidationService : IOrderValidationService
{private readonly IProductRepository _productRepository;public OrderValidationService(IProductRepository productRepository){_productRepository = productRepository;}public async Task<bool> ValidateOrderAsync(Order order, Buyer buyer, CancellationToken cancellationToken){// 验证库存foreach (var item in order.OrderItems){if (await _productRepository.GetStockAsync(item.ProductId) < item.Units)return false;}// 验证买家支付方式if (!buyer.HasValidPaymentMethod(order.PaymentMethodId))return false;return true;}
}

11.仓储接口

11.1基本概念

在 DDD(领域驱动设计)中,仓储(Repository)接口是领域层的重要组成部分,它定义了聚合根的持久化接口,而不关心具体的数据库实现。

仓储接口的作用

  • 抽象持久化操作:仓储接口为聚合根提供统一的访问入口,屏蔽数据存储细节(数据库、内存、文件等),领域层只关心聚合的状态和业务逻辑,不关心如何存取数据。
  • 聚合边界保护:仓储只能针对 聚合根 提供访问和操作,聚合内部的子实体或值对象只能通过聚合根来访问。这样保证了聚合的一致性。
  • 提供查询和持久化方法
    • GetByIdAsyncFindAsync:根据 ID 查询聚合根。
    • Add:添加新的聚合根。
    • Update(可选):更新聚合根状态。
    • Remove(可选):删除聚合根。
    • UnitOfWork 属性:提供事务提交的能力(在 eShop 中用于确保操作原子性)。

总结

  • 仓储接口属于领域层,是领域模型与持久化技术之间的桥梁。
  • 只操作聚合根,保护聚合边界。
  • 定义方法:查询、添加、更新(删除可选),以及提供事务管理(UnitOfWork)。
  • 实现细节在基础设施层,领域层只依赖接口,不耦合具体存储技术。
  • 应用场景:应用层、领域事件处理器、领域服务都可以通过仓储接口访问聚合。

11.2泛型仓储接口IRepository<T>

接口作用:抽象仓储,用于操作聚合根。屏蔽数据访问细节,领域层只关心聚合根,不依赖具体 ORM 或数据库。

泛型约束where T : IAggregateRoot保证仓储只能操作聚合根类型,符合 DDD 聚合边界原则。

UnitOfWork 属性

  • 提供工作单元,用于管理事务。
  • 支持跨聚合根的原子操作。
  • 避免领域层直接处理事务,保持领域模型纯净。

总结

  • IRepository<T> 是 DDD 中 领域层操作聚合根的核心接口
  • 它与 UnitOfWork 配合,保证 聚合变更的一致性和事务原子性
  • 仓储不直接操作数据库,也不负责业务逻辑,只提供 访问聚合和事务能力
  • 应用层通过仓储接口操作聚合根,领域层保持纯粹性,不依赖外部实现。

image-20251111002903117

eshopIUnitOfWork并没有提供事务管理,只提供了2个方法:

  • SaveChangesAsync:保存实体。OrderingContext隐式实现(DbContext实现的)。
  • SaveEntitiesAsync:发布领域事件后保存实体。

image-20251111003659055

OrderingContext提供了事务管理,但继承了IUnitOfWork

image-20251111003959860

11.3买家仓储(IBuyerRepository)

领域层只提供接口IBuyerRepositoryIBuyerRepository继承IRepository<Buyer>,基础设施层(BuyerRepository)负责实现:

  • 添加买家(Add)
  • 更新买家(Update)
  • 根据IdentityGuid查找买家(FindAsync)
  • 根据Id查找买家(FindByIdAsync)

image-20251111003426912

11.4订单仓储(IOrderRepository)

领域层只提供接口IOrderRepositoryIOrderRepository继承IRepository<Order>,基础设施层(OrderRepository)负责实现:

  • 添加订单(Add)
  • 更新订单(Update)
  • 根据Id获取订单(GetAsync)

image-20251111004538769

11.5总结

eShop 仓储接口的核心思想

  • 抽象聚合根访问:屏蔽数据库,实现聚合根的持久化操作。

  • 遵守聚合边界:内部子实体只能通过聚合根操作。

  • 支持工作单元:保证跨聚合操作一致性。

  • 易于测试与替换:接口与实现分离,便于 Mock 或替换底层存储。

  • 聚合专用接口:封装统一聚合接口 IRepository<T>IOrderRepositoryIBuyerRepository继承 IRepository<T>,提供聚合特定查询和操作。

12.工厂

12.1基本概念

定义:工厂是一种 专门负责创建复杂对象或聚合根实例的领域服务

目的

  • 将创建对象的逻辑从实体或应用层剥离出来。
  • 尤其适用于聚合根或实体的创建过程复杂,需要初始化多个属性或子实体时。

工厂的职责

  • 创建聚合根或实体:保证创建对象的完整性和合法性。例如:OrderFactory 创建订单时自动生成 OrderId、设置默认状态、初始化子项集合。
  • 封装复杂初始化逻辑:如生成唯一标识、设置默认值、计算初始价格等。
  • 可能依赖外部服务:在工厂中可以注入一些必要的服务,如 ID 生成器、价格策略等。
  • 保持聚合根构造函数简单:构造函数只做基本赋值,复杂初始化由工厂完成。

工厂的类型

类型 描述 示例
聚合工厂(Aggregate Factory) 创建整个聚合根及其子实体 OrderFactory.CreateOrder(userId, items)
实体工厂(Entity Factory) 创建单个实体对象 PaymentMethodFactory.CreatePaymentMethod(cardInfo)
值对象工厂(Value Object Factory) 创建值对象(可选) AddressFactory.CreateAddress(street, city)

12.2实例

Order聚合根中使用静态方法创建了Order对象,这是聚合内部的静态工厂方法

image-20251111011702095

eShop 中没有太多使用工厂的地方,简单学习2个实例

Order静态工厂方法

image-20251111011845354

Buyer静态工厂方法

image-20251111012027040

13.总结

领域层确实不太好理解,这么长时间总算肝完了,总结的话看看领域层的组成结构再理解理解

📌 创作不易,感谢支持!

每一篇内容都凝聚了心血与热情,如果我的内容对您有帮助,欢迎请我喝杯咖啡☕,您的支持是我持续分享的最大动力!

💬 加入交流群(QQ群):576434538

微信打赏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/986217.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

订单服务(Ordering.API)之应用层

前言 应用层(Ordering.API) 1.应用层职责职责 说明1️⃣ 编排业务流程(流程协调者) 调用多个领域对象(聚合根、领域服务),协调它们完成一个完整的用例。例如:下单时,依次校验库存、计算价格、保存订单、发布领域…

订单服务(Ordering.API)之基础设施层

前言 基础设施层(Ordering.Infrastructure) 1.基础设施层职责职责类型 说明1️⃣ 持久化实现(Repository 实现) 实现领域层定义的仓储接口(IOrderRepository、IBuyerRepository等),与数据库交互(通常使用 ORM,如…

订单服务(Ordering.API)之表现层

前言 表现层(Ordering.API) 1.表现层职责职责 说明1️⃣ 用户交互接口 提供给用户或外部系统的访问入口,如 Web API、MVC Controller、Razor 页面、Blazor 页面等。2️⃣ 请求验证 对输入数据进行基础校验,如必填字段…

环信im云即时通讯

环信im云即时通讯一、环信im云介绍 环信im云是即时通讯云 PaaS 平台,开发者可以通过简单的SDK和REST API对接。支持安卓,iOS,Web等客户端SDK对接 提供单聊,群聊,聊天室等即时通讯功能 支持富媒体消息,实时音视频…

Minimal API(Net9)

前言 Minimal API(Net9) Minimal API 是 ASP.NET Core 提供的一种“极简”风格的写法:用非常少的样板代码(通常在 Program.cs)直接注册路由和处理器,适合微服务、后端小服务、快速原型或需要极高性能/低开销的 HTT…

MediatR 中介者模式

前言 MediatR 1.简介 概念 MediatR 是一个 .NET 的轻量级库,用来实现 Mediator(中介者)模式,主要目的是把对象之间的直接调用(紧耦合)替换为通过中介者传递消息(松耦合)。它常用来实现 请求-处理(Request/Res…

FluentValidation 模型校验框架

前言 验证器(FluentValidation) 1.简介 概念 FluentValidation 是一个 .NET 的验证库,用 流式语法(Fluent API) 定义对象验证规则。它的特点:强类型:验证类对应模型类。 流式接口:代码清晰可读。 可扩展:支持自…

IdentityServer4认证授权之授权码模式(Authorization Code+PKCE)

前言 1.配置服务认证授权 订单服务(Ordering.API)配置了认证授权,所以我们使用Scalar调试接口的时候也需要认证服务(Identity.API)颁发的token,上面的测试就是401无权限 首先我们再回忆下我们认证授权配置了那些:认…

IdentityServer4认证授权之OpenId Connect认证流程

前言 1.认证服务日志中间件(RequestLoggingMiddleware) 经过之前的学习,项目基本都能跑起来了(除了HybridApp),但是我还是想有始有终,全部学完,如果你此时还运行不了项目,先看看我的验证过程即可 为了了解认证服…

IdentityServer4认证授权之OpenId Connect方案

前言 1.授权(AddAuthorization) WebhookClient是基于Blazor开发的Web项目,即像客户端,又像服务端,所以认证授权和之前纯后端服务有些不一样,还是一行一行学习吧 这里没有添加授权方案,默认的授权方案是认证登录之…

IdentityServer4认证授权之隐式流模式(Implicit)

前言 1.配置服务认证授权 订单服务(Ordering.API)配置了认证授权,所以我们使用Scalar调试接口的时候也需要认证服务(Identity.API)颁发的token,上面的测试就是401无权限 首先我们再回忆下我们认证授权配置了那些:认…

.NET Core 微服务之RabbitMQ分布式链路追踪

前言 1.分布式追踪 分布式追踪是一种用来观察、分析分布式系统中一次请求在多个服务之间调用路径的技术。 分布式追踪的核心概念 Trace(跟踪/链路) Trace = 一次业务请求的完整链路。 例如:用户下单从开始到结束的完…

Asp.Net Core 过滤器之Filter

过滤器(Filter) 1.概念 过滤器(Filter)是 ASP.NET Core MVC 的一种 拦截器机制,可以在 Controller 或 Action 执行前后插入逻辑。 它类似中间件,但只作用于 MVC/Web API 请求(控制器和操作方法),不能拦截静态文…

Asp.Net Core 请求管道中间件之Middleware

前言 中间件(Middleware) 概念 中间件是 ASP.NET Core 请求处理管道(pipeline) 中的一段可插拔组件,接收 HttpContext、执行任意逻辑、并决定是否把请求交给管道中的下一个中间件。管道呈“洋葱模型”(请求顺序进入…

Git推送从失败到成功的解决方案

问题描述 今天在尝试将本地代码推送到GitHub远程仓库时,遇到了推送失败的问题。使用git push命令后,出现了以下错误: fatal: unable to access https://github.com/guchen66/IT.Tangdao.Core.git/: OpenSSL SSL_rea…

Microsoft-Store-error

Microsoft-Store-error导航 (返回顶部)1. Store初始化失败1.1 为何要卸载Edge 1.2 如何卸载Edge 1.3 为何删除Edge会影响微软商店2. 微软商店无法联网2.1 解除网络隔离的办法有:3. CheckNetIsolation解除网络隔离3.1 查…

STM32 HAL库 硬件IIC 从机一些问题整理(转载)

一、从机双地址,在回调函数中获取主机访问的地址:void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *I2cHandle) {uint16_t slaveaddrcode = I2C_GET_ADDR_MATCH(I2cHandle);printf("R:%d\r\n",sl…

本地人推荐的火锅,天台火锅/麻辣火锅/老火锅/市井火锅/川渝火锅约会地点推荐榜单

专业视角下的川渝火锅团建选址指南 随着企业团建需求的多元化发展,川渝火锅凭借其独特的社交属性与浓郁的文化氛围,已成为企业团建活动的优选场所。本文基于市场调研数据与消费者反馈,从空间容量、服务质量、特色体…

OBDSTAR P003+ Kit for DC706 Tablets: Simplify ECU, EEPROM, Flash IMMO Data Diagnostics Programming

Troubleshooting ECU & IMMO Challenges on European/American Cars? Meet the OBDSTAR P003+ Kit The Pain Point: Decrypting ECU & IMMO Data on European/American Vehicles For European and American m…

全程复盘:一次枚举值永远 Cloud2的坑——从玄学随机到只读属性

问题描述 在 .NET 6 + WPF 程序中,使用 TangdaoDataFaker<MusicInfo>.Build(200000) 生成测试数据时,控制台 20 条并行日志显示 QQ / Cloud / Kugou 随机分布,但 DataGrid 界面整屏只显示 Cloud2。同一套代码…