Микросервисы и CQRS: оптимизируем затраты на продукт

Микросервисы и CQRS: оптимизируем затраты на продукт
Эта статья для представителей бизнеса с запросом на разработку ИТ-решения, чьи технические знания не актуальны или недостаточны для принятия обоснованных решений. То есть для тех, кто отошел от “дел технических” и работает на стороне стратегии, маркетинга или клиентского сервиса. Мы подскажем, как запустить продукт и избежать неоправданных затрат, связанных с архитектурой. А также посмотрим, как использование CQRS поможет при реализации функционала в разных клиентах приложения, и являются ли микросервисы той самой панацеей.
Почему архитектура важна для бизнеса
Ни один программный продукт невозможно рассматривать в отрыве от конкретных бизнес-процессов. Софт существует не сам по себе, а для того чтобы автоматизировать процессы, улучшить пользовательский опыт, помочь бизнесу добиться определенных показателей. Архитектура ИТ-решения строится с учетом бизнес-архитектуры. Таким образом, она является воплощением общего видения бизнеса и закладывает предпосылки для развития, масштабирования и оптимизации продукта.
Итак, какие преимущества дает грамотная и своевременная проработка архитектуры ИТ-решения?
Снижение стоимости разработки
Архитектура снижает стоимость разработки не только за счет переиспользования элементов, но и за счет повышения эффективности коммуникаций и работы команды. Это можно делать в рамках одного модуля или проекта и таким образом снижать стоимость всей системы. Если не позаботиться о том, чтобы заложить архитектуру, то на более поздних этапах внесение изменений может быть дорогостоящим.
Амортизация рисков
То, как система будет выглядеть, из каких частей состоять, с какими бизнес-процессами пересекаться и взаимодействовать, определяется архитектурой. Экономия средств происходит за счет того, что архитектурное проектирование указывает на риски и помогает избежать таковых на ранних стадиях. Это намного дешевле последующего устранения последствий.
Ускорение выхода на рынок
Возможность сокращать время выхода на рынок особенно важна в условиях повышенной конкуренции. Бизнес, который способен удовлетворять потребности рынка быстрее и лучше других, получает конкурентное преимущество
Готовность бизнеса к изменениям
Сегодня бизнес оперирует в условиях быстро меняющихся рынков. Способность адаптировать бизнес-модель и быстро вносить изменения в продукт завязано на архитектуру. Если карта развития архитектуры не будет выстроена в соответствии с картой развития ИТ-продукта и бизнеса, то эта способность будет утрачена.
Выбор оптимальных технических решений
Архитектура продукта диктует ряд технических решений. Выбор языка программирования, платформы, фреймворка, инструментов разработки и т.д. определяет уровень текущих и будущих затрат. Технические средства исполнения определяют скорость изменений в продукте. Это весьма критично, в таких ситуациях, когда MVP не справляется с наплывом клиентов, выдавая отказы.
Две крайности при разработке
При создании программных продуктов возникают вопросы:
- Когда именно стоит приступать к архитектурному проектированию?
- Какой стиль и шаблоны использовать?
- Насколько тщательно следует прорабатывать архитектурные решения?
Мы сталкивались с разными подходами и мнениями. На одном конце спектра – “в MVP архитектура не нужна”, на другом – “с самого начала нужно проработать микросервисы, чтобы обеспечить гибкость и масштабируемость продукта”. Это две крайности, которые могут стоить бизнесу больших денег. Ниже рассмотрим, как выбрать золотую середину.
Типичная ситуация: “большой комок грязи”
К “большому комку грязи” – программной системе с нераспознаваемой архитектурой – приходят постепенно. А начинается все с запроса от бизнеса, который звучит примерно так: “Нам бы побыстрее выйти в релиз и получить первых клиентов. А потом уже давайте думать, что делать с клиентами и как улучшать продукт”.
Для этого очень часто используется эволюционный подход. Он заключается в том, что сначала разрабатывается MVP (минимальный жизнеспособный продукт) без анализа нефункциональных требований и проработки архитектуры. Затем начинается долгий этап доработок. В результате система плохо функционирует и развивать ее все сложнее.

Мы видели много случаев отсутствия регулярной практики по архитектурному проектированию. И каждый раз это неизбежно приводило к плачевным результатам. Например, наплыв первых клиентов приводит к сбоям в работе системы, т. к. она не выдерживает высокой нагрузки. Система “валится”. Команда “тушит пожары”. Клиенты недовольны и уходят. В результате бизнес рушится, не сумев масштабироваться.
В другом случае возникает необходимость переписывания системы целиком. На это тратится больше сил и средств, чем на всю предыдущую разработку. В таких случаях приходится переучивать всех пользователей для работы с новой системой. Это в свою очередь негативно отражается на лояльности пользователей и потенциале последующих продаж.
Микросервисы не спасут
Что такое микросервисы
Существуют разные типы, способы и паттерны моделирования архитектуры. Микросервисы – один из возможных стилей архитектуры, главной особенностью которого является модульность. Такая архитектура противопоставляется монолитной, в которой разные компоненты объединены в одно целое.
В микросервисной архитектуре создаются небольшие, независимые модульные сервисы. Каждый сервис решает определенную задачу. Сервисы взаимодействуют через API (application program interface) – это посредник, который связывает все элементы воедино. Каждый API запрограммирован на выполнение определенной бизнес-цели. Использование отдельных сервисов позволяет использовать наиболее подходящий технический стек для каждой конкретной задачи.

О чем помнить при подготовке микросервисов
Микросервисы очень популярны. При этом некоторые заказчики на волне хайпа требуют, чтобы все было реализовано с использованием микросервисной архитектуры, не зная реальную стоимость ее применения. Кроме того, чтобы получить пользу, надо уметь правильно микросервисы готовить. Недостаточно просто сделать несколько независимых проектов.
При разделении программного продукта на микросервисы возникает много вопросов по взаимодействию сервисов. Например, как отследить цепочку вызовов различных сервисов, которая привела к конкретной ошибке? Или как понять, какой сервис сейчас является узким местом в работе системы? Эти вопросы с разной степенью успешности решаются либо готовыми инфраструктурными решениями (например, Elastic для логирования), либо под них надо разрабатывать какие-то свои инфраструктурные сервисы.
Еще один момент о котором необходимо помнить – целостность данных. Поэтому заранее необходимо заложить механизмы обеспечения целостности данных. К сожалению, про это часто забывают. При эксплуатации это приводит к потери или рассинхронизации данных.
Кроме того, грамотно разделить систему на независимые микросервисы на начальном этапе, как правило, не получается. Еще не известен ни точный функционал проектируемой системы, ни структура предметной области. Следовательно, неправильно выбранное разделение по сервисам приводит к сложностям и дополнительным расходам при реализации необходимых сценариев работы системы. В некоторых случаях – к полной невозможности корректного функционирования.
Микросервисы могут стоить дорого
В зависимости от проекта, стоимость развертывания всей инфраструктуры для нескольких сервисов в нескольких конфигурациях, проверка работоспособности, масштабируемости и т.д. варьируется от 100 тысяч до 1 миллиона рублей.
В большинстве случаев, крупные вложения в разработку инфраструктуры, особенно на начальном этапе, не оправданы. Неизвестно, будет ли система работать сколько-нибудь продолжительное время, сколько пользователей к нам придет или проект будет закрыт из-за несостоятельности бизнес-идеи.
Продуманная архитектура – залог успеха
Останется ли заказчик или пользователь доволен работой системы зависит от того, насколько грамотно она спроектирована. Описание и разделение предметной области является ключевым моментом в планировании архитектуры. Даже при разработке традиционных монолитных систем это крайне важно. При построении микросервисов неправильное разделение на слабо связанные контексты приведет к нежелательным последствиям: очень сложному внесению изменений, невозможности отследить и проверить все сценарии поведения. Ошибки будут возникать и нарастать как снежный ком в самых неожиданных местах. Чтобы этого избежать, необходимо понять работу будущеей системы на всех уровнях, кто и как будет ей пользоваться. Поэтому крайне важно задуматься об архитектурном проектировании как можно раньше.
Пример
Скажем, мы делаем интернет-магазин. Для того, чтобы спроектировать положительный пользовательский опыт, необходимо позаботиться о комплектации заказа, удобной оплате и оформлении доставки. Для этого разрабатывается удобный интерфейс комплектовщика, производится интеграция с платежной системой и системой складской логистики. Крайне важно понимать хотя бы основные бизнес-процессы, в которых будет принимать участие наш программный продукт, т.е. бизнес-архитектуру. В противном случае мы можем упустить очень большую часть потребностей. В свою очередь это приведет к ситуации, когда все вроде сделали правильно, но пользоваться продуктом нельзя. И, как следствие, нужны доработки. А это расходы, которые можно было избежать.
Не редкость в таких ситуациях отсутствие средств: они тратятся на функции не столь критичные на данном этапе. Не проработав до конца все взаимодействия и интеграции, мы можем начать делать дорогие и бессмысленные вещи. Например, CRM-систему в то время как у заказчика нет особенных требований, и можно использовать готовое решение “из коробки”. И только проработав и поняв окружение, можно обоснованно принимать решение о выборе программной архитектуры, разделения системы на слабосвязанные контексты и т.д.
Так с чего начать?
На практике, для выработки таких архитектурных решений хорошо помогает одно- или двухдневный воркшоп, на котором прорабатывается в том числе и низкодетализированный дизайн, и основные сценарии использования. В случае со стартапами, чрезвычайно важным шагом является проработка Business Canvas вместе с подрядчиком для того, чтобы все стороны оценили жизнеспособность идеи. Одним из результатов воркшопа будет являться документ, описывающий нефункциональные требования к разрабатываемой системе.

Не исключено, что результатом может быть и закрытие проекта сразу после воркшопа. Он поможет стейкхолдерам увидеть несостоятельность бизнес-замысла, сэкономив время и деньги на технической реализации. Как бы странно ни звучало, даже в таких ситуация очень сильно возрастает доверие между участниками проекта.
Резонный вопрос, надо ли это делать и дорого ли это? Отвечаем: стоимость воркшопа примерно равна стоимости двухдневной работы организованной команды. Исправление ошибок и доработки однозначно обойдутся заказчику намного дороже, чем такой воркшоп.
Стоит помнить о том, что с течением времени бизнес-идея может измениться из-за внешних обстоятельств. К примеру, сейчас происходят очень большие изменения в работе практически любого бизнеса. Мы точно знаем, что мир уже не будет таким, как раньше (привет, COVID-19!). Нужно быть готовым к переменам и адаптировать свою бизнес-модель и продукт под новые реалии рынка.
Как мы упоминало выше, такая гибкость закладывается адекватной архитектурой и использованием проверенных практик. Например, применение шаблона CQRS на логическом уровне архитектуры приложения (о том, что это такое, расскажем чуть ниже) в начале разработки позволит впоследствии выделять физические сервисы при развитии бизнеса и продукта.
Переиспользование написанной функциональности станет возможным за счет разделения на независимые компоненты. При этом, если не пытаться сразу разделить сервисы физически, то команда сэкономит деньги на мониторинге, реализации распределенной транзакционной работы и на других зависимостях, которые пока еще не нужны для текущего уровня развития продукта.
CQRS на уровне логической архитектуры
Коротко о CQRS
CQRS (Command-Query Responsibility Segregation — разделение команд и запросов) — популярный и широко применяемый шаблон проектирования архитектуры программ. Его суть заключается в том, что все методы (т. е. определенные наборы действий) в программе могут быть двух типов. Это либо запросы, после выполнения которых состояние программы не изменяется, либо команды, которые состояние изменяют. Другими словами, запросы могут выполнять лишь чтение данных, в то время как команды обязательно включают в себя запись.

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

Если соблюдать базовые принципы SOLID, а именно Dependency Inversion и Dependency Injection, то при хорошей настройке контейнеров инверсии управления все команды и запросы становятся небольшими кусочками, которые можно переиспользовать в разных частях вашей системы. Так и было в этом случае, что однозначно дало положительный результат.
На схеме видно 3 части системы, которые вполне могут иметь схожую работу с моделью:
- Взаимодействие офисного приложения с моделью (Admin office Desktop App)
- Взаимодействие приложения механика с моделью (Ship Desktop Mechanic app)
- Взаимодействие сервиса синхронизации с моделью (SyncService)
Мы заметили, что люди, которые используют приложение в офисе, намного чаще читают данные с кораблей, нежели заполняют. При этом пользователи на кораблях в Offline-зоне наоборот чаще вводят данные в приложение, чем читают. Что это дает? Ценность заключается в том, что это отличная база для масштабирования системы. Если возникнет необходимость, то выделить окружающие контексты из кусочков запросов в одни физические сервисы с оптимизированной под чтение базой данных, а контексты из команд – в другие сервисы будет намного легче. Всё это уже будет зависеть от бизнес-задач и от направлений развития архитектуры.
Примеры того, как внедрить такое разделение у себя в проекте
Ниже я привожу пару основных классов на языке C#, которые можно просто переиспользовать в своих решениях.
/// <summary> /// Универсальная фабрика для создания запросов. /// Должна быть реализована в Composition Root(web api проект, главный проект сайта и т.д.) /// </summary> public interface IHandlersFactory { IQueryHandler<TQuery, TResult> CreateQueryHandler<TQuery, TResult>(); IAsyncQueryHandler<TQuery, TResult> CreateAsyncQueryHandler<TQuery, TResult>(); ICommandHandler<TCommand> CreateCommandHandler<TCommand>(); IAsyncCommandHandler<TCommand> CreateAsyncCommandHandler<TCommand>(); } /// <summary> /// Базовый интерфейс для выполнения команды /// </summary> public interface ICommandHandler<TCommand> { void Execute(TCommand command); } /// <summary> /// Базовый интерфейс для выполнения запроса /// </summary> public interface IQueryHandler<TQuery, TResult> { TResult Execute(TQuery query); } /// <summary> /// Фабрика для Ninject, создающая типизированные команды и запросы /// </summary> public class NinjectFactory : IHandlersFactory { private readonly IResolutionRoot _resolutionRoot; public NinjectFactory(IResolutionRoot resolutionRoot) { _resolutionRoot = resolutionRoot; } public IAsyncCommandHandler<TCommand> CreateAsyncCommandHandler<TCommand>() { return _resolutionRoot.Get<IAsyncCommandHandler<TCommand>>(); } public IAsyncQueryHandler<TQuery, TResult> CreateAsyncQueryHandler<TQuery, TResult>() { return _resolutionRoot.Get<IAsyncQueryHandler<TQuery, TResult>>(); } public ICommandHandler<TCommand> CreateCommandHandler<TCommand>() { return _resolutionRoot.Get<ICommandHandler<TCommand>>(); } public IQueryHandler<TQuery, TResult> CreateQueryHandler<TQuery, TResult>() { return _resolutionRoot.Get<IQueryHandler<TQuery, TResult>>(); } }
Пример Биндинга запросов через Ninject
public override void Load() { // queries Bind<IQueryHandler<GetCertificateByIdQuery, Certificate>>().To<GetCertificateByIdQueryHandler>(); Bind<IQueryHandler<GetCertificatesQuery, List<Certificate>>>().To<GetCertificatesQueryHandler>(); Bind<IQueryHandler<GetCertificateByShipQuery, List<Certificate>>>().To<GetCertificateByShipQueryHandler>(); …………. }
После инъекции IHandlerFactory в ваш класс вы получаете возможность использовать свои команды и запросы следующим образом:
Пример выполнения запроса: Ship ship = mHandlersFactory.CreateQueryHandler<GetShipByIdQuery, Ship>().Execute(new GetShipByIdQuery(id)); Пример выполнения команды: mHandlersFactory.CreateCommandHandler<DeleteReportCommand>() .Execute(new DeleteReportCommand(report));
Но, конечно, всё надо применять ситуативно и с умом. Для этого и нужно разработать архитектуру. Другой момент, что не надо её продумывать на 300 шагов вперёд. Она должна ситуативно развиваться вместе с продуктом и давать ответы минимум на 3 вопроса:
- Почему структура моего ИТ-продукта именно такая? Почему в этом месте надо реализовывать именно так?
- Если я придумаю крутую реализацию, то как она ляжет на общую картину системы?
- Каким образом будут соблюдаться нефункциональные требования системы в целом?
Также очень важным этапом является момент выделения Bounded Context в системе. Данная практика хорошо помогает изобразить структуру бизнеса заказчика, найти с ним общий язык и управлять регрессией в продукте. Но об этом уже следующая статья.
Преимущества CQRS на уровне логической архитектуры
Базовая архитектура рассмотренного приложения была выстроена с соблюдением разных принципов и шаблонов проектирования: MVVM, SOLID, CQRS и т.д. Это позволило переиспользовать функциональность фич для разных клиентов приложения. При этом, внедрение не занимало много времени и было достаточно недорогим.
Реализация данного подхода не потребовала дополнительных затрат, так как при старте разработки у команды были уже наработанные классы и один уровень понимания архитектуры приложения. В последующих доработках этот подход полностью себя оправдал: порядка 40% функциональности можно было гибко переиспользовать. При таком подходе заказчик существенно сократил затраты на реализацию функций, определённые части которых дублируются для разных клиентов приложения. Мы оценили экономию средств в 30-40%.
Напоследок
Agile-подход к разработке ошибочно трактуется как “не надо ничего проектировать заранее, надо ввязаться в бой, а там война – план покажет”. А если нам будет не хватать скорости работы или скорости изменения программы, мы зарядим серебряную пулю – микросервисы. Ведь на всех конференциях рассказывают, что микросервисы – это просто, и они решают сразу все проблемы. Такой оптимизм, как правило, ведет к потере денег и неработающему продукту. С другой стороны, проектировать все заранее в нашем быстро меняющемся мире практически невозможно. Как всегда надо находить баланс.
Во-первых, проработка архитектуры позволяет осознанно выбрать подходящее решение на текущем этапе и в итоге сэкономить денег на разработку.
Во-вторых, документирование архитектурных решений позволит в дальнейшем понять, почему именно такие решения были приняты. Более того, такой документ экономит время команды, исключая лишние обсуждения.
В-третьих, периодическая валидация изменившихся условий позволяет снизить риски отказов в работе продукта или системы и принять взвешенное решение о необходимости доработки или изменения.
Таким образом, следование грамотным практикам непосредственно при написании кода позволяет получить хороший фундамент, на основании которого можно будет масштабировать решение в дальнейшем.
Doroteya Ker O'Conner
Un buen blog! Voy a marcar unos pocos de estos .. Doroteya Ker O’Conner
Aleksey Sergeevich Palatkin
Спасибо вам за спам))