Re[20]: DDD протаскивание других слоев через параметры методов Domain
От: Sinclair Россия https://github.com/evilguest/
Дата: 03.12.20 15:28
Оценка: 53 (6) +4
Здравствуйте, Sharov, Вы писали:

S>Тут хорошая стать и отличная картинка на эту тему -- https://blog.pragmatists.com/domain-driven-design-vs-anemic-model-how-do-they-differ-ffdee9371a86?gi=40c25922c8e9

По-моему — очередной булшит.
1. В нормальной, современной архитектуре нет никаких отдельных E и DAO. Это какой-то онанизм — сначала грузить из базы DAO, потом их превращать в E, потом обрабатывать, потом обратно превращать в DAO, и сохранять в базу.
Так никто не делает. Более того — чувак явно забыл о том, что есть ещё и DTO — ведь сервис общается и с клиентами. Поэтому полный ЖЦ этой халабуды выходит примерно таким: "получили DTO от клиента — отдали в сервис — тот преобразовал его в Е — поднял из базы пачку DAO — преобразовал их в E — как-то обработал — потом преобразовал часть E в новые DAO и сохранил в базу, а часть в DTO и отдал клиенту".
(Да, анемика по Фаулеру — говно. Впрочем, DDD по Фаулеру тоже выходит говном, из чего мы заключаем, что проблема не в анемике и не в домейн/рич, а в Фаулере.)
Забавно, что автор и сам это понимает, поэтому в тексте у него сказано "Services make use of DAO in order to implement the business logic for specific functionality". Да, совершенно верно — сервис так и работает в терминах DAO. Никакие entity ему не нужны — зачем, ведь в них же нет никакой бизнес-логики. Рассказывать про то, что "Usually, each of our entities represents separate business case, so the number of DAO classes match the number of entities. " можно только в целях напугать детишек: у-у-у, не ходите в анемик, там каждую сущность в модели надо описывать дважды, ДВАЖДЫ!

2. Идея в том, что сервис сначала подымет Entities в память при помощи DTO, а уж потом поработает над ними, описанная в статье — полный булшит, который отстал от жизни лет эдак на пятнадцать. Ну, наверное в какой-нибудь кровавой жаве так до сих пор и делают; а в нормальных современных сервисах стараются в app server лишнего не подымать. Задача сервиса — выразить бизнес-транзакцию в терминах операций над DTO; сами эти операции не выполняются над DTO, а преобразуются напрямую в SQL и уезжают в базу, где и выполняются. Если я хочу поменять статус заказа с "оплачен" на "отгружен", то я не делаю сначала select * from orders where id = @orderId в OrderDTO, и не создаю из него OrderEntity при помощи OrderEntityFactory, чтобы потом ему сделать setStatus, а потом orderDto.UpdateFrom(OrderEntity) и unitOfWork.Save(true). Нет, всё это — пережиток эпохи "а теперь дайте мне ферму из 16 PowerEdge, чтобы ваши остатки на складах подбивались хотя бы за ночь".
Делается нормальный linq-statement, который изменяет статус у того, что нужно:
using LinqToDB;

using (var db = new DbNorthwind())
{
  db.Order
    .Where(o => .ID == orderId)
    .Set(o => o.Status, newStatus)
    .Update();
}

Всё. Это превращается в SQL стейтмент, который выполняется напрямую в сервере, без подъёма килобайтов в память и длинного удержания локов.
Да, не всегда получается сделать именно так, но, поскольку мы нафиг выкинули ентити в пользу чистых "DTO", такое получается сделать гораздо чаще, чем в той убогой анемике, о которой врёт Фаулер и его фанаты.
В тех случаях, когда нам реально надо сделать что-то относительно сложное в коде сервиса, мы тащим в память не все DTO, и не каждый из них целиком — ведь на linq проекции и прочие SQL трансформации делаются лёгким движением руки, без мучительного выписывания каких-то там aggregation root или прочего. Если мне потребуется в логике проверять средний чек данного клиента — я не буду заводить для этого целый класс, будет по месту сформирован linq expression, который его достаёт сразу в виде decimal. Благодаря этому общение между сервисом и СУБД сводится к минимуму — select avg(amount) from ... всё ещё в разы быстрее, чем делать select * from, а потом усреднять в памяти.

3. Опять же читаем статью, "the application with the anemic model has more repositories than the one with the rich model". Смотрим на картинку — автор врёт даже сам себе. На его же картинке с Anemic есть ровно 0 (ноль) репозиториев. Что, в общем-то, верно отражает современную реальность. Это в говно-DDD, которое он пропагандирует, нам нужны репозитории на каждый агрегат. А в современной анемик-архитектуре "репозиториев" примерно столько, сколько есть граней у сервиса. Скажем, вся бухгалтерия — это один "репозиторий", весь складской учёт — это ещё один; управление подписками — третий; human resources — четвёртый.
И при этом эти репозитории, в отличие от DDD, не содержат никакого императивного кода. Никаких там Find или Update, специфических для каждого типа Entity. Достаточно собственно DbContext, который описывает типы. Плюс, возможно, набор extension-методов к нему и к IQueryable, которые позволяют быстро компоновать между собой куски нужного функционала. Скажем, если нам надо часто вычислять средний чек по каким-то критериям, то мы можем отдельно построить функцию decimal Average(IQueryable<decimal> source), и в коде сервиса обращаться уже к ней, с уместными в каждом конкретном случае селектами.

В итоге появляется возможность на стороне сервисов совмещать объектную и функциональную декомпозицию; а большую часть кода из DDD/рич писать вообще не нужно и даже вредно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.