0Pricing
PHP Academy · Урок

Строительные блоки DDD: сущности и объекты-значения

Моделируйте предметную область с помощью насыщенных сущностей и неизменяемых объектов-значений

«Строительные блоки DDD: сущности и объекты-значения» — бесплатный урок PHP Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения PHP Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс PHP Academy содержит 4 уроков всего.

Тактическое ядро

Тактические паттерны предметно-ориентированного проектирования дают Вам словарь для моделирования бизнес-понятий в коде. Два фундаментальных строительных блока — это сущности, то есть объекты, определяемые идентичностью во времени, и объекты-значения, определяемые своими атрибутами и рассматриваемые как неизменяемые. Правильное понимание этого различия формирует всё остальное в предметном слое.

Идентичность и значение

Сущность имеет устойчивую идентичность, которая сохраняется даже при изменении её атрибутов: Customer остаётся тем же клиентом после переезда. Объект-значение не имеет идентичности; два объекта-значения с одинаковыми атрибутами взаимозаменяемы, как две купюры по 5 долларов. Спросите себя: «если каждое поле изменится, останется ли это всё той же вещью?» Если да, это сущность.

Объект-значение

Объекты-значения неизменяемы и самостоятельно проверяют свою корректность. Свойства PHP 8.1 с модификатором readonly обеспечивают неизменяемость, а конструктор защищает инварианты, поэтому недопустимый экземпляр никогда не сможет существовать.

<?php
final class Email {
    public function __construct(public readonly string $value) {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("Invalid email: $value");
        }
    }
    public function equals(Email $other): bool {
        return strtolower($this->value) === strtolower($other->value);
    }
}
$a = new Email('Jane@Example.com');
var_dump($a->equals(new Email('jane@example.com'))); // true

Равенство по значению

Объекты-значения сравниваются по структурному равенству, а не по ссылке. Предоставьте явный метод equals(), вместо того чтобы полагаться на ==, поскольку нестрогое сравнение в PHP имеет неожиданные правила. Объект-значение Money должен учитывать и сумму, и валюту.

<?php
final class Money {
    public function __construct(
        public readonly int $cents,
        public readonly string $currency
    ) {}
    public function equals(Money $o): bool {
        return $this->cents === $o->cents && $this->currency === $o->currency;
    }
    public function add(Money $o): self {
        if ($this->currency !== $o->currency) {
            throw new DomainException('Currency mismatch');
        }
        return new self($this->cents + $o->cents, $this->currency);
    }
}
$sum = (new Money(500, 'USD'))->add(new Money(250, 'USD'));
echo $sum->cents, PHP_EOL; // 750

Неизменяемость и методы изменения через копирование

Поскольку объекты-значения неизменяемы, «изменить» один из них означает вернуть новый экземпляр. Такие методы, как add() или withAmount(), ничего не изменяют: они создают новое значение. Это устраняет ошибки, вызванные общими ссылками, и позволяет безопасно свободно передавать объекты-значения по всему домену.

<?php
final class DateRange {
    public function __construct(
        public readonly DateTimeImmutable $start,
        public readonly DateTimeImmutable $end
    ) {
        if ($end < $start) { throw new InvalidArgumentException('end before start'); }
    }
    public function withEnd(DateTimeImmutable $end): self {
        return new self($this->start, $end);
    }
}
$r = new DateRange(new DateTimeImmutable('2026-01-01'), new DateTimeImmutable('2026-01-10'));
$r2 = $r->withEnd(new DateTimeImmutable('2026-02-01'));
echo $r->end->format('Y-m-d'), ' / ', $r2->end->format('Y-m-d'), PHP_EOL;

Сущность

Сущность определяется идентичностью — обычно идентификатором, сгенерированным доменом, а не автоинкрементом базы данных. Равенство определяется идентификаторами. Её атрибуты могут изменяться через методы с насыщенным поведением, которые защищают инварианты.

<?php
final class Customer {
    private Email $email;
    public function __construct(
        public readonly string $id,
        Email $email
    ) { $this->email = $email; }

    public function changeEmail(Email $new): void { $this->email = $new; }
    public function email(): Email { return $this->email; }
    public function sameIdentityAs(Customer $o): bool { return $this->id === $o->id; }
}
$c = new Customer('cus_1', new Email('a@b.com'));
$c->changeEmail(new Email('c@d.com'));
echo $c->email()->value, PHP_EOL; // c@d.com

Генерация идентичности

Предпочтительно генерировать идентичность в домене — например, UUID — до сохранения, а не ждать этого от базы данных. Благодаря этому можно создать полностью корректную сущность в памяти, ссылаться на неё из разных агрегатов и тестировать код без базы данных. Идентификатор является частью модели, а не артефактом хранения.

Богатое поведение, а не анемичные данные

Распространённый антишаблон — анемичная доменная модель: сущности представляют собой наборы общедоступных методов чтения и записи, тогда как вся логика находится в классах «сервисов». DDD переносит поведение внутрь сущности. $order->cancel() инкапсулирует правила отмены, вместо того чтобы позволять вызывающему коду напрямую менять поле состояния.

<?php
final class Order {
    private string $status = 'open';
    public function __construct(public readonly string $id) {}
    public function cancel(): void {
        if ($this->status === 'shipped') {
            throw new DomainException('Cannot cancel a shipped order');
        }
        $this->status = 'cancelled';
    }
    public function status(): string { return $this->status; }
}
$o = new Order('o1');
$o->cancel();
echo $o->status(), PHP_EOL; // cancelled

Объекты-значения повсюду

Заменяйте одержимость примитивами объектами-значениями. Вместо того чтобы передавать повсюду string $email, int $cents и string $currency, объединяйте их в Email и Money. Преимущества:

  • Проверка выполняется один раз — при создании.
  • Доменные правила, например проверка совпадения валют, находятся рядом с данными.
  • Сигнатуры типов документируют назначение и предотвращают путаницу.

О сопоставлении с хранилищем

Объекты-значения часто сопоставляются со встроенными столбцами, а не с отдельными таблицами (в объектно-реляционном отображении, #[Embeddable]). Сущности сопоставляются со строками, ключом которых служит их идентичность. Не помещайте детали хранения в сами доменные объекты: модель не должна знать, что хранится в MySQL. Слой объектно-реляционного отображения или написанный вручную преобразователь переводит данные между богатой доменной моделью и базой данных.

Составные объекты-значения

Объекты-значения можно составлять друг из друга. Address объединяет несколько примитивов в одно целостное понятие со своими правилами равенства и форматирования. Благодаря этому сущность содержит один содержательный тип вместо пяти разрозненных строк, а правила адреса находятся в одном месте.

<?php
final class Address {
    public function __construct(
        public readonly string $street,
        public readonly string $city,
        public readonly string $postcode
    ) {
        if ($postcode === '') { throw new InvalidArgumentException('postcode required'); }
    }
    public function equals(Address $o): bool {
        return $this->street === $o->street
            && $this->city === $o->city
            && $this->postcode === $o->postcode;
    }
    public function oneLine(): string {
        return "{$this->street}, {$this->city} {$this->postcode}";
    }
}
echo (new Address('1 Main St', 'Ankara', '06000'))->oneLine(), PHP_EOL;

Быстрая проверка

Сущность или объект-значение?

Итоги

Вы изучили два основных строительных блока DDD. Сущности определяются устойчивой идентичностью, содержат богатое поведение и защищают свои инварианты с помощью методов. Объекты-значения неизменяемы, самостоятельно проверяют свою корректность и сравниваются по равенству атрибутов, заменяя одержимость примитивами. Правильное моделирование этих объектов, при котором логика находится внутри них, а не в анемичных наборах данных, служит основой для агрегатов, репозиториев и остального доменного слоя.

Часто задаваемые вопросы

Урок «Строительные блоки DDD: сущности и объекты-значения» бесплатный?

Да — полный текст урока «Строительные блоки DDD: сущности и объекты-значения» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс PHP Academy, подпишись на CoddyKit PRO. Курс PHP Academy содержит 4 уроков всего.

Чему я научусь в уроке «Строительные блоки DDD: сущности и объекты-значения»?

Моделируйте предметную область с помощью насыщенных сущностей и неизменяемых объектов-значений Ты практикуешь PHP Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать PHP Academy?

Предыдущий опыт не требуется. PHP Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Строительные блоки DDD: сущности и объекты-значения»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке PHP Academy?

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

Все уроки этого курса

  1. Строительные блоки DDD: сущности и объекты-значения
  2. Агрегаты, репозитории и фабрики
  3. Доменные события и доменные службы
  4. Ограниченные контексты и контекстное отображение
← Назад к PHP Academy