共用方式為


Seedwork (網域模型的可重複使用基類和介面)

小提示

此內容是適用於容器化 .NET 應用程式的電子書.NET 微服務架構摘錄,可在 .NET Docs 或免費下載的 PDF 中取得,可脫機讀取。

.NET 微服務架構的容器化 .NET 應用程式電子書封面縮圖。

方案資料夾包含 SeedWork 資料夾。 此資料夾包含自定義基類,您可以做為網域實體和值物件的基底。 使用這些基類,讓每個網域的物件類別中沒有多餘的程序代碼。 這些類別類型的資料夾稱為 SeedWork ,而不是 Framework。 它稱為 SeedWork ,因為資料夾只包含一小部分可重複使用的類別,而不能真正被視為架構。 Seedwork 是由 Michael Feathers 所介紹,並由 Martin Fowler 推廣,但您也可以將該資料夾命名為 Common、SharedKernel 或類似資料夾。

圖 7-12 顯示類別,這些類別會形成排序微服務中定義域模型的種子。 它有一些自定義基類,例如 EntityValueObjectEnumeration,以及幾個介面。 這些介面 (IRepositoryIUnitOfWork) 會通知基礎結構層需要實作的內容。 這些介面也會透過應用層的依賴注入來使用。

SeedWork 資料夾中所含類別的螢幕快照。

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; }
}

其他資源