Seedwork (klasy bazowe wielokrotnego użytku i interfejsy na potrzeby modelu domeny)
Napiwek
Ta zawartość jest fragmentem książki eBook, architektury mikrousług platformy .NET dla konteneryzowanych aplikacji platformy .NET dostępnych na platformie .NET Docs lub jako bezpłatnego pliku PDF, który można odczytać w trybie offline.
Folder rozwiązania zawiera folder SeedWork . Ten folder zawiera niestandardowe klasy bazowe, których można użyć jako podstawy dla jednostek domeny i obiektów wartości. Użyj tych klas bazowych, aby nie mieć nadmiarowego kodu w klasie obiektów każdej domeny. Folder dla tych typów klas nosi nazwę SeedWork , a nie coś takiego jak Framework. Jest to nazywane SeedWork , ponieważ folder zawiera tylko niewielki podzbiór klas wielokrotnego użytku, których naprawdę nie można uznać za strukturę. Seedwork to termin wprowadzony przez Michael Feathers i spopularyzowany przez Martina Fowlera, ale można również nazwać ten folder Common, SharedKernel lub podobne.
Rysunek 7–12 przedstawia klasy tworzące rozmieszczanie modelu domeny w kolejności mikrousługi. Ma kilka niestandardowych klas bazowych, takich jak Entity, ValueObject i Enumeration, oraz kilka interfejsów. Te interfejsy (IRepository i IUnitOfWork) informują warstwę infrastruktury o tym, co należy zaimplementować. Te interfejsy są również używane za pośrednictwem wstrzykiwania zależności z warstwy aplikacji.
Szczegółowa zawartość folderu SeedWork zawierająca klasy podstawowe i interfejsy: Entity.cs, Enumeration.cs, IAggregateRoot.cs, IRepository.cs, IUnitOfWork.cs i ValueObject.cs.
Rysunek 7–12. Przykładowy zestaw klas bazowych i interfejsów modelu domeny "seedwork"
Jest to typ kopiowania i wklejania, który wielu deweloperów współużytkuje między projektami, a nie formalną strukturą. W dowolnej warstwie lub bibliotece można mieć pliki seedworks. Jeśli jednak zestaw klas i interfejsów jest wystarczająco duży, warto utworzyć pojedynczą bibliotekę klas.
Niestandardowa klasa podstawowa jednostki
Poniższy kod jest przykładem klasy bazowej jednostki, w której można umieścić kod, który może być używany w taki sam sposób przez dowolną jednostkę domeny, taką jak identyfikator jednostki, operatory równości, lista zdarzeń domeny na jednostkę itp.
// 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);
}
}
Poprzedni kod korzystający z listy zdarzeń domeny dla jednostki zostanie wyjaśniony w następnych sekcjach podczas koncentracji na zdarzeniach domeny.
Kontrakty repozytorium (interfejsy) w warstwie modelu domeny
Kontrakty repozytorium to po prostu interfejsy .NET, które wyrażają wymagania kontraktowe repozytoriów, które mają być używane dla każdej agregacji.
Same repozytoria z kodem EF Core lub innymi zależnościami infrastruktury i kodem (Linq, SQL itp.) nie mogą być implementowane w modelu domeny; repozytoria powinny implementować tylko interfejsy zdefiniowane w modelu domeny.
Wzorzec związany z tą praktyką (umieszczenie interfejsów repozytorium w warstwie modelu domeny) jest wzorcem interfejsu oddzielonego. Jak wyjaśnił Martin Fowler, "Użyj interfejsu oddzielonego, aby zdefiniować interfejs w jednym pakiecie, ale zaimplementować go w innym. W ten sposób klient, który potrzebuje zależności do interfejsu, może być całkowicie nieświadomy implementacji."
Zgodnie ze wzorcem oddzielonego interfejsu warstwa aplikacji (w tym przypadku projekt internetowego interfejsu API dla mikrousługi) ma zależność od wymagań zdefiniowanych w modelu domeny, ale nie jest to bezpośrednia zależność od warstwy infrastruktury/trwałości. Ponadto można użyć wstrzykiwania zależności, aby odizolować implementację, która jest implementowana w warstwie infrastruktury/trwałości przy użyciu repozytoriów.
Na przykład poniższy przykład z interfejsem IOrderRepository definiuje operacje, które klasa OrderRepository będzie musiała zaimplementować w warstwie infrastruktury. W bieżącej implementacji aplikacji kod musi tylko dodawać lub aktualizować zamówienia do bazy danych, ponieważ zapytania są podzielone zgodnie z uproszczonym podejściem 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; }
}
Dodatkowe zasoby
- Martin Fowler. Oddzielony interfejs.
https://www.martinfowler.com/eaaCatalog/separatedInterface.html