Запутался с DDD
От: vaa  
Дата: 19.10.21 04:46
Оценка:
Основной опыт на C#. Интересуюсь также другими ЯП.
В целом C# представляет довольно гибкий инструмент.
Смущает то, что большая часть бестпрактик в нем имеют смысл соглашений.
От readonly который защищает только ссылку, но не граф объекта.
До например дефолтного ОРМ типа EF, который на корню рушит идеологию DDD создания объектов через конструктор.
Т.е. использование EF в парадигме DDD сильно затруднено.
Кроме того у программиста не знакомого с соглашениями всегда есть возможность вызвать ef напрямую минуя домен.
Если пойти дальше и посмотреть на F# то там DDD имеет гораздо более острую форму.
т.е. в C# может быть св-во
public class Order
{
 //...
 
 public bool IsApproved { get; protected set; }
 
 public IEnumerable<OrderHistoryEntry> HistoryEntries
 {
  get { return historyEntries; }
 }
 
 public void Approve()
 {
  IsApproved = true;
 
  var orderHistoryEntry = new OrderHistoryEntry(this, OrderHistoryType.Approved);
 
  historyEntries.Add(orderHistoryEntry);
 }
}

В F# так не рекомендуют
type Order = private UnApproved  |  Approved               
module Order =
    let create () = Order.UnApproved
    let approve order = 
        match order with
        | UnApproved -> Approved
        | _ -> order


По сути в пределе получается произведение состояний (по классу на каждое состояние).
И вроде чем больше корректности тем лучше, но тогда может возникнуть дурная бесконечность.
Не так ли?
☭ ✊ В мире нет ничего, кроме движущейся материи.
Отредактировано 19.10.2021 4:47 Разраб . Предыдущая версия .
Re: Запутался с DDD
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 19.10.21 07:41
Оценка: +1
Здравствуйте, vaa, Вы писали:

vaa>По сути в пределе получается произведение состояний (по классу на каждое состояние).

Да

vaa>И вроде чем больше корректности тем лучше, но тогда может возникнуть дурная бесконечность.

Это ты о чем?

vaa>Не так ли?

Не так

Напиши вопрос который тебя реально интересует.
Re[2]: Запутался с DDD
От: vaa  
Дата: 20.10.21 02:55
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Здравствуйте, vaa, Вы писали:


vaa>>По сути в пределе получается произведение состояний (по классу на каждое состояние).

G>Да
Вот в этом примере используется DDD но как-то странно.
https://github.com/dotnet-architecture/eShopOnWeb/blob/master/src/ApplicationCore/Entities/BasketAggregate/Basket.cs

vaa>>И вроде чем больше корректности тем лучше, но тогда может возникнуть дурная бесконечность.

G>Это ты о чем?
Допустим есть подтвержденный и неподтвержденный заказы, для каждого нужно отдельную DTO для БД, отдельную для Json, каждый случай требует отдельной обработки
или можно как в примере выше сделать чистый ДДД домен но в инфраструктурном коде использовать возможности платформы, чтобы обойти ограничения ООП.
Нет ли тут противоречия?

vaa>>Не так ли?

G>Не так

G>Напиши вопрос который тебя реально интересует.

Будет ли правильно создавать сущности для каждого слоя (БД, домен, json, веб)?
В проекте выше домен(Entity) json(ItemDTO) и веб(ItemViewModel) присутствуют, однако для слоя БД (EFCore) используются сущности домена, что слегка обескураживает.
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[3]: Запутался с DDD
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 20.10.21 08:29
Оценка: 6 (1) +1
Здравствуйте, vaa, Вы писали:

vaa>Здравствуйте, gandjustas, Вы писали:


G>>Здравствуйте, vaa, Вы писали:


vaa>>>По сути в пределе получается произведение состояний (по классу на каждое состояние).

G>>Да
vaa>Вот в этом примере используется DDD но как-то странно.
vaa>https://github.com/dotnet-architecture/eShopOnWeb/blob/master/src/ApplicationCore/Entities/BasketAggregate/Basket.cs
И?
Я вообще не видел ни одной reference implementation для DDD для любой нетривиальной задачи.

vaa>>>И вроде чем больше корректности тем лучше, но тогда может возникнуть дурная бесконечность.

G>>Это ты о чем?
vaa>Допустим есть подтвержденный и неподтвержденный заказы, для каждого нужно отдельную DTO для БД, отдельную для Json, каждый случай требует отдельной обработки
EF умеет делать TablePerHierarcy. То есть ты можешь сделать дерево классов и несколько DbSet с этими классами. EF автоматически уложит их в одну таблицу в БД и будет дописывать предикаты выборки при обращении к разным DbSet.
Читай тут https://docs.microsoft.com/en-us/ef/core/modeling/inheritance

vaa>или можно как в примере выше сделать чистый ДДД домен но в инфраструктурном коде использовать возможности платформы, чтобы обойти ограничения ООП.

Что значит "чистый ДДД домен"?
И что мешает это сделать как в примере выше, если у тебя дерево классов?

vaa>Нет ли тут противоречия?

Есть противоречия между DDD и классическим ООП.
1) В программе объекты со своими типами и фиксированным набором полей, а в базе таблицы и произвольные проекции
2) В программе объекты имеют один фиксированный класс, определяющий поведение, а в базе состояние, меняющееся со временем.


G>>Напиши вопрос который тебя реально интересует.

vaa>Будет ли правильно создавать сущности для каждого слоя (БД, домен, json, веб)?
Посмотри на это с экономической точки зрения. Будет ли правильным написать в несколько раз больше строк кода, чтобы решить ту же задачу, или обойтись минимумом? Имхо ответ очевиден.
Если для решения задачи необходимо создавать отдельные классы для разных слоев, то вопроса бы никогда не возникло.

vaa>В проекте выше домен(Entity) json(ItemDTO) и веб(ItemViewModel) присутствуют, однако для слоя БД (EFCore) используются сущности домена, что слегка обескураживает.

Мне кажется в проекте выше даже ItemDTO лишние, DTO является подмножеством данных Entity, поэтому можно было бы отдавать и получать непосредственно Entity и не заморачиваться.
https://github.com/dotnet-architecture/eShopOnWeb/blob/master/src/PublicApi/CatalogItemEndpoints/CatalogItemDto.cs
https://github.com/dotnet-architecture/eShopOnWeb/blob/master/src/ApplicationCore/Entities/CatalogItem.cs

ViewModel не смотрел, возможно там так же.
Re[3]: Запутался с DDD
От: gyraboo  
Дата: 20.10.21 08:47
Оценка:
Здравствуйте, vaa, Вы писали:

vaa>Допустим есть подтвержденный и неподтвержденный заказы, для каждого нужно отдельную DTO для БД, отдельную для Json, каждый случай требует отдельной обработки

vaa>или можно как в примере выше сделать чистый ДДД домен но в инфраструктурном коде использовать возможности платформы, чтобы обойти ограничения ООП.
vaa>Нет ли тут противоречия?

А чем ООП тут не угодило? Разве эту задачу нельзя решить классической ООП?
Re[4]: Запутался с DDD
От: vaa  
Дата: 20.10.21 09:49
Оценка:
Здравствуйте, gyraboo, Вы писали:

G>А чем ООП тут не угодило? Разве эту задачу нельзя решить классической ООП?


Ну например, EF диктует свои правила для сущностей. обязательно наличие сеттеров, определенных конструкторов.
Очень много нюансов которые сложно обойти чтобы сделать невозможным не валидным состояние агрегата.
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[5]: Запутался с DDD
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 20.10.21 10:22
Оценка: 104 (3)
Здравствуйте, vaa, Вы писали:

vaa>Здравствуйте, gyraboo, Вы писали:


G>>А чем ООП тут не угодило? Разве эту задачу нельзя решить классической ООП?


vaa>Ну например, EF диктует свои правила для сущностей. обязательно наличие сеттеров, определенных конструкторов.

https://docs.microsoft.com/en-us/ef/core/modeling/backing-field?tabs=data-annotations
https://docs.microsoft.com/en-us/ef/core/modeling/constructors
Мешающих ограничений довольно мало. Ты просто мало читал документацию.

vaa>Очень много нюансов которые сложно обойти чтобы сделать невозможным не валидным состояние агрегата.

Валидность состояния может также завесить от операции. Например нельзя отправить заказ клиенту с некорректным shipping address, но при этом вполне можно принять оплату при заполненном billing address. Такие правила в рамках системы типов выразить невозможно.

Вот серия статей про моделирование предметной области с помощью типов (когда-нибудь я сделаю перевод, потому что статьи прекрасны)
https://ericlippert.com/2015/04/27/wizards-and-warriors-part-one/
https://ericlippert.com/2015/04/30/wizards-and-warriors-part-two/
https://ericlippert.com/2015/05/04/wizards-and-warriors-part-three/
https://ericlippert.com/2015/05/07/wizards-and-warriors-part-four/
https://ericlippert.com/2015/05/11/wizards-and-warriors-part-five/

Обязательно дочитай до конца. В последней статье самый сок.
Re[6]: Запутался с DDD
От: vaa  
Дата: 21.10.21 02:58
Оценка:
Здравствуйте, gandjustas, Вы писали:

vaa>>Очень много нюансов которые сложно обойти чтобы сделать невозможным не валидным состояние агрегата.

G>Валидность состояния может также завесить от операции. Например нельзя отправить заказ клиенту с некорректным shipping address, но при этом вполне можно принять оплату при заполненном billing address. Такие правила в рамках системы типов выразить невозможно.

G>Вот серия статей про моделирование предметной области с помощью типов (когда-нибудь я сделаю перевод, потому что статьи прекрасны)

G>https://ericlippert.com/2015/05/11/wizards-and-warriors-part-five/
Интересно, в заключении автор изобрел функциональное программирование.
Не понятно почему он боится исключений. Это такой же способ управлять потоком программы как и if|else.
К тому же в его коде все время фигурирует void (в этом кстати косяк сеттеров в C# — они void, так что здесь без исключений уже никуда).
Достаточно из каждого метода изменяющего состояние ввести возврат https://docs.microsoft.com/ru-ru/dotnet/fsharp/language-reference/results
Но для этого нужно будет знать все ошибки домена. Примерно так https://youtu.be/Bn132AtZLhc?t=1937
Для C# есть отличная альтернатива https://github.com/louthy/language-ext
правда без вариантов/алгебраических типов тут не будет такой строгости как в F#.

Опять же, взгляд на проблему с другой стороны https://blog.byndyu.ru/2010/05/domain-driven-design.html
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[7]: Запутался с DDD
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 21.10.21 08:30
Оценка: 3 (1)
Здравствуйте, vaa, Вы писали:

vaa>Здравствуйте, gandjustas, Вы писали:


vaa>>>Очень много нюансов которые сложно обойти чтобы сделать невозможным не валидным состояние агрегата.

G>>Валидность состояния может также завесить от операции. Например нельзя отправить заказ клиенту с некорректным shipping address, но при этом вполне можно принять оплату при заполненном billing address. Такие правила в рамках системы типов выразить невозможно.

G>>Вот серия статей про моделирование предметной области с помощью типов (когда-нибудь я сделаю перевод, потому что статьи прекрасны)

G>>https://ericlippert.com/2015/05/11/wizards-and-warriors-part-five/
vaa>Интересно, в заключении автор изобрел функциональное программирование.
Чем оно функциональное? Ни ФВП, ни АТД, ни иммутабельности нет.

vaa>Не понятно почему он боится исключений. Это такой же способ управлять потоком программы как и if|else.

Исключение это не type safety, из-за которой собственно пытаются городить иерархии классов в стиле DDD. Если ты все сводишь к if-else и исключениям, то тебе не нужны кучи классов.
Об этом и речь в статье.

vaa>К тому же в его коде все время фигурирует void (в этом кстати косяк сеттеров в C# — они void, так что здесь без исключений уже никуда).

И что? Можно код значительно улучшить, не используя void?

vaa>Достаточно из каждого метода изменяющего состояние ввести возврат https://docs.microsoft.com/ru-ru/dotnet/fsharp/language-reference/results

Увы без pattern-matching такой подход не работает. Посмотри на дату статьи.

И еще раз повторю: смысл всех DDD-паттернов — выразить правила и ограничения предметной области в виде классов для compile-time проверки корректности.
Все что уносит нас в рантайм-проверку требует другой архитектуры. Result = Some(x)|None это тоже рантайм проверка.

Липперт в своей статье описал эту архитектуру:

The classic paradigm for OOP still makes perfect sense: encode the fundamental, unchanging relationships between business elements into the type system. Here the fundamental unchanging relationships are things like “commands are evaluated in the context of state and rules, to produce a sequence of actions”, so that’s where the design should have started in the first place.


Перевожу:

Классическая парадигма ООП все еще имеет смысл: выразить в системе типов фундаментальные, неизменные отношения между элементами модели. Здесь фундаментальные неизменные отношения — это "команды выполняются в контексте состояния и правил, и производят набор действий по изменению состояния". Именно с этого и должно было начаться проектирование.



В программе это можно выразить несколькими способами:
1) framework-based: команды — методы контроллера, бизнес-правила — в виде правил валидации входных данных и функции проверки на выходе, действия — функции изменения состояния в БД с помощью запросов или ORM. (это называется transaction script у фаулера)
2) domain-model: бизнес-правила, команды и исполнители команд — отдельные классы, увязанные через application service, все "внешние" зависимости через отдельные сервисы\репозитарии.
3) functional: команды — АТД, бизнес-правила и исполнители — "чистые" (монадические) функции, применения действия — аналог IO монады, связка всего этого через монадные операторы
Отредактировано 21.10.2021 8:40 gandjustas . Предыдущая версия . Еще …
Отредактировано 21.10.2021 8:40 gandjustas (опечатка) . Предыдущая версия .
Re[7]: Запутался с DDD
От: Слава  
Дата: 21.10.21 14:17
Оценка:
Здравствуйте, vaa, Вы писали:

vaa>Для C# есть отличная альтернатива https://github.com/louthy/language-ext


Это ужасная альтернатива, которая делает невозможной работу с кодом.
Re[8]: Запутался с DDD
От: vaa  
Дата: 22.10.21 01:38
Оценка:
Здравствуйте, Слава, Вы писали:

С>Здравствуйте, vaa, Вы писали:


vaa>>Для C# есть отличная альтернатива https://github.com/louthy/language-ext


С>Это ужасная альтернатива, которая делает невозможной работу с кодом.


Можете пояснить свою мысль на примере?
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[8]: Запутался с DDD
От: vaa  
Дата: 22.10.21 01:55
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Чем оно функциональное? Ни ФВП, ни АТД, ни иммутабельности нет.

Ну он предложил убрать поведение, т.е. сделал игровые сущности частями состояния игры.
и выделил все операции над этим структурами в команды.

"Лучше иметь 100 функций, работающих на одной структуре данных, чем 10 функций на 10 структурах данных." — Алан Перлис


Вот кстати похожие рассуждения на аналогичную тему https://blog.ploeh.dk/2017/01/27/from-dependency-injection-to-dependency-rejection/

vaa>>К тому же в его коде все время фигурирует void (в этом кстати косяк сеттеров в C# — они void, так что здесь без исключений уже никуда).

G>И что? Можно код значительно улучшить, не используя void?
потому что void делает поведение неопределенным. нельзя понять произошло что-то в set или нет. именно поэтому появились TryParse. Комично но интелисайнс все время предлагает заменить свойства на методы.
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[9]: Запутался с DDD
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 22.10.21 08:15
Оценка:
Здравствуйте, vaa, Вы писали:

vaa>Здравствуйте, gandjustas, Вы писали:


G>>Чем оно функциональное? Ни ФВП, ни АТД, ни иммутабельности нет.

vaa>Ну он предложил убрать поведение, т.е. сделал игровые сущности частями состояния игры.
vaa>и выделил все операции над этим структурами в команды.
И каким образом это ФП? Это как раз DDD, CQRS и прочие малопонятные аббревиатуры.

vaa>

vaa>"Лучше иметь 100 функций, работающих на одной структуре данных, чем 10 функций на 10 структурах данных." — Алан Перлис

То есть плодить Entity\EntityDTO\EntityViewModel таки не имеет смысла?

vaa>Вот кстати похожие рассуждения на аналогичную тему https://blog.ploeh.dk/2017/01/27/from-dependency-injection-to-dependency-rejection/

Покажи код.

vaa>>>К тому же в его коде все время фигурирует void (в этом кстати косяк сеттеров в C# — они void, так что здесь без исключений уже никуда).

G>>И что? Можно код значительно улучшить, не используя void?
vaa>потому что void делает поведение неопределенным. нельзя понять произошло что-то в set или нет. именно поэтому появились TryParse.
TryParse появилась потому что ошибка парсинга далеко не всегда неожиданная ситуация. И реализовывать сценарий через отлов исключений слишком многословно и работает медленно.

vaa>Комично но интелисайнс все время предлагает заменить свойства на методы.

Что за интелисенс такой?
Re[10]: Запутался с DDD
От: vaa  
Дата: 22.10.21 08:29
Оценка:
Здравствуйте, gandjustas, Вы писали:

vaa>>

vaa>>"Лучше иметь 100 функций, работающих на одной структуре данных, чем 10 функций на 10 структурах данных." — Алан Перлис

G>То есть плодить Entity\EntityDTO\EntityViewModel таки не имеет смысла?
Тут мы вернулись к сабжу. в смысле я запутался. не знаю.
Возможно. Не зря же придумали опыт. Возможно Алан Перлис очень опытный мэн.

G>Покажи код.

вот резервация билетов https://blog.ploeh.dk/2017/02/02/dependency-rejection/

G>TryParse появилась потому что ошибка парсинга далеко не всегда неожиданная ситуация. И реализовывать сценарий через отлов исключений слишком многословно и работает медленно.

просто решили что кортежи ненужная роскошь.
Традиционно все динамично развивающиеся языки постепенно плохо воплощают возможности common lisp

G>Что за интелисенс такой?

☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[11]: Запутался с DDD
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 22.10.21 09:33
Оценка: +1
Здравствуйте, vaa, Вы писали:

vaa>Здравствуйте, gandjustas, Вы писали:


vaa>>>

vaa>>>"Лучше иметь 100 функций, работающих на одной структуре данных, чем 10 функций на 10 структурах данных." — Алан Перлис

G>>То есть плодить Entity\EntityDTO\EntityViewModel таки не имеет смысла?
vaa>Тут мы вернулись к сабжу. в смысле я запутался. не знаю.
Я же писал выше. Смотри с экономической точки зрения. Если можно решить задачи у написать меньше, то так и надо делать.


G>>Покажи код.

vaa>вот резервация билетов https://blog.ploeh.dk/2017/02/02/dependency-rejection/
Так я об этом выше писал:

3) functional: команды — АТД, бизнес-правила и исполнители — "чистые" (монадические) функции, применения действия — аналог IO монады, связка всего этого через монадные операторы

Это один из вариантов, который можно сделать на ФЯ типа F#. На C# такое писать неудобно.


G>>TryParse появилась потому что ошибка парсинга далеко не всегда неожиданная ситуация. И реализовывать сценарий через отлов исключений слишком многословно и работает медленно.

vaa>просто решили что кортежи ненужная роскошь.
Дело не в кортежах. Дело в паттерн-матчинге. Мало возвращать из функции (success, result). Надо чтобы вызывающий код мог удобно этот результат разобрать, причем так, чтобы не игнорировать один из вариантов.
Re[9]: Запутался с DDD
От: Слава  
Дата: 21.11.21 17:20
Оценка:
Здравствуйте, vaa, Вы писали:

С>>Это ужасная альтернатива, которая делает невозможной работу с кодом.

vaa>Можете пояснить свою мысль на примере?

Прошу прощения за столь поздний ответ. Вот пример, не новый: http://rsdn.org/forum/jetbrains/6628171.1
Автор: Слава
Дата: 02.12.16


Я после этого на всю жизнь зарёкся такое в проекты тянуть. Если кому-то нужен хаскель, то и писать надо на хаскеле.
Re[10]: Запутался с DDD
От: vaa  
Дата: 22.11.21 02:06
Оценка:
Здравствуйте, Слава, Вы писали:

С>Прошу прощения за столь поздний ответ. Вот пример, не новый: http://rsdn.org/forum/jetbrains/6628171.1
Автор: Слава
Дата: 02.12.16


Согласен, код не самый приятный. Все из-за того что в книжках пугают что исключения это дорого. А по мне, это очень гибкий способ управления потоком в C#.
Не такой крутой как перезапуски и сигналы в лиспе, но все же.

С>Я после этого на всю жизнь зарёкся такое в проекты тянуть. Если кому-то нужен хаскель, то и писать надо на хаскеле.


Возможно виноват сишарп старый, возможно то что в 16 году еще не было бестпрактик. 10ка по функциональней будет. должно быть поудобней.

Вот пример как можно писать на lang-ext:
          return await CreateContext(id)
                    .Bind(RegisterOnTwitter)
                    .Bind(AuthenticateOnTwitter)
                    .Bind(Tweet)
                    .Bind(UpdateUser)
                    .Do(context => businessLogger.LogSuccessRegister(context.Id))
                    .Map(context => context.Url)
                    .IfFail(failure =>
                    {
                        businessLogger.LogFailureRegister(id, failure);
                        return (string)null;
                    });

выглядит не так уж и плохо.
PS от идеи использовать DDD отказался в процессе работы. Понял, что код становится "экспоненциально" сложен.
☭ ✊ В мире нет ничего, кроме движущейся материи.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.