XiHan Framework 处于预览阶段

查看获取方式
跳转到内容

XiHan.Framework.Domain ​

领域契约包:实体协议、值对象标记、领域事件与规约。只有接口与零依赖的组合器,没有实体基类,供定义聚合的应用与持久化子系统共同引用。

项内容
包 IDXiHan.Framework.Domain
环层Ring 2(抽象)
能力—(纯编译期契约,没有清单)
特性入口—
配置节—
依赖XiHan.Framework.Primitives
AOT兼容
稳定性Stable

概述 ​

实体按需实现协议接口,持久化 Provider 按实体实现的接口维护对应字段:IHasCreationTime 的创建时间、IHasModificationTime 的修改时间、IHasConcurrencyStamp 的并发戳、ISoftDeletable 的删除标记、IMultiTenant 的租户。聚合根 IAggregateRoot<TKey> 用组合方式持有 DomainEventBuffer 收集领域事件,提交前由 Transaction 阶段派发。Specification<T> 把条件表示成表达式,同一条件既能在内存判定,也能交给查询提供器翻译。

本包不提供实体基类、审计基类或仓储;仓储与查询在 Persistence.Abstractions。

何时使用 ​

定义实体、聚合、值对象与查询条件时引用它。只用持久化的应用不必单独引用:Persistence.Abstractions 与 Profiles.Standard 已传递引用本包。

安装与启用 ​

bash
dotnet add package XiHan.Framework.Domain --prerelease

当前是预览版,安装要带 --prerelease;从源码或本地包源引用见快速上手。本包没有特性,不需要 UseFeature。

主要类型 ​

类型命名空间说明
IEntity<TKey>XiHan.Framework.Domain.Entities有标识的实体,成员 TKey Id { get; },TKey : notnull
IAggregateRoot<TKey>XiHan.Framework.Domain.Entities聚合根:IEntity<TKey> + IHasDomainEvents
IHasCreationTimeXiHan.Framework.Domain.EntitiesDateTimeOffset CreatedAt,新增时写入
IHasModificationTimeXiHan.Framework.Domain.EntitiesDateTimeOffset? ModifiedAt,更新时写入
IHasConcurrencyStampXiHan.Framework.Domain.Entitiesstring ConcurrencyStamp,新增与更新时刷新,更新时比对
ISoftDeletableXiHan.Framework.Domain.Entitiesbool IsDeleted、DateTimeOffset? DeletedAt,删除改为打标
IMultiTenantXiHan.Framework.Domain.Entitiesstring TenantId,新增时写入当前租户,查询按当前租户过滤
IDomainEventXiHan.Framework.Domain.Events领域事件,成员 DateTimeOffset OccurredAt(UTC)
IHasDomainEventsXiHan.Framework.Domain.EventsDomainEvents(尚未派发的事件)与 ClearDomainEvents()
DomainEventBufferXiHan.Framework.Domain.Events有界事件缓冲:Add、Items、Clear,缺省容量 DefaultCapacity(256)
IValueObjectXiHan.Framework.Domain.ValueObjects值对象标记,配合 record 取得值相等
ISpecification<T>XiHan.Framework.Domain.SpecificationsCriteria 表达式与 IsSatisfiedBy
Specification<T>XiHan.Framework.Domain.Specifications规约实现:And、Or、Not,可继承
SpecificationXiHan.Framework.Domain.Specifications工厂:All<T>()(恒真,按类型缓存)、Of<T>(criteria)

使用示例 ​

带软删除、创建时间与领域事件的聚合:

csharp
using XiHan.Framework.Domain.Entities;
using XiHan.Framework.Domain.Events;

namespace SimpleApp.Payments;

public sealed class Payment : IAggregateRoot<Guid>, IHasCreationTime, ISoftDeletable
{
    // 一笔支付的事件很少,容量给小一些也够
    private readonly DomainEventBuffer _events = new(capacity: 16);

    public Guid Id { get; init; }

    public decimal Amount { get; set; }

    public DateTimeOffset CreatedAt { get; set; }

    public bool IsDeleted { get; set; }

    public DateTimeOffset? DeletedAt { get; set; }

    public IReadOnlyList<IDomainEvent> DomainEvents => _events.Items;

    public void Refund(DateTimeOffset now) => _events.Add(new PaymentRefunded(Id, Amount, now));

    public void ClearDomainEvents() => _events.Clear();
}

public sealed record PaymentRefunded(Guid PaymentId, decimal Amount, DateTimeOffset OccurredAt) : IDomainEvent;

规约的组合与内存判定:

csharp
using SimpleApp.Payments;
using XiHan.Framework.Domain.Specifications;

var large = Specification.Of<Payment>(p => p.Amount >= 1000m);
var recent = Specification.Of<Payment>(p => p.CreatedAt >= DateTimeOffset.UtcNow.AddDays(-7));
var largeOrOld = large.Or(recent.Not());

var payment = new Payment { Amount = 50m, CreatedAt = DateTimeOffset.UtcNow.AddDays(-30) };
var matched = largeOrOld.IsSatisfiedBy(payment);   // true:不是近 7 天的

行为与限制 ​

  • DomainEventBuffer 的容量小于 1 时构造抛出 ArgumentOutOfRangeException;已达容量时 Add 抛出 InvalidOperationException,不丢弃事件。缓冲不是线程安全的,聚合在一个调用里使用。
  • Specification<T> 第一次 IsSatisfiedBy 时编译 Criteria 并缓存委托;只交给查询提供器的规约不编译。And / Or 把右侧表达式的参数替换为左侧参数,组合结果只有一个参数,能被 EF Core 等 LINQ 提供器翻译。
  • 规约里只用目标 Provider 能翻译的成员与运算;可空值类型与 null 的比较在三个持久化 Provider 上都按是否有值判定。
  • 接口只描述字段,不含行为:字段何时写入、怎样过滤由持久化 Provider 按 RFC-0005 执行,见持久化。
  • IValueObject 不参与任何持久化映射。

相关 ​

Released under The MIT License