Cqrs vs N-layer

Incoding CQRS vs NLayer

Disclaimer: данная статья является личным мнением автора, которое складывается из опыта и знаний полученных при написание проектов.

Как было раньше

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

Эта практика имеет так же минусы:

  • “Разбухание” исходного кода, проблема чаще всего происходит из-за добавления связующих слоев таких как  facade layer, communication layer.
  •  Скрытие деталей за множеством уровней слоев ( данная проблема очень хорошо описана в статье ).

В рамках n-layer систем часто используется слой service  layer, который предназначен для скрытия деталей работы бизнес процессов и логики приложения, путем агрегации схожих задач  в один класс, но он же и  является  основным источником следующих проблем:

  • Трудности поддержки и расширения тесно связанных методов ( проблема будет рассмотрена более детально в  данной статье )
  • Увеличение сложности написания unit test

Так же одной из причин ухода от n-layer можно выделить то, что  после прихода популярности к таким практикам гибких методологий разработки как Scrum, kanban стало очевидно, что сложные многоуровневые архитектуры имеют ряд  и других проблем:

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

Дорога к CQRS

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

Incoding Framework CQRS – это набор базовых и готовых классов  ( command, query, dispatcher, Event broker  ), которые полностью покрывают большинство популярных сценариев разработки бизнес приложений.

Основные понятия:

  • CommandBase – базовый класс для команд
  • QueryBase – базовый класс для запросов
  • IEvent – реализация событий
  • IEventSubscriber – реализация подписчиков событий
  • DefaultDispatcher – стандартный диспетчер для выполнения команд и запросов
  • DefaultEventBroker – стандартный брокер для публикации сообщений

примечание: большинство реализаций Incoding Framework можно заменить на свои с помощью IoC.

Ключевые классы для разработчика являются CommandBase и QueryBase, которые имеют следующие инструменты:

  • Repository  – Уровень базы данных
  • EventBroker – Коммуникация между разными частями приложения

примечание: основное отличие  команд от запросов при работе с базой данных в том, что запрос никогда не делает commit ( submit changes ) в базу данных, а команда всегда делает.

Далее в статье будет рассмотрено решения типовой  задачи на примере разработки “API для создания order”, которая будет поэтапно усложнятся раскрывая все минусы и плюсы разных подходов.

Задача 1

Условие :  Создание order

решение: service layer

примечание: реализация алгоритма

примечание: использование сервиса

решение: Incoding CQRS

примечание: реализация алгоритма

примечание: использование команды

 Вывод: Реализация Incoding CQRS заметно выигрывает из-за:

  • Готовой инфраструктуры ( repository , dispatcher )
  • Единой точки выполнения всех command и query, которая решает проблему с  дублированием однотипного кода  в множестве методов сервиса  (пример реализация  Unit of  work )
    справка: в случаи с сервисами такое возможно только при использование АОП.

примечание: код примеров подготовлены под написание Unit Test, чему свидетельствует введение зависимостей в конструкторы классов, для дальнейшей подмены заглушками.

Задача 2


Условие:  Редактирование order

решение: service layer

 примечание: реализация алгоритма

 примечание: использование сервиса

решение: Incoding CQRS

примечание: реализации алгоритма

примечание: использования команды

Вывод: После усложнения задачи наглядно видно, как затрудняется расширение класса UserService из-за того, что  невозможно рассматривать задачи Add и Edit независимо друг от друга ( плохой уровень декомпозиции ), что в свою очередь приводит к высокой связанности компонентов из-за чего усложняется:

  • Изменения одной задачи не затрагивая связанные
  • Написание unit test  ( пример алгоритма service layer показывает, что потребовалось добавить ещё один интерфейс IOrderRepository в зависимости конструктора, хотя он и не используется в методе Add )
  • Разделение ответственности между разработчиками в рамках одной группы

Задача 3


Условие:  Отправка email после создания или редактирования order

решение: service layer

 примечание: реализация алгоритма

решение: Incoding CQRS

 примечание: реализация алгоритмов

 примечание: реализация события

 примечание: реализация подписчика

Вывод: Решение с Incoding Framework покажется немного более сложным за счет разделения ответственности ( сохранение в базу данных от отправки email), но именно благодаря этому появляется возможность проводить тестирование каждого компонента независимо и использовать повторно события из разных команд и запросов ( в случаи с сервисами это достигается через наследование, но имеет ряд проблем рассмотренных в заключение этой статьи).

примечание: показано только изменения в работе сервисов и команд, потому что controller остался неизменный.

Заключение

После рассмотрения решения задачи, можно увидеть, что подход cqrs ( в частности Incoding CQRS ) более располагает к разделению задач на мелкие полностью атомарные ( независимые ) блоки, нежели аналогичные решения с применением Service layer.

Часто основной причиной  в пользу выбора Service layer является возможность использовать ООП, а в частности наследование одного класса сервисов от других классов, тем самым расширяя его функционал общими методами,  что позволяет убрать дубляж, примером этого можно привести метод GetUser или GetStaff,  которые можно использоваться в большинстве сервисов и по этой причине методы выносятся в базовый класс UserServiceBase, от которого в дальнейшем наследуются уже конкретные реализации, такие как OrderService. Правда  этот подход имеет так же ряд проблем, связанных с поддержкой этого наследования  в дальнейшей перспективе расширения приложения, потому что не все дочерние классы будут использовать методы базового класса или же когда дочерние классы используют методы базового класса, но с определенными булевскими переменными в сигнатуре или перегрузкой с помощью полиморфизма, что приводит к следующим проблемам:

  • Плохой связанности методов внутри класса
  • Высокой степени сложности изменения кода и его тестирования.

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

Стоит отметить так же удобство общения с заказчиком и поддержки гибких методологий разработки из-за:

  • Мелкие команды и запрос позволяют строить спринт при высоком уровне декомпозиции
  • Команды и запрос являются прототипом бизнес действия, таким как “Add Order”, “Approve payment”, “Confirm user” и так далее.

Список материалов  о CQRS

Список материалов  о реализации Incoding Framework CQRS

  • Готовое тестовое приложение  IncMusicStore
  • Проект Browsio, который  раскрывает большую часть потенциала
  • Официальная документация
  • Вопросы можно задавать в комментариях к посту или контактах указанных на сайте.

P.S. Incoding Framework CQRS прошел проверку на гибкость в множестве сложных бизнес приложений компании .

Vlad Kopachinsky

I am a Senior developer at Incoding Software in Russian Federation. I created the Incoding Framework for rapid development

More Posts - Website - Twitter - Facebook - LinkedIn

Leave a Reply