Моделирование по философии Domain Driven Design. Понятие доменной модели. Исследование предметной области и выделение ограниченных контекстов.
- 00:00:37 Постановка задачи
- 00:01:09 Аббревиатура DDD
- 00:01:45 Модель солнечной системы
- 00:04:32 Пример ларька с шаурмой
- 00:06:26 Подобласти бизнеса
- 00:07:44 Единый язык
- 00:08:29 Отражение бизнеса в коде
- 00:09:32 Разделение данных
- 00:14:14 Ограниченные контексты
- 00:15:27 Разделение на модули по контекстам
- 00:16:52 Анализ бизнеса в DDD
- 00:18:16 Автоматизация процессов вместо данных
- 00:19:07 DDD не про код
- 00:20:40 Применение к нашему проекту
- 00:22:59 Связанность и рост проекта
- 00:24:44 Удобство деления на микросервисы
- 00:25:54 Риск неверного разбиения
- 00:27:03 Что в итоге
Скрытый контент (код, слайды, ...) для подписчиков.
Открыть →Чтобы не пропускать новые эпизоды подпишитесь на наш канал @deworkerpro в Telegram
Спасибо.
Это прекрасно.
Стало еще лучше, появились картинки :) . Мне не хватало рисунков в разработке рабочего места и деплоя. Прожал на будущую кнопку "лайк".
Спасибо, все лаконично и этим круто, и отдельное спасибо за kleki. Также недавно открыл для себя draw.io, удобно набрасывать схемки клиенту с любого устройства.
Ждем с нетерпением картинок с кодом :)
Спасибо!!! А почтовый сервер будет подниматься в этом мастер-классе или будут заглушки?
Будут заглушки для девелопмента.
Для борьбы с антиспамом удобнее использовать сторонние сервисы, чем поднимать савой почтовик.
Да, было бы круто почтовый сервер (напр. image: tvial/docker-mailserver:latest) и UI с ROUNDCUBEMAIL_DB_TYPE=pgsql....
Спасибо, Дмитрий.
Спасибо за подробно изложенный материал!
Доброй ночи, Дмитрий! С праздником 9 мая!)
Проектируя по DDD ограниченными контекстами решил выделить все справочники в отдельный контекст: Справочники (Dictionaries). И появилось несколько вопросов:
Правильно. Должно быть только указание ID. Это не связь с самим объектом.
Если там перечислены все справочники подряд, то это получается не один смысловой контекст, а просто смесь из разных списков, не связанных по смыслу друг с другом.
Категории мероприятий имеет смысл оставить в мероприятиях, если они используются только там и без мероприятий они никому больше не нужны. Категории организаций оставить в организациях. А страны с регионами и городами уже можно вынести в контекст Geo.
Благодарю! А если категории мероприятий используется в двух местах?
Тогда уже можно вынести.
Отлично. Это уяснил. Дмитрий, ещё появился такой интересный вопрос. Думаю, будет интересно будет услышать этот ответ на вопрос в вашем уроке по проектированию доменных сущностей.
В своих уроках вы часто используете в качестве ID - UUID. Но везде использовать UUID не даёт понимания при анализе базы данных. Поэтому некоторые ID формируют более смысловые. Например первый символ может быть Первой буквы названия сервиса. Или используя сущность + другие значения. В итоге из тематики медицины есть такой пример: PatientTHX1138
Если не сложно включите небольшой разбор в свои лекции про генерацию UUID, а так же как это можно делать на системах, которые уже работают. На сколько знаю ID не меняется, как и не удаляются данные, из-за соблюдения консистенции данных. В таком случае придётся делать поле CODE, если правильно мыслю)
И можно ли использовать SLUG в качестве ID? Только вот проблема. Слуг иногда меняется. Например у пользователя или организации.
С точки зрения самой БД идентификаторы в анализе практически не нуждаются, так как лежат в своих таблицах и полях и никто напрямую смотреть на БД обычно не заходит.
А потом вдруг захочется переименовать сервис и такие идентификаторы придётся тоже переименовывать.
Это всё определяет вопрос, какой идентификатор использовать: суррогатный (UUID, Auto Increment) или естественный (slug, email, patient). В случае суррогатного его формат не важен и значение точно никогда не изменится.
А с пользовательскими уже сложнее. Например, для записи PatientTHX1138 таблицу patients можно создать с составным первичным ключом (id=1138, type=TNX).
Так что либо UUID или Auto Increment, либо что-то своё, либо хранить одновременно UUID для связей + что-то другое вроде slug для отображения посетителю.
И ещё забыл уточнить. Важный момент. У меня много категорий для мероприятий, для организаций, для дисциплин. У меня есть два пути: 1. В категории добавить type и указывать (type = events, discipline, organization). В этом случае большой плюс, что не надо будет городить много сущностей, верстки и т.п. 2. Каждая категория отдельно, но так как большинство будут использоваться в 2-х и более местах придется поместить в справочник. Тогда будут названия сущностей вроде EventCategory OrganizationCategory...
Какой подход 1 или 2 лучше?
Если разделять, то больше гибкости, так как можно программировать их со своими нюансами независимо. В этом случае проблема лишней вёрстки и кода частично решается разработкой общих виджетов и сервисов, работающих с interface Category или TreeCategory и подобными обобщениями. Если не разделять, то сэкономим на коде, но везде будем таскать type. Так что вопрос выбора сложный.
А подскажите, пожалуйста, как поступить в ситуации, если у сущности в различных состояниях могут быть (не)заполнены какие-то поля? Например, возьмём модель такси, там есть сущность заказа, у которой при статусе
new
полеdriver
пустое, но через какое-то время водитель берёт заказ и заказ переходит в статусbooked
. Т.е. получается статус заказа и водитель являются неким инвариантом, при каком-то статусе водителя нет, а при каком-то есть. Т.е. если подходить к вопросу в лоб, то не получается соблюсти данный инвариант:Как можно побороть такую ситуацию в общем случае? (данная модель дана для примера)
Для каждой операции делаем метод, переводящий всё сразу в новое состояние:
и в нём производим все заполнения с проверками.
Но вместо одного большого класса Order можно разбить процессы на несколько более мелких классов. Тогда взятие заказа можно производить отдельным классом Booking:
Тогда внутри Booking эти поля будут сразу обязательными.
Да, отдельным классом наверно это хорошее решение. Т.к. тогда это решит проблему, что
$order->getDriver()
когда-то есть, а когда-то нет. Спасибо.С одним классом с методами:
тоже проблемы нет.
Да, но тогда как решить проблему?
Если это проблема, то решить как:
Добрый день, Дмитрий!
Хотел бы узнать ваше мнение по правильному применению практик DDD.
У моем проекте модель предметной области DDD разделена на 3 модуля, каждый из которых размещен в собственном каталоге (условно Model1, Model2, Model3). Должны ли корневые сущности-агрегаты моделей всех 3-х модулей реализовывать один общий интерфейс RootAggregateInterface или допустимо, чтобы каждый из 3-х модулей реализовывал свой собственный интерфейс RootAggregateInterface со своими индивидуальными методами, необходимыми только конкретному модулю?
Допустимо выносить общие системные вещи. В случае разнесения моддулей на отдельные микросервисы такие вещи просто выносятся в composer-пакеты и подключаются к каждому проекту.
Добрый день, Дмитрий!
Очень не хватает обзорного видео по общим принципам DDD, в котором Вы бы рассказали о своём их понимании. Хотелось бы услышать о луковичной, гексагональной и иных архитектурах приложения, о существующих уровнях DDD (презентационный, операционный, доменный, инфраструктурный) и о возможных связях между уровнями в практическом применении к разработке на Slim и Symfony.
Правильно ли я понимаю, что, например, в Вашем подходе к разработке на Slim классы сущностей относятся к уровню модели, классы репозиториев и Fluser - к инфраструктурному уровню, классы внутри UseCase (Command, Handler) - к операционному уровню, а классы Action - к презентационному уровню.
Одним словом, хотелось бы увидеть что-то типа этого.
Спасибо за вашу работу.
Принципы DomainDD - это единый язык с бизнесом и ограниченные контексты. Аналогично с этим TestDD лишь призывает писать тесты до кода. А BehaviorDD призывает делать тесты, описывающие поведение системы на человекопонятном языке, а не в техническом виде. Как именно писать сам код тестов эти практики явно не говорят.
То есть стратегический DDD говорит лишь про организацию разработки, а не про программный код. А дальше уже тактический DDD всего лишь говорит, какие уже существующие паттерны будет удобно использовать для этих целей.
Это просто варианты чистой архитектуры, которые у себя можно использовать для написания фреймворконезависимого кода. Которые всё взаимодействие чистого ядра со внешним миром производят через адаптеры.
Про разделение на уровни обсуждали здесь и здесь в комментариях к другим эпизодам. Ещё про эти уровни говорил в докладе.
Горизонтальное разделение на уровни нам менее важно, чем вертикальное разделение на модули.
Дмитрий, спасибо за Ваш развернутый ответ.
Заинтересовала Ваша схема в комментариях с разбиением по слоям:
Возник вопрос: в каком пространстве имен правильно будет располагать интерфейсы доменного уровня: interface UserRepository, interface PasswordHasher и interface Flusher? Все интерфейсы расположить в одном пространстве имен \App ? Или существует какой-то более грамотный подход?
Для простого приложения можно именовать всё по папкам от корневого App:
Но вместо App можно папке src присвоить нэймспейс по имени приложения:
Если приложение модульное, то можно делать модули внутри App:
либо обходиться без App:
Это дело вкуса.
Очень полезно, спасибо!
Или войти через: