解决方案文件夹包含 SeedWork 文件夹。 此文件夹包含自定义基类,你可以将其用作域实体和值对象的基类。 使用这些基类,以便在每个域的对象类中没有冗余代码。 这些类型的类的文件夹称为 SeedWork ,而不是 框架。 它称为 SeedWork ,因为该文件夹仅包含一小部分可重用类,而不能真正被视为框架。 Seedwork 是由 Michael Feathers 引入的术语,由 Martin Fowler 推广,但你也可以将该文件夹命名为 Common、SharedKernel 或类似文件夹。
图 7-12 显示了在订单微服务中构成域模型基础的类。 它具有一些自定义基类,例如 Entity, ValueObject以及 Enumeration几个接口。 这些接口(IRepository 和 IUnitOfWork)告知基础结构层需要实现哪些内容。 这些接口也通过应用层的依赖注入来使用。
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; }
}
其他资源
- 马丁·福勒 Separated Interface.(分隔接口)
https://www.martinfowler.com/eaaCatalog/separatedInterface.html