方案資料夾包含 SeedWork 資料夾。 此資料夾包含自定義基類,您可以做為網域實體和值物件的基底。 使用這些基類,讓每個網域的物件類別中沒有多餘的程序代碼。 這些類別類型的資料夾稱為 SeedWork ,而不是 Framework。 它稱為 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” 基類和介面的範例
這是許多開發人員在專案之間共用的複製和貼上重複使用類型,而不是正式架構。 您可以在任何圖層或程式庫中擁有 seedworks。 不過,如果類別和介面集合夠大,您可能想要建立單一類別庫。
自定義實體基類
下列程式代碼是 Entity 基類的範例,您可以在其中放置任何網域實體使用相同的程式代碼,例如實體標識元、 等號運算符、每個實體的網域事件清單等等。
// 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; }
}