Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Подсказка
Это фрагмент из электронной книги «Архитектура микрослужб .NET для контейнеризованных приложений .NET», доступной в документации .NET или в виде бесплатного скачиваемого PDF-файла, который можно прочитать в автономном режиме.
Папка решения содержит папку SeedWork . Эта папка содержит пользовательские базовые классы, которые можно использовать в качестве базы для сущностей домена и объектов значений. Используйте эти базовые классы, чтобы в классе объектов каждого домена не было избыточного кода. Папка для этих типов классов называется SeedWork и не похожа на Платформу. Она называется SeedWork , так как папка содержит только небольшое подмножество повторно используемых классов, которые действительно не могут считаться платформой. Seedwork — это термин, представленный Майклом Пейзером и популяризированным Мартином Фаулером, но вы также можете назвать эту папку 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"
Это повторное использование методом копирования и вставки, которое многие разработчики применяют между проектами, а не формальная основа. В любом слое или библиотеке можно использовать начальные работы. Однако если набор классов и интерфейсов становится достаточно большим, может потребоваться создать одну библиотеку классов.
Базовый класс пользовательской сущности
Следующий код является примером базового класса 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 и т. д.), не должны быть реализованы в модели домена; репозитории должны реализовывать только интерфейсы, определенные в модели домена.
Шаблон, связанный с этой практикой (размещение интерфейсов репозитория на уровне модели домена) является шаблоном разделенного интерфейса. Как объяснил Мартин Фаулер, "Используйте разделенный интерфейс для определения интерфейса в одном пакете, но реализуйте его в другом. Таким образом, клиент, которому требуется зависимость от интерфейса, может быть полностью не осведомлён о реализации.
Следование шаблону разделенного интерфейса позволяет слою приложения (в данном случае проекту Web API для микросервиса) иметь зависимость от требований, определенных в модели домена, но не иметь прямой зависимости от слоя инфраструктуры/сохранения. Кроме того, можно использовать Dependency Injection для изоляции реализации, которая реализуется на слое инфраструктуры или хранения данных с помощью репозиториев.
Например, в следующем примере с интерфейсом 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; }
}
Дополнительные ресурсы
- Мартин Фаулер. Разделенный интерфейс.
https://www.martinfowler.com/eaaCatalog/separatedInterface.html