Sdílet prostřednictvím


Seedwork (opakovaně použitelné základní třídy a rozhraní pro doménový model)

Tip

Tento obsah je výňatek z eBooku, architektury mikroslužeb .NET pro kontejnerizované aplikace .NET, které jsou k dispozici na .NET Docs nebo jako zdarma ke stažení PDF, které lze číst offline.

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

Složka řešení obsahuje složku SeedWork . Tato složka obsahuje vlastní základní třídy, které můžete použít jako základ pro entity domény a objekty hodnot. Tyto základní třídy použijte, abyste v každé třídě objektu domény neměli redundantní kód. Složka pro tyto typy tříd se nazývá SeedWork a ne něco jako Framework. Nazývá se SeedWork , protože složka obsahuje jen malou podmnožinu opakovaně použitelných tříd, které nelze považovat za architekturu. Seedwork je termín, který představil Michael Feathers a popularizoval Martin Fowler , ale můžete také pojmenovat tuto složku Common, SharedKernel nebo podobně.

Obrázek 7–12 znázorňuje třídy, které tvoří seedwork doménového modelu v mikroslužbě řazení. Má několik vlastních základních tříd, jako jsou Entity, ValueObject a Výčet, a několik rozhraní. Tato rozhraní (IRepository a IUnitOfWork) informují vrstvu infrastruktury o tom, co je potřeba implementovat. Tato rozhraní se také používají prostřednictvím injektáže závislostí z aplikační vrstvy.

Screenshot of the classes contained in the SeedWork folder.

Podrobný obsah složky SeedWork obsahující základní třídy a rozhraní: Entity.cs, Enumeration.cs, IAggregateRoot.cs, IRepository.cs, IUnitOfWork.cs a ValueObject.cs.

Obrázek 7–12 Ukázková sada základních tříd a rozhraní "seedwork" doménového modelu

Jedná se o typ kopírování a vkládání, které mnoho vývojářů sdílí mezi projekty, nikoli formální architekturu. Edworks můžete mít v libovolné vrstvě nebo knihovně. Pokud je však sada tříd a rozhraní dostatečně velká, můžete chtít vytvořit knihovnu s jednou třídou.

Vlastní základní třída entity

Následující kód je příkladem základní třídy Entity, kde můžete umístit kód, který lze použít stejným způsobem pro libovolnou entitu domény, jako je ID entity, operátory rovnosti, seznam událostí domény na entitu atd.

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

Předchozí kód, který používá seznam událostí domény pro každou entitu, se při zaměření na události domény vysvětlí v dalších částech.

Kontrakty úložiště (rozhraní) ve vrstvě doménového modelu

Kontrakty úložišť jsou jednoduše rozhraní .NET, která vyjadřují požadavky na kontrakty úložišť, která se mají použít pro každou agregaci.

Samotná úložiště s kódem EF Core nebo jinými závislostmi infrastruktury a kódem (Linq, SQL atd.) nesmí být implementována v rámci doménového modelu; úložiště by měla implementovat pouze rozhraní, která definujete v doménovém modelu.

Vzor související s tímto postupem (umístění rozhraní úložiště do vrstvy doménového modelu) je model odděleného rozhraní. Jak vysvětluje Martin Fowler: "Použití odděleného rozhraní k definování rozhraní v jednom balíčku, ale implementujte ho v jiném. Klient, který potřebuje závislost na rozhraní, tak nemusí o implementaci zcela vědět."

Podle vzoru Oddělené rozhraní umožňuje aplikační vrstvě (v tomto případě projekt webového rozhraní API pro mikroslužbu) mít závislost na požadavcích definovaných v doménovém modelu, ale ne přímou závislost na vrstvě infrastruktury/trvalosti. Kromě toho můžete pomocí injektáže závislostí izolovat implementaci, která se implementuje ve vrstvě infrastruktury nebo trvalosti pomocí úložišť.

Například následující příklad s IOrderRepository rozhraní definuje, jaké operace OrderRepository třída bude muset implementovat na vrstvě infrastruktury. V aktuální implementaci aplikace stačí kód pouze přidat nebo aktualizovat objednávky do databáze, protože dotazy jsou rozdělené podle zjednodušeného přístupu 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; }
}

Další materiály