Published on

Как применять Clean Architecture в ASP.NET CORE - Domain

Authors
  • avatar
    Name
    Николай Маракасов
    Twitter
    @Twitter

В прошлой статье я рассмотрел прикладной слой Clean Architecture, теперь пришло время изучить центральный слой — доменный.

Domain

На диаграмме, которую мы разбирали в первой статье, этот слой назывался Entities, но я предпочитаю называть его Domain, так как это ближе к предметно-ориентированному проектированию (DDD).

В зависимости от сложности бизнес-логики этот уровень может проектироваться по-разному, но его главное предназначение — реализация корпоративных бизнес-правил.

Пример структуры проекта:
domain

Как и в слое Application, я организую код на основе бизнес-контекста, а не технических аспектов. Вместо структуры:

- Entities
    - Orders
- Interfaces
    - OrdersRepository
- Exceptions
- Services

мы группируем код так, как показано на изображении выше, складывая всю логику, связанную с заказами, в директорию Orders. Исключение составляют технические детали, такие как интерфейсы IEntity или IDomainEvent.

Реализация сущности Order

Рассмотрим сущность Order, которая описывает заказ. Она содержит не только данные, но и поведение — в нашем случае логику переключения статусов заказа.

public sealed class Order : IEntity<Guid>
{
    public Order(Guid id, List<OrderLine> orderLines)
    {
        if (id == Guid.Empty)
            throw new ArgumentException("Order ID cannot be empty");
        
        if (orderLines == null || orderLines.Count == 0)
            throw new ArgumentException("Order lines cannot be empty");
        
        Id = id;
        Status = OrderStatus.Created;
        OrderLines = orderLines;
    }

    public Guid Id { get; }
    
    public ICollection<OrderLine> OrderLines { get; init; }
    public OrderStatus Status { get; private set; }

    public void Complete()
    {
        if (Status == OrderStatus.Completed)
            throw new OrderChangeStatusException("Order is already completed");
        
        Status = OrderStatus.Completed;
    }
}

Почему важно инкапсулировать логику в сущности?

  1. Изоляция бизнес-правил — логика переключения статусов теперь не разбросана по всему коду, а находится в одном месте.
  2. Облегчение работы прикладного слояApplication выполняет лишь координацию процессов, не вдаваясь в детали реализации бизнес-логики.

Репозиторий IOrderRepository

Интерфейс репозитория отвечает за работу с доменной сущностью Order и, как правило, содержит базовые методы. Он располагается в том же модуле, где и сама сущность.

public interface IOrderRepository
{
    Task<Order> GetById(Guid orderId, CancellationToken cancellationToken);
    Task<int> Update(Order order, CancellationToken cancellationToken);
    Task<int> Add(Order order, CancellationToken cancellationToken);
    Task<int> Delete(Order order, CancellationToken cancellationToken);
}

Реализация этого интерфейса находится в слое Infrastructure.

Когда выносить логику в домен?

Бизнес-логику можно начинать реализовывать на уровне Application и по мере её усложнения постепенно переносить в доменный слой. Помимо методов сущностей, бизнес-правила могут быть вынесены в доменные сервисы — их стоит использовать, когда:

  • Логику нельзя привязать к конкретной сущности.
  • Бизнес-процесс требует дополнительных зависимостей.

Заключение

Как я уже отмечал, сложность доменного слоя зависит от масштабов приложения и выбранного подхода к проектированию. Однако главное — этот слой должен быть изолирован от технических деталей и других частей системы, так как он является ядром приложения.