Агрегация и спецификация в облегченном DDD

Обсуждаем, как правильно строить приложения
Ответить
springimport
Сообщения: 6
Зарегистрирован: 2016.05.25, 17:17

Агрегация и спецификация в облегченном DDD

Сообщение springimport » 2018.03.09, 19:56

Первый раз разбираюсь в DDD потому что по-другому уже никак. Дальше будет много текста который должнен объяснить проблемы и попытки их решения.
Началось все с одновременной работы с API (magento 2) и БД. Те сущности которые не связаны, относительно легко управлялись через простые модели, но ад начался где есть смешение. Например, есть заказы из API и платежи из БД. Это и подтолкнуло на путь DDD.
Сделал несколько слоев. В целом, решил использовать свой упрощенный вариант DDD:
API - ApiClient - ApiEntity - Repository (Entity) - Service - Controller
MySQL - Model - Repository (Entity) - Service - Controller

Пример выбрки: контроллер UserController запрашивает команду getAll() в сервисе UserService который запрашивает пользователей в UserRepository ожидая что ему вернут \EntityInterface User[] (решил что Entity будут моделями Yii потому что так проще, хотя и не по канонам). После получения Entities контроллер может их отобразить.
Пример добавления: контроллер UserController загружает данные в UserEntity и валидирует. После отправляет их в сервис который отправляет их в репозиторий который их добавляет.
По незнанию столкнулся с несколькими проблемами:
  • Во-первых, оказывается, очень нужна какая-то "спецификация". Ведь у разных источников разные способы фильтрации и нельзя за репозиторием знать их реализацию. У yii есть простая реализация в Query; у Symofony все очень грамотно, но сложно; другие библиотеки по типу rulez сложны и не очень подходят. По сути нужно простое findBy(['a' => 'b']) или $criteria->where('a = b')->orWhere('a = c')->andWhere('d = e'). Другое дело это все потом переводить в формат для источника.
    Пока что репозиторий содержит функцию гидратора и это тоже мешает сделать фильтрацию: я бы хотел фильтровать по "полям" которые могут быть в другом формате. Получается, гидратор нужен и тут для конвертации.
  • Во-вторых, тоже внезапно оказалось что неправильно будет если хранить объекты как-попало. Рано или поздно получится что в памяти будут 2 заказа или пользователя с разными данными, но по сути это будет один и тот же пользователь. Для этого придумали aggregate root. Только как это реализовать не очень понятно.
    Использую yiitech/embedded для загрузки вложенных объектов и работы с ними. Проблема в том что он создает новые объекты которые не будут связаны с моим потенциальным aggregate root.
В целом, мне кажется что это правильный путь. Он позволяет абстрагироваться от любых проблем и работать с простыми моделями прям как в туториале yii :)
Только реализация всего напоминает написание своего фреймворка и yii тут не сильно помогает, а иногда и мешает. "Чем дальше в лес - тем толще партизаны" - чем больше реализовываю тем больше нерешенных вопросов.

Хочу узнать, нормальная ли такая облегченная реализация DDD и есть ли какие-то несложные реализации аграгации и спецификации?

anton_z
Сообщения: 354
Зарегистрирован: 2017.01.15, 15:01

Re: Агрегация и спецификация в облегченном DDD

Сообщение anton_z » 2018.03.10, 02:34

springimport писал(а):
2018.03.09, 19:56
Началось все с одновременной работы с API (magento 2) и БД. Те сущности которые не связаны, относительно легко управлялись через простые модели, но ад начался где есть смешение. Например, есть заказы из API и платежи из БД. Это и подтолкнуло на путь DDD.
DDD не предназначен для решения вашей проблемы. Он предназначен для другого. Вам надо грамотно распределить обязанности между объектами и всего лишь. Опишите Ваши варианты использования на бумаге, разберите их пошагово, выделите объекты, распределите поведение между ними на основе принципов GRASP.

Судя по описанию бизнес-логика у Вас достаточно простая, обычный e-commerce, ни к чему вам изолировать домен и создавать всякие Services, Hydrators и Specification ради того, чтобы быть DDD. Писали запрос на QueryBuilder, было удобно - ну так и пишите, не надо ничего придумывать - простоты эти "изобретения" вам точно не добавят.
springimport писал(а):
2018.03.09, 19:56
Только реализация всего напоминает написание своего фреймворка
Такое ощущение обычно указывает на то, что DDD неприменим. Если очень грубо, то DDD это для проектов от 10 разработчиков где есть сложный для понимания незнакомый домен для которого нет готовых решений (типа magento) и нет или мало аналогов (негде пример посмотреть). В одиночку или вдвоем/втроем, я бы к DDD не подходил.
springimport писал(а):
2018.03.09, 19:56
Во-вторых, тоже внезапно оказалось что неправильно будет если хранить объекты как-попало. Рано или поздно получится что в памяти будут 2 заказа или пользователя с разными данными, но по сути это будет один и тот же пользователь. Для этого придумали aggregate root. Только как это реализовать не очень понятно.
Использую yiitech/embedded для загрузки вложенных объектов и работы с ними. Проблема в том что он создает новые объекты которые не будут связаны с моим потенциальным aggregate root.
Если вам мешает yiitech/emdedded - не используйте его. Такие расширения иногда могут быть не до конца продуманы концептуально и поэтому при их использовании могут возникать проблемы. (это не про yiitech а в общем - мешает - не пользуйся)

Проблема нескольких объектов в памяти, представляющих одну сущность решается двумя способами:

1. При помощи паттерна Identity Map, который можно реализовать при помощи хеша id => Entity в репозитории,

2. Изменением способа работы с БД. Данные сущности всегда соответствуют данным БД - то есть при любых изменениях, сразу вызывается save(). Это реализуется в yii очень просто, так как ActiveRecord как паттерн изначально для этого и задумывался:

Код: Выделить всё


class Entity extends CActiveRecord 
{

    public function doBusinessLogic($something)
    {
        $this->state = $something;
        $this->update(['state']);
    }
}

Т.е. в клиетском коде модели нет такого:

Код: Выделить всё


$model->setAttributes($_POST);

if($model->validate()) {

    $model->doBusinessLogic();

    $model->save();

}



А есть вот это:

Код: Выделить всё


$form = new ModelForm();

$form->setAttributes($_POST);

if($form->validate()) {
    
    $model->doBusinessLogic($form->data()); //save() внутри и так у любого метода, который изменяет данные модели
    
}


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

Валидацию не делайте прямо в сущности (так можно только в совсем рудиментарных случаях) - делайте отдельный класс формы. Пусть сущности будут всегда валидными.
springimport писал(а):
2018.03.09, 19:56

Он позволяет абстрагироваться от любых проблем
Таких способов работы не существует. DDD имеет четкие границы применимости как и любой другой инструмент.
Последний раз редактировалось anton_z 2018.03.13, 00:45, всего редактировалось 1 раз.

springimport
Сообщения: 6
Зарегистрирован: 2016.05.25, 17:17

Re: Агрегация и спецификация в облегченном DDD

Сообщение springimport » 2018.03.12, 16:06

anton_z писал(а):
2018.03.10, 02:34

DDD не предназначен для решения вашей проблемы. Он предназначен для другого. Вам надо грамотно распределить обязанности между объектами и всего лишь. Опишите Ваши варианты использования на бумаге, разберите их пошагово, выделите объекты, распределите
поведение между ними на основе принципов GRASP.
Может и не предназначен... Мне нужно редактировать данные из API? Нужно. Это можно сделать без дополнительных моделей? Нет. Это можно сделать без репозиториев? Можно, только это будут те же репозитории. Вот так я прихожу к выводу что DDD нормально вписывается.
anton_z писал(а):
2018.03.10, 02:34

Судя по описанию бизнес-логика у Вас достаточно простая, обычный e-commerce, ни к чему вам изолировать домен и создавать всякие Services, Hydrators и Specification ради того, чтобы быть DDD. Писали запрос на QueryBuilder, было удобно - ну так и пишите, не надо ничего придумывать - простоты эти "изобретения" вам точно не добавят.
Бизнес-логика не особо сложная, конечно. Мне кажется что вы не поняли с чем я имею дело. У меня бд используется в 30% случаях, а API в 70%. И QueryBuilder мне не поможет.

Ну и ссылку вам на https://github.com/magento/magento2/blo ... /Order.php. Я бы тоже хотел чтобы m2 не была такая сложная.
anton_z писал(а):
2018.03.10, 02:34

Проблема нескольких объектов в памяти, представляющих одну сущность решается двумя способами:

1. При помощи паттерна Identity Map, который можно реализовать при помощи хеша id => Entity в репозитории,

2. Изменением способа работы с БД. Данные сущности всегда соответствуют данным БД - то есть при любых изменениях, сразу вызывается save(). Это реализуется в yii очень просто, так как ActiveRecord как паттерн изначально для этого и задумывался:

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

Валидацию не делайте прямо в сущности (так можно только в совсем рудиментарных случаях) - делайте отдельный класс формы. Пусть сущности будут всегда валидными.

Таких способов работы не существует. DDD имеет четкие границы применимости как и любой другой инструмент.
Вы опять про базу, с ней нет проблем. "Попробуйте сохранять каждый раз состояние в API": каждый запрос "стоит" где-то 200 мс. Я бы посмотрел на это.
Иногда начинаются сложности при создании заказа где нужно отправить где-то 15 запросов, а вы про "при любых изменениях". Видимо вы предлагаете переходить с калькулятора C4 на какой-нибудь c5.18xlarge с 72 cpu.
anton_z писал(а):
2018.03.10, 02:34
CActiveRecord
yii2.

noLogicOnlyWar
Сообщения: 75
Зарегистрирован: 2017.07.04, 20:53

Re: Агрегация и спецификация в облегченном DDD

Сообщение noLogicOnlyWar » 2018.03.12, 23:43

Может и не предназначен... Мне нужно редактировать данные из API? Нужно. Это можно сделать без дополнительных моделей? Нет. Это можно сделать без репозиториев? Можно, только это будут те же репозитории. Вот так я прихожу к выводу что DDD нормально вписывается.
Описали типичный crud, но вывод - ддд нормально вписывается?
Вы опять про базу, с ней нет проблем. "Попробуйте сохранять каждый раз состояние в API": каждый запрос "стоит" где-то 200 мс. Я бы посмотрел на это.
Вам написали первый вариант, но вы его не заметили и сразу начали критиковать 2й?
Иногда начинаются сложности при создании заказа где нужно отправить где-то 15 запросов, а вы про "при любых изменениях". Видимо вы предлагаете переходить с калькулятора C4 на какой-нибудь c5.18xlarge с 72 cpu.
curl_multi_exec, нода, асинхронная шина команд, да хоть reactphp.

anton_z
Сообщения: 354
Зарегистрирован: 2017.01.15, 15:01

Re: Агрегация и спецификация в облегченном DDD

Сообщение anton_z » 2018.03.13, 00:55

springimport писал(а):
2018.03.12, 16:06

Иногда начинаются сложности при создании заказа где нужно отправить где-то 15 запросов, а вы про "при любых изменениях". Видимо вы предлагаете переходить с калькулятора C4 на какой-нибудь c5.18xlarge с 72 cpu.
Я действительно имел ввиду базу так как вы писали про QueryBuilder yii и про проблему уникальности, которая возникает и с базой. Я привел Вам два решения. С API второе решение не очень хорошо работает и то вы не описали вначале, что у вас API достаточно медленный, бывает когда API на том же сервере и все очень быстро. Можете "ржать" сколько угодно.
springimport писал(а):
2018.03.12, 16:06
Может и не предназначен... Мне нужно редактировать данные из API? Нужно. Это можно сделать без дополнительных моделей? Нет. Это можно сделать без репозиториев? Можно, только это будут те же репозитории. Вот так я прихожу к выводу что DDD нормально вписывается.
DDD это не репозитории. Что вы подразумеваете под DDD?
springimport писал(а):
2018.03.12, 16:06

Ну и ссылку вам на https://github.com/magento/magento2/blo ... /Order.php. Я бы тоже хотел чтобы m2 не была такая сложная.
А чего там сложного? Гигантский конструктор, геттеры, сеттеры, арифметика на 4к строк. Там нет ничего, в чем бы не смог разобраться любой грамотный пхпшник - бизнес-правила и все.

springimport
Сообщения: 6
Зарегистрирован: 2016.05.25, 17:17

Re: Агрегация и спецификация в облегченном DDD

Сообщение springimport » 2018.03.13, 16:13

anton_z писал(а):
2018.03.13, 00:55
Я действительно имел ввиду базу так как вы писали про QueryBuilder yii и про проблему уникальности, которая возникает и с базой. Я привел Вам два решения. С API второе решение не очень хорошо работает и то вы не описали вначале, что у вас API достаточно медленный, бывает когда API на том же сервере и все очень быстро. Можете "ржать" сколько угодно.
API на том же сервере, но само API не сделать быстрее, оно просто тяжелое.
anton_z писал(а):
2018.03.13, 00:55
DDD это не репозитории. Что вы подразумеваете под DDD?
Как для начинающего, для меня это: репозитории, гидраторы (пока что в репозиториях), модели, сервисы (команды) и легкие контроллеры. Хотелось бы внедрить еще aggregate root и "спецификации" для репозиториев. Все это не от хорошей жизни: если я бы мог, то с удовольствием бы сделал как в туториале yii где контроллер делает load, validate и save без каких-то сложных правил. Но в моем случае модели абстрагируют код от API и базы. Я уже пробовал все смешивать и получалось это не очень. Уверен что для простого API в стиле "взять твит" или "отправить смс" все это не нужно.
anton_z писал(а):
2018.03.13, 00:55
А чего там сложного? Гигантский конструктор, геттеры, сеттеры, арифметика на 4к строк. Там нет ничего, в чем бы не смог разобраться любой грамотный пхпшник - бизнес-правила и все.
Там все в стиле "нет ничего такого", а в итоге получается так что просто сделать var_dump объекта нельзя (тупо не хватает памяти). Сложность идет от большого количества абстракций которые сами по себе логичны.

anton_z
Сообщения: 354
Зарегистрирован: 2017.01.15, 15:01

Re: Агрегация и спецификация в облегченном DDD

Сообщение anton_z » 2018.03.14, 00:56

springimport писал(а):
2018.03.13, 16:13
Там все в стиле "нет ничего такого", а в итоге получается так что просто сделать var_dump объекта нельзя (тупо не хватает памяти). Сложность идет от большого количества абстракций которые сами по себе логичны.
Отладчик в помощь (xdebug, zend debugger).
springimport писал(а):
2018.03.13, 16:13

Как для начинающего, для меня это: репозитории, гидраторы (пока что в репозиториях), модели, сервисы (команды) и легкие контроллеры. Хотелось бы внедрить еще aggregate root и "спецификации" для репозиториев. Все это не от хорошей жизни: если я бы мог, то с удовольствием бы сделал как в туториале yii где контроллер делает load, validate и save без каких-то сложных правил. Но в моем случае модели абстрагируют код от API и базы. Я уже пробовал все смешивать и получалось это не очень. Уверен что для простого API в стиле "взять твит" или "отправить смс" все это не нужно.
Как и все новички, Вы восприняли DDD с тактической стороны. Это ошибка. DDD создан в первую очередь как способ создания модели предметной области, которую легко поддерживать в актуальном состоянии и легко изменять согласно новым требованиям. Для этого разрабатывается Единый Язык для общения с заказчиками/их представителями, исходный код программы делается максимально близким к ментальной модели (выраженной в виде диаграмм классов и взаимодействия) для облегчения входа в проект (поняли модель на диаграммах и по описанию - легко разобрались в коде).

То что вы написали это паттерны реализации. Они являются одним из инструментов применяемых в DDD, но они ему не тождественны. Все что Вы хотите, можно назвать Doctrine ORM для API, т.е. DataMapper+UnitOfWork для API.

Почему вы все способы разработки делите на "туториал yii" и DDD? Подходов гораздо больше. Может пробовать просто ООП? Просто разбить код на классы и методы не используя туториалов к yii (а опираясь на возможности, предоставляемые yii) и монструозных методологий и паттернов?

Вот чем Вам Specifcation например, может помочь?

springimport
Сообщения: 6
Зарегистрирован: 2016.05.25, 17:17

Re: Агрегация и спецификация в облегченном DDD

Сообщение springimport » 2018.03.14, 18:42

anton_z писал(а):
2018.03.14, 00:56
Отладчик в помощь (xdebug, zend debugger).
Да, только им и можно отлаживать объекты.
anton_z писал(а):
2018.03.14, 00:56
Как и все новички, Вы восприняли DDD с тактической стороны. Это ошибка. DDD создан в первую очередь как способ создания модели предметной области, которую легко поддерживать в актуальном состоянии и легко изменять согласно новым требованиям. Для этого разрабатывается Единый Язык для общения с заказчиками/их представителями, исходный код программы делается максимально близким к ментальной модели (выраженной в виде диаграмм классов и взаимодействия) для облегчения входа в проект (поняли модель на диаграммах и по описанию - легко разобрались в коде).

То что вы написали это паттерны реализации. Они являются одним из инструментов применяемых в DDD, но они ему не тождественны. Все что Вы хотите, можно назвать Doctrine ORM для API, т.е. DataMapper+UnitOfWork для API.

Почему вы все способы разработки делите на "туториал yii" и DDD? Подходов гораздо больше. Может пробовать просто ООП? Просто разбить код на классы и методы не используя туториалов к yii (а опираясь на возможности, предоставляемые yii) и монструозных методологий и паттернов?
Про это тоже думал и понял что не обязательно плохо если у меня будет своя реализация. Тем более, нет серебряной пули (а жаль :).
Что-то мне подсказывает что в DDD модели являются иконой и все вокруг них строится, а в случае работы с API модели теряют часть власти над данными. Конечно, если API не просто прослойка над бд (rest).
anton_z писал(а):
2018.03.14, 00:56
Вот чем Вам Specifcation например, может помочь?
Мне не совсем он нужен, просто он наиболее близок по смыслу: он фильтрует имеющиеся результаты, а мне нужен фильтр к не имеющимся. На данный момент в репозитории появляется все больше методов типа findByCustomer, findByCustomerWithOrders и т.д. что в хорошем случае должно быть заменено на findBy($criteria) где

Код: Выделить всё

Criteria|array $criteria
.

Ответить