Тайм-коды: 2:32 Начало. План лекции 3:20 Основные принципы DDD. Ограниченные контексты (bounded context). Problem space, solution space 10:55 Единый язык (ubiquitous language) 15:58 Фокус на доменной модели. Многослойные архитектуры 21:51 Разница между анемичной и богатой моделью 23:32 Богатая модель = Инкапсулированная модель. Про понятие инкапсуляции 27:31 (Не) анемичная доменная модель в рамках функционального программирования 30:17 Рефакторинг анемичной модели через два шага. Строгая типизация 34:15 Уменьшение количества методов, которые изменяют состояние класса 41:06 Вопрос: влияние единого языка на развитие проекта 42:38 Изоляция доменной модели 49:21 Взаимосвязь между инкапсуляцией и изоляцией доменной модели 53:19 DDD трилемма. Изоляция > Инкапсуляция > Быстродействие 59:57 Конец доклада. Вопросы 1:00:56 Как склонить коллег к переходу к богатой модели от анемичной? 1:02:20 Интеграция между Entity Framework и DDD. Наследование от POCO (или DTO?) 1:04:55 Что-то там про передачу интерфейса 1:06:41 Разница между subdomain и bounded context 1:09:00 Рефакторинг при database first approach 1:11:30 Стоит ли использовать DDD прототипирование?
Пока лучшее что я видел по DDD для чайников (коим и являюсь). 33 минута, по этой логике и Name нужно обернуть в класс ведь можно указать и пробел или символ и т.п. Важно найти баланс но с другой стороны обернуть в класс не сложно оставив различные валидации и усовершенствования класса своим потомкам)))
56:00 для таких инвариантов придется знание о доменной модели выносить в бд - уникальный индекс на email. И тогда при вставке бд выдаст ошибку, и ее можно будет обработать на доменном уровне. Ни один из описанных в видео способов не явлется рабочим в конкурентной среде. Если два пользователя решили одновременно сменить email на другой одинаковый, то выборка из бд и проверка в памяти потенциально могут у обоих завершиться успешно. И оба вставят в бд один и тот же email. Если вынести проверку на уровень бд, то и код в доменной модели станет проще. Да, можно сказать, что мы переносим проверку инварианиа на инфраструктурный уровень - уровень бд. Но вообще-то бд является отражением наших сущностей в доменной модели. Мы не переносим эту логику в реопозитрий. Мы переносим ее в саму бд - место хранения инфы о нашем домене. Ну и это единственный адекватный рабочий способ. Ну либо можно брать блокировки (или распределеоные блокировки), но вот это уже перенос сохранения инварианта в инфраструктуру
а если у нас подтверждение на изменение пользовательского email зависит не только от его уникальности, но и от других факторов (например подтверждение модератором)? и количество таких условий может множится и бизнес логика может проходить сквозь всю структуру. тогда мы опять попадаем в анемичную модель.
33:15 Очень хочется посмотреть, что будет с этой скидкой, если она будет зависеть от чего-то ещё, кроме статуса. И вообще, как можно поместить функцию расчета скидки в объект Статус?! А если от статуса будет ещё что-то зависеть? Тоже сделаем методом в классе Статус?!
Спасибо коллегам из мира java для windows 😊 В пет проекте так и делал, пользуясь С и О принципами солид, не догадываясь про ДДД. Оказывается ДДД расширяет эти принципы. Очень похоже про те штуки, за которые топил Егор Бугаенко. Единственное не согласен, что надо лепить все в один класс. Я бы выделил емеил и статус в отдельную бизнес сущность, и создал класс ответственный за эту сущность и пусть он ее меняет. Книгу записал в todo.
58:18 Если мы выберем вариант с инкапсуляцией и интерфейсом, то можно прекрасно всё тестировать без моков. При этом 3 вариант где плохое быстродействие не обеспечит инкапсуляции. Я имею ввиду вариант прокинуть все заказы в кастомера, т.к. ничто нам не мешает прокинуть пустой или неполный массив (и вообще будет не очевидно какой список кидать в метод). Как по мне нормальная реализация 3го варианта (с инкапсуляцией, изоляцией, но плохой скоростью) это запихнуть список кастомеров в другую модель.
Мне кажется, что Владимир на 41:13 приуменьшает роль DDD в проекте. Потому что, на мой взгляд, хотя я только его ещё изучаю, DDD даёт не только единый язык и взаимопонимание, но и в целом раскладывает всё в проекте по полочкам, позволяя легко в нём ориентироваться, легко его поддерживать и вносить изменения. Благодаря DDD избегается превращение проекта в то, что адепты DDD называют «большим комом грязи». А это уже очень полезно и в разы улучшает поддерживаемость кода и экономит по-настоящему огромные ресурсы компании, обеспечивая при этом высокую функциональность, feature-richness продукта. По-моему, DDD в корне меняет всё, он просто переворачивает всю игру с головы на ноги, раскладывая все яйца по своим корзинам. DDD - это must have на любых проектах, связанных с реальным миром, реальными предметными областями и бизнесом в частности. DDD - это инструкция к тому, как правильно писать код, как сделать его чистым и логичным, и никак не меньше. Ну а в целом доклад очень крутой, всё чётко и по полочкам. Особенно понравилось как показано превращение анемичной модели в богатую. Вообще, когда что-то на примерах показывается - это очень хорошо воспринимается всегда. За это прям большое спасибо! А насчёт трилеммы. Лично я не думаю, что использование репозитория в модели - это страшно, потому что репозиторий - это основа любой программы, так как он сохраняет стейт, загружает его, работает с ним, а это - основа основ. Поэтому бояться его и тем более отказываться от него в пользу меньшего быстродействия, по-моему, не разумно. Наоборот, работая с репозиторием в модели, ты видишь, где необходима транзакционная целостность, а значит видишь какие агрегаты ты должен объединить в один, чтобы транзакции по нему проходили нормально. Лично я бы поставил на инкапсуляцию + быстродействие. Смещать логику в контроллеры - это тоже ерунда, так как логика приложения должна быть в модели, и не важно, что эта логика зависит от репозитория. Просто считаем репозиторий частью модели - вот и всё, ведь по сути так оно и есть. Репозиторий хранит данные по бизнесу, а разве это не часть бизнеса? Списки e-mail'ов, адресов - это ведь всё часть бизнеса, это не что-то, связанное с инфраструктурой. Это реальные данные по бизнесу. К тому же репозиторий - это виртуальная сущность, которая изолирует модель от прямой связи с базой. Базу репозитория всегда можно заменить. Это вообще может под капотом быть какой-нибудь API, или частично API, частично БД, частично вообще файловая система или LDAP - всё, что угодно, может внутри содержать репозиторий, и это никак не повлияет на модель, потому что репозиторий всегда выдаёт лишь те данные, которые реально связаны с моделью и являются частью бизнеса.
Согласен! Отличный коммент! Действительно, что плохого в том, чтобы модель могла делать save, пусть даже в абстрактную БД, чтобы потом, в случае смены БД, можно было через адаптер подменить логику Хранилища! Более того, что если Фабрика, создающая Агрегат, будет также создавать Хранилище и подсовывать его Агрегату на старте?
56:30 если это не 1c в коробке, отказываться от быстродействия - нерационально. Значит, в 80% случаев сразу забываем про инкапсуляцию и толстые модели, а валидацию и бизнес-правила размещаем в замечательные сервисы и контроллеры, очень удобные для тестирования.
Такой вопрос, разбили на контексты и написали приложение. Через год понимаем что помимо Продаж и Поддержка нужен еще контекст Маркетинг(или любой другой), как быть в такой ситуации? Насколько деление на контексты является гибким решением? А бывает так что "добавьте вот такую маленькую фичу", фича и правда маленькая но не ложится в существующие контексты, а создавать новый может быть дорого для бизнеса.
Вероятность того, что "маленькая" фича станет со временем большой > 0. В этом случае поддержка изменённого существующего контекста будет тоже дорога для бизнеса, т.к. будет требовать всё более и более объёмной регрессии при будущих модификациях этого контекста с "маленьким" костылём. Это нужно иметь в виду, ну а поступать так, как считаете нужным. Ведь лучше ошибиться и получить опыт, чем пытаться найти "общий" ответ там, где его нет)
Так добавление нового контекста никак не влияет на другие контексты, в этом и есть смысл, чтобы ограничить влияние контекстов. Так что новый контекст добиваться легко
Почему вы пропускаете тот момент что CustomerService так же инкапсулирует в себя DataLayer + CacheLayer? Можно приводить примеры более как то приближенные к жизни?
Вот те раз , всегда боролись и пропагандировали за анемичную модель , из-за ее слабой связанности и гибкости в плане расширения возможных операций над ней , и тут на тебе , снова в 90-е ) . У богатой модели много проблем , это паутина , в которой с ростом модели не разобраться , ее тяжело масштабировать , а при внезапно новом бизнес правиле многое переписывать. При анемичном подходе , нам ничего не мешает возложить валидацию консистентности модели на доменные сервисы , а клиентам отдавать record-ы дабы не могли накосячить с моделью напрямую. , ну и нет никаких проблем с добавлением новых бизнес правил , при этом не затрагивая существующие модели и методы работы с ними.
Сервисная модель полна излишеств и не отражает сущность бизнеса. Глядя на неё ты не понимаешь, для чего она существует. В ней также перемешана логика и инфраструктура. В богатой модели как раз таки всё понятно. Она описывает саму себя. Главное - всегда рисовать её диаграмму.
Аналогичное чувство появилось, только связал с серединой 2000-х. Очередные ребята сделали "открытие". "Я не знаю почему так лучше, но бизнесу нравится"
Спасибо за доклад. В примере с изменением почтового адреса можно соблюсти изоляцию, инкапсуляцию и быстродействие: ChangeEmail(Email email, List usersWithEmail) а в контролере выбрать не всех пользователей а только тех что имеют такую же почту, оверхед - 1 запрос к БД, но запрос все равно должен быть, лист будет либо пустым либо содержать одного пользователя. Понятно, что не всегда можно провернуть такой фокус.
В этой ситуации контроллер всё ещё частично "догадывается" об ограничениях, которые существуют в предметной области, поэтому предоставляет все необходимые данные для принятия решения, но зачем-то отдаёт само право принятия решения доменной сущности. При изменении и расширении этих требований необходимо будет актуализировать код как в контроллере, так и в модели. Точно так же можно было бы в метод просто передавать булево значение, указывающее на то, есть ли другой активный пользователь с данным имейлом. В общем, на мой скромный взгляд, это решение, где контроллер лезет в домен, но мы пытаемся обмануть себя и сделать вид, что это по каким-то формальным признакам не так.
не вижу проблемы передать в модель репозиторий через интерфейсную ссылку. Да, это говорит о том, что это где то храниться, но где и как - не открывает. Можно хранить по факту хоть в блокноте
Тоже не понимаю почему автор так против прокидывания интерфейса в модель. Если очень хочется то можно сегрегировать интерфейс. Хотя у автора есть прямо серия статей на эту тему, стоит почитать.
Проблема начинается когда у тебя в модель 10 разных зависимостей на разные подсистемы добавить нужно чтобыреализовать новые требования, таким образом модель превращается в монстра который делает все
@@mikhailsloushch5052 альтернативы нет, есть DDD трилема в которой нужно делать выбор, в конкретном примере я бы подумал нужно ли знать Customer уникальный он или нет, может с этим другая сущность модели должна разбираться, или можно в Customer передавать результаты проверки уникальности (флаг/статус) а не саму лямбду с вызовом функции проверки
Супер, спасибо! Чуть-чуть не уловил на 47 минуте, там где Владимир говорит, что Изоляция доменной области нарушается, если Customer умеет сам себя save в БД. А как тогда будет правильно? Его должен сохранять контроллер? Ну т.е. согласно триллемы DDD (55 минута) выбираем вариант Изоляция+Время ?
Можно уточнить с момент - Должны быть Рич модели, которые знают сами что можно делать с ними, но они не должны знать как себя сохранить? То есть, рич на пол-шишечки?!
А еще возникает вопрос, как быть, если какое-то поле сущности lazy-инициализируемо. При обращении к этому полю его значение нужно получить из другого сервиса, например. В этом случае сущность должна еще и знать как себя дозагрузить.
@@serg4568нет, как раз тут сущность не может быть на полшишечки дозагруженной, она должна быть целостная. Лучше конкретный пример привести, что за lazy свойство такое может быть у модели. Если значение этого поля находится в другом сервисе, то скорее всего это поле не этой модели, а что-то совершенно другое
Спасибо за доклад! Поддержу предыдущих комментаторов - отличная подача и разъяснения, очень помогли! ЗЫ. Единственное замечание: было бы хорошо поменьше использовать англицизмы при докладах на русском языке, но я понимаю, что когда практики английского больше, чем русского, это может происходить невольно (читай "на-автомате").
52:30 "есть ложь, есть большая ложь", а есть код на слайдах. Стоит вспомнить про concurrency при смене Email, а если это еще и в транзакции должно происходить... Тех кто так будет писать просто столкнут с лестницы 😅.
@@LotmineRu Так вроде как репозиторий и не в домене лежит, только его интерфейс, а сам репозиторий в инфраструктуре. Вопрос был больше в том, почему наличие репозитория обязательно требует БД?
@@ryazanov13 ссылочная прозрачность же, класть в домен реализацию или интерфейс - принципиальной разницы нет вводя интерфейс, хранилище все равно никуда не пропадает, зависимость все равно есть, хоть мы ее "замели под ковер"
@@LotmineRu не понял причём тут ссылочная прозрачность. Если вы не используете интерфейс, то: 1) для замены реализации нужно менять домен, что нарушает OCP и DIP 2) невозможно использовать несколько реализаций 3) сложно тестировать (как следствие из п.2 , не можем написать фейковую реализацию) 4) невозможно вынести домен/инфраструктуру в отдельный пакет/гит репозиторий и т.п. И повторюсь вопрос изначально был в том, почему автор решил что без БД не нужен репозиторий. У нас вполне может быть репозиторий который хранит данные в памяти.
Есть вопрос: что делать с рич моделью когда она большая много полей и поддержание инвариантности делает количество методов тоже огромным. Как решать ? Делить модель ? Понятно что это наверно нечастый случай но
Очень холиварный вопрос, тестить без интерфейсов будет так себе. Засунуть всё в модель не очень хорошая идея. Возможно хотя бы сделать модель и интерфейс действий, потом это всё унаследовать в конечную сервис-модель, но опять же это не общепринято. Но искать логику по моделям размазанную, идея не очень хорошая. Триллема бы не появилась, если бы работали отдельно. К тому же CQRS или сам подход разделения чтения и записи сомнителен в озвученном подходе, и прелестно ложится на интерфейсный. Всё таки хорошо, когда есть домен, есть действия домена и объединение это в сущность домена, которая может действия.
52:20 Зачем вытягивать из базы ВСЕГО кастомера вместе с его заказами и другими данными для изменения е-мейла??? Вы представляете какие это тормоза для простой, по сути, операции? А ещё там есть проверка на равенство "вытянутого" кастомера с самим собой. То есть, ещё надо оператор перезагрузить или метод Equals() перезагрузить. Иначе у вас будет только проверка равенства ссылок на два объекта. Короче, много кода для реализации простого функционала + тормоза в работе.
Какие тормоза, ты в онлайн магазе ежедневно делаешь тыщу заказов? А если действительно есть что-то такое, то наверняка оно и смоделировано будет иначе.
@@LotmineRu , да, вы правы. Оно моделируется иначе. Это только в туториалах или при очень маленьких объемах данных вытягивают весь объект со всеми зависимостями чтобы поменять одно поле. В нормальном приложении никто не будет так делать. А если ещё и агрегаты использовать, то там вообще начинается веселье. А вам всего-то надо ОДНО поле поменять и сделать это можно двумя запросами: 1. Проверить есть ли вообще кто-то в базе с таким е-мейлом: db.Customers.Any(c => c.Email == email) 2. Если нет, то Update одного поля по ключу customerId. И все. Бизнес правило уникальности е-мейла обеспечено. Не надо всего пользователя поднимать из базы со всеми его данными, а потом ещё ненужный код писать. Проблему вижу в репозитории, так как чаще всего за ним стоит EF (опять же благодаря туториалам =)) ), а EF не умеет делать Update без предварительного Select. Решается заменой EF на ORM, которая умеет.
@@Berill82 я имел ввиду, что в одних случаях нормально, когда что-то содержит коллекцию чего-то, когда понятно, что в этой коллекции и не бывает 100500 элементов а этот пример с уникальностью емейла... такой себе, уникальность емела зачастую не такое уж важное бизнес-правило, чтобы его непременно нужно было пихать в домен (но допускаю, в каких-то бизнесах оно может быть реально важным.. но чаще - нет) страшную вещь скажу - можно констрейнт в бд повесить и не париться, никто еще от этого не умирал
@@LotmineRu , даже если оставить в стороне обновление е-мейла. Мне не понятно другое. Это же не эффективно загружать весь объект, чтобы потом произвести над ним какие-то действия. В этом примере у нас не такой уж и большой объект. А ведь потом он начинает обрастать дополнительными свойствам по мере того как бизнес начинает добавлять новый функционал. И вот как тут быть? Количество таблиц, в которых содержится вся связанная с пользователем информация, растет. Загрузка этой информации начинает тормозить, запросы усложняются и т.д. И вот тут я вижу проблему в самом подходе. Нам не нужна, допустим, информация о заказах для обновления персональных данных. Тогда зачем ее загружать?!
@@Berill82 Зачем на информация о заказах для обновления персональных данных? Т.е. почему эти две обязанности живут вместе? Ну и как бы предметная область развивается. Если поначалу что-то было ок, а потом становится как-то не ок - очевидно, нужно перерабатывать модель вслед за развитием предметной области. Короче, я бы сильно не зацикливался на примерах, это ж примеры, они не для работы в проде, а для наглядной демонстрации концепций. Во-вторых, я также же бы сильно не зацикливался на производительности, модель важнее. Вот когда припрет - тогда и надо думать об оптимизации.
37:30 ну и что в этом хорошего? А если мы захотим чтобы это настраивалось в системе? Например, не по количеству, а по стоимости, которую каждый год индексируем. Будем каждый год патчить систему? Или пихать в класс ссылку на бизнес правила? В чем проблема проверять валидность и менять статус при сохранении модели? А если это праздничная акция? Опять все менять на уровне толстой модели вместо бизнес-правил? Вообще, когда лектор сказал о синхронизации между дублирующимися моделями разных контекстов, он вообще задумывался о Highload и throughput? Overengineering в чистом виде.
для чего синхронизировать поля модели из одного контекста в другой, а потом ещё постоянно контролировать какие из них можно только читать, если можно просто хранить ID на модель из другого контекста, и всё выше перечисленное можно будет избежать
Владимир не очень честно поступает, показывая полную модель с передачей репозитория в агрегат =) В такой реализации модель выглядит "грязнее", чем модель без асинхронного поведения. Репозиторий это прикладной сервис и ему нечего делать в доменной модели Честнее было бы передавать доменный сервис как интерфейс IUniqueEmailCheck
Согласен. Для себя мы выбрали другой вариант - использовать делегаты. Подробности можно посмотреть тут ua-cam.com/video/Bd83nPK_K3U/v-deo.html Как вам такое решение?
@@andrewtsvetsih2675 тоже думал над вариантом с делегатами, но обобщенными. В вашем докладе довольно изящно и выразительно выглядит, спасибо. Может быть асинхронное поведение агрегата через делегаты будет проще "продать" сторонникам анемичной или чистой модели =)
@@andrewtsvetsih2675 можно еще специализированные интерфесы делать (Interface segregation principle from SOLID). Но это если возможно. Но это не решает дилему описанную Владимиром, функция не получает ссылочной прозрачности.
@@IgorPchelko Спасибо за ответ. Есть похожий подход в докладе ua-cam.com/video/_dQRAsVhCqA/v-deo.html Я считаю, что интерфейсам репозиториев (и любой другой инфраструктуры) в модели не место. А вот как их убрать из модели - это вопрос! Делать еще один уровень интерфейсов вокруг инфраструктуры - это просто лишний уровень абстракции. Так как реализация такого интерфейса будет просто прокидывать вызов к инфраструктуре. Это сильно усложнит проект а выигрыш минимален. Уж лучше напрямую инфраструктуру из модели вызывать. На мой взгляд делегаты - это лучшее решение т.к. они не создают новый уровень и изолируют модель от инфраструктуры. Хотя может я вас неверно понял. Можете привести пример как будет реализация с Interface segregation? Спасибо
@@andrewtsvetsih2675 Просто не воспринимайте репозиторий как обязательную завязку на инфраструктуру и БД , в общем смысле это просто интерфейс доступа к коллекции сущностей , разве доменная модель не имеет права знать о том , какие сущности существуют , если от этого зависит бизнес логика ? Другое дело , что реализация интерфейса уже зависит от инфраструктуры и должна быть размещена за рамками домена.
Интересно, в чем проблема использования ICustomerRepo в домен модели, это слабая зависимость в угоду быстродействия. Эти интерфейсы и описывают в слое домена. Также не понял зачем простые вещи типа "Email" делать value object-ом, почему то мне кажется, в книжках это рекомендация , а не правило. Автор шарит, но зачем то перебарщивает с этим "православным DDD" из книжек
@@D0nSergio видимо, автор имеет ввиду, что проблема не в композиционной зависимости (ведь интерфейс от неё итак избавил), а в семантической. то есть, попытка уйти от зависимости одной доменной области от другой на чисто ментальном уровне
36:15 Интересно так получается, т.е. если у вас несколько областей, которые добавляют ордера, то почему они все не используют AddOrder? Ну т.е. в случае с CustomerService мы нарушаем инварианты если в обход CustomerService меняем Customer, а в случае с богатой моделью мы нарушаем инварианты если в обход модели изменяем состояние модели (например через рефлекшны или др. способы доступа к памяти). Это получается больше вопрос договорённости: не используй рефлекшны и др. прямой доступ к памяти/используй только соответствующий сервис. Используй только соответствующий репозиторий и т.д.
@@DenisMakarovPersonal я не сторонник использования рефлекшнов, но кто-то же использует. Это просто пример. Можно сделать sql запрос на update в обход любых моделей, без всяких рефлекшнов, можно создать другую модель которая будет мапиться в ту же таблицу, и никакая инкапсуляция не спасёт, кто запрещает? Ну только лишь соглашения.
@@ryazanov13 более того, бизнес вообще может не использовать софт и всё делать на бумажках. Соглашение двух людей - сделка, нескольких людей - договор, государства - закон, нескольких государств - международные конвенции.
Хм... Программирование - точная наука. Но, сам процесс - творческий. Любой паттерн - превращает искусство программиста в ширпотреб для масс. Это НЕ плохо, но и не надо безоговорочно следовать правилам. Следовать надо контексту задачи и окружения. Тот же customer в 99% случаев принадлежит "третьей стороне" (каталогу пользователей). И, смысл городить цепочку вызовов "потому, что так в паттерне" вместо прямого обращения к каталогу на проверку дублей e-mail? Да и алгоритмы посика/сравнения в службе наверняка поинтереснее (оптимальнее) самописных. Всё больше убеждаюсь, что со времен изобретения ООП, SOA, витрин данных, "наросла" масса модных трактовок/методик суть которых - Думай прежде чем что-то написать и помни о тех, кто будет править код после тебя.
если бы я не знал аглийский то ничего бы непонял. на пример, Сущность ето Entity? @Vladimir should you publish this to Pluralsight as another DDD course? Something like "DDD in one hour"?
Отличный доклад - редко когда видно, что человек понимает о чем говорит, а значит и может донести мысль до слушателя
Владимир, Вы потрясающий докладчик! Давно не слушал настолько крутого доклада!
шикарный доклад и огромное спасибо Владимиру за талант понятно доносить материал
Тайм-коды:
2:32 Начало. План лекции
3:20 Основные принципы DDD. Ограниченные контексты (bounded context). Problem space, solution space
10:55 Единый язык (ubiquitous language)
15:58 Фокус на доменной модели. Многослойные архитектуры
21:51 Разница между анемичной и богатой моделью
23:32 Богатая модель = Инкапсулированная модель. Про понятие инкапсуляции
27:31 (Не) анемичная доменная модель в рамках функционального программирования
30:17 Рефакторинг анемичной модели через два шага. Строгая типизация
34:15 Уменьшение количества методов, которые изменяют состояние класса
41:06 Вопрос: влияние единого языка на развитие проекта
42:38 Изоляция доменной модели
49:21 Взаимосвязь между инкапсуляцией и изоляцией доменной модели
53:19 DDD трилемма. Изоляция > Инкапсуляция > Быстродействие
59:57 Конец доклада. Вопросы
1:00:56 Как склонить коллег к переходу к богатой модели от анемичной?
1:02:20 Интеграция между Entity Framework и DDD. Наследование от POCO (или DTO?)
1:04:55 Что-то там про передачу интерфейса
1:06:41 Разница между subdomain и bounded context
1:09:00 Рефакторинг при database first approach
1:11:30 Стоит ли использовать DDD прототипирование?
Спасибо, наконец-то я узнал, что такое инкапсуляция! Спустя год и 8 месяцев учёбы
Прям ответил на все мои вопросы которые возникали при мыслях о том как правильно попилить монолит
Пример по моему подобран удачно.
Сразу становится понятно про разделение контекста в случае схожих сущностей (Customer и Product)
Спасибо за доклад
Интересный подход. Вроде как и много сказанное здесь звучит логично и правильно, но и очень непривычно. Спасибо за доклад
Пока лучшее что я видел по DDD для чайников (коим и являюсь).
33 минута, по этой логике и Name нужно обернуть в класс ведь можно указать и пробел или символ и т.п. Важно найти баланс но с другой стороны обернуть в класс не сложно оставив различные валидации и усовершенствования класса своим потомкам)))
Супер! Отличный доклад, спасибо.
56:00 для таких инвариантов придется знание о доменной модели выносить в бд - уникальный индекс на email. И тогда при вставке бд выдаст ошибку, и ее можно будет обработать на доменном уровне.
Ни один из описанных в видео способов не явлется рабочим в конкурентной среде. Если два пользователя решили одновременно сменить email на другой одинаковый, то выборка из бд и проверка в памяти потенциально могут у обоих завершиться успешно. И оба вставят в бд один и тот же email.
Если вынести проверку на уровень бд, то и код в доменной модели станет проще.
Да, можно сказать, что мы переносим проверку инварианиа на инфраструктурный уровень - уровень бд.
Но вообще-то бд является отражением наших сущностей в доменной модели.
Мы не переносим эту логику в реопозитрий. Мы переносим ее в саму бд - место хранения инфы о нашем домене.
Ну и это единственный адекватный рабочий способ. Ну либо можно брать блокировки (или распределеоные блокировки), но вот это уже перенос сохранения инварианта в инфраструктуру
а если у нас подтверждение на изменение пользовательского email зависит не только от его уникальности, но и от других факторов (например подтверждение модератором)? и количество таких условий может множится и бизнес логика может проходить сквозь всю структуру. тогда мы опять попадаем в анемичную модель.
Индекс в базе должен быть в любом случае, вне зависимости от того какую грань трилемы мы выбрали
на редкость хорошая лекция
очень толковый доклад, спасибо
Книга по тестированию супер! рекомендую всем, must read
Очень емко, точно, мне все стало понятно, спасибо!
33:15 Очень хочется посмотреть, что будет с этой скидкой, если она будет зависеть от чего-то ещё, кроме статуса. И вообще, как можно поместить функцию расчета скидки в объект Статус?! А если от статуса будет ещё что-то зависеть? Тоже сделаем методом в классе Статус?!
Ответ чуть далее по ходу - там где требуется использовать внешние данные инвариант должен быть неизменяемым.
Спасибо коллегам из мира java для windows 😊
В пет проекте так и делал, пользуясь С и О принципами солид, не догадываясь про ДДД. Оказывается ДДД расширяет эти принципы. Очень похоже про те штуки, за которые топил Егор Бугаенко. Единственное не согласен, что надо лепить все в один класс. Я бы выделил емеил и статус в отдельную бизнес сущность, и создал класс ответственный за эту сущность и пусть он ее меняет.
Книгу записал в todo.
Владимир отличный пример привел! Спасибо за доклад!
58:18 Если мы выберем вариант с инкапсуляцией и интерфейсом, то можно прекрасно всё тестировать без моков. При этом 3 вариант где плохое быстродействие не обеспечит инкапсуляции. Я имею ввиду вариант прокинуть все заказы в кастомера, т.к. ничто нам не мешает прокинуть пустой или неполный массив (и вообще будет не очевидно какой список кидать в метод). Как по мне нормальная реализация 3го варианта (с инкапсуляцией, изоляцией, но плохой скоростью) это запихнуть список кастомеров в другую модель.
Мне кажется, что Владимир на 41:13 приуменьшает роль DDD в проекте. Потому что, на мой взгляд, хотя я только его ещё изучаю, DDD даёт не только единый язык и взаимопонимание, но и в целом раскладывает всё в проекте по полочкам, позволяя легко в нём ориентироваться, легко его поддерживать и вносить изменения. Благодаря DDD избегается превращение проекта в то, что адепты DDD называют «большим комом грязи». А это уже очень полезно и в разы улучшает поддерживаемость кода и экономит по-настоящему огромные ресурсы компании, обеспечивая при этом высокую функциональность, feature-richness продукта. По-моему, DDD в корне меняет всё, он просто переворачивает всю игру с головы на ноги, раскладывая все яйца по своим корзинам. DDD - это must have на любых проектах, связанных с реальным миром, реальными предметными областями и бизнесом в частности. DDD - это инструкция к тому, как правильно писать код, как сделать его чистым и логичным, и никак не меньше.
Ну а в целом доклад очень крутой, всё чётко и по полочкам. Особенно понравилось как показано превращение анемичной модели в богатую. Вообще, когда что-то на примерах показывается - это очень хорошо воспринимается всегда. За это прям большое спасибо!
А насчёт трилеммы. Лично я не думаю, что использование репозитория в модели - это страшно, потому что репозиторий - это основа любой программы, так как он сохраняет стейт, загружает его, работает с ним, а это - основа основ. Поэтому бояться его и тем более отказываться от него в пользу меньшего быстродействия, по-моему, не разумно. Наоборот, работая с репозиторием в модели, ты видишь, где необходима транзакционная целостность, а значит видишь какие агрегаты ты должен объединить в один, чтобы транзакции по нему проходили нормально. Лично я бы поставил на инкапсуляцию + быстродействие. Смещать логику в контроллеры - это тоже ерунда, так как логика приложения должна быть в модели, и не важно, что эта логика зависит от репозитория. Просто считаем репозиторий частью модели - вот и всё, ведь по сути так оно и есть.
Репозиторий хранит данные по бизнесу, а разве это не часть бизнеса? Списки e-mail'ов, адресов - это ведь всё часть бизнеса, это не что-то, связанное с инфраструктурой. Это реальные данные по бизнесу. К тому же репозиторий - это виртуальная сущность, которая изолирует модель от прямой связи с базой. Базу репозитория всегда можно заменить. Это вообще может под капотом быть какой-нибудь API, или частично API, частично БД, частично вообще файловая система или LDAP - всё, что угодно, может внутри содержать репозиторий, и это никак не повлияет на модель, потому что репозиторий всегда выдаёт лишь те данные, которые реально связаны с моделью и являются частью бизнеса.
отличный коммент! Спасибо
Согласен! Отличный коммент! Действительно, что плохого в том, чтобы модель могла делать save, пусть даже в абстрактную БД, чтобы потом, в случае смены БД, можно было через адаптер подменить логику Хранилища! Более того, что если Фабрика, создающая Агрегат, будет также создавать Хранилище и подсовывать его Агрегату на старте?
Спасибо, отличный доклад
56:30 если это не 1c в коробке, отказываться от быстродействия - нерационально. Значит, в 80% случаев сразу забываем про инкапсуляцию и толстые модели, а валидацию и бизнес-правила размещаем в замечательные сервисы и контроллеры, очень удобные для тестирования.
Такой вопрос, разбили на контексты и написали приложение. Через год понимаем что помимо Продаж и Поддержка нужен еще контекст Маркетинг(или любой другой), как быть в такой ситуации? Насколько деление на контексты является гибким решением? А бывает так что "добавьте вот такую маленькую фичу", фича и правда маленькая но не ложится в существующие контексты, а создавать новый может быть дорого для бизнеса.
Вероятность того, что "маленькая" фича станет со временем большой > 0. В этом случае поддержка изменённого существующего контекста будет тоже дорога для бизнеса, т.к. будет требовать всё более и более объёмной регрессии при будущих модификациях этого контекста с "маленьким" костылём. Это нужно иметь в виду, ну а поступать так, как считаете нужным. Ведь лучше ошибиться и получить опыт, чем пытаться найти "общий" ответ там, где его нет)
Так добавление нового контекста никак не влияет на другие контексты, в этом и есть смысл, чтобы ограничить влияние контекстов. Так что новый контекст добиваться легко
Почему вы пропускаете тот момент что CustomerService так же инкапсулирует в себя DataLayer + CacheLayer? Можно приводить примеры более как то приближенные к жизни?
Очень хороший доклад!
Вот те раз , всегда боролись и пропагандировали за анемичную модель , из-за ее слабой связанности и гибкости в плане расширения возможных операций над ней , и тут на тебе , снова в 90-е ) . У богатой модели много проблем , это паутина , в которой с ростом модели не разобраться , ее тяжело масштабировать , а при внезапно новом бизнес правиле многое переписывать.
При анемичном подходе , нам ничего не мешает возложить валидацию консистентности модели на доменные сервисы , а клиентам отдавать record-ы дабы не могли накосячить с моделью напрямую. , ну и нет никаких проблем с добавлением новых бизнес правил , при этом не затрагивая существующие модели и методы работы с ними.
Сервисная модель полна излишеств и не отражает сущность бизнеса. Глядя на неё ты не понимаешь, для чего она существует. В ней также перемешана логика и инфраструктура. В богатой модели как раз таки всё понятно. Она описывает саму себя. Главное - всегда рисовать её диаграмму.
Аналогичное чувство появилось, только связал с серединой 2000-х. Очередные ребята сделали "открытие". "Я не знаю почему так лучше, но бизнесу нравится"
А, вот это вообще пушка: "вот в таком подходе сделаете за 2 дня, а в старом займёт 2 месяца".
на контексты надо разбивать, про это сказано вначале
Нам бы проблемы трехлетней давности. Печально вспоминать из 2024г.
Спасибо за доклад. В примере с изменением почтового адреса можно соблюсти изоляцию, инкапсуляцию и быстродействие: ChangeEmail(Email email, List usersWithEmail) а в контролере выбрать не всех пользователей а только тех что имеют такую же почту, оверхед - 1 запрос к БД, но запрос все равно должен быть, лист будет либо пустым либо содержать одного пользователя. Понятно, что не всегда можно провернуть такой фокус.
В этой ситуации контроллер всё ещё частично "догадывается" об ограничениях, которые существуют в предметной области, поэтому предоставляет все необходимые данные для принятия решения, но зачем-то отдаёт само право принятия решения доменной сущности. При изменении и расширении этих требований необходимо будет актуализировать код как в контроллере, так и в модели.
Точно так же можно было бы в метод просто передавать булево значение, указывающее на то, есть ли другой активный пользователь с данным имейлом.
В общем, на мой скромный взгляд, это решение, где контроллер лезет в домен, но мы пытаемся обмануть себя и сделать вид, что это по каким-то формальным признакам не так.
не вижу проблемы передать в модель репозиторий через интерфейсную ссылку. Да, это говорит о том, что это где то храниться, но где и как - не открывает. Можно хранить по факту хоть в блокноте
Тоже не понимаю почему автор так против прокидывания интерфейса в модель. Если очень хочется то можно сегрегировать интерфейс. Хотя у автора есть прямо серия статей на эту тему, стоит почитать.
Проблема начинается когда у тебя в модель 10 разных зависимостей на разные подсистемы добавить нужно чтобыреализовать новые требования, таким образом модель превращается в монстра который делает все
@@DenisMakarovPersonal а какая альтернатива, если для системы важна производительность?
@@mikhailsloushch5052 альтернативы нет, есть DDD трилема в которой нужно делать выбор, в конкретном примере я бы подумал нужно ли знать Customer уникальный он или нет, может с этим другая сущность модели должна разбираться, или можно в Customer передавать результаты проверки уникальности (флаг/статус) а не саму лямбду с вызовом функции проверки
Супер, спасибо! Чуть-чуть не уловил на 47 минуте, там где Владимир говорит, что Изоляция доменной области нарушается, если Customer умеет сам себя save в БД. А как тогда будет правильно? Его должен сохранять контроллер? Ну т.е. согласно триллемы DDD (55 минута) выбираем вариант Изоляция+Время ?
Можно уточнить с момент - Должны быть Рич модели, которые знают сами что можно делать с ними, но они не должны знать как себя сохранить? То есть, рич на пол-шишечки?!
А еще возникает вопрос, как быть, если какое-то поле сущности lazy-инициализируемо. При обращении к этому полю его значение нужно получить из другого сервиса, например. В этом случае сущность должна еще и знать как себя дозагрузить.
Она должна знать все о себе в терминах предметной области. Сохранение это уже техническая, инфраструктурная деталь и об этом модели знать не надо.
@@serg4568нет, как раз тут сущность не может быть на полшишечки дозагруженной, она должна быть целостная. Лучше конкретный пример привести, что за lazy свойство такое может быть у модели.
Если значение этого поля находится в другом сервисе, то скорее всего это поле не этой модели, а что-то совершенно другое
@serg4568, Вот-вот, поэтому кажется что Агрегат должен всегда иметь ссылку на своё Хранилище и дёргать по необходимости
спасибо за практические примеры, Очень важный момент
что такое рефорт для рефакторинга??
Классный доклад!
Спасибо. Очень ценно. Докладчик - супер!
Спасибо за доклад! Поддержу предыдущих комментаторов - отличная подача и разъяснения, очень помогли!
ЗЫ. Единственное замечание: было бы хорошо поменьше использовать англицизмы при докладах на русском языке, но я понимаю, что когда практики английского больше, чем русского, это может происходить невольно (читай "на-автомате").
ну это же не доклад 1с. Когда все коммуникации на английском, сложно не использовать понятный для всех сленг
Глупости! Всё там норм с английским, человек умный и трудолюбивый! Вы лучше поучитесь, как надо работать
смотрите, вот заимствованные слова из других языков, которые Вы сами используете:
комментатор
доклад
практика
автомат
🫳🏻
⤵️
⤵️
🎤
💢
52:30 "есть ложь, есть большая ложь", а есть код на слайдах. Стоит вспомнить про concurrency при смене Email, а если это еще и в транзакции должно происходить... Тех кто так будет писать просто столкнут с лестницы 😅.
Жаль, что pluralsight закрыт для России. Хотел там подписаться на курсы автора
очень крутой доклад
1:06:30 Как это? Если приложение работает в памяти, это ещё не значит что в нём нет списка кастомеров => репозитория кастомеров хранящего этот список.
Таким образом, мы переходим к главному вопросу DDD - можно ли выкинуть репозитории из домена?
@@LotmineRu Так вроде как репозиторий и не в домене лежит, только его интерфейс, а сам репозиторий в инфраструктуре. Вопрос был больше в том, почему наличие репозитория обязательно требует БД?
@@ryazanov13 ссылочная прозрачность же, класть в домен реализацию или интерфейс - принципиальной разницы нет
вводя интерфейс, хранилище все равно никуда не пропадает, зависимость все равно есть, хоть мы ее "замели под ковер"
@@LotmineRu не понял причём тут ссылочная прозрачность. Если вы не используете интерфейс, то:
1) для замены реализации нужно менять домен, что нарушает OCP и DIP
2) невозможно использовать несколько реализаций
3) сложно тестировать (как следствие из п.2 , не можем написать фейковую реализацию)
4) невозможно вынести домен/инфраструктуру в отдельный пакет/гит репозиторий и т.п.
И повторюсь вопрос изначально был в том, почему автор решил что без БД не нужен репозиторий. У нас вполне может быть репозиторий который хранит данные в памяти.
реально отличный доклад, жаль что уже 2 года прошло(
почему жаль?
@@vladislavbondarenko6153 потому что прошло уже 2 года,а я только дополз до ddd
@@MrChelovek68 и я))
@@vladislavbondarenko6153 ахахаха, ну штош, велкам ту зэ клаб,бадди)))
Есть вопрос: что делать с рич моделью когда она большая много полей и поддержание инвариантности делает количество методов тоже огромным. Как решать ? Делить модель ? Понятно что это наверно нечастый случай но
Wow!Killlin!!!
Супер спасибо
Очень холиварный вопрос, тестить без интерфейсов будет так себе. Засунуть всё в модель не очень хорошая идея. Возможно хотя бы сделать модель и интерфейс действий, потом это всё унаследовать в конечную сервис-модель, но опять же это не общепринято.
Но искать логику по моделям размазанную, идея не очень хорошая. Триллема бы не появилась, если бы работали отдельно.
К тому же CQRS или сам подход разделения чтения и записи сомнителен в озвученном подходе, и прелестно ложится на интерфейсный.
Всё таки хорошо, когда есть домен, есть действия домена и объединение это в сущность домена, которая может действия.
По тестам у автора есть топ 1 книга в этой области
Не думал, что рекомендуется заводить 2 разные сущности кастомера физически.
"гайд о создании микросервисной архитектуры внутри монолита"
52:00 Получается циклическая зависимость: CustomerRepository зависит от Customer и наоборот
Черт. Нельзя еще раз лайкнуть...
52:20 Зачем вытягивать из базы ВСЕГО кастомера вместе с его заказами и другими данными для изменения е-мейла??? Вы представляете какие это тормоза для простой, по сути, операции? А ещё там есть проверка на равенство "вытянутого" кастомера с самим собой. То есть, ещё надо оператор перезагрузить или метод Equals() перезагрузить. Иначе у вас будет только проверка равенства ссылок на два объекта. Короче, много кода для реализации простого функционала + тормоза в работе.
Какие тормоза, ты в онлайн магазе ежедневно делаешь тыщу заказов? А если действительно есть что-то такое, то наверняка оно и смоделировано будет иначе.
@@LotmineRu , да, вы правы. Оно моделируется иначе. Это только в туториалах или при очень маленьких объемах данных вытягивают весь объект со всеми зависимостями чтобы поменять одно поле. В нормальном приложении никто не будет так делать. А если ещё и агрегаты использовать, то там вообще начинается веселье. А вам всего-то надо ОДНО поле поменять и сделать это можно двумя запросами:
1. Проверить есть ли вообще кто-то в базе с таким е-мейлом: db.Customers.Any(c => c.Email == email)
2. Если нет, то Update одного поля по ключу customerId.
И все. Бизнес правило уникальности е-мейла обеспечено. Не надо всего пользователя поднимать из базы со всеми его данными, а потом ещё ненужный код писать.
Проблему вижу в репозитории, так как чаще всего за ним стоит EF (опять же благодаря туториалам =)) ), а EF не умеет делать Update без предварительного Select. Решается заменой EF на ORM, которая умеет.
@@Berill82 я имел ввиду, что в одних случаях нормально, когда что-то содержит коллекцию чего-то, когда понятно, что в этой коллекции и не бывает 100500 элементов
а этот пример с уникальностью емейла... такой себе, уникальность емела зачастую не такое уж важное бизнес-правило, чтобы его непременно нужно было пихать в домен (но допускаю, в каких-то бизнесах оно может быть реально важным.. но чаще - нет)
страшную вещь скажу - можно констрейнт в бд повесить и не париться, никто еще от этого не умирал
@@LotmineRu , даже если оставить в стороне обновление е-мейла. Мне не понятно другое. Это же не эффективно загружать весь объект, чтобы потом произвести над ним какие-то действия. В этом примере у нас не такой уж и большой объект. А ведь потом он начинает обрастать дополнительными свойствам по мере того как бизнес начинает добавлять новый функционал. И вот как тут быть? Количество таблиц, в которых содержится вся связанная с пользователем информация, растет. Загрузка этой информации начинает тормозить, запросы усложняются и т.д. И вот тут я вижу проблему в самом подходе. Нам не нужна, допустим, информация о заказах для обновления персональных данных. Тогда зачем ее загружать?!
@@Berill82 Зачем на информация о заказах для обновления персональных данных? Т.е. почему эти две обязанности живут вместе?
Ну и как бы предметная область развивается. Если поначалу что-то было ок, а потом становится как-то не ок - очевидно, нужно перерабатывать модель вслед за развитием предметной области.
Короче, я бы сильно не зацикливался на примерах, это ж примеры, они не для работы в проде, а для наглядной демонстрации концепций.
Во-вторых, я также же бы сильно не зацикливался на производительности, модель важнее. Вот когда припрет - тогда и надо думать об оптимизации.
37:30 ну и что в этом хорошего? А если мы захотим чтобы это настраивалось в системе? Например, не по количеству, а по стоимости, которую каждый год индексируем. Будем каждый год патчить систему? Или пихать в класс ссылку на бизнес правила? В чем проблема проверять валидность и менять статус при сохранении модели? А если это праздничная акция? Опять все менять на уровне толстой модели вместо бизнес-правил?
Вообще, когда лектор сказал о синхронизации между дублирующимися моделями разных контекстов, он вообще задумывался о Highload и throughput? Overengineering в чистом виде.
для чего синхронизировать поля модели из одного контекста в другой, а потом ещё постоянно контролировать какие из них можно только читать, если можно просто хранить ID на модель из другого контекста, и всё выше перечисленное можно будет избежать
круто !
Владимир не очень честно поступает, показывая полную модель с передачей репозитория в агрегат =) В такой реализации модель выглядит "грязнее", чем модель без асинхронного поведения. Репозиторий это прикладной сервис и ему нечего делать в доменной модели
Честнее было бы передавать доменный сервис как интерфейс IUniqueEmailCheck
Согласен. Для себя мы выбрали другой вариант - использовать делегаты. Подробности можно посмотреть тут ua-cam.com/video/Bd83nPK_K3U/v-deo.html
Как вам такое решение?
@@andrewtsvetsih2675 тоже думал над вариантом с делегатами, но обобщенными. В вашем докладе довольно изящно и выразительно выглядит, спасибо. Может быть асинхронное поведение агрегата через делегаты будет проще "продать" сторонникам анемичной или чистой модели =)
@@andrewtsvetsih2675 можно еще специализированные интерфесы делать (Interface segregation principle from SOLID). Но это если возможно. Но это не решает дилему описанную Владимиром, функция не получает ссылочной прозрачности.
@@IgorPchelko Спасибо за ответ. Есть похожий подход в докладе ua-cam.com/video/_dQRAsVhCqA/v-deo.html
Я считаю, что интерфейсам репозиториев (и любой другой инфраструктуры) в модели не место. А вот как их убрать из модели - это вопрос!
Делать еще один уровень интерфейсов вокруг инфраструктуры - это просто лишний уровень абстракции. Так как реализация такого интерфейса будет просто прокидывать вызов к инфраструктуре. Это сильно усложнит проект а выигрыш минимален. Уж лучше напрямую инфраструктуру из модели вызывать.
На мой взгляд делегаты - это лучшее решение т.к. они не создают новый уровень и изолируют модель от инфраструктуры.
Хотя может я вас неверно понял. Можете привести пример как будет реализация с Interface segregation?
Спасибо
@@andrewtsvetsih2675 Просто не воспринимайте репозиторий как обязательную завязку на инфраструктуру и БД , в общем смысле это просто интерфейс доступа к коллекции сущностей , разве доменная модель не имеет права знать о том , какие сущности существуют , если от этого зависит бизнес логика ?
Другое дело , что реализация интерфейса уже зависит от инфраструктуры и должна быть размещена за рамками домена.
Жертвуем быстродействием и получаем замену email за 15 секунд на запрос. И риск out of memory.
Интересно, в чем проблема использования ICustomerRepo в домен модели, это слабая зависимость в угоду быстродействия. Эти интерфейсы и описывают в слое домена. Также не понял зачем простые вещи типа "Email" делать value object-ом, почему то мне кажется, в книжках это рекомендация , а не правило. Автор шарит, но зачем то перебарщивает с этим "православным DDD" из книжек
@@D0nSergioпро valueobject тоже не догоняю. за коим нам стремиться, чтобы все объекты были immutable? может лучше тогда уж на haskell писать?😂
@@D0nSergio видимо, автор имеет ввиду, что проблема не в композиционной зависимости (ведь интерфейс от неё итак избавил), а в семантической. то есть, попытка уйти от зависимости одной доменной области от другой на чисто ментальном уровне
36:15 Интересно так получается, т.е. если у вас несколько областей, которые добавляют ордера, то почему они все не используют AddOrder? Ну т.е. в случае с CustomerService мы нарушаем инварианты если в обход CustomerService меняем Customer, а в случае с богатой моделью мы нарушаем инварианты если в обход модели изменяем состояние модели (например через рефлекшны или др. способы доступа к памяти). Это получается больше вопрос договорённости: не используй рефлекшны и др. прямой доступ к памяти/используй только соответствующий сервис. Используй только соответствующий репозиторий и т.д.
менять поля класса рефлекшеном это конечно сильный пример )))
@@DenisMakarovPersonal я не сторонник использования рефлекшнов, но кто-то же использует. Это просто пример. Можно сделать sql запрос на update в обход любых моделей, без всяких рефлекшнов, можно создать другую модель которая будет мапиться в ту же таблицу, и никакая инкапсуляция не спасёт, кто запрещает? Ну только лишь соглашения.
@@ryazanov13 более того, бизнес вообще может не использовать софт и всё делать на бумажках. Соглашение двух людей - сделка, нескольких людей - договор, государства - закон, нескольких государств - международные конвенции.
@@bfg5244 смотря какой бизнес. Если у вас бизнес - компьютерная игра или антиспам сервис, не понятно, что вы будете на бумажках делать :)
👍
Вот сомневаюсь - например вычисление процентов по кредиту -там 100500 интеграций. Вот не место им сущности Заемщик
Хм...
Программирование - точная наука. Но, сам процесс - творческий.
Любой паттерн - превращает искусство программиста в ширпотреб для масс.
Это НЕ плохо, но и не надо безоговорочно следовать правилам. Следовать надо контексту задачи и окружения.
Тот же customer в 99% случаев принадлежит "третьей стороне" (каталогу пользователей). И, смысл городить цепочку вызовов "потому, что так в паттерне" вместо прямого обращения к каталогу на проверку дублей e-mail? Да и алгоритмы посика/сравнения в службе наверняка поинтереснее (оптимальнее) самописных.
Всё больше убеждаюсь, что со времен изобретения ООП, SOA, витрин данных, "наросла" масса модных трактовок/методик суть которых - Думай прежде чем что-то написать и помни о тех, кто будет править код после тебя.
если бы я не знал аглийский то ничего бы непонял. на пример, Сущность ето Entity? @Vladimir should you publish this to Pluralsight as another DDD course? Something like "DDD in one hour"?
жаль что лайк можно поставить только 1 раз))