В чем разница и когда что использовать? Это был один из вопросов, на которые я пытался получить ответ.
Попытаюсь тут описать ту практику, которую считаю не плохой. С примерами на PHP. Постараюсь описывать на простом языке — без использования сложной терминологии.
Лучше всего это два подхода понимаются в слоистой архитектуре(слой презентации, доменный, , инфраструктураприложение — те, которые на текущий момент я использую в итоговом примере).
DTO(Data Transfer Object)
DTO — это объект класса необходим для передачи структурированной информации из одного места(метода, функции, слоя) в другое.
Важный момент передавать стараться только скаляры(не объекты), может быть исключения, но лучше попытаться все же не использовать объекты.
-
Передача данных, где много параметров на примере отправки письма:
// у нас есть функция отправки заказного письма - внутри который мы эмитируем выбор города // Передача через параметры // тут мы вызываем ее и передаем набор данных // Проблемы: много параметров, function sendMail1( string $name, string $family, string $country, string $city, string $street, int $numberHome, ?int $room = null ) { selectCity($city); } sendMail1('Иван', 'Иванов', 'Россия', 'Ставрополь', 'ул.Мира', 2, 186); // Передача через ассоциативный массив // Проблемы: // нужно точно знать название ключа (подсказок совсем нет) // проверять на существование ключа (может быть sity или вообще не быть) // проверять соответствие типу данных (придет null где не надо или строка вместо числа) // может содержать неконтролируемый поток информации(например при передаче request()) и без дебаггера вообще не разобраться что находится внутри function sendMail2(array $client) { selectCity($client['city']); } $client = [ 'name' => 'Иван', 'family' => 'Иванов', 'country' => 'Россия', 'city' => 'Ставрополь', 'street' => 'ул.Мира', 'numberHome' => 2, 'room' => 186 ]; sendMail2($client); // Использование DTO // Должен быть максимально простой и без возможности изменения (readonly) function sendMail3(ClientMailDTO $client) { selectCity($client->city); } final readonly class ClientMailDTO { public function __construct( public string $name, public string $family, public string $country, public string $city, public string $street, public int $numberHome, public ?int $room = null ) {} } sendMail3(new ClientMailDTO( 'Иван', 'Иванов', 'Россия', 'Ставрополь', 'ул.Мира', 2, 186 ));
Из плюсов DTO — это больше удобство, которое обычно выражается при использовании «слоистых» приложений или сервисов, чтоб выглядел более лаконично.
Что же касается практической пользы то это:
-
Не изменяемость значений, после их передачи
-
более удобный способ обращений к коллекции принимаемых данных, так как IDE подсвечивает возможные варианты. Особенно удобно, когда много входящих данных и есть подобно названные внутренние переменные.
Подсказка phpstorm -
Если нам необходимо получить из какого то метода эти данные, то тут — однозначно только DTO (причину по которой не подходит массив — описано выше)
// Возвращаем данные для отправки письма в виде DTO function getMailData(): ClientMailDTO { ... return new ClientMailDTO( 'Иван', 'Иванов', // и т.д. ) }Минусы — увеличивает объем кода и усложняет проект(опять же, зависит от контекста).
php < 8
Если у Вас нет readonly код будет немного длиннее, но в целом тоже можно использовать
class ClientMailPhp7DTO { public function __construct( private string $name, private string $family, private string $country, private string $city, private string $street, private int $numberHome, private ?int $room = null ) {} public function getName(): string { return $this->name; } public function getFamily(): string { return $this->family; } // ... дальше по аналогии }
Value object (объект значение)
VO — задача передать уже не просто данные, а так сказать валидированные на сколько это возможно.
В основном нужен для слоистой архитектуры, а именно доменного слоя или бизнес логики.
class NumberHomeVO{ public function __construct( readonly public int $value ) { // Номер дома должен быть больше 0 if ($this->value<0){ throw new Exception('Не корректный номер дома'); } } } function repairHome(NumberHomeVO $home) { // Оправляем рабочего на этот номер дома)) SendWorker($home->value);// тут мы точно знаем, что значение на корректное(на сколько это возможно для дальнейшей обработки(поиска по бд или создании в бд) }
VO Так же могут быть и составными, когда корректность данных зависит ни от одного элемента, а от нескольких
class ManyVO { /** * @throws Exception */ public function __construct( readonly public int $summa, readonly public string $currency ) { if ($this->currency !== 'USD' || $this->currency !== 'RUB') { throw new Exception('Не корректная сумма денег'); } } }
То есть на выходить мы получает корректный объект данных, со стороны бизнес требований(определенная валюта)
Проверки могут быть сложнее и их количество может быть гораздо больше
Пакет для упрощения проверки на валидность
Для проверок можно так же использовать пакет `Webmozart\Assert;`
use Webmozart\Assert\Assert; /** * Проверка email на валидность */ final readonly class Email { public function __construct( public string $value, ) { Assert::notEmpty($this->value); Assert::email($this->value); } }
Реализация VO
Используем объекты реализованный на основе классов VO для передачи в конструктор, таким образом внутри класса Post мы получаем валидные данные, с которыми уже можем работать — не переживая о некорректном содержании.
final class Post { public function __construct( public TitleVO $name, public EmailVO $emailVO, //... и т.д. ) {} }
Теперь работа с DTO и VO на примере
Пример старался написать максимально простым, что хотелось показать:
-
DTO — для передачи данных из слоя
PRESENTATIONв слойAPPLICATION -
VO — для валидации и преобразования данных в какие то бизнес сущности
-
Обработка идет так же по всем слоям, то есть уровень понимания
исключенияна каждом слое свой, как и обработка его. Например-
Презентация — что то не так
-
Приложение — записать в лог ошибку, что б дальше дебажить
-
// PRESENTATION // передаем в сервис только простые данные из приходящих откуда-то(например по API) //Controller class PostController { public function create(Request $request) { try { PostService::create(new PostCreateDTO( $request->get('title'), $request->get('text'), )); // ответ 201 - все хорошо } catch (Exception $e) { // ответ 400 - не корректный запрос } } } // APPLICATION // В приложении описываем - с какими данными будем работать class PostCreateDTO { public function __construct( readonly public string $title, readonly public string $text, ) {} } class PostService { /** * @throws Exception */ static function create(PostCreateDTO $DTO): void { // преобразуем простые данные в уже логически корректные данные и создаем (бизнес корректную, валидную) Post сущность try { $post = new PostModel( new TitleVO($DTO->title), new ContentVO($DTO->text) ); } catch (Exception $e) { // тут выбираем действия, если создать не вышло Log::error($e->getMessage()); throw new Exception('Проверьте корректность данных'); } // ... как то дальше сохраняем пост или же что-то еще делаем } } // DOMAIN // тут описываем какие у нас будут правила по этому объекту // у VO выкидываем исключения, что б дальше обработать их class TitleVO { /** * @throws Exception */ public function __construct( readonly public string $value, ) { if (empty($this->value) || strlen($this->value) < 2) { throw new Exception('Не корректное название'); } } } class ContentVO { /** * @throws Exception */ public function __construct( readonly public string $value, ) { if (empty($this->value) || strlen($this->value) < 40) { throw new Exception('Не корректное содержимое'); } } } final class PostModel { public function __construct( TitleVO $title, ContentVO $content, ) {} }
Код написал больше для понимания использования DTO и VO, максимально упрощенный. Так же не писал про инфраструктуру, счет ее излишней в примере.
Что же касается SOLID принципов, DDD, чистой архитектуры и т.д. старался не освещать в текущей статье, что бы порог вхождения(понимание) было как можно более комфортным.
ссылка на оригинал статьи https://habr.com/ru/articles/916590/
Добавить комментарий