Что такое доменный слой (Domain Layer) в архитектуре приложения?

«Что такое доменный слой (Domain Layer) в архитектуре приложения?» — вопрос из категории Архитектура, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Доменный слой — это ядро приложения, содержащее бизнес-логику, правила и сущности предметной области. Он полностью независим от инфраструктуры (БД, HTTP, фреймворка) и слоя представления. В моих проектах на PHP (часто с DDD-подходом) он включает:

  • Сущности (Entities): Объекты с идентификатором, жизненным циклом (например, User, Order).
  • Объекты-значения (Value Objects): Неизменяемые объекты без идентификатора (например, EmailAddress, Money).
  • Агрегаты (Aggregates): Кластер связанных сущностей и VO с корневым агрегатом (например, Order как корень с OrderItem внутри).
  • Доменные сервисы (Domain Services): Логика, не принадлежащая естественным образом какой-либо сущности.
  • События домена (Domain Events): Факты, произошедшие в домене (например, OrderWasPlaced).

Пример сущности Order с бизнес-правилами:

class Order
{
    private string $id;
    private OrderStatus $status;
    /** @var OrderLine[] */
    private array $lines = [];
    private ?DateTimeImmutable $paidAt = null;

    private function __construct(string $id) {
        $this->id = $id;
        $this->status = OrderStatus::NEW;
    }

    public static function create(string $id): self {
        return new self($id);
    }

    public function addLine(Product $product, int $quantity): void {
        // Доменное правило: нельзя изменить оплаченный заказ
        if ($this->status === OrderStatus::PAID) {
            throw new DomainException('Cannot modify a paid order.');
        }
        // Доменное правило: минимальное количество
        if ($quantity < 1) {
            throw new DomainException('Quantity must be at least 1.');
        }
        $this->lines[] = new OrderLine($product, $quantity);
    }

    public function markAsPaid(): void {
        // Доменное правило: оплатить можно только новый заказ
        if ($this->status !== OrderStatus::NEW) {
            throw new DomainException('Only new orders can be paid.');
        }
        $this->status = OrderStatus::PAID;
        $this->paidAt = new DateTimeImmutable();
        $this->recordEvent(new OrderPaid($this->id)); // Генерация события
    }

    public function totalAmount(): Money {
        // Вычисление на основе правил домена
        $total = Money::USD(0);
        foreach ($this->lines as $line) {
            $total = $total->add($line->subtotal());
        }
        return $total;
    }
}

Главный принцип: Доменный слой — это самая важная и стабильная часть. Его можно протестировать изолированно, без БД или веб-сервера, что я и делаю с помощью юнит-тестов.