Как стать автором
Обновить
2
0

Пользователь

Отправить сообщение

Посмотрел подробнее - хендлеры доменных ивентов вызываются в DomainEventPublisher из RequestResponseLoggingMiddleware уже после коммита транзакции. Так что предложенная реализация не подходит ни для доменных ивентов, так как результаты нельзя сохранить в одной транзакции, ни для интеграционных ивентов, так как нельзя сделать Outbox и обеспечить at-least-once гарантию доставки.

А еще хендлер доменного ивента может изменить какой-то агрегат, который тоже захочет отправить об этом ивент. Как решить этот кейс?

Выбран неудачный пример для демонстрации доменных ивентов. Уведомления - это типичный пример для Outbox паттерна. Здесь обсуждаются интеграционные событие, а не доменные.

На запись у linq2db тоже есть преимущества, он умеет делать BulkCopy (для EF есть расширение, но оно платное почти для всех компаний) и Merge. Надеюсь в докладе об этом будет )

Как видим, SQL без спецификаций более эффективен

Стоило сравнивать не только косты, которые являются оценкой, но и реальное время выполнения запроса.
Hash Left Join (cost=26608.37..36174.56 rows=5 width=20) (actual time=1815.390..2405.787 rows=3 loops=1) - без спецификаций
Nested Loop Left Join (cost=0.87..136740.12 rows=5 width=48) (actual time=559.034..1088.576 rows=3 loops=1) - со спецификациями
Запрос со спецификацией работает в 2,2 раза быстрее )

А с оптимизированными запросами для спецификаций (где добавлены дополнительные джойны) что-то не то. В запросе есть фильтр по passenger.update_ts (p.update_ts < @__dateTo_2 OR p.update_ts IS NULL), но в плане при сканировании таблицы passenger нет фильтра по update_ts, такого не может быть (Index Scan using passenger_pkey on passenger p). Этот план от какого-то другого запроса.

Хорошо бы аккуратно перепроверить все зпросы и планы из статьи, а также их реальное время выполнения.

И при реализации AndSpecification можно обойтись без Expression.Invoke при помощи подмены параметра. EF для обоих вариантов строит запрос, но для подмены параметра запрос получается более читаемым, это удобнее при отладке

Гораздо лучше будут читаться выражения состоящие из спецификаций.

Тут надо добавить, что выражения должны состоять только из спецификаций, текущая реализация спецификации не позволяет комбинировать её с произвольным Linq выражением. Нельзя написать что-то вроде x => IdAbove(100) && x.Id > 100, такое выражение не скомпилируется

Плюсую, отличный вопрос! Непонятно зачем делать селект анонимного типа, а потом конвертировать этот анонимный тип в кукую-то еще реальную модель. Может лучше сразу использовать реальный тип? Избавимся от конвертации.
И кстати здесь анонимный тип передается в extension-метод, а дальше написано что этого делать нельзя )

return anonimousModels.ToКакиетоТоМодели():

Неплохая статья. Но про вариант 5 с доменными ивентами есть замечания:
1. Не сказано, что сохранение юзера и хендлер должны работать в одной транзакции. И хендлер должен вызываться до вставки юзера.
2. Нет реализации отправки ивента. Автор статьи где-то писал "в тырнете много вариантов", но лучше было показать свой вариант
3. Про ивенты не сказано что проверка email на уникальность становится гораздо менее очевидной

репозитории являются приватными

можно сдерать сэмпл на гитхабе, верно? )

для комбинации двух Expression используется вызов left.Or(right)

такой код не скомпилируется так как метод Or - статический (ни в Expression, ни в Expresions<> нет нестатического метода и это два разных класса)
Expression<Func<int, bool>> left = x => x > 0;
Expression<Func<int, bool>> right = y => y > 0;
var res = left.Or(right);

А вот такой скомпилируется (правда выдаст исключение)
Expression<Func<int, bool>> left = x => x > 0;
Expression<Func<int, bool>> right = y => y > 0;
var res = Expression.Or(left, right);

Это все к чему - нет стандартного способа комбинации выражений. Это делается через подмену параметра, которую надо писать самому. Или использовать библиотеку PredicateBuilder, где все уже сделано. Очень странно что в статье про это ни слова не сказано.

В классе Expression<> нет методов And и Or. Они есть в классе Expression, но они статические. Так что код left.And(right) не должен компилироваться, вместо него должно быть написано Expression.And(left, right). Либо есть какой-то экстеншн-метод And, которого нет в коде из статьи. Так что вопрос про комбинацию актуален, в статье ответ на него увидеть нельзя.

И оставшийся без ответа вопрос про рабочий сэмпл кода тоже актуален )

Как реализована комбирнация выражений left.And(right) и left.Or(right)?

А есть ссылка на гитхаб с рабочим сэмплом?

Как скомбинировать спецификации, у которых разные Skip и Take?

public interface ICreateTodoListCommandArgs

Непонятно зачем нужен этот интерфейс. Какие у него могут быть реализации кроме CreateTodoListRequest?

если TResult будет составным

Ни резу такого не видел. Можете привести несколько примеров из реальной жизни?

А такая реализация, согласитесь, уже не выглядит адекватной.

Хотелось бы более адекватную аргументацию :)

Вводя понятие основной проекции переизобретается велосипед снапшот - это построенный по первым N ивентам слепок агрегата. Тогда если в команде нужен агрегат, он строится не по всем ивентам, а начиная с N+1. Если снапшот обновлять после каждого нового ивента, то получится ровно то, что в статье называется основной проекцией.

Кстати снапшот никак не влияет на eventual consistency. Не важно, строится в команде агрегат по всём ивентам с первого или с N+1, всё равно между добавлением ивентов в ивент стор и обновлением проекций для чтения будет лаг.

  1. Мне нравится термин Vertical Slice Architecture, который придумал Джимми Богард, автор медиатора. Это наводит порядок в терминах: вертикал слайсы - это реализация application логики в виде отдельного хендлера для каждого юскейса (при этом не важно команда это или запрос), а cqrs - это разделение стеков чтения и записи (при этом не важно сделан application уровень сервисами или хендлерами).

    ВонВернон в RedBook называет выделением команды в отдельные хендлеры Специализированным подходом. Но при этом непонятно что делать с запросами. И больше я этого термина нигде не встречал )

    Так что мне больше всего нравится термин вертикал слайсы

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

И есть пара комментариев:
1. На картинке написано "CQRS via Mediatr" (хотя больше нигде в тексте про CQRS нет упоминаний). Подход Vertical Slice Architecture, где Application логика реализована в виде отдельного хендлера на каждый юскейс (и Медиатор как инструмент, облегчающий реализацию такого подхода), никак не связаны с CQRS. CQRS - это про разделение стеков чтения и записи на основе отдельных моделей для чтения и записи. Можно реализовать CQRS сделав ReadApplicationService и WriteApplicationService, не обязательно при этом вытаскивать каждый юскейс в отдельный хендлер. Также и вертикал слайсы с помощью медиатора могут быть реализованы в без CQRS с его отдельными моделями и стеками для чтения и записи. Так что вертикал слайсы и CQRS это независимые вещи.

2. Про плюшки - нпонятно какие достоинства от сериализуемости команд. Мне например никогда не приходилось их логгировать. Логгировать удобно HTTP реквесты или мессаджи, т.е. контракт, а зачем логгировать команды? Они передаются от контроллеров к хендлерам, они не должны сериализоваться

3. Отдельный тип для команды так же позволяет нам легко помечать ее с помощью атрибута (это пригодится нам позже).
Не увидел где пригодилось :) Наверное речь об АОП (по списку атрибутов хендлера добавлять ему бехавиоры)? Но удобнее делать это через маркерные интерфейсы, сейчас даже MS DI контейнер поддерживает gneric constraints.

Похоже в статье путаница в терминах DI и IoC.
IoC - это принцип, а DI контейнер - инструмент, помогающий реализовать этот принцип (неймспейс Microsoft.Extensions.DependencyInjection называется правильно, неправильно было бы Microsoft.Extensions.IoC).
Можно ли использовать DI контейнер без IoC? Конечно, DI-контейнеру все равно в каких слоях находятся внедряемые зависимости.
Можно ли реализовать принцип IoC без DI контейнера? Тоже можно, принцип был описан до того, как DI контейнер стал инструментом по умолчанию (хотя в контейнером конечно удобнее).

Для платежей и заказов нужно хранить историю перехода между состояниями независимо от того, используется ES или нет. Так что пример в статье неудачный (
Можете привести пример системы, где хранение агрегата в виде списка ивентов действительно дает профит?
И второй вопрос: можно хранить не ивенты, а завести таблицу журнала и хранить там историю состояний, чем это лучше/хуже, чем хранение ивентов?

1

Информация

В рейтинге
6 014-й
Зарегистрирован
Активность