Model View Dispatcher (cqrs over mvc)
Доброго всем времени суток, в этой статье хочу осветить ещё один компонент из библиотеки Incoding Framework.Model View Dispatcher (MVD) — позволяет избавится от избыточного кода (а именно asp.net mvc controller) и упростить навигацию по проекту, уменьшив количество абстракций между клиентским и серверным кодом.На хабре имеются несколько статей о IML и CQRS, которые входят в состав framework
Знакомство с IML Incoding CQRSпримечание: статья была опубликована, но в то время были проблемы с аккаунтом на хабре и она прошла не замеченной IML vs AngularJsпримечание: статья подверглась критике (объективной и субъективной) Ответ на IMl vs AngularJsпримечание: нельзя назвать успехом, но уже немного лучше MVD фигурировал в некоторых статьях, но как часть примера демонстрирующая возможности IML, поэтому я решил рассказать о нем отдельно, но обо всем по порядку…Зачем? Рассмотрим сценарий применения asp.net mvc + cqrsController public ActionResult Details (GetUserDetailsQuery query) { var model = dispatcher.Query (query); return Json (model); } примечание: action отвечает за binding и передачу query в Dispatcher для возврата полученных данных в виде Json View $.get ('@Url.Action («Details», «Controller»)', callback) примечание: не хватает листинга Query для полной картины, но становится очевидно избыточность ActionДиета Чтобы посадить controller на «диету» можно воспользоватся паттерном Mediator (как один из вариантов), который опирается на Interface и Generic, но это только позволяет упростить и объединить код, но не решить проблему полностью, потому что все равно приходится писать однотипные Controller/Action.Model View Dispatcher (MVD) — позволяет выполнять Command/Query в «обход» asp.net mvc. Для демонстрации перепишем предыдущую задачу, но с MVD
$.get ('@Url.Dispatcher ().Query (new GetUserDetailsQuery ()).AsJson ()', callback) Подсчитаем бонусы Не нужен Action Проще навигация, потому что теперь действия на странице отражают картину (go to delcaration без посредников) на серверного кода Строгая типизация при построении URL (visual studio intelisence, refactor utilities) Нет MVC??? Чтобы получить ответ, посмотрим как подключить MVD к проекту: Создать DispatcherController (начиная с версии 1.1 устанавливается через nuget), который унаследовать от DispatcherControllerBase
public class DispatcherController: DispatcherControllerBase { public DispatcherController () : base (typeof (T).Assembly) { } } примечание: конструктор принимает Assembly в котором объявлены Command/QueryDispatcherControllerBase содержит следующие методы (Actions):
Query (string incType, string incGeneric, bool? incValidate) Render (string incView, string incType, string incGeneric) Push (string incType, string incGeneric) Composite (string incTypes) QueryToFile (string incType, string incGeneric, string incContentType, string incFileDownloadName) пример url, после вызова которого будет выполнен Push command Url.Action («Push», «Dispatcher», new { incType = typeof (Command).Name }) Альтернативный способ через DSL (domain specific language) Url.Dispatcher ().Push (new Command ()) примечание: дело не только в лаконичности синтаксиса (хотя это важно), но также в абстракции от деталей (имена параметров и т.д.)Обратить внимание
MVD использует IDispatcher, который зарегистрирован в IoCFactory (пример со StructureMap в качестве провайдера) В качестве ActionResult возвращается IncodingResult, который формирует JSON{ success: true/false, data: something/null, redirectTo: url/null } Вывод: текущая реализация MVD опирается на asp.net mvc, по факту просто делает обобщенный (не generic) Controller, но можно использовать httphandler или другой http обработчик в качестве платформы.
Что умеет ?
MVD покрывает большинство сценариев, которые встречаются при веб-разработке на платформе asp.net mvc: примечание: исходный код примеров на GitHubPush
Url.Dispatcher ().Push (new AddUserCommand
{
Id = »59140B31–8BB2–49BA-AE52–368680D5418A»,
Name = «Vlad»
})
примечание: вопросы валидирования далее
Push generic
Url.Dispatcher ().Push (new AddEntityCommand
if (! ModelState.IsValid) return IncodingResult.Error (ModelState)
try { dispatcher.Push (composite); return IncodingResult.Success (); } catch (IncWebException exception) { foreach (var pairError in exception.Errors) { foreach (var errorMessage in pairError.Value) ModelState.AddModelError (pairError.Key, errorMessage); } return IncodingResult.Error (ModelState) } примечание: реальный код несколько сложнее из-за дополнительной логики, но пользователь Incoding Framework абстрагирован от этих деталей и работает с TryPush или MVD pushВ catch мы перехватываем только IncWebException (на основе которого заполняем ModelState), а остальные exception считаем провальными и оставляем их обработку на совесть global.asax.А как было раньше? Ответ поможет проанализировать, чем же решение на базе Incoding Framework лучше того, что имеется в стандартном asp.net mvc public ActionResult Add (AddUserCommand command) { if (ModelState.IsValid) return View (command);
return Execute (command); } Если ModelState содержит ошибки, возвращается View, которое строится на основе command (хорошо, если не используется Container с дополнительными списками, которые тоже придется заново строить), чтобы сохранить состояние. Такое поведение введет к следующим проблема: Заново строится форма, что занимает время и увеличивает трафик Возвращаемый результат является html (пусть и упакованный в json), что не позволяет повторно использовать action, как API для сторонних (мобильных) приложений Итог MVD является очень хорошим союзником для борьбы с однотипными Action, которые приводят к «разбуханию» кода, что особенно критично на поздних этапах проекта. MVD можно использовать без IML, но тогда теряется возможность использовать Selector в routes, что негативно скажется на типизации, из-за того, что придется в «ручную» (pure js) собирать параметры.Конечно, могут быть сценарии с которыми MVD не способен (временно или просто не возможно реализовать) справится, но ничто не мешает написать Controller и Action для конкретных (может это всего 5% — 10%) случаев.Получается все ради того, чтобы убрать дубляж ? Если MVD поможет сократить код на 10–15% и ускорить разработку, то это уже очень хороший результат, но мы пошли дальше и реализовали возможность строить схему end point (идея взята у wcf endpoint)…Исходный код, как документация ? Диалог между разработчиком api и мобильного приложения: Api — я добавил новый запрос, по адресу /GetUsers? Active=true Api —, а также новые поля для создания user (Comment, City, State) Api — о, чуть не забыл City это справочник и его можно получить по запросу /GetCities Api — и ещё один момент, заказчик просил, чтобы State был числом, так что учти это Мобильное приложение — ок, опиши в документе и назови %sitename%-api-%current-version%примечание: основной проблемой является %current-version%, потому что приходится поддерживать актуальность документации, после каждого изменения в коде Тот же диалог, но с mvd end-points Api — обновил код Мобильное приложение — уже можно смотреть appDomain/Dispatcher/Endpoints? Api — конечно, там кстати песочница где можно проверить command/queryпримечание: песочница, так же удобна и для разработчика API, так как часто разработка идет без UI (user interface) и чтобы быстро проверить на работоспособность (unit test покрывает код, но интеграционные тоже нужны) можно воспользоватся авто-сгенерированной формой примечание: пример такой страницы, которая находится на ранней стадии разработки, но отражает общую картину, то есть по большей части не хватает внимания к деталям, чтобы упаковать это решение в готовую библиотеку.Вывод: возможность строить документацию на основе исходного кода, повышает обратную связь между разработчиками API и мобильного приложения. Кроме документации, можно расширить функционал, реализовав статистику запросов, профилирование и многое другое, что становится возможным реализовать из-за унифицированного кода, который можно разбирать и анализировать через рефлексию.
P.S. опубликована новая версия (пока beta) Incoding Framework в nuget, где появилось много нововведений и доработок. Я приведу несколько, а остальные постараюсь расписать в отдельных статьях (полный список на нашем bugtracker)
Упрощения условий Break.If (r=>r.Is (()=>Selector.Jquery.Self ()).And.Is (()=>«id».ToId ()== 12)) // Old Break.If (()=>Selector.Jquery.Self () && id.ToId () == 12) // New Реализация EF, RavenDB, MongoDb провайдеров (одна command и разные ORM на github и inc-todo-ravendb) Упрощения синтаксиса inDsl.Core ().JQuery.Attributes.SetAttr (HtmlAttribute.Checked) // Now inDsl.Attr.Set (HtmlAttr.Checked) // Future примечание: причина статуса beta (в конце мая уже будет стабильная версия) в том, что ещё не все наши проекты переведены на эту версию и остался ряд задач, которые планировали для этого релиза