Size: a a a

Software Design/Architecture/Zen

2021 November 28

k

knopkod4v in Software Design/Architecture/Zen
а в чём отличие от апдейта?
источник

SF

Segmentation Fault in Software Design/Architecture/Zen
Ну да, выходит этот паттерн не подходит на случаи, когда состояние сущностей подвержено изменениям
источник

k

knopkod4v in Software Design/Architecture/Zen
не понял как ты этот вывод сделал
источник

SF

Segmentation Fault in Software Design/Architecture/Zen
Репозиторий - это прежде всего абстракция. Как мне в этой абстракции объявить метод изменения данных сущности, если update - антипаттерн?
источник

k

knopkod4v in Software Design/Architecture/Zen
не объявляй. Меняй саму сущность
источник

SF

Segmentation Fault in Software Design/Architecture/Zen
Ок, сущность поменял. Изменения остались в оперативке. Как изменения попадут в хранилище?
источник

k

knopkod4v in Software Design/Architecture/Zen
this
источник

SF

Segmentation Fault in Software Design/Architecture/Zen
А, ну понятно, должен быть маппер сущности на представление в хранилище...
источник

AB

Andrey Bakharev in Software Design/Architecture/Zen
Представь, что у тебя нет внешнего хранилища. Зачем тогда нужен update?
Чтобы в репозитории данные обновились? Ну так скорее всего там по ссылке передача, а значит данные в репозитории уже обновлены
источник

E

Egor in Software Design/Architecture/Zen
вы подменили явное неявным оставив апдейт
источник

SP

Sergey Protko in Software Design/Architecture/Zen
оставив или убрав?
источник

E

Egor in Software Design/Architecture/Zen
апдейт остался - просто происходит он с использованием ссылки, при использовании ссылки в языке с несколькими тредами возникают проблемы, значит будет происходить вызов некого метода апдейт
источник

SP

Sergey Protko in Software Design/Architecture/Zen
тут вопрос семантики ссылок. Если у вас репозиторий возвращает "копию" объекта а не ссылку на него, то update нужен. Если вы предпочитаете работать с репозиторием как с коллекцией, которая возвращает вам ссылку - то ссылка у репозитория на объекты выданные есть и нужно просто сообщить кому-то кто явно выше места использования репозитория что "операция завершена - комить изменения в базу". Это вот работа unit of work.

На одном объекте это все может выглядеть эквивалентно и потому могут возникать сложности с пониманием подхода. Рассматривать надо вопрос "границ транзакции" и как они определяются в системе. Тут можно взять 2 крайности: отсутствие явных границ в коде и тотальное разбиение всего стэйта с использованием паттерна агрегат.

Классика которую можно встретить в подавляющем большинстве проектов с ОРМ-ками:

- достаем сущность
- передаем сущность в какой-нибудь сервис "сделай что-то"
- этот сделай что-то достает другую сущность по связи и передает ее другому сервису "сделай вот это вот с ним"

Теперь у нас выбор - либо мы вручную следим за тем что поменялось и вручную апдейтим (явно) либо мы хотим что бы система сама за этим следила. Почему? Отношения объектов могут быть нетривиальны, и если мы мутируем стэйт в сервисах то либо мы размазываем персистенс по всей логики приложения (что бы отслеживать изменения) либо мы будем эти изменения неявно трекать снаружи запоминая какие объекты наш репозиторий возвращал и какие объекты по связям загружались.

Вот это "трекать снаружи" - это и есть unit of work паттерн. Он в целом (как может подсказывать название) и является нашей границей транзакции - тот стэйт который мог быть изменен в процессе операции. Этот подход очень выгоден когда не хочется вообще следить за такими мелочами как "кто какой стэйт трогает в системе". Это удобно и для большинства людей проще. Позволяет избавиться от целой кучи очень сложной логики которая может пораждать баги.

Минус паттерна - сам он очень сложен в реализации, очень требователен к ресурсам (по факту это такой dirty matching стэйта) и в целом как говаривал Фаулер "сделать универсальный UoW в рамках проекта не окупается". Если вам уже достался готовый (от гибернейта например или вашей ORM) то вы получаете это бесплатно и грех не пользоваться.

Другая крайность - это более жесткие требования к операциям и стэйту. Паттерн агрегат по сути рекламентирует что "никаких достал сущности снаружи по связям" быть не может - вы достаете граф объектов целиком и он сохраняется целиком. Можно делать дифы на уровне отдельных агрегатов и т.д. То есть наш агрегат и есть unit of work в этом смысле. Просто существенно проще.

Причем мы можем так же либо трекать ссылку на агрегаты возвращаемые репозиторием а потом коммитить изменения, либо - если мы пойдем еще дальше и скажем что один агрегат одна логическая транзакция, мы можем в целом инкапсулировать контроль управления за процедурой обновления в репозиторий. Передать мол в методы получения стэйта метод операции над агрегатом:


repo.get(aggregateId, (aggregate) => aggregate.changeState())


и наш get сам уже разберется когда там чего коммитить и т.д.  Тут уже вопрос хотим ли мы так упарываться. подход при котором мы репозиторий снаружи просим закоммитить все изменения все так же хорош. Поскольку стэйт агрегата не меняется снаружи мы получаем такой вот компромис между удобством (не надо трекать сильно изменения по приложению) и сложностью реализации (все же трекать агрегаты проще чем весь граф объектов который мы можем по связям загрузить)
источник

NF

Nikita Fedorov in Software Design/Architecture/Zen
В случае с монолитом можно ещё абузить Double dispatch c chain of responsibility вызывая хэндлеры на сущности до завершения транзакции и проверяя в них аля:
needExec => hasChanges(order, x => x.isPayed) 
handle(newOrder, oldOrder, changes) {
// do something
}
Вместо сервиса внутри сервиса внутри сервиса
источник

SP

Sergey Protko in Software Design/Architecture/Zen
непонятно. ты предлагаешь "нормально делай нормально будет"?
источник

NF

Nikita Fedorov in Software Design/Architecture/Zen
Ну типа вынул order, чето поменял, делаешь коммит, вызываются хендлеры для типа Order, в хендлер из ORM пробрасываются изменения которые ты внес и объект до и после изменений, и в этих хэндлерах ты уже пишешь какую-то дополнительную логику. Если ты в этом хэндлере че-то поменял в транзакции, то эта транзакция вложена в ту что вызвала хэндлер. И так они вызываются пока все нужные хэндлеры не отработают.
(точно так же как с событиями агрегатов, тут разницы особой нет, в плане кода вестимо)
источник

NF

Nikita Fedorov in Software Design/Architecture/Zen
это чуть круче чем сервисы вызывающие сервисы, но не так круто как агрегаты с явными событиями
источник

NF

Nikita Fedorov in Software Design/Architecture/Zen
+ Из плюсов это то что чтобы добавить новую логику реакции на изменения нужно добавить только новый хэндлер, не меняя order
- Из минусов очевидно как и у расширенных конечных автоматов - бродкаст и не именованные события, а так же сильная зависимость от ОRM вестимо
источник

NF

Nikita Fedorov in Software Design/Architecture/Zen
хотя в EFSM дела похуже, там бродкаст на весь автомат, а тут можно немного сузить по типу, так что это определенно лучше EFSM
источник

AI

Arthur Irgashev in Software Design/Architecture/Zen
Так это можно и не в монолите делать
источник