Viktor Love
2 min readNov 30, 2018

--

Тэкс, у меня есть пример попытки выстроить кое-что поверх CQRS. Но и сам CQRS он частично иллюстрирует. Увы, это не рабочий проект, поэтому там много нет.

Вот файл с набором сообщений и их обработчиков:

Например, глянем на пару EmailExists и EmailExistsHandler.

EmailExists является формулировкой запроса. Мы говорим, что хотим проверить, существует ли такой email в бд. Там в самом классе еще всякий мусор есть (ToString, Equals, GetHashCode), который к самому CQRS не относится. Сводится оно к этому:

class EmailExists : IQuery<bool> {
public string Email { get; set; }
}

Тут минимум информации: что нужно узнать (имя класса EmailExists), с какими параметрами (свойство Email), и что вернется в результате (bool).

Дальше, у нас есть EmailExistsHandler. Это обработчик запроса. Поскольку это семпл, то тут нету работы с бд. Но оно должно быть в рабочем проекте, так что представим, что мы добавляем эту логику. Тогда нужно ожидать в конструкторе какой-нибудь DbConnection. И в методе Handle мы будем дергать бд и возвращать данные.

class EmailExistsHandler : IQueryHandler<EmailExists, bool> {
private DbConnection dbConnection;
EmailExistsHandler(DbConnection dbConnection) {
this.dbConnection = dbConnection;
}
public async Task<bool> Handle(EmailExists query) {
// do something with dbConnection and query
}
}

Тут видно, что нам, увы, приходится дублировать тип “bool”. Я пока не знаю как от этого избавиться.

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

diContainer.Register<
IQueryHandler<EmailExists, bool>,
EmailExistsHandler
>()

И это будет работать.

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

Теперь посмотрим на сам вызов:

Вызов выглядит вот так:

var userExists = await bundle.Query(new EmailExists() { Email = email });

Нам не нужно знать, какие зависимости нужны EmailExistsHandler. Это разруливает DI. В этом преимущество разделения на сообщение и обработчик. С другой стороны, если вы не используете DI, то вам это разделение не особо нужно.

С третьей стороны, DI позволяет писать универсальную кэширующую прослойку на все обработчики сообщений.

Сам пример не иллюстрирует структуру проекта, потому что это PoC. Структура проекта — сложный вопрос. В принципе, для начала вы спокойно можете себе оставаться в парадигме DAL/BAL разделения. Просто у вас будут DAL Query и BAL Query.

Дальнейшее продвижение заключается в том, чтобы делить Query на следующие: публичные и приватные.

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

Примеров без NDA на это, увы, нет.

--

--

Viktor Love
Viktor Love

Written by Viktor Love

Software Engineer from Ukraine. TypeScript, React, C#, Angular.

Responses (1)