Seedwork(适用于域模型的可重用基类和接口)

提示

此内容摘自电子书《适用于容器化 .NET 应用程序的 .NET 微服务体系结构》,可在 .NET 文档上获取,也可作为免费可下载的 PDF 脱机阅读。

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

解决方案文件夹包含 SeedWork 文件夹 。 此文件夹包含可以用作域实体和值对象的基础的自定义基类。 使用这些基类,从而在每个域的对象类中避免冗余代码。 这些类型的类的文件夹称为 SeedWork,而不是类似于 Framework 。 它称为“SeedWork” ,因为该文件夹仅包含可重用类的一个小型子集,不能将其视为一个框架。 Seedwork 是由 Michael Feathers 引入的一个术语,并且由 Martin Fowler 推广普及,但也可将该文件夹命名为 Common、SharedKernel 或类似名称。

图 7-12 显示在排序的微服务中形成域模型的 seedwork 的类。 它还包含 Entity、ValueObject 和 Enumeration 等自定义基类,以及一些接口。 这些接口(IRepository 和 IUnitOfWork)告知基础结构层需要实现的内容。 还可通过应用程序层中的依赖关系注入使用这些接口。

Screenshot of the classes contained in the SeedWork folder.

SeedWork 文件夹的详细内容,包含基类和接口:Entity.cs、Enumeration.cs、IAggregateRoot.cs、IRepository.cs、IUnitOfWork.cs 和 ValueObject.cs。

图 7-12。 域模型“seedwork”基类和接口的示例集

这是许多开发者在项目之间共享的复制和粘贴重用类型,不是正式框架。 seedwork 可存在于任何层或库中。 但是,如果类和接口的集足够大,可能需要创建单个类库。

自定义实体基类

以下代码是实体基类的示例,可在其中放置可由任何域实体以相同方式使用的代码,例如实体 ID、相等运算符、每个实体的域事件列表等。

// COMPATIBLE WITH ENTITY FRAMEWORK CORE (1.1 and later)
public abstract class Entity
{
    int? _requestedHashCode;
    int _Id;
    private List<INotification> _domainEvents;
    public virtual int Id
    {
        get
        {
            return _Id;
        }
        protected set
        {
            _Id = value;
        }
    }

    public List<INotification> DomainEvents => _domainEvents;
    public void AddDomainEvent(INotification eventItem)
    {
        _domainEvents = _domainEvents ?? new List<INotification>();
        _domainEvents.Add(eventItem);
    }
    public void RemoveDomainEvent(INotification eventItem)
    {
        if (_domainEvents is null) return;
        _domainEvents.Remove(eventItem);
    }

    public bool IsTransient()
    {
        return this.Id == default(Int32);
    }

    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is Entity))
            return false;
        if (Object.ReferenceEquals(this, obj))
            return true;
        if (this.GetType() != obj.GetType())
            return false;
        Entity item = (Entity)obj;
        if (item.IsTransient() || this.IsTransient())
            return false;
        else
            return item.Id == this.Id;
    }

    public override int GetHashCode()
    {
        if (!IsTransient())
        {
            if (!_requestedHashCode.HasValue)
                _requestedHashCode = this.Id.GetHashCode() ^ 31;
            // XOR for random distribution. See:
            // https://learn.microsoft.com/archive/blogs/ericlippert/guidelines-and-rules-for-gethashcode
            return _requestedHashCode.Value;
        }
        else
            return base.GetHashCode();
    }
    public static bool operator ==(Entity left, Entity right)
    {
        if (Object.Equals(left, null))
            return (Object.Equals(right, null));
        else
            return left.Equals(right);
    }
    public static bool operator !=(Entity left, Entity right)
    {
        return !(left == right);
    }
}

使用每个实体的域事件列表的上一个代码将在下一部分(重点是域事件)介绍。

域模型层中的存储库协定(接口)

存储库协定是表达存储库的协定要求的 .NET 接口,用于每个聚合。

存储库本身(包含 EF Core 代码或其他任何基础结构依赖项和代码(Linq、SQL 等)不能在域模型内实现,存储库应仅实现你在域模型中定义的接口。

这种做法(在域模型层中放置存储库接口)的相关模式是分隔接口模式。 正如 Martin Fowler 所述,“使用分隔接口在一个包中定义接口,但在另一个包中实现它。 这样一来,需要接口依赖项的客户端可能完全不会识别出实现。”

通过遵循分隔接口模式,应用程序层(在此情况下是微服务的 Web API 项目)可具有在域模型中定义的要求的依赖项,但没有基础结构/持久性层的直接依赖项。 此外,可以使用依赖项注入隔离实现,可在使用存储库的基础结构/持久性层中实现这一点。

例如,包含 IOrderRepository 接口的下面的示例定义 OrderRepository 类在基础机构层中实现所需的操作。 在应用程序的当前实现中,代码只需将订单添加或更新到数据库,因为查询根据简化的 CQRS 方法拆分。

// Defined at IOrderRepository.cs
public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order);

    void Update(Order order);

    Task<Order> GetAsync(int orderId);
}

// Defined at IRepository.cs (Part of the Domain Seedwork)
public interface IRepository<T> where T : IAggregateRoot
{
    IUnitOfWork UnitOfWork { get; }
}

其他资源