diff --git a/ru-RU/Chapter4/Entities.md b/ru-RU/Chapter4/Entities.md
new file mode 100644
index 0000000..fe101fc
--- /dev/null
+++ b/ru-RU/Chapter4/Entities.md
@@ -0,0 +1,1851 @@
+Глава 4. Сущности (Entities)
+==
+
+--------
+
+
+
+
+Мы говорили о преимуществах попытки сначала смоделировать все в Домене как Объект Значения. Но при моделировании Домена, вероятно, возникнут ситуации, когда вы обнаружите, что какая-то концепция во повсеместном языке требует потока идентичности.
+
+
+
+## Введение
+
+
+Четкие примеры объектов, требующих идентификатора:
+
+- Человек. У человека всегда есть удостоверение личности, и оно всегда одинаково с точки зрения его имени или удостоверения личности.
+
+- Заказ в системе электронной коммерции. В таком контексте каждый новый созданный порядок имеет свою собственную идентичность, и она одинакова с течением времени.
+
+
+У этих понятий есть идентичность, которая неизменна со временем. Независимо от того, сколько раз данные в концепциях меняются, их идентичности остаются прежними.
+Именно это делает их сущностями, а не Объектами Значений. С точки зрения реализации PHP, они были бы простыми классами.
+Например, рассмотрим следующее в случае человека:
+
+
+
+```php
+namespace Ddd\Identity\Domain\Model;
+
+class Person
+{
+ private $identificationNumber;
+ private $firstName;
+ private $lastName;
+ public function __construct(
+ $anIdentificationNumber, $aFirstName, $aLastName
+ ) {
+ $this->identificationNumber = $anIdentificationNumber;
+ $this->firstName = $aFirstName;
+ $this->lastName = $aLastName;
+ }
+ public function identificationNumber()
+ {
+ return $this->identificationNumber;
+ }
+ public function firstName()
+ {
+ return $this->firstName;
+ }
+ public function lastName()
+ {
+ return $this->lastName;
+ }
+}
+}
+```
+
+
+Или рассмотрим следующее в случае заказа:
+
+
+
+```php
+namespace Ddd\Billing\Domain\Model\Order;
+
+class Order
+{
+ private $id;
+ private $amount;
+ private $firstName;
+ private $lastName;
+ public function __construct(
+ $anId, Amount $amount, $aFirstName, $aLastName
+ ) {
+ $this->id = $anId;
+ $this->amount = $amount;
+ $this->firstName = $aFirstName;
+ $this->lastName = $aLastName;
+ }
+ public function id()
+ {
+ return $this->id;
+ }
+ public function firstName()
+ {
+ return $this->firstName;
+ }
+ public function lastName()
+ {
+ return $this->lastName;
+ }
+}
+```
+
+
+
+
+
+
+## Объекты Vs. Примитивные типы
+
+Большую часть времени Идентификатор Сущности представляется как примитивный тип - обычно строка или целое число. Но использование объекта Value для его представления имеет больше преимуществ:
+
+- Объекты Value являются неизменяемыми, поэтому их нельзя изменять.
+
+- Объекты-значения - это сложные типы, которые могут иметь пользовательское поведение, то, что примитивные типы не могут иметь. Возьмем в качестве примера операцию равенства.
+С помощью Value Objects операции равенства могут моделироваться и инкапсулироваться в свои собственные классы, делая концепции переходящими от неявных к явным.
+
+
+
+Рассмотрим возможную реализацию для OrderId, идентификатора заказа, который превратился в объект Value:
+
+
+
+```php
+namespace Ddd\Billing\Domain\Model;
+
+class OrderId
+{
+ private $id;
+ public function __construct($anId)
+ {
+ $this->id = $anId;
+ }
+ public function id()
+ {
+ return $this->id;
+ }
+ public function equalsTo(OrderId $anOrderId)
+ {
+ return $anOrderId->id === $this->id;
+ }
+}
+```
+
+
+
+Существует несколько реализаций, которые можно использовать для реализации идентификатора заказа. Приведенный выше пример довольно прост.
+Как описано в главе 3, «Объекты значений», можно сделать метод __construct () частным и использовать статические фабричные методы для создания новых экземпляров.
+Поговорите со своей командой, проведите эксперимент и договоритесь. Поскольку идентификаторы сущностей не являются сложными Объектами Значений, мы рекомендуем вам здесь не беспокоиться слишком много.
+
+
+Возвращаясь к Order, пришло время обновить ссылки на OrderId:
+
+
+
+```php
+class Order
+{
+ private $id;
+ private $amount;
+ private $firstName;
+ private $lastName;
+ public function __construct(
+ OrderId $anOrderId, Amount $amount, $aFirstName, $aLastName
+ ) {
+ $this->id = $anOrderId;
+ $this->amount = $amount;
+ $this->firstName = $aFirstName;
+ $this->lastName = $aLastName;
+ }
+ public function id()
+ {
+ return $this->id;
+ }
+ public function firstName()
+ {
+ return $this->firstName;
+ }
+ public function lastName()
+ {
+ return $this->lastName;
+ }
+ public function amount()
+ {
+ return $this->amount;
+ }
+}
+```
+
+- Наша сущность имеет идентификатор, смоделированный с использованием Value Object.
+
+Рассмотрим различные способы создания идентификатора заказа.
+
+
+
+
+## Операция идентификации
+
+
+Как было указано ранее, идентификатор сущности определяет его. Таким образом, обработка этого является важным аспектом Сущности.
+Обычно существует четыре способа определения Identity of an Entity: механизм персистентности предоставляет Identity, клиент предоставляет Identity,
+само приложение предоставляет Identity или другой ограниченный контекст предоставляет Identity.
+
+
+
+
+### Механизм хранения (БД) генерирует Идентификатор
+
+Обычно самый простой способ генерации Identity - делегировать его механизму персистентности, потому что подавляющее большинство механизмов персистентности
+поддерживают некую генерацию Identity - как атрибут `AUTO_INCREMENT` MySQL или последовательности Postgres и Oracle.
+Это, хотя и просто, имеет главный недостаток: мы не будем иметь Идентичность Сущности, пока мы не сохраним ее. Таким образом, в некоторой степени, если мы идем с Mechanism Generated Identity,
+мы свяжем операцию Identity с базовым хранилищем персистентности:
+
+
+
+```sql
+CREATE TABLE `orders` (
+ `id` int(11) NOT NULL auto_increment,
+ `amount` decimal (10,5) NOT NULL,
+ `first_name` varchar(100) NOT NULL,
+ `last_name` varchar(100) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+```
+
+
+И тогда мы могли бы рассмотреть этот код:
+
+
+
+```php
+namespace Ddd\Identity\Domain\Model;
+
+class Person
+{
+ private $identificationNumber;
+ private $firstName;
+ private $lastName;
+ public function __construct(
+ $anIdentificationNumber, $aFirstName, $aLastName
+ ) {
+ $this->identificationNumber = $anIdentificationNumber;
+ $this->firstName = $aFirstName;
+ $this->lastName = $aLastName;
+ }
+ public function identificationNumber()
+ {
+ return $this->identificationNumber;
+ }
+ public function firstName()
+ {
+ return $this->firstName;
+ }
+ public function lastName()
+ {
+ return $this->lastName;
+ }
+}
+```
+
+
+Если вы когда-либо пытались создать собственный ORM, вы уже испытали эту ситуацию.
+
+Каков подход к созданию нового Person? Если база данных собирается создать идентификатор, нужно ли передавать его в конструктор?
+Когда и где магия, которая обновит Person? Что произойдет, если мы в конечном итоге не будем сохранять Сущность?
+
+
+
+
+#### Суррогатная идентичность
+
+Иногда при использовании ORM для отображения сущностей в хранилище сохраняемости накладываются некоторые ограничения -
+например, доктрина требует целочисленное поле, если используется стратегия генератора IDENTITY. Это может привести к конфликту с моделью домена, если для нее требуется другой тип удостоверения.
+Простейшим способом обработки такой ситуации является использование супертипа слоя, в котором помещается поле Identity, созданное для хранилища сохраняемости:
+
+
+
+
+```php
+namespace Ddd\Common\Domain\Model;
+
+abstract class IdentifiableDomainObject
+{
+ private $id;
+ protected function id()
+ {
+ return $this->id;
+ }
+ protected function setId($anId)
+ {
+ $this->id = $anId;
+ }
+}
+```
+
+
+
+```php
+namespace Acme\Billing\Domain;
+
+use Acme\Common\Domain\IdentifiableDomainObject;
+
+class Order extends IdentifiableDomainObject
+{
+ private $orderId;
+ public function orderId()
+ {
+ if (null === $this->orderId) {
+ $this->orderId = new OrderId($this->id());
+ }
+ return $this->orderId;
+ }
+}
+```
+
+
+
+
+#### Active Record Vs. Data Mapper для Богатых Доменных Моделей
+
+Каждый проект всегда сталкивается с решением, какая ORM должна использоваться. Есть много хороших ОRM для PHP: Doctrine, Propel, Eloquent, Paris, и много остальных.
+
+Большинство из них являются реализациями Active Record. Реализация Active Record в основном подходит для приложений CRUD, но она не является идеальным решением для моделей Rich Domain по следующим причинам:
+
+- Шаблон активной записи предполагает связь «один к одному» между сущностью и таблицей базы данных. Таким образом, она связывает конструкцию базы данных с конструкцией объектной системы.
+А в модели Rich Domain иногда Сущности строятся с информацией, которая может поступать из разных источников данных.
+
+
+- Продвинутые вещи, такие как коллекции и наследование, сложно реализовать.
+
+
+- Большинство реализаций вынуждают использовать посредством наследования некие конструкции, которые навязывают несколько конвенций.
+Это может привести к постоянной утечке в модель домена путем соединения модели домена с ORM. Единственная реализация Active Record, которая не навязывает наследование от базового класса, -Castle ActiveRecord от Castle Project, .NET framework.
+В то время как это приводит к некоторой степени разделения между устойчивостью и проблемами Домена в созданных Сущностях, это не отделяет детали устойчивости низкого уровня от конструкции домена высокого уровня.
+
+
+
+Как упоминалось в предыдущей главе, в настоящее время лучшим ORM для PHP является Doctrine, которая является реализацией шаблона Отображения Данных. Data Mapper отделяет проблемы персистентности от проблем домена, что приводит к появлению сущностей, свободных от персистентности.
+Это делает инструмент лучшим для тех, кто хочет построить модель Rich Domain.
+
+
+
+
+
+### Клиент предоставляет Идентификатор
+
+Иногда при работе с определенными Доменами Идентификация приходят естественным образом, когда клиент использует Модель Домена. Это, скорее всего, идеальный случай, потому что Идентичность может быть легко смоделирована. Рассмотрим рынок продажи книг:
+
+
+
+```php
+namespace Ddd\Catalog\Domain\Model\Book;
+
+class ISBN
+{
+ private $isbn;
+ private function __construct($anIsbn)
+ {
+ $this->setIsbn($anIsbn);
+ }
+ private function setIsbn($anIsbn)
+ {
+ $this->assertIsbnIsValid($anIsbn, 'The ISBN is invalid.');
+ $this->isbn = $anIsbn;
+ }
+ public static function create($anIsbn)
+ {
+ return new static($anIsbn);
+ }
+ private function assertIsbnIsValid($anIsbn, $string)
+ {
+ // ... Validates an ISBN code
+ }
+}
+```
+
+
+Согласно Википедии: Международный стандартный номер книги (ISBN) является уникальным числовым коммерческим идентификатором книги.
+ISBN назначается каждому изданию и варианту (за исключением повторной печати) книги. Например, электронная книга, обратная страница и печатное издание одной и той же книги будут иметь разные ISBN.
+Длина ISBN составляет 13 цифр, если она назначена 1 января 2007 года или позднее, и 10 цифр, если она назначена до 2007 года. Метод присвоения ISBN является национальным и варьируется от страны к стране,
+часто в зависимости от того, насколько велика издательская индустрия в стране.
+
+Самое интересное в ISBN то, что он уже определен в Домене, это действительный идентификатор, потому что он уникален, и его можно легко проверить.
+Это хороший пример идентификатора, предоставленного клиентом:
+
+
+
+```php
+namespace Ddd\Catalog\Domain\Model\Book;
+
+class Book
+{
+ private $isbn;
+ private $title;
+ public function __construct(ISBN $anIsbn, $aTitle)
+ {
+ $this->isbn = $anIsbn;
+ $this->title = $aTitle;
+ }
+}
+```
+
+
+Теперь, это простой вопрос использования его:
+
+
+
+```php
+$book = new Book(
+ ISBN::create('...'),
+ 'Domain-Driven Design in PHP'
+);
+```
+
+
+
+
+
+### Приложение создаёт Идентификатор
+
+Если клиент не может предоставить идентификатор в целом, предпочтительным способом обработки операции идентификации является разрешение приложению генерировать идентификаторы, обычно через UUID.
+Это наш рекомендуемый подход в случае, если у вас нет сценария, как показано в предыдущем разделе.
+
+**Согласно Википедии:**
+Цель UUID - дать возможность распределенным системам однозначно идентифицировать информацию без существенной централизованной координации. В этом контексте слово «уникальный» следует понимать как практически уникальный, а не как гарантированный уникальный.
+Поскольку идентификаторы имеют конечный размер, два различных элемента могут совместно использовать один и тот же идентификатор. Это форма хеш-коллизии.
+Размер идентификатора и процесс генерации должны быть выбраны так, чтобы сделать это достаточно маловероятным на практике.
+Любой может создать UUID и использовать его для идентификации чего-либо с разумной уверенностью, что тот же идентификатор никогда не будет непреднамеренно создан кем-либо для идентификации чего-либо другого.
+Поэтому информация, помеченная UUID, может быть впоследствии объединена в единую базу данных без необходимости разрешения конфликтов идентификаторов (ID).
+
+
+
+> Существует несколько библиотек в PHP, которые генерируют UUID, и они могут быть найдены на Packagist: https ://packagist.org/search/?q=uuid.
+> Лучшая рекомендация - разработанная Беном Рэмзи: https ://github.com/ramsey/uuid
+> потому что имеет много наблюдателей на GitHub и миллионы установок на Packagist.
+
+
+
+Предпочтительным местом для создания Идентификатора будет Репозиторий (подробнее об этом в Главе 10.
+
+Пример Репозитория:
+
+
+
+```php
+namespace Ddd\Billing\Domain\Model\Order;
+
+interface OrderRepository
+{
+ public function nextIdentity();
+ public function add(Order $anOrder);
+ public function remove(Order $anOrder);
+}
+```
+
+
+При использовании Доктрины необходимо создать пользовательский Репозиторий, реализующий такой интерфейс. Он в основном создает новый Идентификатор и использует EntityManager для сохранения и удаления сущностей.
+
+Небольшое изменение состоит в том, чтобы поместить реализацию nextIdentity в интерфейс, который станет абстрактным классом:
+
+
+
+```php
+namespace Ddd\Billing\Infrastructure\Domain\Model\Order;
+
+use Ddd\Billing\Domain\Model\Order\Order;
+use Ddd\Billing\Domain\Model\Order\OrderId;
+use Ddd\Billing\Domain\Model\Order\OrderRepository;
+use Doctrine\ORM\EntityRepository;
+
+class DoctrineOrderRepository
+ extends EntityRepository
+ implements OrderRepository
+{
+ public function nextIdentity()
+ {
+ return OrderId::create();
+ }
+ public function add(Order $anOrder)
+ {
+ $this->getEntityManager()->persist($anOrder);
+ }
+ public function remove(Order $anOrder)
+ {
+ $this->getEntityManager()->remove($anOrder);
+ }
+}
+```
+
+
+Давайте быстро рассмотрим конечную реализацию объекта OrderId Value Object:
+
+
+
+
+```php
+namespace Ddd\Billing\Domain\Model\Order;
+
+use Ramsey\Uuid\Uuid;
+
+class OrderId
+{
+ private $id;
+ private function __construct($anId = null)
+ {
+ $this->id = $id ? :Uuid::uuid4()->toString();
+ }
+ public static function create($anId = null )
+ {
+ return new static($anId);
+ }
+}
+```
+
+
+Основная проблема, связанная с этим подходом, как будет показано в следующих разделах, заключается в том, насколько просто сохранять объекты, содержащие объекты значений.
+Однако сопоставление встроенных объектов значений, находящихся внутри сущности, может быть сложным в зависимости от ORM.
+
+
+
+
+### Другой ограниченный контекст генерирует идентификатор
+
+Вероятно, это наиболее сложная стратегия создания Identity, поскольку она вынуждает локальную сущность зависеть не только от локальных событий ограниченного контекста, но и от внешних событий ограниченного контекста.
+Так что с точки зрения технического обслуживания стоимость была бы высокой.
+
+Другой ограниченный контекст предоставляет интерфейс для выбора идентификатора из локальной сущности. Некоторые из экспонируемых свойств могут восприниматься как собственные.
+
+Когда необходима синхронизация между сущностями ограниченных контекстов, она обычно может быть достигнута с помощью управляемой событиями архитектуры в каждом из ограниченных контекстов, которые должны быть уведомлены при изменении исходной сущности.
+
+
+
+
+
+## Сохраняющиеся сущности
+
+В настоящее время, как обсуждалось ранее в главе, лучшим инструментом для сохранения состояния сущности в постоянном хранилище является доктрина ORM.
+Доктрина имеет несколько способов определения метаданных Сущности: с помощью аннотаций в коде Сущности, XML, YAML или простого PHP. В этой главе мы подробно обсудим, почему аннотации не являются лучшими для использования при сопоставлении сущностей.
+
+
+
+### Настройка доктрины
+
+Прежде всего, мы должны установить Доктрину через Composer. В корневой папке проекта должна быть выполнена следующая команда:
+
+
+
+> php composer.phar require "doctrine/orm=^2.5"
+
+
+
+Затем эти строки позволяют настроить доктрину:
+
+
+
+```php
+require_once '/path/to/vendor/autoload.php';
+
+use Doctrine\ORM\Tools\Setup;
+use Doctrine\ORM\EntityManager;
+
+$paths = ['/path/to/entity-files'];
+$isDevMode = false;
+
+// the connection configuration
+$dbParams = [
+ 'driver' => 'pdo_mysql',
+ 'user' => 'the_database_username',
+ 'password' => 'the_database_password',
+ 'dbname' => 'the_database_name'
+];
+
+$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
+$entityManager = EntityManager::create($dbParams, $config);
+```
+
+
+
+
+### Маппинг Entities
+
+По умолчанию в документации доктрины представлены примеры кода с использованием аннотаций. Итак, мы начинаем пример кода, используя аннотации и обсуждая, почему их следует избегать, когда это возможно.
+Для этого мы вернем класс Order, обсуждавшийся ранее в этой главе.
+
+
+
+
+#### Маппинг Entities с помощью аннотированного кода
+
+Когда была выпущена доктрина, броским способом отображения объектов в примерах кода было использование аннотаций.
+
+**Что такое аннотация?**
+Аннотация - это особая форма метаданных. В PHP он помещен в комментарии к исходному коду. Например, PHPDocumentor использует аннотации для построения информации API, а PHPUnit использует некоторые аннотации для указания поставщиков данных или для предоставления ожиданий относительно исключений, порождаемых фрагментом кода:
+
+
+
+```php
+class SumTest extends PHPUnit_Framework_TestCase
+{
+ /** @dataProvider aMethodName */
+ public function testAddition() {
+ //...
+ }
+}
+```
+
+
+Чтобы сопоставить объект Order с хранилищем, исходный код Order должен быть изменен для добавления аннотаций доктрины:
+
+
+
+```php
+use Doctrine\ORM\Mapping\Entity;
+use Doctrine\ORM\Mapping\Id;
+use Doctrine\ORM\Mapping\GeneratedValue;
+use Doctrine\ORM\Mapping\Column;
+
+/** @Entity */
+class Order {
+
+ /** @Id @GeneratedValue(strategy="AUTO") */
+ private $id;
+
+ /** @Column(type="decimal", precision="10", scale="5") */
+ private $amount;
+
+ /** @Column(type="string") */
+ private $firstName;
+
+ /** @Column(type="string") */
+ private $lastName;
+
+ public function __construct(
+ Amount $anAmount,
+ $aFirstName,
+ $aLastName
+ ) {
+ $this->amount = $anAmount;
+ $this->firstName = $aFirstName;
+ $this->lastName = $aLastName;
+ }
+ public function id()
+ {
+ return $this->id;
+ }
+ public function firstName()
+ {
+ return $this->firstName;
+ }
+ public function lastName()
+ {
+ return $this->lastName;
+ }
+ public function amount()
+ {
+ return $this->amount;
+ }
+}
+
+```
+
+
+Затем, чтобы сохранить Сущность в постоянном хранилище, так же легко сделать следующее:
+
+
+
+```php
+$order = new Order(
+ new Amount(15, Currency::EUR()),
+ 'AFirstName',
+ 'ALastName'
+);
+$entityManager->persist($order);
+$entityManager->flush();
+
+```
+
+
+На первый взгляд, этот код выглядит просто, и это может быть простым способом указать информацию сопоставления. Но это имеет определенную цену. Что странного в окончательном коде?
+
+Во-первых, проблемы домена смешиваются с проблемами инфраструктуры. Order является концепцией домена, тогда как Table, Column и т. д. являются проблемами инфраструктуры.
+
+В результате эта Сущность тесно связана с информацией отображения, указанной аннотациями в исходном коде.
+Если бы Сущность требовалось сохранить с помощью другого менеджера Сущности и с другими метаданными сопоставления, это было бы невозможно.
+
+Аннотации, как правило, приводят к побочным эффектам и плотной связи, поэтому их лучше не использовать.
+
+Так какой лучший способ указать информацию о сопоставлении? Лучший способ - это способ, позволяющий отделить информацию о сопоставлении от самой сущности.
+Это может быть достигнуто с помощью сопоставления XML, сопоставления YAML или PHP. В этой книге мы рассмотрим сопоставление XML.
+
+
+
+
+#### Маппинг Entities с помощью XML
+
+Чтобы отобразить объект Order с помощью сопоставления XML, код настройки доктрины должен быть слегка изменен:
+
+
+
+```php
+
+require_once '/path/to/vendor/autoload.php';
+
+use Doctrine\ORM\Tools\Setup;
+use Doctrine\ORM\EntityManager;
+
+$paths = ['/path/to/mapping-files'];
+$isDevMode = false;
+
+// the connection configuration
+$dbParams = [
+ 'driver' => 'pdo_mysql',
+ 'user' => 'the_database_username',
+ 'password' => 'the_database_password',
+ 'dbname' => 'the_database_name',
+];
+
+$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
+$entityManager = EntityManager::create($dbParams, $config);
+
+```
+
+
+Файл сопоставления должен быть создан по пути, по которому доктрина будет выполнять поиск файлов сопоставления,
+а файлы сопоставления должны быть названы в честь полного имени класса, заменяя обратную косую черту \ точками.
+
+
+Рассмотрим следующее:
+`Acme\Billing\Domain\Model\Order`
+
+файл сопоставления будет иметь имя:
+ `Acme.Billing.Domain.Model.Order.dcm.xml`
+
+
+Кроме того, удобно, чтобы все файлы сопоставления использовали специальную XML-схему, созданную специально для указания информации сопоставления:
+https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd
+
+
+
+
+
+#### Сопоставление Идентификатора Сущности
+
+Наш идентификатор, OrderId, является объектом значения. Как видно из предыдущей главы, существуют различные подходы к отображению объекта Value Object с использованием доктрины, встраиваемых объектов и пользовательских типов.
+Если в качестве идентификаторов используются объекты значений, лучшим вариантом является использование пользовательских типов.
+
+Интересной новой особенностью Doctrine 2.5 является то, что теперь можно использовать Объекты в качестве идентификаторов для Сущностей, если они реализуют магический метод __toString ().
+Таким образом, мы можем добавить __toString () к нашим объектам Identity Value Object и использовать их в наших сопоставлениях:
+
+
+
+```php
+namespace Ddd\Billing\Domain\Model\Order;
+
+use Ramsey\Uuid\Uuid;
+
+class OrderId
+{
+// ...
+ public function __toString()
+ {
+ return $this->id;
+ }
+}
+
+```
+
+
+Проверьте реализацию пользовательских типов доктрины. Они наследуют GuidType, поэтому их внутреннее представление будет UUID.
+Необходимо указать собственный перевод базы данных. Затем необходимо зарегистрировать пользовательские типы, прежде чем использовать их.
+Если требуется справка по этим шагам, то [Custom Mapping Types](https://www.doctrine-project.org/projects/doctrine-orm/en/3.1/cookbook/custom-mapping-types.html) являются хорошей ссылкой.
+
+
+
+```php
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+use Doctrine\DBAL\Types\GuidType;
+
+class DoctrineOrderId extends GuidType
+{
+ public function getName()
+ {
+ return 'OrderId';
+ }
+
+ public function convertToDatabaseValue($value, AbstractPlatform $platform)
+ {
+ return $value->id();
+ }
+
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ return new OrderId($value);
+ }
+}
+
+```
+
+
+Наконец, мы настроим регистрацию пользовательских типов. Опять же, мы должны обновить начальную загрузку:
+
+
+
+```php
+require_once '/path/to/vendor/autoload.php';
+// ...
+
+\Doctrine\DBAL\Types\Type::addType(
+ 'OrderId',
+ 'Ddd\Billing\Infrastructure\Domain\Model\DoctrineOrderId'
+);
+
+$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
+$entityManager = EntityManager::create($dbParams, $config);
+
+```
+
+
+
+
+
+#### Окончательный файл сопоставления
+
+Со всеми изменениями, мы наконец-то готовы, так что давайте посмотрим на окончательный файл сопоставления.
+Наиболее интересной детализацией является проверка того, как идентификатор сопоставляется с нашим определенным пользовательским типом для OrderId:
+
+
+
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+
+## Тестирование Entities
+
+Относительно легко протестировать Сущности, просто потому, что они являются простыми старыми PHP-классами с действиями, производными от концепции Домена, которую они представляют.
+В центре внимания теста должны быть инварианты, которые защищает Сущность, потому что поведение на Сущностях, вероятно, будет смоделировано вокруг этих инвариантов.
+
+Например, для простоты предположим, что для блога необходима модель домена. Один из возможных вариантов мог бы заключаться в следующем:
+
+
+
+```php
+class Post
+{
+ private $title;
+ private $content;
+ private $status;
+ private $createdAt;
+ private $publishedAt;
+
+ public function __construct($aContent, $title)
+ {
+ $this->setContent($aContent);
+ $this->setTitle($title);
+ $this->unpublish();
+ $this->createdAt(new DateTimeImmutable());
+ }
+
+ private function setContent($aContent)
+ {
+ $this->assertNotEmpty($aContent);
+ $this->content = $aContent;
+ }
+
+ private function setTitle($aPostTitle)
+ {
+ $this->assertNotEmpty($aPostTitle);
+ $this->title = $aPostTitle;
+ }
+
+ private function setStatus(Status $aPostStatus)
+ {
+ $this->assertIsAValidPostStatus($aPostStatus);
+ $this->status = $aPostStatus;
+ }
+
+ private function createdAt(DateTimeImmutable $aDate)
+ {
+ $this->assertIsAValidDate($aDate);
+ $this->createdAt = $aDate;
+ }
+
+ private function publishedAt(DateTimeImmutable $aDate)
+ {
+ $this->assertIsAValidDate($aDate);
+ $this->publishedAt = $aDate;
+ }
+
+ public function publish()
+ {
+ $this->setStatus(Status::published());
+ $this->publishedAt(new DateTimeImmutable());
+ }
+
+ public function unpublish()
+ {
+ $this->setStatus(Status::draft());
+ $this->publishedAt = null ;
+ }
+
+ public function isPublished()
+ {
+ return $this->status->equalsTo(Status::published());
+ }
+
+ public function publicationDate()
+ {
+ return $this->publishedAt;
+ }
+}
+
+```
+
+
+```php
+class Status
+{
+ const PUBLISHED = 10;
+ const DRAFT = 20;
+ private $status;
+ public static function published()
+ {
+ return new self(self::PUBLISHED);
+ }
+ public static function draft()
+ {
+ return new self(self::DRAFT);
+ }
+ private function __construct($aStatus)
+ {
+ $this->status = $aStatus;
+ }
+ public function equalsTo(self $aStatus)
+ {
+ return $this->status === $aStatus->status;
+ }
+}
+
+```
+
+
+
+Чтобы проверить эту модель домена, мы должны убедиться, что тест охватывает все Post инварианты:
+
+
+
+```php
+class PostTest extends PHPUnit_Framework_TestCase
+{
+ /** @test */
+ public function aNewPostIsNotPublishedByDefault()
+ {
+ $aPost = new Post(
+ 'A Post Content',
+ 'A Post Title'
+ );
+ $this->assertFalse(
+ $aPost->isPublished()
+ );
+ $this->assertNull(
+ $aPost->publicationDate()
+ );
+ }
+
+ /** @test */
+ public function aPostCanBePublishedWithAPublicationDate()
+ {
+ $aPost = new Post(
+ 'A Post Content',
+ 'A Post Title'
+ );
+ $aPost->publish();
+ $this->assertTrue($aPost->isPublished());
+ $this->assertInstanceOf(
+ 'DateTimeImmutable',
+ $aPost->publicationDate()
+ );
+ }
+}
+
+```
+
+
+
+
+### Дата Время (DateTimes)
+
+Поскольку DateTimes широко используется в сущностях, мы считаем важным указать конкретные подходы к единицам тестирования сущностей, которые имеют поля с типами дат.
+Учтите, что Пост является новым, если он был создан в течение последних 15 дней:
+
+
+
+```php
+class Post
+{
+ const NEW_TIME_INTERVAL_DAYS = 15;
+ // ...
+ private $createdAt;
+
+ public function __construct($aContent, $title)
+ {
+ // ...
+ $this->createdAt(new DateTimeImmutable());
+ }
+
+ // ...
+ public function isNew()
+ {
+ return
+ (new DateTimeImmutable())
+ ->diff($this->createdAt)
+ ->days <= self::NEW_TIME_INTERVAL_DAYS;
+ }
+}
+
+```
+
+
+Метод isNew () должен сравнивать два значения DateTimes. Это сравнение между датой создания Post и сегодняшней датой. Мы вычисляем разницу и проверяем, меньше ли она указанного количества дней.
+Как выполнить единичное тестирование метода isNew ()? Как мы продемонстрировали в реализации, трудно воспроизвести конкретные потоки в наших тестовых комплектах. Какие у нас есть варианты?
+
+
+
+
+#### Передача все дат в виде параметров
+
+Одним из возможных вариантов может быть передача всех дат в качестве параметров при необходимости:
+
+
+
+```php
+class Post
+{
+ // ...
+ public function __construct($aContent, $title, $createdAt = null)
+ {
+ // ...
+ $this->createdAt($createdAt ?: new DateTimeImmutable());
+ }
+
+ // ...
+ public function isNew($today = null)
+ {
+ return
+ ($today ? :new DateTimeImmutable())
+ ->diff($this->createdAt)
+ ->days <= self::NEW_TIME_INTERVAL_DAYS;
+ }
+}
+
+```
+
+Это самый простой подход для целей модульного тестирования. Просто проходите различные пары дат, чтобы проверить все возможные сценарии со 100-процентным охватом.
+Однако, если учесть код клиента, который создает и запрашивает результат метода isNew (), все выглядит не так хорошо. Полученный в результате код может быть немного странным из-за постоянной передачи текущего DateTime:
+
+
+
+```php
+$aPost = new Post(
+ 'Hello world!',
+ 'Hi',
+ new DateTimeImmutable()
+);
+$aPost->isNew(
+ new DateTimeImmutable()
+);
+
+```
+
+
+
+
+
+#### Класс тестирования
+
+Другой альтернативой является использование шаблона Test Class. Идея состоит в том, чтобы расширить класс Post новым, которым мы можем манипулировать, чтобы форсировать определенные сценарии.
+Этот новый класс будет использоваться только в целях модульного тестирования. Плохая новость в том, что мы должны немного изменить исходный класс Post, извлекая некоторые методы и изменяя некоторые поля и методы с частных на защищенные.
+Некоторые разработчики могут беспокоиться об увеличении видимости свойств класса только из-за причин тестирования. Однако мы считаем, что в большинстве случаев это того стоит:
+
+
+
+```php
+class Post
+{
+ protected $createdAt;
+ public function isNew()
+ {
+ return
+ ($this->today())
+ ->diff($this->createdAt)
+ ->days <= self::NEW_TIME_INTERVAL_DAYS;
+ }
+ protected function today()
+ {
+ return new DateTimeImmutable();
+ }
+ protected function createdAt(DateTimeImmutable $aDate)
+ {
+ $this->assertIsAValidDate($aDate);
+ $this->createdAt = $aDate;
+ }
+}
+
+```
+
+
+Как вы видите, мы извлекли логику для получения сегодняшней даты в метод today (). Таким образом, применяя шаблон метода, можно изменить его поведение от производного класса.
+Нечто подобное происходит с методом и полем createAt. Теперь они защищены, поэтому их можно использовать и переопределять в производных классах:
+
+
+
+```php
+class PostTestClass extends Post
+{
+ private $today;
+
+ protected function today()
+ {
+ return $this->today;
+ }
+ public function setToday($today)
+ {
+ $this->today = $today;
+ }
+}
+
+```
+
+
+С учетом этих изменений теперь мы можем протестировать наш исходный класс Post с помощью тестирования PostTestClass:
+
+
+
+```php
+class PostTest extends PHPUnit_Framework_TestCase
+{
+ // ...
+
+ /** @test */
+ public function aPostIsNewIfIts15DaysOrLess()
+ {
+ $aPost = new PostTestClass(
+ 'A Post Content',
+ 'A Post Title'
+ );
+ $format = 'Y-m-d';
+ $dateString = '2016-01-01';
+
+ $createdAt = DateTimeImmutable::createFromFormat(
+ $format,
+ $dateString
+ );
+
+ $aPost->createdAt($createdAt);
+
+ $aPost->setToday(
+ $createdAt->add(
+ new DateInterval('P15D')
+ )
+ );
+
+ $this->assertTrue($aPost->isNew());
+
+ $aPost->setToday(
+ $createdAt->add(
+ new DateInterval('P16D')
+ )
+ );
+
+ $this->assertFalse($aPost->isNew());
+ }
+}
+
+```
+
+Последняя деталь: при таком подходе невозможно достичь 100-процентного покрытия в классе Post, потому что метод today () никогда не будет выполняться.
+Однако он может быть охвачен другими тестами.
+
+
+
+
+
+#### External Fake
+
+Другой вариант заключается в переносе вызовов конструктора DateTimeImmutable или именованных конструкторов с использованием нового класса и некоторых статических методов.
+При этом мы можем статически изменить результат этих методов, чтобы вести себя по-разному на основе конкретных сценариев тестирования:
+
+
+
+```php
+class Post
+{
+ // ...
+ private $createdAt;
+ public function __construct($aContent, $title)
+ {
+ // ...
+ $this->createdAt(MyCustomDateTimeBuilder::today());
+ }
+ // ...
+ public function isNew()
+ {
+ return
+ (MyCustomDateTimeBuilder::today())
+ ->diff($this->createdAt)
+ ->days <= self::NEW_TIME_INTERVAL_DAYS;
+ }
+}
+
+```
+
+
+Для получения текущего DateTime теперь используется статический вызов MyCustomDateTimeBuilder:: today (). Этот класс также имеет несколько методов установки, чтобы подделать результат для возврата в следующих вызовах:
+
+
+
+```php
+class PostTest extends PHPUnit_Framework_TestCase
+{
+ // ...
+ /** @test */
+ public function aPostIsNewIfIts15DaysOrLess()
+ {
+ $createdAt = DateTimeImmutable::createFromFormat(
+ 'Y-m-d',
+ '2016-01-01'
+ );
+ MyCustomDateTimeBuilder::setReturnDates(
+ [
+ $createdAt,
+ $createdAt->add(
+ new DateInterval('P15D')
+ ),
+ $createdAt->add(
+ new DateInterval('P16D')
+ )
+ ]
+ );
+ $aPost = new Post(
+ 'A Post Content' ,
+ 'A Post Title'
+ );
+ $this->assertTrue(
+ $aPost->isNew()
+ );
+ $this->assertFalse(
+ $aPost->isNew()
+ );
+ }
+}
+
+```
+
+
+Основная проблема этого подхода заключается в том, что он статически связан с объектом.
+В зависимости от вашего варианта использования, также будет сложно создать гибкий поддельный объект.
+
+
+
+
+#### Рефлексия
+
+Можно также использовать методы Reflection для построения нового класса Post с пользовательскими датами.
+Рассмотрим
+
+[Mimic](https://github.com/keyvanakbary/mimic)
+
+простую функциональную библиотеку для прототипирования объектов, гидратации данных и отображения данных:
+
+
+
+```php
+namespace Domain;
+
+use mimic as m;
+
+class ComputerScientist {
+ private $name;
+ private $surname;
+
+ public function __construct($name, $surname) {
+ $this->name = $name;
+ $this->surname = $surname;
+ }
+
+ public function rocks() {
+ return $this->name . ' ' . $this->surname . ' rocks!';
+ }
+}
+
+assert(m\prototype('Domain\ComputerScientist') instanceof Domain\ComputerScientist);
+
+m\hydrate('Domain\ComputerScientist', array(
+ 'name' => 'John',
+ 'surname' => 'McCarthy'
+))->rocks(); //John McCarthy rocks!
+
+assert(m\expose(new Domain\ComputerScientist('Grace', 'Hopper')) == array(
+ 'name' => 'Grace',
+ 'surname' => 'Hopper'
+));
+
+```
+
+
+> **Поделиться и обсудить**
+>Обсудите с коллегами по работе, как правильно протестировать объекты с фиксированнымидатами и придумайте дополнительные альтернативы.
+
+
+
+Если вы хотите узнать больше о тестировании шаблонов и подходов, ознакомьтесь с книгой xUnit Test Patterns: Refactoring Test Code by Gerard Meszaros.
+
+
+
+
+
+## Валидация
+
+Валидация является очень важным процессом в нашей модели домена. Она проверяет не только правильность атрибутов, но и правильность целых объектов и их состава. Для поддержания этой модели в допустимом состоянии требуются различные уровни проверки.
+То, что объект состоит из допустимых атрибутов (базовых), не обязательно означает, что объект (в целом) является действительным. И наоборот: действительные объекты необязательно равны действительным составам.
+
+
+
+
+### Валидация атрибутов
+
+Некоторые люди понимают проверку как процесс, посредством которого служба проверяет состояние данного объекта. В этом случае проверка соответствует подходу Design-by-contract, который состоит из предварительных условий, постусловий и инвариантов.
+Одним из таких способов защиты отдельного атрибута является использование главы 3 «Объекты значений». Чтобы сделать наш дизайн более гибким для изменений, мы концентрируемся только на утверждении предварительных условий Домена, которые должны быть выполнены.
+Здесь мы будем использовать охрану как простой способ проверки предварительных условий:
+
+
+
+```php
+class Username
+{
+ const MIN_LENGTH = 5;
+ const MAX_LENGTH = 10;
+ const FORMAT = '/^[a-zA-Z0-9_]+$/';
+ private $username;
+
+ public function __construct($username)
+ {
+ $this->setUsername($username);
+ }
+
+ private function setUsername($username)
+ {
+ $this->assertNotEmpty($username);
+ $this->assertNotTooShort($username);
+ $this->assertNotTooLong($username);
+ $this->assertValidFormat($username);
+ $this->username = $username;
+ }
+
+ private function assertNotEmpty($username)
+ {
+ if (empty($username)) {
+ throw new InvalidArgumentException('Empty username');
+ }
+ }
+
+ private function assertNotTooShort($username)
+ {
+ if (strlen($username) < self::MIN_LENGTH) {
+ throw new InvalidArgumentException(sprintf(
+ 'Username must be %d characters or more',
+ self::MIN_LENGTH
+ ));
+ }
+ }
+
+ private function assertNotTooLong($username)
+ {
+ if (strlen($username) > self::MAX_LENGTH) {
+ throw new InvalidArgumentException(sprintf(
+ 'Username must be %d characters or less',
+ self::MAX_LENGTH
+ ));
+ }
+ }
+
+ private function assertValidFormat($username)
+ {
+ if (preg_match(self:: FORMAT, $username) !== 1) {
+ throw new InvalidArgumentException(
+ 'Invalid username format'
+ );
+ }
+ }
+}
+
+```
+
+
+Как видно из приведенного выше примера, для создания объекта «Значение имени пользователя» необходимо выполнить четыре предварительных условия. Оно:
+
+- Не должно быть пустым
+- Должно содержать не менее 5 символов
+- Должно содержать менее 10 символов
+- Должен иметь формат буквенно-цифровых символов или знаков подчеркивания
+
+
+Если все предварительные условия выполнены, атрибут будет установлен, и объект будет успешно построен. В противном случае будет создано исключение InvalidArgumentException, выполнение будет остановлено, и клиенту будет выдана ошибка.
+
+Некоторые разработчики могут рассмотреть этот вид проверочного защитного программирования. Однако мы не проверяем, является ли ввод строкой или что значения null не разрешены.
+Мы не можем избегать людей, использующих наш код неправильно, но мы можем контролировать правильность нашего состояния Домена. Как видно из главы 3, «Объекты значений», проверка также может помочь нам в обеспечении безопасности.
+
+[Defensive programming](https://en.wikipedia.org/wiki/Defensive_programming) не плохая вещь.
+В общем, это имеет смысл при разработке компонентов или библиотек, которые будут использоваться в качестве третьей стороны в других проектах.
+Однако при разработке собственного ограниченного контекста этих дополнительных параноидных проверок (нулей, основных типов, подсказок типа и т. д.) можно избежать, чтобы увеличить скорость разработки, полагаясь на охват набора модульных тестов.
+
+
+
+
+### Проверка всего объекта
+
+Бывают случаи, когда объект, состоящий из допустимых свойств в целом, все еще может считаться недействительным. Может быть заманчиво добавить этот вид проверки к самому объекту, но обычно это ==антипаттерн==. Проверка на более высоком уровне изменяется в ритме, отличном от ритма самой логики объекта. Кроме того, рекомендуется разделять эти обязанности.
+Проверка информирует клиента о любых обнаруженных ошибках или собирает результаты для последующего просмотра, так как иногда мы не хотим останавливать выполнение при первом признаке неисправности.
+
+Абстрактный и повторно используемый валидатор может быть примерно таким:
+
+
+
+```php
+abstract class Validator
+{
+ private $validationHandler;
+
+ public function __construct(ValidationHandler $validationHandler)
+ {
+ $this->validationHandler = $validationHandler;
+ }
+ protected function handleError($error)
+ {
+ $this->validationHandler->handleError($error);
+ }
+ abstract public function validate();
+}
+
+```
+
+В качестве конкретного примера мы хотим проверить весь объект Location, состоящий из допустимых объектов Country, City и Postcode Value Objects.
+Однако эти отдельные значения могут находиться в невалидном состоянии во время проверки. Может быть, город не является частью страны, или почтовый индекс не соответствует формату города:
+
+
+
+```php
+class Location
+{
+ private $country;
+ private $city;
+ private $postcode;
+ public function __construct(
+ Country $country, City $city, Postcode $postcode
+ ) {
+ $this->country = $country;
+ $this->city = $city;
+ $this->postcode = $postcode;
+ }
+ public function country()
+ {
+ return $this->country;
+ }
+ public function city()
+ {
+ return $this->city;
+ }
+ public function postcode()
+ {
+ return $this->postcode;
+ }
+}
+```
+
+
+Validator проверяет состояние объекта Location в целом, анализируя значение взаимосвязей между свойствами:
+
+
+
+```php
+class LocationValidator extends Validator
+{
+ private $location;
+ public function __construct(
+ Location $location, ValidationHandler $validationHandler
+ ) {
+ parent:: __construct($validationHandler);
+ $this->location = $location;
+ }
+ public function validate()
+ {
+ if (!$this->location->country()->hasCity(
+ $this->location->city()
+ )) {
+ $this->handleError('City not found');
+ }
+ if (!$this->location->city()->isPostcodeValid(
+ $this->location->postcode()
+ )) {
+ $this->handleError('Invalid postcode');
+ }
+ }
+}
+
+```
+
+
+После установки всех свойств мы сможем проверить Сущность, скорее всего, после некоторых описанных процессов. На поверхности это выглядит так, как если бы местоположение подтвердило себя.
+Однако это не так. Класс Location делегирует эту проверку конкретному экземпляру валидатора, разделяя эти две четкие обязанности:
+
+
+
+```php
+class Location
+{
+ // ...
+ public function validate(ValidationHandler $validationHandler)
+ {
+ $validator = new LocationValidator($this, $validationHandler);
+ $validator->validate();
+ }
+}
+
+```
+
+
+
+
+#### Decoupling сообщений валидации
+
+С некоторыми незначительными изменениями в существующей реализации мы можем отделить сообщения проверки от средства проверки:
+
+
+
+```php
+class LocationValidationHandler implements ValidationHandler
+{
+ public function handleCityNotFoundInCountry();
+ public function handleInvalidPostcodeForCity();
+}
+```
+
+
+
+```php
+class LocationValidator
+{
+ private $location;
+ private $validationHandler;
+
+ public function __construct(
+ Location $location,
+ LocationValidationHandler $validationHandler
+ ) {
+ $this->location = $location;
+ $this->validationHandler = $validationHandler;
+ }
+ public function validate()
+ {
+ if (!$this->location->country()->hasCity(
+ $this->location->city()
+ )) {
+ $this->validationHandler->handleCityNotFoundInCountry();
+ }
+ if (! $this->location->city()->isPostcodeValid(
+ $this->location->postcode()
+ )) {
+ $this->validationHandler->handleInvalidPostcodeForCity();
+ }
+ }
+}
+
+```
+
+
+Нам также необходимо изменить подпись метода проверки следующим образом:
+
+
+
+```php
+class Location
+{
+ // ...
+ public function validate(
+ LocationValidationHandler $validationHandler
+ ) {
+ $validator = new LocationValidator($this, $validationHandler);
+ $validator->validate();
+ }
+}
+```
+
+
+
+
+### Валидация композиции объектов
+
+Проверка достоверности композиций объектов может быть сложной задачей. Таким образом, предпочтительным способом достижения этого является использование доменной службы.
+Затем служба взаимодействует с репозиториями, чтобы получить действительный агрегат. Из-за возможных сложных графов объектов, которые могут быть созданы,
+агрегат может находиться в промежуточном состоянии, требуя предварительной проверки других агрегатов.
+События домена можно использовать для уведомления других частей системы о проверке определенного элемента.
+
+
+
+
+
+### Entities и Events домена
+
+Мы рассмотрим главу 6 «Доменные события» в будущем; однако важно подчеркнуть, что операции, выполняемые с сущностями, могут инициировать события домена.
+Этот подход используется для передачи изменения Домена другим частям Приложения или даже другим Приложениям, как показано в Главе 12, Интеграция ограниченных контекстов:
+
+
+
+```php
+class Post
+{
+ // ...
+ public function publish()
+ {
+ $this->setStatus(
+ Status::published()
+ );
+ $this->publishedAt(new DateTimeImmutable());
+ DomainEventPublisher::instance()->publish(
+ new PostPublished($this->id)
+ );
+ }
+ public function unpublish()
+ {
+ $this->setStatus(
+ Status::draft()
+ );
+ $this-> publishedAt = null;
+ DomainEventPublisher::instance()->publish(
+ new PostUnpublished($this->id)
+ );
+ }
+ // ...
+}
+```
+
+
+События домена могут даже запускаться при создании нового экземпляра нашей Сущности:
+
+
+
+```php
+class User
+{
+ // ...
+ public function __construct(UserId $userId, $email, $password)
+ {
+ $this->setUserId($userId);
+ $this->setEmail($email);
+ $this->setPassword($password);
+ DomainEventPublisher::instance()->publish(
+ new UserRegistered($this->userId)
+ );
+ }
+}
+```
+
+
+
+
+### В заключении
+
+Некоторые понятия в Домене требуют Идентичность - то есть изменения их внутренних состояний не меняют их собственные уникальные идентичности.
+Мы видели, как моделирование Identity как объекта-значения приносит такие преимущества, как неизменяемость, в дополнение к логике для работы с самой Identity.
+Мы также показали несколько способов предоставления Identity, переформулированных в следующих указателях:
+
+- Механизм персистентности: Простой в реализации, но вы не будете иметь Identity до сохранения объекта, что задерживает и затрудняет распространение событий.
+
+
+- Суррогатный ID: Некоторым ОРМ требуется дополнительное поле в Сущности, чтобы сопоставить Идентификатор с сохраняющимся механизмом.
+
+
+- Предоставляется клиентом: Иногда Identity подходит под концепцию Домена и вы можете смоделировать его внутри вашего Домена.
+
+
+- Создано приложением: Для создания идентификаторов можно использовать библиотеку.
+
+
+- Генерируется ограниченным контекстом: возможно, самая сложная стратегия. Другие ограниченные контексты обеспечивают интерфейс для создания идентификатора.
+
+
+
+Мы видели и обсуждали Doctrine как механизм персистентности, мы смотрели на недостатки использования шаблона Active Record и, наконец, мы проверили различные уровни проверки Сущности:
+
+- Проверка атрибутов: Проверка специфики в состоянии объекта через предварительные условия, постусловия и инварианты.
+
+
+- Проверка всего объекта: поиск непротиворечивости объекта в целом. Извлечение проверки во внешнюю службу является хорошей практикой.
+
+
+- Композиции объектов: Сложные композиции объектов можно проверить с помощью доменных служб. Хороший способ донести это до остальной части приложения - через События домена.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+