DDD, связность модулей

Обсуждаем, как правильно строить приложения
paurlift
Сообщения: 26
Зарегистрирован: 2017.01.29, 20:16

DDD, связность модулей

Сообщение paurlift »

Всем привет! Накопилось много вопросов, по которым с коллегами не можем прийти к однозначному мнению.
Хотелось бы спросить мнение у форумчан (часто встречаю много полезного на этом форуме)

1) Такой пример - клиент оставляет отзыв на сайте.

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


namespace domain/client;

use domain/review/Review;
use domain/review/ReviewDto;

class Client implements ManageReviewInterface, AuthorInterface
{
	private $name;
	private $surname;
	
	public function createReview($reviewId, $placeId, ReviewDto $reviewData)
	{
		return new Review($reviewId, $placeId, $reviewData, $this)
	}
	
	public function authorFullName()
	{
		return $this->name . ' ' . $this->surname;
	}
}

-----------------------

namespace domain/client;

use domain/review/ReviewDto;

interface ManageReviewInterface
{
	public function createReview($placeId, ReviewDto $reviewData);
}

-----------------------

namespace domain/client;

interface AuthorInterface
{
	public function authorFullName();
}

------------------------

namespace domain/review;

class Review 
{
	public finction __construct($reviewId, $placeId, ReviewDto $reviewData, AuthorInterface $author)
	{
		$this->reviewId= (int)$reviewId;
		$this->placeId = (int)$placeId;
		$this->message = $reviewData->message;
		$this->authorName = $author->authorFullName();
	}
}

namespace domain/review;

class ReviewDto 
{
	public $message;
	public $rating;
}

Я думаю смысл понятен. Код сильно урезан. По этому коду несколько вопросов.
1.1) На сколько корректно когда entity из одного модуля создает entity из другого модуля? По DDD так вроде и должно быть, код получается красивый ($client->createReview()), в реальной жизни так и происходит. Здесь есть одно НО - тем самым делаем coupling, хотя опять же связность однонаправленная, что допустимо.
1.2) Используете ли Вы DTO вот таким вот образом? Уточню, в реальности в ReviewDto 10 характеристик, часть из которых не обязательны.
Смущает то что типично DTO используется между Application и Presentation.
1.3) Местонахождения интерфейсов.
AuthorInterface может реализовывать помимо клиента менеджер или партнер или еще какие-то сущности в системе. 1 вариант - такие интерфейсы лучше класть в папку domain/contracts. 2 вариант - класть в папку к каждой сущности (копипаст)
ManageReviewInterface возможно должен быть в модуле review, тогда связность 2-х модулей еще возрастает или также в папке domain/contacts, потому что много кто может управлять созданием и редактирование отзывов.

2) Mapper для ValueObject (дальше VO для краткости).
У нас сделано следующим образом - Repository достает данные и отдает mapper, который с помощью расширения от SamDark - hydrator собирает сущности.Были ли у вас кейсы, когда тебе нужно будет собрать вместо сущности VO?

3) Mapper или Repository знает о domain.
Много примеров типа $mapper->populate('domain/client/Client', $data)
Тем самым инфраструктура (persistence) начинает что-то знать о слое выше (namespace Entity), что в какой-то мере нарушает принцип слоенной архитектуры.
Еще есть что-то вот такое, для агрегатов:

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


namespace infrastructure/persistence/mappers/custom;

use domain/clinet/PhoneNumber;

ClientMapper {
	public function populate($data)
	{
		...
		foreach ($data['phoneNumber'] as $phoneNumber) {
			$client->phoneNumbers[] = new PhoneNumber($phoneNumber);
		} 
	}
}
3.1) Mapper - это же инфраструктура? Почему он знает так много о domain?
3.2) Собственно здесь еще раз возвращаемся ко 2 вопросу, возможно пользоваться mapper для VO и будет что-то типа:

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

	$client->phoneNumbers[] = $phoneNumberMapper->populate($poneNumber);
тем самым не будет хотя бы не будет явного обращения к слою выше.

4) Может ли Repository искать что-то кроме entity, возвращать массивы? Пример - данные для отчета. Или сохранять какую-то статистику, которая позже отсылается в Google Analytics. В этих задачах уже речь не идет о DDD и моделях.
4.1) У репозитория есть интерфейс, где он должен находится? (в application или рядом с реализацией)

5) NextIdentity. В Entity всегда должны быть соблюдены все инварианты и она должны быть валидна. База (MySQL) была спроектирована так, что отличительная особенность entity это Auto Increment Id.
Насколько корректно в транзакции вставлять в базу пустую строку в методе Repository->nextIdentity, чтобы получить и занять ID? Если не удалось создать объект, то транзакция откатывается и пустая строка из базы удаляется. Это пахнет жестким костылем, но в MySQL по-другому никак.

6) Есть ли такое понятие, что Domain слой должен быть максимально независим от других и при желании его можно перенести в другой проект? В реальной жизни сложно придумать такой пример, когда слой бизнеса можно перенести в другое приложение, пусть даже и бизнес схож по деятельности

7) Интефейсы инфраструктурных сервисов кладем на доменный уровень, чтобы не лишать себя возможности использовать их в Domain Service?

Пример TransactionMangerInterface лежать должен где-то на доменном уровне (domain/contacts/TransactionMangerInterface), а его реализация на инфраструктурном уровне (infrastructure/persistence/transaction/TransactionManger). Но в 90% мы его используем в Application Service
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: DDD, связность модулей

Сообщение anton_z »

По поводу интерфейсов. Никакие два модуля не могут ничего знать друг о друге. Даже об интерфейсах.

Каждый модуль должен объявлять интерфейсы для зависимостей, реализации которых ему должен предоставить КОРЕНЬ КОМПОНОВКИ (приложение). В корне компоновки могут содержаться адаптеры, обеспечивающие реализацию того или иного интерфейса модуля с помощью классов другого модуля. Например, в модуле интернет-магазина (ecommerce) у вас объявлен интерфейс для оплаты с помощью платежного агрегатора PaymentAggregatorInterface. У вас есть модуль для платежей через Robokassa, Interkassa и т.д. В приложении можете создать реализацию интерфейса PaymentAggregatorInterface с помощью данных модулей. Также можно сделать модуль-связку: rk-ecommerce и положить реализацию туда. В свою очередь модуль платежной системы может требовать реализацию какого-то своего интерфейса для обработки событий прихода денег, проверки возможности оплаты и.т.п. Реализация последнего также может находиться в корне компоновки или модуле-связке.

В итоге получаете два полностью независимых друг от доуга модуля (ecommerce, rk) и корень комноновки, который знает как их связать.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: DDD, связность модулей

Сообщение anton_z »

paurlift писал(а): 2017.02.23, 15:34
6) Есть ли такое понятие, что Domain слой должен быть максимально независим от других и при желании его можно перенести в другой проект? В реальной жизни сложно придумать такой пример, когда слой бизнеса можно перенести в другое приложение, пусть даже и бизнес схож по деятельности
Практический смысл тут такой: доменный слой желательно делать независимым для того, чтобы можно было обновиться с одной мажорной версии фреймворка на другую, или вообще сменить фреймворк малой кровью.
Что будет если ваше приложение доживет до Yii3? После выхода придется все переписывать, если зависите от AR и проч.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: DDD, связность модулей

Сообщение anton_z »

paurlift писал(а): 2017.02.23, 15:34 3.1) Mapper - это же инфраструктура? Почему он знает так много о domain?
Все могут знать о домене, в т.ч. инфраструктура. Не бывает независимой инфраструктуры. Она по природе своей пишется под конкретный домен. Домен не должен знать об инфраструктуре и других доменах. Он в центре системы.

Однако, инфраструктура не должна брать на себя функции домена и вызывать его поведение (передавать ему управление).
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: DDD, связность модулей

Сообщение ElisDN »

paurlift писал(а): 2017.02.23, 15:34 1.1) На сколько корректно когда entity из одного модуля создает entity из другого модуля?
Если такое необходимо, то это у Вас один модуль.
paurlift писал(а): 2017.02.23, 15:34 1.2) Используете ли Вы DTO вот таким вот образом?
DTO - это структура, а не объект. Рассматривайте его просто как удобную замену ассоциативному массиву и используйте где угодно.
paurlift писал(а): 2017.02.23, 15:34 1.3) Местонахождения интерфейсов.
Если он нужен для review, то и кладите в review.
paurlift писал(а): 2017.02.23, 15:34 2) Mapper для ValueObject (дальше VO для краткости). Были ли у вас кейсы, когда тебе нужно будет собрать вместо сущности VO?
Без разницы, что собирать: viewtopic.php?f=34&t=42318&p=209359#p209358
paurlift писал(а): 2017.02.23, 15:34 3) Mapper или Repository знает о domain. Mapper - это же инфраструктура? Почему он знает так много о domain?
Естественно, что класс сохранения сущности в базу знает всё о сущности. Лишь бы не наоборот.
paurlift писал(а): 2017.02.23, 15:34 4) Может ли Repository искать что-то кроме entity, возвращать массивы? Пример - данные для отчета.
Repository придуман для помешения в него всей работы с БД. Отчёты - это тоже работа с базой. Для удобства создайте отдельный ReadRepository или ReportRepository, чтобы не путаться с доменным.
paurlift писал(а): 2017.02.23, 15:34 4.1) У репозитория есть интерфейс, где он должен находится?
Интерфейс - в домене. Реализация - в инфраструктуре.
paurlift писал(а): 2017.02.23, 15:34 5) NextIdentity. В Entity всегда должны быть соблюдены все инварианты и она должны быть валидна. База (MySQL) была спроектирована так, что отличительная особенность entity это Auto Increment Id... Это пахнет жестким костылем, но в MySQL по-другому никак.
Сделайте рядом таблицу c единственным автоинкрементным полем id и для получения nextId используйте её.
paurlift писал(а): 2017.02.23, 15:34 6) Есть ли такое понятие, что Domain слой должен быть максимально независим от других и при желании его можно перенести в другой проект?
Домен всегда пишется по ТЗ и у всех уникален. Предполагается перенос не в другой проект, а на другой фреймворк.
paurlift писал(а): 2017.02.23, 15:34 7) Интефейсы инфраструктурных сервисов кладем на доменный уровень, чтобы не лишать себя возможности использовать их в Domain Service?
Интерфейс - в домене. Реализация - в инфраструктуре.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: DDD, связность модулей

Сообщение anton_z »

paurlift писал(а): 2017.02.23, 15:34
4) Может ли Repository искать что-то кроме entity, возвращать массивы? Пример - данные для отчета. Или сохранять какую-то статистику, которая позже отсылается в Google Analytics. В этих задачах уже речь не идет о DDD и моделях.
Да. Могут.
paurlift писал(а): 2017.02.23, 15:34
7) Интефейсы инфраструктурных сервисов кладем на доменный уровень, чтобы не лишать себя возможности использовать их в Domain Service?

Пример TransactionMangerInterface лежать должен где-то на доменном уровне (domain/contacts/TransactionMangerInterface), а его реализация на инфраструктурном уровне (infrastructure/persistence/transaction/TransactionManger). Но в 90% мы его используем в Application Service.
Я и сам для себя не смог выработать критериев для разделения между application (имеются в виду ApplicationServices, Transformers, Requests и проч) и domain.

P.S. DDD конечно хорош, жизнеспособная методология, но он разлетается в пух и прах по производительности/скорости разработки/удобству, когда надо сделать замену SQL запросу UPDATE на множество строк по какому-либо бизнес-критерию (WHERE amount > 100 AND created_at>...). Database-driven подход жив!) Банки на нем и сидят все вроде.
nootropil
Сообщения: 46
Зарегистрирован: 2015.11.21, 18:45

Re: DDD, связность модулей

Сообщение nootropil »

anton_z писал(а): 2017.02.23, 16:37 Database-driven подход жив!) Банки на нем и сидят все вроде.
ИМХО, сидят они скорее на TDD + whatever (в том числе и DDD).
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: DDD, связность модулей

Сообщение zelenin »

anton_z писал(а): 2017.02.23, 16:37
paurlift писал(а): 2017.02.23, 15:34
4) Может ли Repository искать что-то кроме entity, возвращать массивы? Пример - данные для отчета. Или сохранять какую-то статистику, которая позже отсылается в Google Analytics. В этих задачах уже речь не идет о DDD и моделях.
Да. Могут.
paurlift писал(а): 2017.02.23, 15:34
7) Интефейсы инфраструктурных сервисов кладем на доменный уровень, чтобы не лишать себя возможности использовать их в Domain Service?

Пример TransactionMangerInterface лежать должен где-то на доменном уровне (domain/contacts/TransactionMangerInterface), а его реализация на инфраструктурном уровне (infrastructure/persistence/transaction/TransactionManger). Но в 90% мы его используем в Application Service.
Я и сам для себя не смог выработать критериев для разделения между application (имеются в виду ApplicationServices, Transformers, Requests и проч) и domain.
ну а где еще использовать инфраструктурные сервисы, как не в Application?
Domain + Infra - бизнес-логика + реализация
Application - use cases (!). Как и в каких случаях применять два предыдущих слоя. Это декорация двух слоев в зависимости от кейсов.
anton_z писал(а): 2017.02.23, 16:37 P.S. DDD конечно хорош, жизнеспособная методология, но он разлетается в пух и прах по производительности/скорости разработки/удобству, когда надо сделать замену SQL запросу UPDATE на множество строк по какому-либо бизнес-критерию (WHERE amount > 100 AND created_at>...). Database-driven подход жив!) Банки на нем и сидят все вроде.
эм. ну в данном случае у нас будет доменный сервис с mysql-реализацией на голом query. ddd - красивый.)
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: DDD, связность модулей

Сообщение anton_z »

zelenin писал(а): 2017.02.23, 18:15 эм. ну в данном случае у нас будет доменный сервис с mysql-реализацией на голом query. ddd - красивый.)
Нет, не будет, по следующим причинам:
1. Бизнес-логика уехала в инфраструктуру.
2. Модифицируем большое количество сущностей без самих сущностей. А как же доменные события? Сущности не контролируют свое состояние при изменении.

Что будет если я в UPDATE напишу SET amount = amount - 100 на пачку записей? А у меня должно быть доменное событие, когда баланс меньше 30 и я должен отослать email об этом?
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: DDD, связность модулей

Сообщение anton_z »

zelenin писал(а): 2017.02.23, 18:15 ну а где еще использовать инфраструктурные сервисы, как не в Application?
Domain + Infra - бизнес-логика + реализация
Application - use cases (!). Как и в каких случаях применять два предыдущих слоя. Это декорация двух слоев в зависимости от кейсов.
А где должны быть всякие DataTransformers, Requests? В domain или application?
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: DDD, связность модулей

Сообщение ElisDN »

anton_z писал(а): 2017.02.24, 00:53 А где должны быть всякие DataTransformers, Requests? В domain или application?
Если имеются в виду трансформеры полей форм из Symfony и HTTP Request, то это вообще с контроллерами в UI.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: DDD, связность модулей

Сообщение zelenin »

anton_z писал(а): 2017.02.24, 00:47
zelenin писал(а): 2017.02.23, 18:15 эм. ну в данном случае у нас будет доменный сервис с mysql-реализацией на голом query. ddd - красивый.)
Нет, не будет, по следующим причинам:
1. Бизнес-логика уехала в инфраструктуру.
нет, это же доменный сервис. хочешь реализуй в инфраструктуре прямым запросом, хочешь с помощью репозитория и выборки тысяч сущностей и их апдейтом - бизнес-задача будет реализована с помощью бизнес-понятий.
anton_z писал(а): 2017.02.24, 00:472. Модифицируем большое количество сущностей без самих сущностей. А как же доменные события? Сущности не контролируют свое состояние при изменении.
это ты только что придумал - мы не говорили о каких-то дополнительных требованиях, и даже о каком-либо смысле апдейта. Все нужно рассматривать конкретными кейсами. когда-то нам не нужны события, когда-то нужны, но мы можем их генерить пачками в сервисе, когда-то придется грузить пачку сущностей.
anton_z писал(а): 2017.02.24, 00:47Что будет если я в UPDATE напишу SET amount = amount - 100 на пачку записей? А у меня должно быть доменное событие, когда баланс меньше 30 и я должен отослать email об этом?
будет то, что ты знаешь, что данный кейс надо рассматривать с другой стороны и на быстродействии не сэкономить. В конце концов требования не ты выставляешь, а доменный эксперт, которому ты задашь вопрос - рассматривать ли эту операцию как миллион операций над одиночными сущностями с сохранением бизнес-правил или как пакетную одиночную операцию над миллионом сущностей без сохранения бизнес-правил. А потом ты еще берешь и проверку условия про <30 выносишь из сущности в сервис с кондишнами, и делаешь две реализации (тут уже подумать нужно).
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: DDD, связность модулей

Сообщение ElisDN »

anton_z писал(а): 2017.02.24, 00:47 Что будет если я в UPDATE напишу SET amount = amount - 100 на пачку записей? А у меня должно быть доменное событие, когда баланс меньше 30 и я должен отослать email об этом?
Ну либо разбиваете в пух и прах пакетными операциями ради мнимой производительности, либо обходите сущности в цикле с логикой и событиями. Проблема-то в чём?
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: DDD, связность модулей

Сообщение anton_z »

zelenin писал(а): 2017.02.24, 01:20
это ты только что придумал - мы не говорили о каких-то дополнительных требованиях, и даже о каком-либо смысле апдейта. Все нужно рассматривать конкретными кейсами. когда-то нам не нужны события, когда-то нужны, но мы можем их генерить пачками в сервисе, когда-то придется грузить пачку сущностей.

будет то, что ты знаешь, что данный кейс надо рассматривать с другой стороны и на быстродействии не сэкономить. В конце концов требования не ты выставляешь, а доменный эксперт, которому ты задашь вопрос - рассматривать ли эту операцию как миллион операций над одиночными сущностями с сохранением бизнес-правил или как пакетную одиночную операцию над миллионом сущностей без сохранения бизнес-правил. А потом ты еще берешь и проверку условия про <30 выносишь из сущности в сервис с кондишнами, и делаешь две реализации (тут уже подумать нужно).
Да, это я все придумал) Проблема заключается в том, что я пытаюсь выработать для себя правила, где DDD заканчивается и начинается database-driven приложения. Я думал, что если делаем по DDD то весь код, выполняющий бизнес операции должен находиться в доменном модуле и не может находиться в инфраструктурных реализациях или в хранимых процедурах в БД.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: DDD, связность модулей

Сообщение anton_z »

ElisDN писал(а): 2017.02.24, 01:22
anton_z писал(а): 2017.02.24, 00:47 Что будет если я в UPDATE напишу SET amount = amount - 100 на пачку записей? А у меня должно быть доменное событие, когда баланс меньше 30 и я должен отослать email об этом?
Ну либо разбиваете в пух и прах пакетными операциями ради мнимой производительности, либо обходите сущности в цикле с логикой и событиями. Проблема-то в чём?
Ну производительность как раз и не мнимая. Если вытягивать по одной, а потом делать UPDATE, то тут еще проблемы с конкурентным изменением могут быть, надо блокировки применять... Простой UPDATE здесь лучше.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: DDD, связность модулей

Сообщение ElisDN »

anton_z писал(а): 2017.02.24, 12:37 Если вытягивать по одной, а потом делать UPDATE, то тут еще проблемы с конкурентным изменением могут быть, надо блокировки применять...
Через микросекунду после вашего SET amount = amount - 100 также прилетит SET amount = 93 и свою строку затрёт. В любом случае понадобится блокировка с version = version + 1.
anton_z писал(а): 2017.02.24, 12:37 Ну производительность как раз и не мнимая.
Сэкономили 100 серверных секунд в месяц - получили премию в 10 копеек.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: DDD, связность модулей

Сообщение anton_z »

ElisDN писал(а): 2017.02.24, 12:52 Через микросекунду после вашего SET amount = amount - 100 также прилетит SET amount = 93 и свою строку затрёт. В любом случае понадобится блокировка с version = version + 1.
Заблуждаетесь. Не в любом. UPDATE ... SET anount=anount-10. Тут если такой же запрос прилетит, ничего не затрется.

По поводу производительности - есть еще память, нагрузка на сеть, время выполнения в конце концов. Если по одной перебирать мтллион или два - тут можно и полдня задачу выполнять. БД это сделает за секунды/минуты.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: DDD, связность модулей

Сообщение anton_z »

ElisDN писал(а): 2017.02.24, 01:13
anton_z писал(а): 2017.02.24, 00:53 А где должны быть всякие DataTransformers, Requests? В domain или application?
Если имеются в виду трансформеры полей форм из Symfony и HTTP Request, то это вообще с контроллерами в UI.
Нет, трансформеры и запросы/dto, которые используются Application сервисами.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: DDD, связность модулей

Сообщение ElisDN »

anton_z писал(а): 2017.02.24, 13:33 Не в любом. UPDATE ... SET amount=amount-10. Тут если такой же запрос прилетит, ничего не затрется.
С сайта к Вам именно amount = 93 прилетит по save.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: DDD, связность модулей

Сообщение anton_z »

ElisDN писал(а): 2017.02.24, 14:37
anton_z писал(а): 2017.02.24, 13:33 Не в любом. UPDATE ... SET amount=amount-10. Тут если такой же запрос прилетит, ничего не затрется.
С сайта к Вам именно amount = 93 прилетит по save.
Не не не. Под SET amount = amount-10 я имею ввиду SQL выражение, именно таким запрос и пойдет в БД. Реляционная алгебра)
Ответить