S>Это почему? Я заранее не знаю из чего состоит автомобиль и как каждая его часть задействована в езде?
Я же дал вам список примеров. Что именно в них непонятно? S>Каким образом?
Именно таким. В первой версии колёсам не надо было знать, куда мы поворачиваем, а во второй — надо.
S>Почему придется перекладывать, будут соотв. абстракции, которые будут отвечать за тот или иной аспект. Что и куда перекладывается?
Я же привёл примеры. Попробуйте прикинуть, какие сигнатуры будут у ваших классов для описания автомобиля.
S>Примеры с обратной кинематикой и способы решения диффуров? Это какие-то очень низкоуровневые детали.
Примеры с автомобилем. Мне не жалко, я ещё раз их приведу:
Дальше вы начинаете вызывать методы "Ехать влево" у составляющих агрегат элементов. Вот вы заложились на то, что у руля этот метод нужно вызывать, а у колёс и двигателя — нет.
Завтра к вам приходит заказчик и рассказывает, что в его модели автомобиля задние колёса умеют подруливать, поэтому им тоже нужно "знать" о повороте всего автомобиля влевою
А послезавтра он говорит, что теперь эти колёса должны подруливать интеллектуально — при перестроениях между полосами они должны подруливать в ту же сторону, а при манёврах в ограниченном пространстве в противоположную.
Ещё через неделю выясняется, что нужно контролировать занос — и при потере сцепления с дорогой (о чём "знают" только колёса, да и то при сравнении их данных друг с другом, а не по отдельности) нужно сбрасывать газ на двигателе.
И каждый раз, как поступает новое требование, вам приходится править всю иерархию классов, причём как интерфейс, так и реализацию.
S>Пускай. У кого будет не десяток, а сотня абстракций. Главное, что все они спрятаны за абстракцией Car.
Нет конечно. Нет такой абстракции "Car". Она тут совершенно не нужна. Есть "кинематическая схема", и она никакая не абстрактная, а совершенно конкретная. У неё нет никакого "поведения" в смысле ООП.
Если вам завтра надо будет решать задачу управления катером, а не автомобилем — вы что, будете с нуля писать новую абстракцию Boat?
А зачем? Чем эта абстракция будет отличаться от абстракции Car? Ладно, пусть там будет не Car/Boat, а абстракция Vehicle. Нужны ли будут для кинематики катера и автомобиля классы Boat:Vehicle и Car:Vehicle?
Если да, то зачем?
S>Техническое за Car, может бензина нет или другая поломка. Во всех остальных случаях -- зависит от контекста. Может документов каких нету?
Это не ответ. Сможем ли мы повторно использовать "абстракцию Car" во всех трёх доменах, или придётся всякий раз писать её заново?
Сможем ли мы реализовать в одном конкретном "классе SpecificCar" все три абстракции?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Вот это нужно как-то обосновывать. Потому, что связи не являются "свойствами" в каком-то объективном смысле — они не "принадлежат" сущности.
Здесь нет связей, здесь есть именно свойства сущностей.
S>Вот у нас есть отдел, есть его начальник. "Руководить" — это связь между отделом и сотрудником; она является "свойством" сотрудника в той же степени, что и отдела.
Это не связь, а совершенно отдельный доменный объект — "роль". У сотрудника может быть много ролей в одном отделе, эти роли могут пересекаться или активироваться по каким-то особым критериям. Вплоть до того, что роль может быть вакантной.
S>Поэтому и непонятно, отчего вы решили, что обязательство — это "свойство" единицы товара, что бы понятие "единица товара" ни означало.
Единица товара это атомарный субъект транзакции учета: пачка молока, красная шляпа, автомобиль. Ведь в реальности ты продаешь покупателю не -1 из строчки количество красных шляп, а конкретный предмет со собственными свойствами и историй. И при наступлении опредленного этапа жизненного цикла заказа у тебя появляется обязательство этот предмет выделить из кучи однотипных и передать его в пользование покупателю.
S>Это понятно. Непонятно, почему вы "поведение" приписываете каким-то конкретным концептам из вашего "домена".
Потому что поведение выражается через свойства доменных сущностей. Поведения в отрыве от сущностей не существует. Т.е. первичный сущности и только относительно сущностей мы можем определять поведение.
S>В домене "торговля товаром" обязательство является свойством контрагента, а не какого-то конкретного товара. Это не товар "перемещается" от поставщика к получателю, это кто-то, обладающий свободой воли, перемещает товар.
В домене торговля никого "обладающего свободой" нет и быть не может. В торговле царствуют алгоритмы имеющие силу закона. Ты не можешь взять товар и переместить его вместо покупателя на свалку просто потому что тебе так захотелось.
S>Так не бывает. Дизайн очень сильно влияет на реализацию, ни о какой ортогональности речь идти не может.
Реализация МОЖЕТ влиять на дизайн, но задача DDD это как раз избежать этого влияния моделируя предметную область в отрыве от искуственных ограничений. Ты можешь сделать дизайн чтобы самые частые операции выполнялись быстрее всего. Например сложить товары и заказы в одну базу. Но такой дизайн всегда получается очень хрупким, непригодным к расширению домена и при изменении требований такой дизайн быстро разваливающийся под собственной тяжестью.
К тому же, в современных системах не принято при выполнении доменной операции сразу бежать и что-то менять в хранилищах. Вместо этого операции накапливаются, а потом материализуются пачкой. Это могут транзакции в БД, CQRS,
функциональные эффекты или старый добрый паттерн Команда из GoF. Тебе никто не мешает в момент материализации переопределить порядок операций и применить любые мыслимые оптимизации, на которые у тебя хватит фантазии. Например, заменять пару SELECT/UPDATE на in place update или вообще на вызов хранимой процедуры которая генерируется из того же DSL
Атомарность заказа это на самом деле плохое требование. Оно сложное в реализации, плохо влияет на производительноть и, главное, не приносит прибыли маскируя проблему. Если мы не продали что-то потому что оно закончилось у нас на складе, это прямой УБЫТОК, потому что мы упустили прибыль. И современные системы строятся так, чтобы такое происходило как можно реже. Поэтому в приоритете динамической ценообразование, предсказание продаж, автоматическое пополнение и другие продвинутые процессы. В тех редких случаях когда такая ситуация произошла, дешевле вернуть покупателю деньги за недоставленный товар чем закладываться на этот сценарий.
S>Нет, это не проекция. То, что вы описываете — это REST-идеология, когда все сценарии выражаются в терминах CRUD-операций. Но она сама по себе работает только тогда, когда у нас нет никакого "поведения" за пределами этих модификаций.
Ну да, REST широко использует проекции данных. Это неудивительно, ведь все что мы делаем, в конечном итоге сводится к машине Тюринга на конечной ленте, а в ней ничего кроме данных не существует.
S>Вот я взял и добавил связь между сотрудником и департаментом — и это привело к определённому результату, независимо от того, оформил ли я это через department.Employees += e или через e.Deparment = department. А может, так вообще нельзя, и нужно создать экземпляр EmployeeContractAddendum, который после подписания обеими сторонами автоматически отразит изменения штатного расписания департамента и должности у сотрудника.
Не всегда проекция возможна, это верно. Но ты и сам разрешил это противоречие добавив новую сущность.
Здравствуйте, Miroff, Вы писали:
M>Здесь нет связей, здесь есть именно свойства сущностей.
Непонятно. Сущность не может быть свойством другой сущности.
M>Это не связь, а совершенно отдельный доменный объект — "роль". У сотрудника может быть много ролей в одном отделе, эти роли могут пересекаться или активироваться по каким-то особым критериям. Вплоть до того, что роль может быть вакантной.
Этот объект совершенно точно не может быть отдельным. Потому что без сотрудника он невозможен, как и без отдела. Можно усложнять доменную модель, которую я предложил — например, так, как предложили вы.
Просто тогда в модели вместо бинарной связи "быть начальником" между сотрудником и отделом появляется тернарная связь "исполнять роль", в которой участвуют отдел, сотрудник, и роль.
M>Единица товара это атомарный субъект транзакции учета: пачка молока, красная шляпа, автомобиль. Ведь в реальности ты продаешь покупателю не -1 из строчки количество красных шляп, а конкретный предмет со собственными свойствами и историй. И при наступлении опредленного этапа жизненного цикла заказа у тебя появляется обязательство этот предмет выделить из кучи однотипных и передать его в пользование покупателю.
Это не означает, что я обязан держать где-то столько строчек, сколько есть экземпляров однотипных товаров у меня на складе.
Вот например, я продаю кому-то 0.435 кг изюма. Что такое "обязательство"? Что тут будет "предметом, выделяемым из кучи однотипных"? 1380 изюминок?
M>Потому что поведение выражается через свойства доменных сущностей. Поведения в отрыве от сущностей не существует. Т.е. первичный сущности и только относительно сущностей мы можем определять поведение.
По-прежнему непонятно, откуда у вас возникла такая фантазия. У красной шляпы нет никакого поведения. И через свойства красной шляпы поведение домена не выражается.
Поведение есть у того, кто эту шляпу продаёт — будь то живой продавец или программа.
M>В домене торговля никого "обладающего свободой" нет и быть не может. В торговле царствуют алгоритмы имеющие силу закона. Ты не можешь взять товар и переместить его вместо покупателя на свалку просто потому что тебе так захотелось.
Чего это вдруг? Запросто перемещу. Для этого мне достаточно оформить накладную на списание. И какое там "поведение" у этого товара мне наплевать — процесс списания регламентируется приказом по компании, и сегодня он один, завтра другой, а послезавтра он зависит от цвета шляпы. Бежать внедрять код логики списания внутрь класса, объектом которого является "красная шляпа" — это последнее, что стоит делать. Именно по этой причине.
M>Реализация МОЖЕТ влиять на дизайн, но задача DDD это как раз избежать этого влияния моделируя предметную область в отрыве от искуственных ограничений. Ты можешь сделать дизайн чтобы самые частые операции выполнялись быстрее всего. Например сложить товары и заказы в одну базу. Но такой дизайн всегда получается очень хрупким, непригодным к расширению домена и при изменении требований такой дизайн быстро разваливающийся под собственной тяжестью.
M>К тому же, в современных системах не принято при выполнении доменной операции сразу бежать и что-то менять в хранилищах. Вместо этого операции накапливаются, а потом материализуются пачкой. Это могут транзакции в БД, CQRS, M>функциональные эффекты или старый добрый паттерн Команда из GoF. Тебе никто не мешает в момент материализации переопределить порядок операций и применить любые мыслимые оптимизации, на которые у тебя хватит фантазии. Например, заменять пару SELECT/UPDATE на in place update или вообще на вызов хранимой процедуры которая генерируется из того же DSL
M>Атомарность заказа это на самом деле плохое требование. Оно сложное в реализации, плохо влияет на производительноть и, главное, не приносит прибыли маскируя проблему. Если мы не продали что-то потому что оно закончилось у нас на складе, это прямой УБЫТОК, потому что мы упустили прибыль. И современные системы строятся так, чтобы такое происходило как можно реже. Поэтому в приоритете динамической ценообразование, предсказание продаж, автоматическое пополнение и другие продвинутые процессы. В тех редких случаях когда такая ситуация произошла, дешевле вернуть покупателю деньги за недоставленный товар чем закладываться на этот сценарий.
Всё верно. Это означает, что подход, в котором "цена" является свойством "товара", непригоден. Даже если мы это свойство делаем вычисляемым. Сам товар не может знать, почём его продают — потому что ценообразование динамическое, и оно зависит и от товара, и от наличия этого товара на складе сейчас, и от покупателя и его предыстории, и от соседних товаров в том же заказе.
Никакая из этих "доменных сущностей" не подходит для размещения логики определения цены. Только какой-то внешний по отношению к этому домену "динамический ценообразователь", который, как правило, стейтлесс.
Вот он скорее всего будет построен по принципам ООП — там тебе и наследование, и полиморфизм, и агрегация, и паттерны Policy, и Factory Method и все лучшие наработки. А вот в "доменных сущностях" (в терминологии ER-модели) никакой логики не будет.
S>>Нет, это не проекция. То, что вы описываете — это REST-идеология, когда все сценарии выражаются в терминах CRUD-операций. Но она сама по себе работает только тогда, когда у нас нет никакого "поведения" за пределами этих модификаций.
M>Ну да, REST широко использует проекции данных. Это неудивительно, ведь все что мы делаем, в конечном итоге сводится к машине Тюринга на конечной ленте, а в ней ничего кроме данных не существует.
S>>Вот я взял и добавил связь между сотрудником и департаментом — и это привело к определённому результату, независимо от того, оформил ли я это через department.Employees += e или через e.Deparment = department. А может, так вообще нельзя, и нужно создать экземпляр EmployeeContractAddendum, который после подписания обеими сторонами автоматически отразит изменения штатного расписания департамента и должности у сотрудника.
M>Не всегда проекция возможна, это верно. Но ты и сам разрешил это противоречие добавив новую сущность.
То, что вы используете общепринятые термины в необщепринятом смысле, только затрудняет коммуникацию.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Miroff, Вы писали:
M>Атомарность заказа это на самом деле плохое требование. Оно сложное в реализации, плохо влияет на производительноть и, главное, не приносит прибыли маскируя проблему. Если мы не продали что-то потому что оно закончилось у нас на складе, это прямой УБЫТОК, потому что мы упустили прибыль. И современные системы строятся так, чтобы такое происходило как можно реже. Поэтому в приоритете динамической ценообразование, предсказание продаж, автоматическое пополнение и другие продвинутые процессы. В тех редких случаях когда такая ситуация произошла, дешевле вернуть покупателю деньги за недоставленный товар чем закладываться на этот сценарий.
Никогда не видел столько заблуждений в одной фразе.
Это же насколько надо не разбираться в:
— архитектуре ПО
— финансах
— маркетинге
— человеческой психологии
чтобы толкать такую чушь...
Здравствуйте, gandjustas, Вы писали:
G>Никогда не видел столько заблуждений в одной фразе. G>Это же насколько надо не разбираться в: G>- архитектуре ПО G>- финансах G>- маркетинге G>- человеческой психологии G>чтобы толкать такую чушь...
Самшено слышать это от человека, у которого корзина обновляется транзакционно со складом
Здравствуйте, Sinclair, Вы писали:
S>Непонятно. Сущность не может быть свойством другой сущности.
С чего бы? Колеса это свойство автомобиля в значении part of.
S>Этот объект совершенно точно не может быть отдельным. Потому что без сотрудника он невозможен, как и без отдела. Можно усложнять доменную модель, которую я предложил — например, так, как предложили вы.
Роль может существовать и без сотрудника и без отдела. Например, ТКРФ определяет роль "представитель профсоюза" необходимую для решения трудовых конфликтов. У тебя в компании может не быть ни профсоюза, ни сотрудников, но роль такая есть в силу закона. А из твоего утверждения следует, что раз в компании нет профсоюза, то и представителей профсоюза не бывает.
S>Просто тогда в модели вместо бинарной связи "быть начальником" между сотрудником и отделом появляется тернарная связь "исполнять роль", в которой участвуют отдел, сотрудник, и роль.
S>Это не означает, что я обязан держать где-то столько строчек, сколько есть экземпляров однотипных товаров у меня на складе.
Именно это и означает. Вам даже государство прямым текстом говорит через честный знак: ребята, надо переходить на поединичный учет. Пора уже, ну сколько можно?
S>Вот например, я продаю кому-то 0.435 кг изюма. Что такое "обязательство"? Что тут будет "предметом, выделяемым из кучи однотипных"? 1380 изюминок?
Это на самом деле операция производства: из единицы хранения ящик изюма ты выделяешь 435г в упаковку и эта упаковка становится новой товарной единицей.
S>Всё верно. Это означает, что подход, в котором "цена" является свойством "товара", непригоден. Даже если мы это свойство делаем вычисляемым. Сам товар не может знать, почём его продают — потому что ценообразование динамическое, и оно зависит и от товара, и от наличия этого товара на складе сейчас, и от покупателя и его предыстории, и от соседних товаров в том же заказе.
Никакая из этих "доменных сущностей" не подходит для размещения логики определения цены. Только какой-то внешний по отношению к этому домену "динамический ценообразователь", который, как правило, стейтлесс.
Именно! Если мы пересматриваем модель ценообразования, нам нужно глубоко анализировать домен, в котором эта цена вычисляется. Нас буквально ЗАСТАВЛЯЮТ отвечать на "неудобные" вопросы, вроде: из чего складывается цена? Каким образом она складывается? Различимы ли с точки зрения цены красная шляпа один, красная шляпа два и черная шляпа три? Должна ли история продаж лапсердаков влиять на цену красных шляп? Сама архитектура провоцирует глубокую аналитику и ресерч, а не просто копировать одни и те же таблички из проекта в проект.
S>То, что вы используете общепринятые термины в необщепринятом смысле, только затрудняет коммуникацию.
Очень трудно говорить с человеком, который ментально застрял где-то в 2006 году)
Я рекомендую тебе попробовать сделать какой-нибудь проект на принципиально новом для тебя стеке, например на Scala c CATS/ZIO. Там у тебя будут нормальные функциональные монады, множественное наследование, частичное наследование, богатая система типов, частичные вычисления и богата система типов. Такой опыт сильно расширит твой архитектурный кругозор пониманием, что вовсе не обязательно ограничиваться двумя отношениями is_a и part_of.
Я понимаю твою позицию, давайте сделаем анемичную модель и распихаем логику по сервисам. На практике это едва ли не худший подход, который можно себе представить Сервисная архитектура довольно быстро вырождается в уродливого неподдерживаемого монстра по нескольким причинам:
1. Сервисы не дают тебе обратной связи о том, что твоя доменная модель неадекватна реальности. Разработчики городят сервисы на сервисы и довольно быстро система теряет концептуальную целостность. Со временем это приводит к потере понимания системы и появлению типичных для этого класса архитектур багов неконсистентности на границах сервисов. В то время как с рич моделью невозможность засунуть метод в какой-то класс это однозначный сигнал что в той модели мира, на основе которой построена эта модель, данная операция невозможна и необходим рефакторинг. Причем рефакторинг должен заключаться в пересмотре модели мира, а в перекладывании методов между классами.
2. Выделение сервисов происходит либо строго по границам доменных сущностей, что эквивалентно рич модели с точностью до положения методов. Либо сервисы выделяются произвольным образом безотносительно реального мира. Индикатором этого служат затруднения разработчиков в именовании сервисов случающиеся у разработчиков. Это как в философии, если ты не можешь что-то поименовать, то этого не существует. И даже если ты выделил сервис по акторам реального мира то со временем в эти сервисы просочатся нехарактерные для этих акторов методы просто потому что так удобнее. И в силу размытости ответственности сервисов из п1. разработчики легко натянут аргументацию почем именно этот метод должен быть именно в этом сервисе. Потому что на самом деле в какой из MiscService класть метод вообще не важно.
3. В силу п.2, иерархии сервисов не коррелируют с иерархиями доменной модели. Это приводит к тому, что сервисы изобилуют проверками сущностей, с которыми они взаимодействуют. Это еще один источник багов, прямо по Аллену. К тому же, это даже не ООП, а откат к процедурному программированию.
Здравствуйте, Miroff, Вы писали:
M>С чего бы? Колеса это свойство автомобиля в значении part of.
По определению. Сущность — концепт предметной области, обладающий идентичностью и с независимым от других сущностей жизненным циклом.
В вашей метафоре колеса существуют совершенно отдельно от автомобиля — например, лежат на складе.
А когда мы их монтируем в автомобиль, то между автомобилем и колесом возникает связь "to be part of" / "to consist of".
M>Роль может существовать и без сотрудника и без отдела. Например, ТКРФ определяет роль "представитель профсоюза" необходимую для решения трудовых конфликтов.
В такой предметной области эта "роль" сущностью не является — у неё нет жизненного цикла. Она существует "вне времени".
В ER-модели она является атрибутом сотрудника, с двумя возможными значениями "да" и "нет".
M>Именно это и означает. Вам даже государство прямым текстом говорит через честный знак: ребята, надо переходить на поединичный учет. Пора уже, ну сколько можно?
Вы уже начали поединичный учёт зерён гречки и изюминок? Нет? Пора уже, ну сколько можно!
M>Это на самом деле операция производства: из единицы хранения ящик изюма ты выделяешь 435г в упаковку и эта упаковка становится новой товарной единицей.
Нет никакой упаковки, и никакой новой товарной единицы.
И чем "ящик изюма" так принципиально отличается от "ящика шляп" или "склада с цементом"?
M>Именно! Если мы пересматриваем модель ценообразования, нам нужно глубоко анализировать домен, в котором эта цена вычисляется. Нас буквально ЗАСТАВЛЯЮТ отвечать на "неудобные" вопросы, вроде: из чего складывается цена? Каким образом она складывается? Различимы ли с точки зрения цены красная шляпа один, красная шляпа два и черная шляпа три? Должна ли история продаж лапсердаков влиять на цену красных шляп? Сама архитектура провоцирует глубокую аналитику и ресерч, а не просто копировать одни и те же таблички из проекта в проект.
Всё верно. При этом DDD нам тут только мешает, т.к. требует вносить эту логику манипулирования сущностями внутрь самих этих сущностей.
M>Очень трудно говорить с человеком, который ментально застрял где-то в 2006 году) Я с радостью выйду из 2006 года, если вы мне дадите ссылку на какую-нибудь литературу, которая систематически излагает "более новую" модель, которой вы оперируете.
M>Я рекомендую тебе попробовать сделать какой-нибудь проект на принципиально новом для тебя стеке, например на Scala c CATS/ZIO. Там у тебя будут нормальные функциональные монады, множественное наследование, частичное наследование, богатая система типов, частичные вычисления и богата система типов. Такой опыт сильно расширит твой архитектурный кругозор пониманием, что вовсе не обязательно ограничиваться двумя отношениями is_a и part_of.
Я придерживаюсь той точки зрения, что стек — вторичен. Первична именно архитектура. Не имеет никакого смысла изучать особенности какого-то конкретного фреймворка, не понимая первооснов.
Например, модель Чена, придуманная ажно в 1975 году, популярна как раз в силу своей агностики по отношению к выбранной технологии. Поэтому можно брать её за основу, а уже потом переходить от концептуальной модели к какой-нибудь логической — например, к реляционной, или объектной, или функциональной.
Кстати, функциональная модель обычно навязывает гораздо более качественную архитектуру, чем лобовое DDD. Потому что в ней манипуляции (функции) отделены от состояния (данных).
Когда мы применяем этот подход к проектированию бизнес-софта с использованием ООП, возникает т.н. "анемик модель", в которой объекты делятся на две несимметричные группы: "сущности", у которых нет поведения, и "обработчики", у которых нет состояния.
M>Я понимаю твою позицию, давайте сделаем анемичную модель и распихаем логику по сервисам. На практике это едва ли не худший подход, который можно себе представить Сервисная архитектура довольно быстро вырождается в уродливого неподдерживаемого монстра по нескольким причинам.
Причины хорошие, но они остаются, скажем так, абстрактными рассуждениями без реального кода.
Вот вы предложили скалу с каким-то фреймворком — ок, давайте рассмотрим, как выглядит в ней решение какой-нибудь типовой задачи. Да хоть с теми же заказами, товарами, покупателями, оплатами и отгрузками.
Эта задача хороша тем, что она подробно задокументирована, например — в спецификации тестов TPC-C.
Решение в стиле 90х для неё есть — там где адский SQL с его хранимками.
Как будет выглядеть её решение в каноническом DDD на Java мы тоже знаем (и понимаем, почему оно превращается в неподдерживаемого монстра, который работает в 100 раз медленнее, чем решение на устаревших технологиях).
Как будет выглядеть модное решение? Ну, хотя бы его фрагмент?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.