Ещё немного про “простые” AC/BC, прежде, чем заняться CQRS.
Так, как “простых” AC в любой системе будет большинство и для них CQRS не нужен, отложим его пока на попозже :)
Persisted ViewModel
В прошлый раз я описывал идею того, как AC сохраняет и читает данные из схемы (БД), принадлежащей BC.
В этой связи стоит обратить внимание на то, что AC такого рода являются некими data-centric, или даже data-driven элементами. Показал данные в UI, пользователь поменял что-то в текстбоксе, сохранил в БД.
Понятно, что операция чтения выполняется гораздо чаще, чем записи.
А раз она выполняется чаще, то можно сделать её быстрее! Для этого можно организовать ещё одно хранилище, базу данных, настолько близко к веб-серверу, насколько можно. Можно сохранять/кешировать нашу инфомарцию там так, чтобы доступ к ней был быстрее.
Можно посмотреть на это с такой стороны, что каждая такая операция AC возвращает определённую, по сути, ViewModel, которая в основном содержит достаточно мало данных (не забываем, что мы находимся в пределах BC и имеем дело только с данными, которые нужны только этому BC, у нас больше нет гигантских объектов).
Тогда можно попытаться сделать так, чтобы одна ViewModel занимала, условно говоря, одну строку в этом новом хранилище-базе-кеше, так будет быстрее эту ViewModel оттуда доставать.
Причём, если мы сумели сделать ViewModel как одну строку, или сущность, то нам, по сути, уже нет разницы, какое хранилище использовать: будет ли это SQL Server (ViewModel per row), будет ли это какая-нибудь Mongo или CoachDB или другое NoSQL-решение (Document per row), главное, чтобы было удобно и быстро, можно даже в файлах хранить :)
Классифицируем это новое хранилище как “Persisted ViewModel”. То есть, в рамках BC мы имеем теперь ещё и Persisted ViewModel, который будет разворачиваться настолько близко к клиентам, насколько возможно, чтобы увеличить скорость работы с ним.
Валидация
Пойдём ещё дальше, подумаем о валидации.
Мы, программисты, иногда путаем валидацию и бизнес-правила. Udi отметил, что нужно чётко разделять эти две категории.
Например “максимальная длина имени пользователя – 25 символов”. Это – валидация.
”Email должен быть уникальным” – это валидация.
Мы часто делаем валидацию на стороне клиента, потом ещё раз проверяем на стороне бизнес-логики, возвращая ошибки, чтобы показать их красненьким и всё такое.
Зачем – большой вопрос. Почему мы так не доверяем собственным клиентам, что валидируем (речь не идёт о бизнес-правилах) их ввод дважды, удивлялся Udi. Что случится, если мы не будем делать валидацию на стороне бизнес-логики?
Наша команда не выполнится, возникнет исключение, “имя пользователя слишком длинное и не влезло в SQL-сервер”, что-то в этом роде.
Почему это может случиться? Например потому, что кто-то попытался обойти валидацию на клиентской части. Какой-нибудь хакер.
Почему мы так заботимся о том, чтобы хакер получил красивые красненькие сообщения, связанные с валидацией, откуда такая забота о хакере – большой вопрос. “Знаете что, я считаю, что хакер даже заслуживает того, чтобы получить ошибку “ваша команда не была выполнена”! Особенно если она будет доставлен ему асинхронно по email!” заявил Udi. :)
Тем не менее.
Persisted ViewModel может использоваться так же и для валидации пользовательского ввода. То есть, нам нужно проверить уникальность пользовательского email, мы можем сделать это с использованием расположенного близко к веб-сервису Persisted ViewModel хранилища.
Промежуточный итог
У нас получилась ситуация, когда простые AC, работающие с неконкурентными данными, имеют возможность быстро-быстро работать с Persisted ViewModel. Причём, поскольку данные неконкурентны, мы можем сразу же в неё и писать прямо внутри этого самого AC, когда пользователь добавляет/изменяет данные.
Сделать это крайне несложно: простые AC, партицированные по business capability и принадлежащие только одному BC данные, ничего лишнего, остаётся только привести их к виду 1 запись – 1 ViewModel в идеале. А часто и это не потребуется, так как такая ситуация часто будет иметь место уже как результат этого вертикального партицирования.
Ещё более того. В таком data-driven AC часто не нужен никакой “слой бизнес-логики”, значит можно пойти ещё дальше и “разговаривать” напрямую с Persisted ViewModel! Особенно это просто, когда в этом качестве выступает NoSQL база данных, они практически все имеют REST/JSON интерфейсы, можно взять данные прямо оттуда и положить данные прямо туда. Нам не нужен никакой DAL, ORM, Database Context, что-то ещё.
Даже SQL Server может выставить наружу по HTTP (если нет возможности “нормального” соединения с сервером) хранимую процедуру. Впрочем, в простоте и удобстве этого решения я как-то не слишком уверен, но вариант такой есть, зато DAL делать не надо.
Один из случаев, вызвавших вопросы в нашей группе (и вызывающих вопросы всегда, как сказал Udi), это то, что, разумеется, в одном BC находятся, например, Order и OrderLines.
Однако, если посмотреть на то, что представляют из себя обе эти сущности, то можно заметить, что Order в рамках BC – это всего лишь OrderId. Ну, CustomerId ещё. А OrderLines – это просто список ProductIds. Больше ничего там нет.
Так ли важно в данном случае хранить две таблицы и делать связь между ними, если у нас там только одни идентификаторы?
Можно, например, сделать из списка ProductIds строку, разделённую запятыми. Not a big deal!
Вместо гигантского объекта Order получаем два Guid’а и ещё список Guisd’ов, разделённых запятыми, одна строка.
”So, where is THE Order?” – это шутка, над которой наша “подгруппа” смеялась на перерывах и вопрос, который в шутку задавался друг другу как фраза для начала какого-то разговора. Ну, например, подходит человек к группе разговаривающих и присоединяется фразой “so, guys, where is the order?”
Потому, что никакого объекта “Order” на самом деле в системе нет.
Вот в следующий раз – точно про CQRS. Обмозговать бы ещё малость.