Как взаимодействуют друг с другом родительский и дочерние контексты, а так же дочерние друг с другом?
Моя практика показывает, что иерархическая структура контекстов хорошо работает когда есть множество небольших контекстов ни как не связанных между собой и другими контекстами, но имеющие общий контекст. Общий контекст, в этом случае, может быть пустым. Своего рода группа контекстов.
Порой, общий/родительский контекст имеет свою логику и дочерние контексты в этом случае имеют к нему доступ (дерево. Все связи направлены в низ, к основанию). Это может делаться для вынесения одинаковой бизнес логики, чтоб не дублировать её в каждом контексте. Не смотря на наличие схожих бизнес процессов, это все ещё разные контексты, которые ни как нельзя объединять в один большой контекст.
Моя практика показывает, что дублирование бизнес процессов в разных контекстах не самое лучшее решение, даже если это дублирование только формальное, на бумаге.
А вот выделение субконтекстов из основного контекста для их изоляции, как в примере с страницами и комментариями, дело довольно спорное, как вы уже подметили.
Если говорим о чём-то типа Хабра, то интеграция нужна.
И опять вы выносить домен в уровень представления. Сначало говорили, что аннотация в домене это протечка инфраструктуры, а теперь сами же вытаскиваете домен в представление.
А если серьезно, то посмотрите хотя бы на эту страницу. Только ли статья на ней и только ли комментарии? На странице есть как минимум сущности из контекста аунтификации, что значит, что сосуществование разных контекстов в одном запросе на уровне представления это норма. И к API это тоже относится.
Как минимум это заставит создавать отдельный модуль интгерации контекстов и страниц, не позволит выбирать комментарии со страницами одним SQL-запросом или хранить их в рамках одного документа в базах типа MongoDB.
Зачем модули интеграции если их не нужно интегрировать?
Один SQL запрос так и так нельзя использовать для извлечения одной страницы и всех её комментариев.
Документ в монге это конечно аргумент, но если использовать монгу как read хранилище, то можно. Структуру read хранилища определяет уровень представления, а не домен.
Я просто пытаюсь понять, какие плюсы в разделении контекстов страниц и комментариев.
Кажется я об этом уже говорил. Критерием для разделения контекстов их размер.
Учитывая сколько мы всего описали только для учёта просмотра страниц, не сложно прикинуть, что контекст получается ой как не маленький. Отсюда и вывод, что нужно делить контекст на контексты поменьше.
Надеюсь не нужно объяснять чем плохи больше контексты?
Я вам говорю о базовых принципах не однократно описанные множеством известных специалистов. Вы можете пренебрегать этими принципами. Это ваше право.
А как на фронте понять что нужно обновлять счётчик?..
Я разве говорил, что нужно понимать это на фронте? Нет. Я говорил, что вы можете отправлять на сервер сообщения о просмотре, по аналогии с GA и YM, а на сервере уже применять просмотры в соответствии с вашей бизнес логикой.
У Вас уже логика прямо на фронте получилась.
Нет. Хотя она там может быть. Вспоминаем SPA.
А на беке получается паутина из обработчиков событий. При этом это один use case.
Паутины нет. События только так где они действительно нужны.
Вы говорити, что Use case один, а так ли это?
Получить пост из хранилища;
Пометить пост как просмотренный;
Поставить бейдж популярному посту;
Обновить блок популярных постов;
Обновить топ на главной;
Обновить статистику просмотров поста у автора в профиле;
Обновить статистику просмотров в админке;
...
Вы собираетесь запихивать все это в один Use case, в один метод, в один GET запрос и пусть весь мир подождёт)))
Совершенно верно. Большие контексты плохо и совсем маленькие контексты тоже плохо. Нужно искать золотую середину. Моя золотая середина это два отдельных контекста комментарии и страницы. Ваша золотая середина это комментарии и страницы в одном контексте. Я уже устал это повторять. Давайте завязывать.
но всё равно нужно записать это в базу в пределах одной транзакции с получением результата запроса.
Ещё раз. Почему в пределах именно одной транзакции? Почему нельзя обработать запрос в фоне?
Даже если вы не используете Commad и Query, а используете методы, то почему метод в духе getPage() должен ещё и писать что-то? Кидать событие, да возможно, но не писать.
Я с самого начала говорил о комментаторе, но называл его автором комментариев. Я уже давно поправился.
Пока это выглядит так, что вы просто любую мало-мальски значимую сущность вытаскиваете в отдельный контекст по умолчанию.
Ну во первых, сущность комментариев не такая уж и "мало-мальски значащая". Мы сейчас общаемся на Хабре в комментариях и я, обсуждая комментарии, ассоциирую их с комментариями Хабра, а комментарии на Хабре не такие уж и мало значащие.
Во вторых, да. Контексты должны быть маленькими. Чем меньше контексты, тем они оптимальней. Большие контексты сродни отсутствию контекстов вообще. Если мне память не изменяет, то именно Вернон говорил об этом в своей книге. Рекомендую почитать если вы ещё не читали.
Разве? Вроде и по Ubiquitous Language прошлись. Напоминаю:
User — в контексте страниц пользователь является автором страниц;
User — в контексте комментариев к страницам пользователь является комментатором.
Можем ещё модераторов добавить.
User — модератор страниц;
User — модератор комментариев;
Модератор страниц может подтвердить или отклонить публикацию страницы;
Модератор комментариев может удалить или заблокировать комментарий.
Можем ещё пробежаться по Use cases страниц и комментариев.
Я повторюсь. Я предлагаю разделить комментарии и страницы на отдельные контексты. Вы можете со мной не согласится и сделать по своему. Я не царь и бог чтоб неповиновение мне каралось смертной казнью)))
Так и думал, что у вас проблемы с архитектурой.
Начнем с простого. Вы в read потоке выполняете запись. Это уже не правильно.
Далее, с чего вы решили, что вам обязательно дожидаться обновления хранилища прежде чем отдавать контент пользователю?
Обновлять щетчики просмотров можно 2 способами. Можно на fronend через js или картинки по аналогии с другими метриками. Можно на backend кидать event "страница просмотрена".
Первый вариант предпочтительней так как в этом случае вы можете полностью или частично закешировать страницу и не дергать бекенд лишний раз. А щетчики на странице в этом случае обновлять через API.
Но даже в случае второго способа с event, вы просто его бросаете и read поток на этом заканчивается. Событие подхватывает слушатель который ставит в очередь команду на обновление щетчика и в обработчике команды, который будет вызван в фоне, вы уже поднимаете домен, применяете свою бизнес логику и обновлять хранилища.
Для оптимизации можно в слушателе события обновлять щетчик в Redis, значение которого сразу пойдет во view, а в процессе обработки просмотра в фоне откатывать щетчики для неугодных просмотров.
Подведя итог, для работы щетчиков поднимать домен не нужно.
Страница вывода поста — обращение как минимум к «посты», «комментарии» и «пользователи», блок «Самые комментируемые посты» — обращение как минимум к контекстам «посты» и «комментарии».
Повторюсь. Это Read поток. Для оптимизации нагрузки рекомендуется не использовать домен на этом уровне (см. CQRS). Для оптимизации нагрузки все эти данные, как правило, кладутся в NoSQL хранилища или вытягиваются простыми запросами к БД и не строят многоуровневые агрегаты, а используются DTO или вообще массивы.
Домен нужен для того, чтоб сформировать бизнес логику обработки Write потока, а доменные события тригерят обновление кеша и снепшотов для Read потока.
Это мнение Эванса и Вернона и я с ними солидарен. Я не хочу сказать, что все должны делать так, но там где есть выделенный домен, как правило задумываются о разделении read и write потоков.
Вот вопрос скорей стоял где та гран
Грань каждый определяет сам. Есть некоторые рекомендации описанные в книгах по DDD, но четких правил нет.
Эванс и Вернон говорили о том, что поднимать весь домен на каждый запрос бессмысленно. Он нужен только для записи, а для представления стоит использовать DTO.
Нет, "также" остаётся. Ключевые слова "также является автором страниц и комментариев", а не про права.
Пунктуация страдает. Я иначе воспринял ваше предложение. Прошу прощения.
Вообще, с формулировкой можно много играться. Например:
User — человек, персонифицированный пользователь системы, прошедший аутентификацию, имеет набор прав на различные действия в системе, автор страниц, комментатор.
Обратите внимание, на то что свойства пользователя перечислены через запятую.
В таком виде эти свойства выглядят как равноправные.
В данном случае запятая эквивалент "и", что должно сигнализировать о возможном смешивании контекстов.
То есть, в проекте существует:
User — человек;
User — персонифицированный пользователь системы;
User — пользователь прошедший аутентификацию;
User — пользователь имеющий набор прав на различные действия в системе;
As you try to model a larger domain, it gets progressively harder to build a single unified model. Different groups of people will use subtly different vocabularies in different parts of a large organization. The precision of modeling rapidly runs into this, often leading to a lot of confusion. Typically this confusion focuses on the central concepts of the domain. Early in my career I worked with a electricity utility — here the word "meter" meant subtly different things to different parts of the organization: was it the connection between the grid and a location, the grid and a customer, the physical meter itself (which could be replaced if faulty). These subtle polysemes could be smoothed over in conversation but not in the precise world of computers. Time and time again I see this confusion recur with polysemes like "Customer" and "Product".
По поводу:
Почему комментарий в контексте страницы должен быть VO, представляющим сущность из другого контекста, а не сущностью в этом контексте или вообще VO в странице?
Учитывая то, о чем мы тут говорим, вопрос надо ставить так: Почему страница в контексте комментариев должна быть VO, представляющим сущность из другого контекста, а не сущностью в этом контексте?
Под различными действиями может быть все что угодно.
В вашем примере лучше сформулировать так:
имеет набор прав на различные действия в системе такие как создание страниц и их комментирование.
Вот уже и нет никаких "так же".
Вообще, права пользователя зависят от контекста и контекст определяет права пользователя, а не наоборот.
Например: read-only пользователь на Хабре не может голосовать за статью. Это ограничение диктуется контекстом статей, а от пользователя здесь только его тип.
В контексте страниц с комментариями нам не важны механизмы аутентификации, но важно, например, является ли пользователь, пытающийся редактировать комментарий его автором или модератором. В этом контексте автор вообще практически объект-значение, может даже из одного поля "идентификатор", а в контексте управления пользователями, пользователь полноценная сущность со своим жизненным циклом и состоянием
Вот об этом я и говорю. Если пользователь это VO по отношению к комментарию и находится с комментариями в разных контекстах, то почему страница не может быть так же VO по отношению к комментарию и быть с ними в разных контекстах?
Может комментарии и страницы нужно разделять, а может и нет. Все зависит от проекта. В контексте маленького бложика я бы их не разделял. В контексте Хабра я бы их выделил в отдельный контекст потому, что у комментариев много своей бизнес логики ни как не связанной со статьями. Собственно, вся связь ограничивается одной фразой из Ubiquitous Language — "прокомментировать статью".
Собственно разделение на контексты надо производить когда в определениях терминов появляется «или», «а также», «с точки зрения», «в рамках» и т. п…
Если так рассуждать, то пол проекта может оказаться в одном контексте.
Например авторизация и аунтификация может оказаться в одном контексте с комментариями.
Комментариям не нужен весь объект пользователя. Им достаточно идентификатора пользователя. Тоже самое с страницами. Комментариям от страницы ни чего не надо кроме идентификатора.
Поэтому я комментарии и выделил в отдельный контекст.
У вас могут быть другие бизнес требования и у вас страницы с комментариями будет в одном контексте.
Так же ни кто не запрещает создавать комментарии в статье.
class Page
{
public function comment(
CommentAuthorId $author_id,
CommentMessage $message
): Comment {
$new_comment = Comment::comment(
$this->id,
$author_id,
$message
);
$this->comments[] = $new_comment;
return $new_comment;
}
}
Моя практика показывает, что иерархическая структура контекстов хорошо работает когда есть множество небольших контекстов ни как не связанных между собой и другими контекстами, но имеющие общий контекст. Общий контекст, в этом случае, может быть пустым. Своего рода группа контекстов.
Порой, общий/родительский контекст имеет свою логику и дочерние контексты в этом случае имеют к нему доступ (дерево. Все связи направлены в низ, к основанию). Это может делаться для вынесения одинаковой бизнес логики, чтоб не дублировать её в каждом контексте. Не смотря на наличие схожих бизнес процессов, это все ещё разные контексты, которые ни как нельзя объединять в один большой контекст.
Моя практика показывает, что дублирование бизнес процессов в разных контекстах не самое лучшее решение, даже если это дублирование только формальное, на бумаге.
А вот выделение субконтекстов из основного контекста для их изоляции, как в примере с страницами и комментариями, дело довольно спорное, как вы уже подметили.
Это как посмотреть на вопрос.
Если контекст единый, то при комментарии, в самом комментарии, мы должны убедиться, что статья опубликована и в противном случае выбросить исключение.
Если же контексты раздельные, то не опубликованных статей в контексте комментариев просто не будет существовать.
Вот вам и преимущество разделения.
И опять вы выносить домен в уровень представления. Сначало говорили, что аннотация в домене это протечка инфраструктуры, а теперь сами же вытаскиваете домен в представление.
А если серьезно, то посмотрите хотя бы на эту страницу. Только ли статья на ней и только ли комментарии? На странице есть как минимум сущности из контекста аунтификации, что значит, что сосуществование разных контекстов в одном запросе на уровне представления это норма. И к API это тоже относится.
Об том и спич
Кажется я об этом уже говорил. Критерием для разделения контекстов их размер.
Учитывая сколько мы всего описали только для учёта просмотра страниц, не сложно прикинуть, что контекст получается ой как не маленький. Отсюда и вывод, что нужно делить контекст на контексты поменьше.
Надеюсь не нужно объяснять чем плохи больше контексты?
Я вам говорю о базовых принципах не однократно описанные множеством известных специалистов. Вы можете пренебрегать этими принципами. Это ваше право.
Я разве говорил, что нужно понимать это на фронте? Нет. Я говорил, что вы можете отправлять на сервер сообщения о просмотре, по аналогии с GA и YM, а на сервере уже применять просмотры в соответствии с вашей бизнес логикой.
Нет. Хотя она там может быть. Вспоминаем SPA.
Паутины нет. События только так где они действительно нужны.
Вы говорити, что Use case один, а так ли это?
Вы собираетесь запихивать все это в один Use case, в один метод, в один GET запрос и пусть весь мир подождёт)))
Нет. Событие это сообщение возникающие в процессе выполнения программы. Само по себе событие ничего не меняет.
Совершенно верно. Большие контексты плохо и совсем маленькие контексты тоже плохо. Нужно искать золотую середину. Моя золотая середина это два отдельных контекста комментарии и страницы. Ваша золотая середина это комментарии и страницы в одном контексте. Я уже устал это повторять. Давайте завязывать.
Ещё раз. Почему в пределах именно одной транзакции? Почему нельзя обработать запрос в фоне?
Даже если вы не используете Commad и Query, а используете методы, то почему метод в духе
getPage()должен ещё и писать что-то? Кидать событие, да возможно, но не писать.Я с самого начала говорил о комментаторе, но называл его автором комментариев. Я уже давно поправился.
Ну во первых, сущность комментариев не такая уж и "мало-мальски значащая". Мы сейчас общаемся на Хабре в комментариях и я, обсуждая комментарии, ассоциирую их с комментариями Хабра, а комментарии на Хабре не такие уж и мало значащие.
Во вторых, да. Контексты должны быть маленькими. Чем меньше контексты, тем они оптимальней. Большие контексты сродни отсутствию контекстов вообще. Если мне память не изменяет, то именно Вернон говорил об этом в своей книге. Рекомендую почитать если вы ещё не читали.
Разве? Вроде и по Ubiquitous Language прошлись. Напоминаю:
Можем ещё модераторов добавить.
Можем ещё пробежаться по Use cases страниц и комментариев.
Я повторюсь. Я предлагаю разделить комментарии и страницы на отдельные контексты. Вы можете со мной не согласится и сделать по своему. Я не царь и бог чтоб неповиновение мне каралось смертной казнью)))
Так и думал, что у вас проблемы с архитектурой.
Начнем с простого. Вы в read потоке выполняете запись. Это уже не правильно.
Далее, с чего вы решили, что вам обязательно дожидаться обновления хранилища прежде чем отдавать контент пользователю?
Обновлять щетчики просмотров можно 2 способами. Можно на fronend через js или картинки по аналогии с другими метриками. Можно на backend кидать event "страница просмотрена".
Первый вариант предпочтительней так как в этом случае вы можете полностью или частично закешировать страницу и не дергать бекенд лишний раз. А щетчики на странице в этом случае обновлять через API.
Но даже в случае второго способа с event, вы просто его бросаете и read поток на этом заканчивается. Событие подхватывает слушатель который ставит в очередь команду на обновление щетчика и в обработчике команды, который будет вызван в фоне, вы уже поднимаете домен, применяете свою бизнес логику и обновлять хранилища.
Для оптимизации можно в слушателе события обновлять щетчик в Redis, значение которого сразу пойдет во view, а в процессе обработки просмотра в фоне откатывать щетчики для неугодных просмотров.
Подведя итог, для работы щетчиков поднимать домен не нужно.
Так в этом и смысл. Незачем городить DDD в CRUD приложениях.
Под "Не менее важная логика" вы подразумеваете бизнес ограничения?
Вы можете привести какой нибудь пример, чтоб мы говорили более предметно?
Повторюсь. Это Read поток. Для оптимизации нагрузки рекомендуется не использовать домен на этом уровне (см. CQRS). Для оптимизации нагрузки все эти данные, как правило, кладутся в NoSQL хранилища или вытягиваются простыми запросами к БД и не строят многоуровневые агрегаты, а используются DTO или вообще массивы.
Домен нужен для того, чтоб сформировать бизнес логику обработки Write потока, а доменные события тригерят обновление кеша и снепшотов для Read потока.
Это мнение Эванса и Вернона и я с ними солидарен. Я не хочу сказать, что все должны делать так, но там где есть выделенный домен, как правило задумываются о разделении read и write потоков.
Грань каждый определяет сам. Есть некоторые рекомендации описанные в книгах по DDD, но четких правил нет.
Эванс и Вернон говорили о том, что поднимать весь домен на каждый запрос бессмысленно. Он нужен только для записи, а для представления стоит использовать DTO.
Конечно же роли пользователя, а не свойства пользователя )))
Пунктуация страдает. Я иначе воспринял ваше предложение. Прошу прощения.
Вообще, с формулировкой можно много играться. Например:
Обратите внимание, на то что свойства пользователя перечислены через запятую.
В таком виде эти свойства выглядят как равноправные.
В данном случае запятая эквивалент "и", что должно сигнализировать о возможном смешивании контекстов.
То есть, в проекте существует:
Вспоминаем Фаулера
По поводу:
Учитывая то, о чем мы тут говорим, вопрос надо ставить так: Почему страница в контексте комментариев должна быть VO, представляющим сущность из другого контекста, а не сущностью в этом контексте?
Я немного сплоховал. Должно быть
CommentatorId, а неCommentAuthorId.У вас не совсем корректная формулировка
Под различными действиями может быть все что угодно.
В вашем примере лучше сформулировать так:
Вот уже и нет никаких "так же".
Вообще, права пользователя зависят от контекста и контекст определяет права пользователя, а не наоборот.
Например: read-only пользователь на Хабре не может голосовать за статью. Это ограничение диктуется контекстом статей, а от пользователя здесь только его тип.
Вот об этом я и говорю. Если пользователь это VO по отношению к комментарию и находится с комментариями в разных контекстах, то почему страница не может быть так же VO по отношению к комментарию и быть с ними в разных контекстах?
Может комментарии и страницы нужно разделять, а может и нет. Все зависит от проекта. В контексте маленького бложика я бы их не разделял. В контексте Хабра я бы их выделил в отдельный контекст потому, что у комментариев много своей бизнес логики ни как не связанной со статьями. Собственно, вся связь ограничивается одной фразой из Ubiquitous Language — "прокомментировать статью".
Если так рассуждать, то пол проекта может оказаться в одном контексте.
Например авторизация и аунтификация может оказаться в одном контексте с комментариями.
Комментариям не нужен весь объект пользователя. Им достаточно идентификатора пользователя. Тоже самое с страницами. Комментариям от страницы ни чего не надо кроме идентификатора.
Поэтому я комментарии и выделил в отдельный контекст.
У вас могут быть другие бизнес требования и у вас страницы с комментариями будет в одном контексте.
Так же ни кто не запрещает создавать комментарии в статье.