Ты не задеваешь, ты просто пишешь много букв без конкретики и додумваешь в своей голове.
Как ты собираешься менять состояние не абы где и как, а в рамках ограниченного скоупа? В любимом тобой ООП этот скоуп определяется классом.
В Go это у тебя будет набор функций, где в качестве ресивера выступает ентитя. Все их ты положишь рядышком, определив тем самым интерфейс взаимодействия со структурой.
Народ, собсно, просит более явный способ группировки.
Критерий Эванс предлагает следующий: continuity у entity и концептуальная неотличимость двух объектов с одинаковыми атрибутами у VO.
В большинстве случаев VO действительно легковесные и без лишних извращений. В принципе, VO полезен сам по себе без всякого DDD.
Эванс во многом опирается на Фаулера и GoF, но вообще там местами есть тонкие отличия, потому как у Эванса рассуждения больше про предметную модель, нежели про техническую реализацию.
Инкапсуляция — это тоже инструмент, хотя и приятный. :-)
Основная цель DDD — UL, отраженный в коде. Инкапсуляция даёт возможность чётко ограничить набор поведений с побочными эффектами.
В Go это просто делается чуть иначе. Да, в случае честного ООП технически будет сложнее стрелять себе в ногу, а где-то требуется больше дисциплины и соглашений. Например, можно договориться, что структуру допустимо изменять только в рамках определенного интерфейса.
Creating extra options for performance tuning can be important because VALUE OBJECTS tend to be numerous. The example of the house design software hints at this. If each electrical outlet is a separate VALUE OBJECT, there might be a hundred of them in a single version of a single house plan. But if all outlets are considered interchangeable, we could share just one instance of an outlet and point to it a hundred times (an example of FLYWEIGHT [Gamma et al. 1995]). In large systems, this kind of effect can be multiplied by thousands, and such an optimization can make the difference between a usable system and one that slows to a crawl, choked on millions of redundant objects. This is just one example of an optimization trick that is not available for ENTITIES. The economy of copying versus sharing depends on the implementation environment. Although copies may clog the system with huge numbers of objects, sharing can slow down a distributed system. When a copy is passed between two machines, a single message is sent and the copy lives independently on the receiving machine. But if a single instance is being shared, only a reference is passed, requiring a message back to the object for each interaction. Sharing is best restricted to those cases in which it is most valuable and least troublesome: • When saving space or object count in the database is critical • When communication overhead is low (such as in a centralized server) • When the shared object is strictly immutable
Например, в определенных случаях может существовать таблица VO где есть синтетический PK и Unique Index по всем значимым атрибутам. И гонять будут только PK между системами. Важно лишь то, что в контексте бизнес-модели у такого объекта не существует identity. Она здесь вылезает не более в качестве технического решения.
Ну успешного вам хранения коллекции VO в ентити без использования json columns. Это как минимум.
Во-вторых, VO не обязан быть минималистичным, как email, он может быть и довольно монструозным. В ряде случаев вам нужно будет обеспечить экзотичные не функциональные требования. Эванс, кстати, разбирает это в своей книге.
Практически любой успешный/долго живущий софт неизбежно обрастает разного рода нюансами. Проблема часто не столько даже в когнитивной сложности, сколько в неочевидном маппинге problem->solution.
То есть имея перед глазами запрос бизнеса, непросто понять, где оно должно быть изменено в коде. И наоборот, глядя в код, непросто описать бизнес процесс в терминах, использующихся экспертами.
Я к тому, что стратегическая часть DDD очень желательна с самого начала проекта, но разве будет менеджмент читать синюю книгу... И тем она и полезна для инженеров, чтоб осознать тщетность технических потуг, если идея не "продана" бизнесу.
Сервисов может быть 100500 и у сервиса по 100500 методов. Если каждый позволяет себе писать в свойства объекта, то инварианты придется собирать по всей системе, а также будет тяжело гарантировать их сохранность.
Если сервис А пишет в свойства X и Y, а сервис B в свойства Y и Z, то если существует правило для свойств X и Z, вы рискуете его нарушить. На практике ещё хуже, сначала правила нет, а потом вдруг нарисовывается. Заниматься поиском всех мест, где оно вовлечено, занятие неблагодарное.
Иными словами, свойства сущности сильно связанные штуки, и потому дистанция до кода, их меняющего, должна быть минимальна.
Глагол to link используют продакты. Ограничение в виде одноразовости проистекает из описания процесса. В требованиях нет кейсов на изменение или сброс ссылки. Если такие требования появятся, будет и соответствующее изменение в коде.
Так никто ж не предлагает вообще все в класс тащить. Для этого и существуют доменные события, доменные сервисы и сервисы уровня приложения.
Я скорее про случаи когда бизнес объект вырождается в DTO с кучей мутабельных свойств типа int или string. И совершенно неочевидно, как эти свойства эволюционируют и взаимодействуют меж собой.
Чисто из недавнего пример:
Нужно сохранить номер заказа во внешней системе, разработчик влепил:
public string? OrderReference {get; set;}
Но можно чуть иначе:
public string? OrderReference {get; private set;}
public Result LinkOnceToOrder(string orderReference)
{
if (OrderReference is not null && orderReference != OrderReference)
{
return Failure();
}
...
}
Кода больше, факт, но и ясности, на мой взгляд, добавляется. Свойство напрямую поменять нельзя. Линкануть можно единожды, анлинкануть вообще нельзя.
В принципе, можно и для OrderReference, при необходимости, свой тип определить или, если поведение сущности меняется, разбить тип самой сущности на два: Xyz и LinkedXyz (имена условные).
Сложность же не только в требованиях, но и в и том, где они находятся. Event storming обычно проводят для формирования целостного представления о системе путем выковыривания священного знания из людских голов.
Если код не является хорошей моделью релевантных бизнес понятий и процессов, то часто приходится разбираться к кому идти с вопросами, а потом организовывать общение по теме. Vernon это называет tacit knowledge.
Отношусь к DDD как к методу обменять бизнесовую сложность на техническую.
Предпочту отразить в коде и сделать более явными бизнес-требования, даже если код становится чуть сложнее для привыкших к процедурному стилю с анемичными моделями. На практике это практически всегда себя оправдывает, потому как при эволюции системы первое на порядки важнее. Глядеть в "простой" код, зная, что он делает, без понимания "почему" и каковы ограничения — боль.
Самый сложный тактический паттерн — агрегат. Выбрать правильные границы с первого захода очень непросто. Да и ORM'ы, к сожалению, далеко не всегда способны в маппинг по-настоящему, когда объекты не являются копиями плоских таблиц в памяти.
Ты не задеваешь, ты просто пишешь много букв без конкретики и додумваешь в своей голове.
Как ты собираешься менять состояние не абы где и как, а в рамках ограниченного скоупа? В любимом тобой ООП этот скоуп определяется классом.
В Go это у тебя будет набор функций, где в качестве ресивера выступает ентитя. Все их ты положишь рядышком, определив тем самым интерфейс взаимодействия со структурой.
Народ, собсно, просит более явный способ группировки.
https://github.com/golang/go/issues/55863
А теперь давай покажи, где ты в моем ткомментарии увидел про VO?
Цель построить здание, подцель — соединить конструкции. Строили и без гвоздей. ;-) Бизнесу нужно ехать, шашечки его не сильно волнуют.
Критерий Эванс предлагает следующий: continuity у entity и концептуальная неотличимость двух объектов с одинаковыми атрибутами у VO.
В большинстве случаев VO действительно легковесные и без лишних извращений. В принципе, VO полезен сам по себе без всякого DDD.
Эванс во многом опирается на Фаулера и GoF, но вообще там местами есть тонкие отличия, потому как у Эванса рассуждения больше про предметную модель, нежели про техническую реализацию.
Инкапсуляция — это тоже инструмент, хотя и приятный. :-)
Основная цель DDD — UL, отраженный в коде. Инкапсуляция даёт возможность чётко ограничить набор поведений с побочными эффектами.
В Go это просто делается чуть иначе. Да, в случае честного ООП технически будет сложнее стрелять себе в ногу, а где-то требуется больше дисциплины и соглашений. Например, можно договориться, что структуру допустимо изменять только в рамках определенного интерфейса.
Вот комментарий Вернона: https://github.com/VaughnVernon/IDDD_Samples/issues/22
Вот выдержка из книги Эванса:
Creating extra options for performance tuning can be important because VALUE OBJECTS tend to be numerous. The example of the house design software hints at this. If each electrical outlet is a separate VALUE OBJECT, there might be a hundred of them in a single version of a single house plan. But if all outlets are considered interchangeable, we could share just one instance of an outlet and point to it a hundred times (an example of FLYWEIGHT [Gamma et al. 1995]). In large systems, this kind of effect can be multiplied by thousands, and such an optimization can make the difference between a usable system and one that slows to a crawl, choked on millions of redundant objects. This is just one example of an optimization trick that is not available for ENTITIES. The economy of copying versus sharing depends on the implementation environment. Although copies may clog the system with huge numbers of objects, sharing can slow down a distributed system. When a copy is passed between two machines, a single message is sent and the copy lives independently on the receiving machine. But if a single instance is being shared, only a reference is passed, requiring a message back to the object for each interaction. Sharing is best restricted to those cases in which it is most valuable and least troublesome:
• When saving space or object count in the database is critical
• When communication overhead is low (such as in a centralized server)
• When the shared object is strictly immutable
Например, в определенных случаях может существовать таблица VO где есть синтетический PK и Unique Index по всем значимым атрибутам. И гонять будут только PK между системами. Важно лишь то, что в контексте бизнес-модели у такого объекта не существует identity. Она здесь вылезает не более в качестве технического решения.
Ну успешного вам хранения коллекции VO в ентити без использования json columns. Это как минимум.
Во-вторых, VO не обязан быть минималистичным, как email, он может быть и довольно монструозным. В ряде случаев вам нужно будет обеспечить экзотичные не функциональные требования. Эванс, кстати, разбирает это в своей книге.
Только тактическая часть, и то не всё. ООП не более чем инструмент.
Практически любой успешный/долго живущий софт неизбежно обрастает разного рода нюансами. Проблема часто не столько даже в когнитивной сложности, сколько в неочевидном маппинге problem->solution.
То есть имея перед глазами запрос бизнеса, непросто понять, где оно должно быть изменено в коде. И наоборот, глядя в код, непросто описать бизнес процесс в терминах, использующихся экспертами.
Я к тому, что стратегическая часть DDD очень желательна с самого начала проекта, но разве будет менеджмент читать синюю книгу... И тем она и полезна для инженеров, чтоб осознать тщетность технических потуг, если идея не "продана" бизнесу.
Легко
Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F# by Scott Wlaschin
https://pragprog.com/titles/swdddf/domain-modeling-made-functional/
Тут надо понимать, что Identity на уровне домена не есть Identity на уровне БД.
И иногда действительно может иметь смысл воткнуть PK в VO, но он, очевидно, никак не должен участвовать в equality.
https://stackoverflow.com/a/78050832/921054
Разница лишь в warning'ах
Самая большая сложность "продать" подход бизнесу. :'-(
Забыли упомянуть важный момент: subdomain discovery (problem space) vs bounded context design (solution space).
Сервисов может быть 100500 и у сервиса по 100500 методов. Если каждый позволяет себе писать в свойства объекта, то инварианты придется собирать по всей системе, а также будет тяжело гарантировать их сохранность.
Если сервис А пишет в свойства X и Y, а сервис B в свойства Y и Z, то если существует правило для свойств X и Z, вы рискуете его нарушить. На практике ещё хуже, сначала правила нет, а потом вдруг нарисовывается. Заниматься поиском всех мест, где оно вовлечено, занятие неблагодарное.
Иными словами, свойства сущности сильно связанные штуки, и потому дистанция до кода, их меняющего, должна быть минимальна.
Глагол to link используют продакты. Ограничение в виде одноразовости проистекает из описания процесса. В требованиях нет кейсов на изменение или сброс ссылки. Если такие требования появятся, будет и соответствующее изменение в коде.
Так никто ж не предлагает вообще все в класс тащить. Для этого и существуют доменные события, доменные сервисы и сервисы уровня приложения.
Я скорее про случаи когда бизнес объект вырождается в DTO с кучей мутабельных свойств типа int или string. И совершенно неочевидно, как эти свойства эволюционируют и взаимодействуют меж собой.
Чисто из недавнего пример:
Нужно сохранить номер заказа во внешней системе, разработчик влепил:
Но можно чуть иначе:
Кода больше, факт, но и ясности, на мой взгляд, добавляется. Свойство напрямую поменять нельзя. Линкануть можно единожды, анлинкануть вообще нельзя.
В принципе, можно и для OrderReference, при необходимости, свой тип определить или, если поведение сущности меняется, разбить тип самой сущности на два: Xyz и LinkedXyz (имена условные).
Сложность же не только в требованиях, но и в и том, где они находятся. Event storming обычно проводят для формирования целостного представления о системе путем выковыривания священного знания из людских голов.
Если код не является хорошей моделью релевантных бизнес понятий и процессов, то часто приходится разбираться к кому идти с вопросами, а потом организовывать общение по теме. Vernon это называет tacit knowledge.
FP нормально обходится без rich объектов, но там обычно получше с системами типов. https://pragprog.com/titles/swdddf/domain-modeling-made-functional/
Отношусь к DDD как к методу обменять бизнесовую сложность на техническую.
Предпочту отразить в коде и сделать более явными бизнес-требования, даже если код становится чуть сложнее для привыкших к процедурному стилю с анемичными моделями. На практике это практически всегда себя оправдывает, потому как при эволюции системы первое на порядки важнее. Глядеть в "простой" код, зная, что он делает, без понимания "почему" и каковы ограничения — боль.
Самый сложный тактический паттерн — агрегат. Выбрать правильные границы с первого захода очень непросто. Да и ORM'ы, к сожалению, далеко не всегда способны в маппинг по-настоящему, когда объекты не являются копиями плоских таблиц в памяти.
При чем тут F#, если претензия была к AOT в .NET
Почему, он же развернуто объяснил, чем не подошёл .NET в их случае.
Можно добавить ещё вариант HTTP1.1+protobuf formatter