基础架构(域模型的可重用基类和接口)

小窍门

此内容摘自电子书《适用于容器化 .NET 应用程序的 .NET 微服务体系结构》,可以在 .NET Docs 上获取,也可以下载免费的 PDF 以供离线阅读。

适用于容器化 .NET 应用程序的 .NET 微服务体系结构电子书封面缩略图。

解决方案文件夹包含 SeedWork 文件夹。 此文件夹包含自定义基类,你可以将其用作域实体和值对象的基类。 使用这些基类,以便在每个域的对象类中没有冗余代码。 这些类型的类的文件夹称为 SeedWork ,而不是 框架。 它称为 SeedWork ,因为该文件夹仅包含一小部分可重用类,而不能真正被视为框架。 Seedwork 是由 Michael Feathers 引入的术语,由 Martin Fowler 推广,但你也可以将该文件夹命名为 Common、SharedKernel 或类似文件夹。

图 7-12 显示了在订单微服务中构成域模型基础的类。 它具有一些自定义基类,例如 EntityValueObject以及 Enumeration几个接口。 这些接口(IRepositoryIUnitOfWork)告知基础结构层需要实现哪些内容。 这些接口也通过应用层的依赖注入来使用。

SeedWork 文件夹中包含的类的屏幕截图。

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://free.blessedness.top/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; }
}

其他资源