PHP: Это DTO или Объект-Значение?

Источник: «Is it a DTO or a Value Object?»
Обычное недоразумение, это различие между DTO и Объект-Значение. И поэтому я искал способ классифицировать эти объекты без ошибок.

Что такое DTO и как его распознать?

DTO — объект, который содержит примитивные данные (строки, логические значения, числа с плавающей запятой, null, массивы этих типов). Он определяет схемы этих данных, явно объявляя имена полей и их типы. Он может только гарантировать наличие всех этих данных, полагаясь на строгость языка программирования: если конструктор имеет обязательный параметр string, вы должны передать строку или не сможете создать экземпляр объекта. Однако DTO не даёт никаких гарантий, что значения действительно имеют смысл с точки зрения бизнеса. Строки могут быть пустыми, целые числа могут быть отрицательными и т.д.

Существуют разные дизайны классов для DTO:

/**
* @object-type DTO
*
* Использование конструктора и публичных readonly-свойств
*/

final class AnExample
{
public function __construct(
public readonly string $field,
// ...
) {
}
}

/**
* @object-type DTO
*
* Использование конструктора с приватными readonly-свойствами
* и публичными геттерами
*/

final class AnotherExample
{
public function __construct(
private readonly string $field,
// ...
) {
}

public function field(): string
{
return $this->field;
}
}

Что касается именования DTO: я рекомендую не добавлять DTO к самому имени. Если вы хотите, что бы было понятно, что это за тип, добавьте комментарий или выдуманную аннотацию (или атрибут), например @object-type. Это будет полезно для разработчиков не знающих об этих типах объектов. Это может подтолкнуть их к поиску статьи о том, что это значит (может быть, этой статьи :)).

Что такое Объект-Значение и как его распознать?

VO (Объект-Значение) — объект, обернувший одно или несколько значений, или Объектов-Значений. Это гарантирует наличие всех данных, и то, что значения имеют смысл с точки зрения предметной области. Строки больше не будут пустыми, числа будут проверены и соответствовать правильному диапазону. Объект-Значение может это гарантировать, генерируя исключения внутри конструктора, являющегося приватным, заставляя клиента использовать один из статических именованных конструкторов. Это позволяет легко распознать Объект-Значение и чётко отличить его от DTO.

final class AnExample
{
private function __construct(
private string $value
) {
}

public static function fromValue(
string $value
): self {
/*
* Генерирует исключение, когда значение
* не соответствует всем ожиданиям.
*/


return new self($value);
}
}

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

Как мы должны использовать эти типы объектов?

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

DTO

DTO следует использовать только в двух местах: где данные входят в приложение или где они покидают приложение. Несколько примеров использования DTO:

  1. Когда контроллер получает HTTP POST запрос, данные могут иметь любую форму. Нам нужно перейти от бесформенных данных к данным со схемой (проверенные ключи и типы). Для этого мы можем использовать DTO. Библиотека форм может заполнить этот DTO на основе отправленных данных формы, или мы можем использовать сериализатор для преобразования тела запроса в виде обычного текста в заполненный DTO.
  2. Когда мы отправляем HTTP POST запрос к веб-сервису, мы можем сначала собрать входные данные в DTO, а затем сериализовать их в тело запроса, которое наш HTTP-клиент может отправить сервису.
  3. С запросами ситуация аналогичная. Здесь мы можем использовать DTO для представления результата запроса. В качестве примера мы можем передать DTO в шаблон, чтобы отобразить представление на его основе. Мы можем использовать DTO, сериализовать его в JSON и отправить обратно, как ответ API.
  4. Когда мы отправляем HTTP GET запрос к веб-сервису, мы можем сначала десериализовать ответ API в DTO. Чтобы мы могли применить к нему известную схему, а не просто обращаться к ключам массива и угадывать типы. Клиентские пакеты API обычно предлагают DTO для запросов и ответов.

Объекты-Значение

Объект-Значение используется везде, где мы хотим убедиться, что значение соответствует нашим ожиданиям, и не хотим проверять его снова. Мы также используем его для накопления поведения, связанного с определённым значением. Например, у нас есть Объект-Значений EmailAddress, мы знаем, что значение было проверено, чтобы выглядеть как валидный адрес электронной почты, поэтому нам не нужно снова проверять его в других местах. Мы также можем добавить методы к объекту, которые извлекают, например, имя пользователя или хоста из адреса электронной почты.

Объекты-Значения часто используются в предметной области, поскольку гарантии или инварианты являются важной частью бизнеса. Но они не могут быть использованы в любом месте приложения, поскольку каждой части приложения требуются способы централизации некоторых правил, предоставления доказательств правильности и накопления связанного поведения.

Вывод

Можно ещё многое сказать об Объектах-Значениях, но цель данной статьи заключалась не в этом (если вы хотите узнать больше, ознакомьтесь с моей книгой Object Design Style Guide или Implementing Domain-Driven Design Вона Вернона (Vaughn Vernon)). Цель состояла в том, чтобы максимально наглядно показать разницу между DTO и Объектами-Значениями и, надеюсь, их больше не будут путать. Вот сводная таблица:

DTO:

Объект-Значение:

Дополнительные материалы

Предыдущая Статья

PHP: Когда использовать трейт

Следующая Статья

Laravel: Ваши контроллеры должны выглядеть так