event sourcing что это

Знакомимся с Event Sourcing. Часть 1

Перевод статьи подготовлен в преддверии старта курса «Java Developer. Professional».

Event sourcing (источники событий, регистрация событий, генерация событий) — это мощный архитектурный шаблон, при котором все изменения, вносимые в состояние приложения, сохраняются в той последовательности, в которой они происходили. Эти записи служат как источником для получения текущего состояния, так и журналом аудита того, что происходило в приложении за время его существования. Event sourcing способствует децентрализованному изменению и чтению данных. Такая архитектура хорошо масштабируется и подходит для систем, которые уже работают с обработкой событий или хотят перейти на такую архитектуру.

Что такое Event Sourcing

Эксперты предметной области обычно описывают свои системы как совокупность сущностей (entity), представляющих собой контейнеры для хранения состояния и событий (event), отображающих изменения сущностей в результате обработки входных данных в рамках различных бизнес-процессов. Часто события инициируется командами (command), исходящими от пользователей, фоновых процессов или интеграций с внешними системами.

По сути, Event sourcing фокусируется на событиях, связанных с изменениями в системе.

Многие архитектурные шаблоны рассматривают сущности как первоочередную концепцию. Эти шаблоны описывают то, как их сохранять, как к ним получать доступ и как их модифицировать. В рамках такого архитектурного стиля события часто находятся «сбоку»: являются последствиями изменения сущностей.

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

Event Sourcing переворачивает этот подход, фокусируясь на реализации событий: на том как они сохраняются и как могут быть использованы для получения состояния сущности. В данном случае в базе данных будет последовательный журнал всех событий, которые произошли за время существования системы.

Ниже показано сравнение хранилища событий (Event Store) с хранилищем сущностей (Entity Store) (подробнее будет описано далее):

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

Принцип работы Event Sourcing

Рассмотрим простой пример с банковским счетом. У нас будет сущность (entity), представляющая собой банковский счет (Bank Account). Для простоты сделаем только один счет без его идентификации с помощью номера счета или каким-либо другим способом. Счет будет хранить текущий остаток средств.

Для счета будут доступны две команды (command): внести деньги (deposit) и снять деньги (withdraw). В командах будет указываться сумма для внесения или снятия. Также определим бизнес-правило, которое проверяет, что команда на снятие средств может быть обработана только в том случае, если запрашиваемая сумма равна или меньше текущего остатка на счете.

При таком подходе можно выделить два события (event) — «Account Credited» (Счет пополнен) и «Account Debited» (Средства списаны со счета). В этих событиях есть информация о сумме (amount), которая была внесена или снята. Здесь можно было бы упростить до одного события с положительной или отрицательной суммой, но в данном примере мы их разделим.

На диаграмме ниже показана модель данных.

Обратите внимание, что события — это «прошедшее время». Они указывают на то, что произошло в системе в момент их записи, и сохраняются только в том случае, если обработка команды прошла успешно. При таком подходе необходимо проявлять осторожность, чтобы не перепутать команды с событиями. Особенно если они зеркально похожи друг на друга.

Последовательность команд может выглядеть следующим образом:

1. deposit < amount: 100 >— внести 100
2. withdraw < amount: 80 >— снять 80
3. withdraw < amount: 50 >— снять 50

Самая простая реализация Event Sourcing требует журнала событий (event log), который представляет собой просто последовательность событий. При обработке команд, приведенных выше, получится такой журнал.

Третья команда не может быть выполнена, так как запрошенная сумма превышает доступный баланс.

Для получения текущего баланса система должна обработать или «сгенерировать» события в порядке их возникновения. Для нашего примера это может выглядеть следующим образом:

Это законченный (хоть и тривиальный) пример Event Sourcing. В реальной системе, скорее всего, этот пример придется расширить.

Возможно, потребуется сохранять последовательность команд, чтобы была возможность идентифицировать, как возникло событие, а также сделать отдельный журнал «ошибочных событий» (error event), в который записывать команды, которые не удалось выполнить, для дальнейшей обработки ошибок и ведения полной истории успешных и неуспешных команд.

Со временем, при увеличении количества команд, может потребоваться сохранять текущий баланс счета, чтобы при получении команды на снятие средств не нужно было обрабатывать полный список событий для определения возможности выполнения команды (т. е. имеется ли на счете достаточно средств). Это пример производного хранилища и, по сути, то же самое, что и хранилище сущностей.

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

Очевидно, что по сравнению с полноценным хранилищем событий (Event Store), это очень примитивный пример. И это одна из причин, по которой многие разработчики используют только хранилище сущностей. В этом случае текущий остаток на счете доступен сразу и нет необходимости обрабатывать все исторические события.

Однако Event Sourcing не исключает хранилища сущностей. Часто хранилища сущностей присутствуют и в Event Sourcing — проектах.

Источник

Введение в CQRS + Event Sourcing: Часть 1. Основы

В первый раз я услышал о CQRS, когда устроился на новую работу. В компании, в которой работаю и по сей день, мне сразу сказали что на проекте, над которым я буду работать используется CQRS, Event Sourcing, и MongoDB в качестве базы данных. Из этого всего я слышал только о MongoDB. Попытавшись вникнуть в CQRS, я не сразу понял все тонкости данного подхода, но почему-то мне понравилась идея разделения модели взаимодействия с данными на две — read и write. Возможно потому что она как-то перекликалась с парадигмой программирования “разделение обязанностей”, возможно потому что была очень в духе DDD.

Вообще многие говорят о CQRS как о паттерне проектирования. На мой взгляд он слишком сильно влияет на общую архитектуру приложения, что бы называться просто “паттерном проектирования”, поэтому я предпочитаю называть его принципом или подходом. Использование CQRS проникает почти во все уголки приложения.

Начнем с CQRS

Что же такое CQRS?
CQRS расшифровывается как Command Query Responsibility Segregation (разделение ответственности на команды и запросы). Это паттерн проектирования, о котором я впервые услышал от Грега Янга (Greg Young). В его основе лежит простое понятие, что вы можете использовать разные модели для обновления и чтения информации. Однако это простое понятие ведет к серьёзным последствиям в проектировании информационных систем. (с) Мартин Фаулер

Не сказать что исчерпывающее определение, но сейчас я попробую объяснить что именно Фаулер имел в виду.
К настоящему времени сложилась такая ситуация что практические все работают с моделью данных как с CRUD хранилищем. CQRS предлагает альтернативный подход, но затрагивает не только модель данных. Если вы используете CQRS, то это сильно отражается на архитектуре вашего приложения.

Вот как я изобразил схему работы CQRS

Читайте также:  Что значит фразеологизм прокрустово ложе

Первое что бросается в глаза это то что у вас уже две модели данных, одна для чтения (Queries), одна для записи (Commands). И обычно это значит что у вас еще и две базы данных. И так как мы используем CQRS + Event Sourcing, то write-база (write-модель) — это Event Store, что-то вроде лога всех действий пользователя (на самом деле не всех, а только тех которые важны с точки зрения бизнес-модели и влияют на построение read-базы). А read-база — это в общем случае денормализировнное хранилище тех данных, которые вам нужны для отображения пользователю. Почему я сказал что read-база денормализированная? Вы конечно можете использовать любую структуру данных в качестве read-модели, но я считаю что при использовании CQRS + Event Sourcing не стоит сильно заморачиваться над нормализвацией read-базы, так как она может быть полностью перестроена в любое время. И это большой плюс, особенно если вы не хотите использовать реляционные базы данных и смотрите в сторону NoSQL.
Write-база вообще представляет собой одну коллекцию ивентов. То есть тут тоже нету смысла использовать реляционную базу.

Event Sourcing

Идея Event Sourcing в том чтобы записывать каждое событие, которое меняет состояние приложения в базу данных. Таким образом получается что мы храним не состояние наших сущностей, а все события которые к ним относятся. Однако мы привыкли к тому чтобы манипулировать именно состоянием, оно храниться у нас в базе и мы всегда можем его посмотреть.
В случае с Event Sourcing мы тоже оперируем с состоянием сущности. Но в отличии от обычной модели мы это состоянием не храним, а воспроизводим каждый раз при обращении.

Если посмотреть на код, который поднимает агрегат из базы, можно и не заметить какую-то разницу с традиционным подходом.

На самом же деле репозиторий не берет из базы готовое состояние агрегата UserAR (AR = Aggregate Root), он выбирает из базы все события которые ассоциируются с этим юзером, и потом воспроизводит их по порядку передавая в метод On() агрегата.

Например у класса агрегата UserAR должен быть следующий метод, для того чтобы восстановить в состоянии пользователя его ID

Из всего состояния агрегата мне нужна только _id юзера, так же я мог бы восстановить состояние пароля, имени и т.д. Однако эти поля могут быть модифицированы и другими событиями, не только User_CreatedEvent соответственно мне нужно будет обработать их все. Так как все события воспроизводятся по порядку, я уверен, что всегда работаю с последним актуальным состоянием агрегата, если я конечно написал обработчики On() для всех событий которые это состояние изменяют.

Давайте рассмотрим на примере создания пользователя как работает CQRS + Event Sourcing.

Создание и отправка команды

Первое что я сделаю — это сформирую и отправлю команду. Для простоты пусть команда создания юзера имеет только самый необходимый набор полей и выглядит следующим образом.

Пусть вас не смущает имя класса команды, оно не соответствует общепринятым гайдлайнсам, но позволяет сразу понять к какому агрегату она относится, и какое действие выполняется.

Затем мне нужен обработчик этой команды. Обработчику команды обязательно нужно передать ID нужного агрегата, по этому ID он получит агрегат из репозитория. Репозиторий строит объект агрегата следующим образом: берет из базы все события которые относятся к этому агрегату, создает новый пустой объект агрегата, и по порядку воспроизведет полученные события на объекте агрегата.
Но так как у нас команды создания — поднимать из базы нечего, значит создаем агрегат сами и передаем ему метаданные команды.

Посмотрим как выглядит конструктор агрегата.

Так же у агрегата обязательно должен быть конструктор без параметров, так как когда репозиторий воспроизводит состояние агрегата, ему надо сначала создать путой экземпляр, а затем передавать события в методы проекции (метод On(User_CreatedEvent created) является одним из методов проекции).
Немного уточню на счет проекции. Проекция — это воспроизведение состояния агрегата, на основе событий из Event Store, которые относятся к этом агрегату. В примере с пользователем — это все события для данного конкретного пользователя. А агрегате все те же события которые сохраняются через метод Apply, можно обработать во время воспроизведения его состояния. В нашем фреймворке для это достаточно написать метод On(/*EventType arg*/), где EventType — тип события которое вы хотите обработать.

Метод Apply агрегата инициирует отправку событий всем обработчикам. На самом деле события будут отправлены только при сохранение агрегата в репозиторий, а Apply просто добавляет их во внутренний список агрегата.
Вот обработчик события(!) создания пользователя, который записывает в read базу собственно самого пользователя.

У события может быть несколько обработчиков. Такая архитектура помогает сохранять целостность данных, если ваши данные сильно денормализированы. Допустим мне надо часто показывать общее количество пользователей. Но у меня их слишком много и операция count по всем очень затратна для моей БД. Тогда я могу написать еще один обработчик события, который будет уже относится к статистике и каждый раз при добавлении пользователя будет увеличивать общий счетчик пользователей на 1. И я буду уверен, что никто не создаст пользователя, не обновив при этом счетчик. Если бы я не использовал CQRS, а была бы у меня обычная ORM, пришлось бы следить в каждом месте где добавляется и удаляется пользовать чтоб обновился и счетчик.
А использование Event Sourcing’а даёт мне дополнительные приемущеста. Даже если я ошибся в каком-то EventHandler’е или не обработал событие везде где мне это надо, я могу легко это исправать просто перегенировав read базу с уже правильной бизнесс логикой.

С созданием понятно. Как происходит изменение агрегата и валидация команды? Рассмотрим пример со сменой пароля.
Я приведу только command handler и метод агрегата ChangePassword(), так как в остальных местах разница в общем не большая.

Command Handler
Aggregate Root

Хочу заметить что очень не желательно чтобы невалидное событие было передано в метод Apply(). Конечно вы сможете его обработать потом в event handler’е, но лучше вообще его не сохранять, если оно вам не важно, так как это только засорит Event Store.
В случае со сменой пароля вообще нет никакого смысла сохранять это событие, если вы конечно не собираете статистку по неудачным сменам пароля. И даже в этом случае следует хорошенько подумать, ножно ли это событие вам во write модели или есть смысл записать его в какое-нибудь темповое хранилище. Если вы предполагаете что бизнес логика валидации события может измениться то тогда сохраняйте его.

Вот собственно и все о чем я хотел рассказать в этой статье. Конечно она не раскрывает все аспекты и возможности CQRS + Event Sourcing, об этом я планирую рассказать в следующих статьях. Так же остались за кадром проблемы которые возникают при использовании данного подхода. И об этом мы тоже поговорим.
Если у вас есть какие-то вопросы, задавайте их в комментариях. С радостью на них отвечу. Так же если есть какие-то предложения по следующим статьям — очень хочется их услышать.

Источник

«А что если», Event Sourcing

Фундамент

«Печенюшки»

Естественно, раз подход применяется, значит, в нем есть то самое, «ОНО», ради чего игра стоит свеч.

Читайте также:  цветы двулетники что это значит
История событий

Начнем с того, что Event Sourcing – это мечта для тех, кто хочет помнить все «как и почему», «что и когда», произошло в системе. Скажем, это аудит записи, только еще лучше за счет хранения информации «почему». Мне запоминался пример, который Greg Young использовал на одной из своих презентаций:

Банк хранит сведения о том, где проживают их клиенты и в один день происходит изменение одного из почтовых адресов. При хранении информации в таблице, мы максимум сможем увидеть, что запись обновилась. Если был подключен аудит, то в целом мы можем посмотреть на историю всех предыдущих значений. Но сможем ли мы узнать, почему она изменилась: может это была изначальная ошибка?, а может клиент переехал в другое место? С помощью Event Sourcing мы как раз и смогли бы ответить на этот вопрос, т.к. у нас было бы 2 события: КлиентПереехал, ИсправленаОшибкаАдреса.

Проекции

Естественно, раз вся полезная информация о сущности сохранена в потоке событий, то напрямую ей воспользоваться нельзя. Для UI нам необходимы плоские модели (проекции), которые мы как раз и создаем, используя обработчики событий, которые называются денормализаторы (denormalizers). Отличительным свойством можно назвать то, что такой обработчик можно менять и добавлять, как и когда угодно. В любой момент проекцию можно «выкинуть», проиграть все события от начала и новая готова. Небольшой пример кода:

Конечно, кроме проекций для интерфейса, денормализация событий – это мощное средство построения разнообразных отчетов и проведения анализа event sourced сущности.

UI ориентированный на процесс

Замена основного хранилища данных (source of Truth) с нормализованных данных в таблицах, и CRUD операций над ними, на записи о событии, подталкивает на изменения к подходу в дизайне. Не выгодно иметь события в стиле CRUD, т.к. тогда теряется весь смысл в их семантике и привязке к происходящему. Значит необходимо строить такой интерфейс, где пользователь выполняет небольшие атомарные действия над сущностью. Т.е. дизайн интерфейса должен быть workflow driven, а не data input-based.

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

Технические моменты

Проблемы

Как можно догадаться, они значительны.

Ад для рефакторинга

Итак, о чудо, наши события immutable, а их поток append-only. Хм, чтобы это значило? А значит это, что любое единожды происшедшее события необходимо поддерживать навечно. Их нельзя ни отменить, ни удалить. Можно создавать новые версии событий, события которые компенсируют ошибки, — а это все новые и новые классы, которые необходимо добавить в имеющиеся обработчики и пронести по всей цепочке использования сущности. Это все равно, что отнять право на ошибку у программиста, и никакого тебе “rollback” плана. Разве что вернуть базу событий к предыдущему снэпшоту, потеряв все действия пользователей, если конечно вы на такое будете согласны 🙂

Нет, конечно, каждый из нас может, психанув, взять кувалду, перфоратор и пойти модифицировать произошедшие события в хранилище, но это скорее будет напоминать исправление данных в SQL базе, модифицируя файлы данных и индексов.

Просуммировав, это значит, что модель событий необходимо тщательно продумывать, отрисовывать и джуниора/мидла к ней не подпустишь. А если события уходят в другие компоненты… вообщем необходимо разграничить сразу внешние события и внутренние, а также запереть их определение в отдельный репозиторий. Ссылаться только через NuGet.

Нет готовых инструментов

Начиная разработку используя Event Sourcing, будьте готовы к тому, что все инструменты для работы с событиями придется написать самим. Конечно, есть EventStore от Jonathan Oliver, но это лишь малая часть. Вам понадобится графический и программный интерфейс их управлением: просмотра и поиска, построения и обновления проекций, создания снэпшотов для оптимизации чтения и др.

Взаимно компенсирующие события

Скажем, если пользователь отписался от новостей, а затем подписался… и так раз 50 (на протяжении года). В итоге есть только текущее состояние, что он подписан или отписан и все ок. Однако, если понадобится построить новый отчет или проекцию, где эти данные используются, то обработка таких событий бесполезно отнимет время, но хорошо загрузит машину.

Поддержка

Поддерживать решение на Event Sourcing дорого и сложно. В первую очередь из-за значительного времени на стадии дизайна, а также большого числа разных событий, которые необходимых создавать под каждый конкретный вид операций для соблюдения семантики.

Выводы

Как сказал один мой знакомый: «Мы не решаем проблемы, а переносим их в более комфортную для себя плоскость». Также можно сказать и про Event Sourcing. Каждый должен определить, нужен ли он для решения конкретной задачи, или нет.

Я же могу сказать следующее: используйте Event Sourcing для хорошо изученных сущностей и точечно, не пытайтесь построить на нем всю систему. Желательно, когда это не первая, а может и не вторая ее реализация.

Источник

Основы CQRS

Данная статья основана на материале из различных статей по CQRS, а также проектов, где применялся такой подход.

Системы управления предприятиями, проектами, сотрудниками давно вошли в нашу жизнь. И пользователи таких enterprise приложений все более требовательны: возрастают требования к масштабируемости, сложность бизнес-логики, требования к системам меняются быстро, да и отчетность требуется в реальном времени.

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

Типовой подход к проектированию приложения

Многослойная архитектура – один из самых популярных способов организации структуры веб-приложений. В простой её вариации, как на приведенной выше схеме, приложение делится на 3 части: слой данных, слой бизнес-логики и слой пользовательского интерфейса.

В слое данных имеется некий Repository, который абстрагирует нас от хранилища данных.
В слое бизнес-логики есть объекты, которые инкапсулируют бизнес-правила (обычно их названия варьируются в пределах Services/BusinessRules/Managers/Helpers). Запросы пользователя проходят от UI сквозь бизнес-правила, и дальше через Repository производится работа с хранилищем данных.

С такой архитектурой запросы на получение и изменение данных, как правило, производятся в одном и том же месте – в слое бизнес-логики. Это довольно простой и привычный способ организации кода, и такая модель может подойти для большинства приложений, если в этих приложениях количество пользователей со временем значительно не меняется, приложение не испытывает больших нагрузок и не требует значительного расширения функционала

Но если веб-ресурс становится достаточно популярным, может стать вопрос о том, что одного сервера для него недостаточно. И тогда встает вопрос о распределении нагрузки между несколькими серверами. Простейший вариант быстро распределить нагрузку – использовать несколько копий ресурса и репликацию базы данных. А учитывая, что все действия такой системы никак не разделены, это порождает новые проблемы.

Классическая многослойная архитектура не обеспечивает лёгкого решения подобных проблем. Поэтому неплохо было бы использовать подходы, в которых эти проблемы решены с самого начала. Одним из таких подходов является CQRS.

Command and Query Responsibility Segregation (CQRS)

CQRS – подход проектирования программного обеспечения, при котором код, изменяющий состояние, отделяется от кода, просто читающего это состояние. Подобное разделение может быть логическим и основываться на разных уровнях. Кроме того, оно может быть физическим и включать разные звенья (tiers), или уровни.

Читайте также:  что делает зачарование веном в майнкрафт

В основе этого подхода лежит принцип Command-query separation (CQS).

В данном методе мы спрашиваем (делаем Query), является ли валидным переданный email. Если да, то получаем в ответ True, иначе False. Кроме возврата значения, здесь также определено, что в случае валидного email сразу присваивать его значение (делаем Command) полю Email.

Несмотря на то что пример довольно простой, вероятна и менее тривиальная ситуация, если представить себе метод Query, который при вызове в нескольких уровнях вложенности меняет состояние разных объектов. В лучшем случае повезет, если не придется столкнуться с подобными методами и их долгой отладкой. Подобные побочные эффекты от вызова Query часто обескураживают, так как сложно разобраться в работе системы.

Если воспользоваться принципом CQS и разделить методы на Command и Query, получим следующий код:

Теперь пользователь нашего класса не увидит никаких изменений состояния при вызове IsEmailValid, он лишь получит результат – валиден ли email или нет. А в случае вызова метода ChangeEmail пользователь явно поменяет состояние объекта.

В CQS у Query есть одна особенность. Раз Query никак не меняет состояние объекта, то методы типа Query можно хорошо распараллелить, разделяя приходящуюся на операции чтения нагрузку.

Если CQS оперирует методами, то CQRS поднимается на уровень объектов. Для изменения состояния системы создается класс Command, а для выборки данных – класс Query. Таким образом, мы получаем набор объектов, которые меняют состояние системы, и набор объектов, которые возвращают данные.

Типовой дизайн системы, где есть UI, бизнес-логика и база данных:

CQRS говорит, что не надо смешивать объекты Command и Query, нужно их явно выделить. Система, разделенная таким образом, будет выглядеть уже так:

Разделение, преследуемое CQRS, достигается группированием операций запроса в одном уровне, а команд – в другом. Каждый уровень имеет свою модель данных, свой набор сервисов и создается с применением своей комбинации шаблонов и технологий. Еще важнее, что эти два уровня могут находиться даже в двух разных звеньях (tiers) и оптимизироваться раздельно, никак не затрагивая друг друга.

Простое понимание того, что команды и запросы являются разными вещами, оказывает глубокое влияние на архитектуру ПО. Например, вдруг становится легче предвидеть и кодировать каждый уровень предметной области. Уровень предметной области (domain layer) в стеке команд нуждается лишь в данных, бизнес-правилах и правилах безопасности для выполнения задач. С другой стороны, уровень предметной области в стеке запросов может быть не сложнее прямого SQL-запроса.

С чего начать при работе с CQRS?

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

Далее объявляется интерфейс обработчика команды.

Он содержит лишь 1 метод, принимающий данные с типом интерфейса, объявленным ранее.

После этого остается определить способ централизованного вызова обработчиков команд в зависимости от конкретного типа переданной команды (ICommand). Эту роль могут выполнять сервисная шина или диспетчер.

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

Пример команды. Допустим, есть интернет-магазин, для него нужно создать команду, которая создаст товар в хранилище. В начале создадим класс, где в его имени указываем то, какое действие производит данная команда.

Все классы, реализующие ICommand, содержит данные – свойства и конструктор с установкой их значений при инициализации – и больше ничего.

Реализация обработчика, то есть уже непосредственно самой команды, сводится к довольно простым действиям: создается класс, который реализует интерфейс ICommandHandler. Аргументом типа указывается команда, объявленная ранее.

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

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

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

Стек запросов имеет дело только с извлечением данных. Запросы используют модель данных, максимально соответствующую данным, применяемым на презентационном уровне. Вам вряд ли нужны какие-либо бизнес-правила – обычно они применяются к командам, которые изменяют состояние.

Здесь в качестве аргумента типа указывается тип возвращаемых данных. Это может быть произвольный тип, например, string или int[].

После объявляется обработчик запросов, где также указывается тип возвращаемого значения.

Вызов команд и запросов

Имея контроллер для обработки запросов пользователя, достаточно передать в качестве зависимости объект нужного диспетчера, после чего сформировать объект запроса или команды и передать методу диспетчера Execute.

Так мы избавляемся от необходимости постоянного увеличения зависимостей при увеличении количества функций системы и уменьшаем количество потенциальных ошибок.

Регистрировать обработчики можно разными способами. С помощью DI-контейнера можно регистрировать по-отдельности или автоматически просматривая сборку с нужными типами. Второй вариант может выглядеть следующим образом:

var container = new Container ();

Здесь используется контейнер SimpleInjector. Регистрируя обработчики методом Register, первым аргументом указывается тип интерфейсов обработчиков команд и запросов, а вторым – сборка, в которой производится поиск классов, реализующих данные интерфейсы. Тем самым не нужно указывать конкретные обработчики, а лишь только общий интерфейс, что крайне удобно.

Что если при вызове Command/Query надо проверять права доступа, записать информацию в лог и тому подобное?
Их централизованный вызов позволяет добавить действия до или после выполнения обработчика, не изменяя ни один из них. Достаточно внести изменения в сам диспетчер, или создать декоратор, который через DI-контейнер подменит исходную реализацию (в документации SimpleInjector довольно хорошо расписаны примеры подобных декораторов).

Заключение

Чтобы приложения были по-настоящему эффективными, они должны подстраиваться под требования бизнеса. Архитектура, основанная на CQRS, значительно упрощает расширение и модификацию рабочих бизнес-процессов и поддерживает новые сценарии. Вы можете управлять расширениями в полной изоляции. Для этого достаточно добавить новый обработчик, зарегистрировать и сообщить ему, как обрабатывать сообщения только требуемого типа. Новый компонент будет автоматически вызываться только при появлении соответствующего сообщения и работать бок о бок с остальной частью системы. Легко, просто и эффективно.

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

Источник

Сказочный портал