При разработке веб-приложений приходится принимать ряд непростых решений. Одно из них — правильный выбор архитектуры проекта. От этого решения будет зависеть скорость разработки, возможность расширения функциональности, бесперебойность работы приложений. В этом материале рассмотрим монолитный и микросервисный подходы к архитектуре, поможем разобраться в их уникальных достоинствах и ограничения, и обсудим стратегии перехода от монолита к микросерисам.
Что такое монолитная архитектура?
Монолитная архитектура представляет собой подход, при котором все составляющие программы объединены в единый комплекс. Все функциональные элементы, включая логику бизнес-процессов и механизмы взаимодействия с базами данных, интегрированы в рамках одного приложения. Процессы разработки, внедрения и масштабирования осуществляются для всей системы как единого целого, без разделения на отдельные компоненты.
Основные характеристики монолитной архитектуры
- Единая кодовая база: вся функциональность приложения реализована в одном проекте или репозитории;
- Общее развертывание: все компоненты приложения развертываются и запускаются как единое целое;
- Сложность изменений: внесение изменений в одну часть системы может затронуть другие части, что требует тщательного тестирования;
- Трудности масштабирования: масштабирование отдельных частей системы может быть затруднено, так как приходится масштабировать всё приложение целиком.
Преимущества монолитной архитектуры
- Простота разработки и тестирования: начальные этапы разработки могут быть проще, так как все компоненты находятся в одном месте;
- Простота развертывания: одно приложение легко развертывать, так как нет необходимости координировать развертывание нескольких сервисов;
- Единая модель данных: единый доступ к данным упрощает работу с базой данных и бизнес-логикой.
Недостатки монолитной архитектуры
- Сложность масштабирования: трудно масштабировать отдельные компоненты, что может приводить к избыточному использованию ресурсов;
- Ограниченная гибкость: изменения в одной части системы могут требовать модификаций в других частях, что снижает гибкость разработки;
- Длительное развертывание: внедрение новых версий может занимать больше времени, так как нужно развернуть все приложение целиком.
Что такое микросервисная архитектура?
Микросервисная архитектура — это концепция, в которой программное решение разбивается на автономные компоненты, или микросервисы. Каждый из них, выполняет специфическую задачу и может проходить этапы разработки, внедрения и расширения вне зависимости от остальных элементов системы. Взаимодействие между микросервисами осуществляется посредством четко регламентированных протоколов обмена данными, например API.
Ключевые аспекты микросервисной архитектуры
- Сегментация: приложение структурировано как набор самостоятельных сервисов, каждый из которых несет ответственность за конкретный функциональный блок;
- Автономность развертывания: каждый сервис может быть запущен в эксплуатацию или модернизирован без воздействия на функционирование других компонентов;
- Эффективность масштабирования: возможность независимого расширения каждого сервиса способствует оптимальному распределению вычислительных мощностей;
- Технологическая гибкость: для реализации каждого сервиса могут применяться наиболее подходящие инструменты и языки программирования.
Преимущества использования микросервисной архитектуры
- Улучшенная масштабируемость: возможность точечного расширения отдельных сервисов в соответствии с их текущей нагрузкой;
- Повышенная адаптивность разработки: различные команды специалистов могут параллельно работать над разными сервисами, используя оптимальные для каждой задачи технологические решения;
- Устойчивость: сбой одного сервиса не приводит к отказу всего приложения.
Недостатки микросервисной архитектуры:
- Сложность управления: координация между множеством сервисов требует дополнительных усилий и инструментов;
- Повышенные требования к инфраструктуре: необходимость управлять развертыванием и мониторингом множества сервисов увеличивает сложность инфраструктуры;
- Сложность тестирования: интеграционное тестирование микросервисов может быть более сложным по сравнению с монолитной архитектурой;
- Дополнительная нагрузка на сетевые ресурсы: для общения между микросервисами используются различные инструменты, которые используют сетевые ресурсы.
Ниже подробнее рассмотрим, почему и когда стоит переходить на микросервисную архитектуру, как подготовиться к этому процессу и какие шаги следует предпринять для успешного разделения монолита на микросервисы.
Когда следует разделять монолит на микросервисы?
Разделение монолитного приложения на микросервисы — это серьезное решение, которое требует тщательной оценки и планирования. Несмотря на множество преимуществ, переход на микросервисную архитектуру также связан с определенными рисками и вызовами. Ниже рассмотрим основные показатели, которые указывают на необходимость смены архитектуры, а также риски и вызовы, связанные с этим процессом.
Затруднения с расширением возможностей
В рамках монолитной структуры расширение возможностей приложения возможно только в целом, что зачастую приводит к нерациональному использованию ресурсов. К примеру, если повышенную нагрузку испытывает лишь один компонент приложения, приходится масштабировать всю монолитную систему, что может оказаться затратным и неэффективным решением.
Сложности в обеспечение отказоустойчивости
Для многих приложений критически важно поддержание высокой доступности и устойчивости к сбоям. В монолитной архитектуре отказ одного компонента системы может привести к выходу из строя всего приложения, что недопустимо для критически важных систем.
Снижение темпов разработки и внедрения
Расширение монолитного приложения усложняет сложность его кодовой базы, что замедляет скорость поставки функциональности. Любые модификации в коде требуют тщательного тестирования, поскольку даже незначительные изменения могут оказать влияние на другие компоненты приложения.
Все эти проблемы обещает решить «распиливание» монолита на микросервисы, но какой ценой? Давайте разбираемся.
Риски и вызовы перехода
Смена архитектуры приложения — это всегда ответственный шаг, к которому нужно подготовиться, заранее оценив все потенциальные проблемы и определив план по из решению. Давайте рассмотрим основные.
Увеличение сложности системы
Микросервисы значительно увеличивают общую сложность системы. Вместо одного приложения необходимо управлять множеством сервисов, каждый из которых имеет свои зависимости, конфигурации и требования к развертыванию. Чтобы избежать проблему, следует четко определить границы ответственности и взаимодействия между сервисами, а также внедрить инструменты оркестрации контейнеров (например, Kubernetes) для автоматизации развертывания и управления микросервисами.
Кросскомандная координация
Как правило над отдельными микросервисами работают разные команды. Это может привести к проблемам в коммуникации и управлении проектом, особенно в крупных организациях. Здесь поможет организация кросс-функциональных команд, которые отвечают за полный жизненный цикл микросервиса — от разработки до эксплуатации.
Изменение процессов разработки и развертывания
Переход на микросервисную архитектуру требует значительных изменений в процессах разработки, тестирования и развертывания. Традиционные подходы могут оказаться неэффективными в новых условиях. Необходимо внедрение CI/CD (Continuous Integration/Continuous Deployment) для автоматизации процессов разработки и развертывания и использование инструментов для автоматического тестирования и мониторинга микросервисов.
Технические аспекты реализации
Одной из ключевых задач при переходе на микросервисную архитектуру является организация взаимодействия между различными сервисами. Для этого используются несколько подходов и технологий. Ниже рассмотрим самые распространенные.
Использование API Gateway
API Gateway выступает в роли единой точки входа для всех внешних запросов к микросервисам. Шлюз обеспечивает маршрутизацию запросов к соответствующим сервисам, агрегацию данных и выполнение таких задач, как авторизация и ограничение доступа. К преимуществам подхода можно отнести централизованное управление доступом и безопасностью, снижение количества запросов к отдельным сервисам за счет агрегации данных и упрощение взаимодействия с клиентами через единый интерфейс. Среди популярных примеров реализации — Kong, NGINX, AWS API Gateway.
Для вертикального масштабирования микросервисного приложения можно создать собственный маршрутизатор запросов. Его основные функции — проверка состояния микросервисов и обработка случаев их недоступности. Такие балансировщики используют следующие алгоритмы:
- Round Robin. Распределяет запросы по кругу, отправляя каждый новый на следующий сервер в списке. После достижения последнего сервера цикл перезапускается;
- Least Connections. Направляет новый запрос на сервер с наименьшим количеством активных соединений;
- Weighted Round Robin. Улучшенная версия Round Robin, учитывающая вес каждого сервера. Серверы с большим весом получают больше запросов;
- Consistent Hashing. Минимизирует перераспределение запросов при изменении количества серверов. Серверы располагаются «по кольцу» согласно их хеш-сумме. Запросы направляются на сервер с ближайшим значением хеша.
Механизмы межсервисной коммуникации
1. RESTful API. Этот метод взаимодействия между микросервисами занимает лидирующие позиции благодаря своей доступности и повсеместному применению. В его основе лежит использование стандартных HTTP-операций (GET, POST, PUT, DELETE) для манипуляций с ресурсами.
2. gRPC. Фреймворк удаленного вызова процедур (RPC) создан специалистами Google. Базируется на протоколе HTTP/2 для передачи информации и использует Protocol Buffers для сериализации данных. gRPC обеспечивает скоростной и оптимизированный обмен информацией, что особенно ценно при взаимодействии микросервисов.
3. GraphQL. Cпециализированный язык запросов к API позволяет клиентским приложениям получать только необходимые им данные. Он эффективен при агрегации информации из разрозненных микросервисов и способен заменить множественные REST-запросы единым обращением GraphQL.
Реализация защиты и контроля доступа
В контексте микросервисной архитектуры вопросы безопасности и авторизации требуют пристального внимания ввиду наличия множества автономных сервисов.
Стратегии обеспечения безопасности включают токены JWT и протоколы OAuth2/OpenID Connect.
JWT (JSON Web Tokens) — специализированный JSON объект, который используется для хранения и передачи зашифрованной информации между различными сервисами, а также для авторизации пользователей приложения. Данный объект состоит из нескольких частей:
- Header: содержит алгоритм подписи и тип токена;
- Payload (полезная нагрузка), хранит в себе полезную информацию, по стандарту имеет несколько зарезервированных ключей, такие как iss (issuer), sub (subject), aud (audience), exp (expiration time), nbf (not before), iat (issued at), jti (JWT ID), однако они не являются обязательными;
- Signature — хеш для проверки подлинности токена.
Для проверки подлинности токена необходимо получить его header и payload, по определенному алгоритму получить хеш и сравнить его с signature.
JWT можно передавать несколькими способами:
- Bearer Token Authorization. Токен сохраняется в заголовке ‘Authorization’, данная передача широко поддерживается всеми видами браузеров, однако не гарантирует безопасность передачи данных;
- Cookie-Based Authorization. Токен хранится в cookie, что позволяет использовать механизмы защиты, такие как защита от XSS (Cross Site Scripting);
- Query Parameter Authorization. Токен передается в строке запроса, данный способ самый простой, однако наименее безопасный;
- Form Data Authorization. Информация передается в теле запроса в формате формы, хорошо подходит для форм POST-запросов, однако не рекомендуется для GET из-за риска утечки токена.
Следует помнить, что токен JWT легко дешифровать, и получить информацию, хранящуюся в payload токена, по этой причине лучше не записывать в токен любую значимую информацию о пользователе, или же использовать механизмы хеширования, однако в таком случае вам потребуется использовать хранилище для токенов. Данный механизм крайне широко используется в веб-приложениях, и является самым популярным решением для реализации защиты и контроля доступа.
OAuth 2.0 (Open Authorization) — это протокол авторизации, который позволяет сторонним приложениям получать ограниченный доступ к пользовательским ресурсам на веб-сервисах без необходимости раскрытия пользовательских учетных данных. Этот протокол широко используется для предоставления безопасного доступа к API и ресурсам через сторонние сервисы.
Основные понятия и участники:
- Resource Owner (Владелец ресурса): пользователь, который обладает ресурсами (например, данными или учетными записями), к которым требуется доступ;
- Client (Сервис клиент): приложение, запрашивающее доступ к ресурсам от имени пользователя;
- Authorization Server (Сервис авторизации): сервер, который аутентифицирует пользователя и предоставляет токены авторизации и доступа клиентам;
- Resource Server (Сервис ресурсов): сервер, на котором хранятся ресурсы (данные или учетные записи), и который проверяет токены доступа, чтобы решить, разрешить ли доступ к ресурсам.
При каждом запросе на сервис ресурсов, между микросервисами отправляется токен, который валидируется сервисом авторизации с помощью дополнительного запроса. Кроме самописного сервиса, авторизовать токен можно и с помощью сторонних приложений (Google, Яндекс).
Управление данными
Разделение ответственности команд и запросов (CQRS) — представляет собой архитектурный шаблон, который разграничивает процессы получения и модификации данных, распределяя их по отдельным моделям. Ключевая концепция заключается в обработке команд на извлечение и внесение информации различными микросервисами, что обеспечивает возможность независимой оптимизации и масштабирования каждого компонента системы.
Достоинства CQRS
- Повышенная производительность. Возможность автономной оптимизации процессов чтения и записи информации способствует повышению производительности каждой операции;
- Масштабируемость. Сегрегация моделей позволяет независимо наращивать мощности для операций чтения и записи. К примеру, можно развернуть несколько копий базы данных для обслуживания запросов на чтение;
- Гибкость. Дифференциация моделей открывает возможности для применения разнообразных технических решений и методологий при работе с данными;
- Упрощенная сложность. Система становится более структурированной, так как каждый компонент (чтение или запись) сфокусирован на решении конкретной задачи.
Ограничения CQRS
- Усложнение имплементации. Требует дополнительных инфраструктурных решений и может усложнить общую архитектуру системы.
- Проблемы синхронизации данных. Разделение моделей чтения и записи может привести к несогласованности информации. Необходима разработка механизмов для обеспечения синхронности данных между моделями.
Централизованный доступ всех сервисов к единой базе данных упрощает получение информации, однако это приводит к мгновенному отображению в пользовательском интерфейсе любых ошибок БД.
База данных для каждого сервиса — принцип разделение, при котором каждому микросервису выделяется собственное хранилище данных, что обеспечивает автономность и инкапсуляцию информационных ресурсов.
Преимущества:
- Усиленная изоляция информационных активов. Каждый микросервис оперирует своим локальным набором данных, минимизируя риски несанкционированного доступа или непреднамеренного влияния со стороны других компонентов системы;
- Гибкость в выборе оптимальных решений для хранения данных. Архитектура позволяет подобрать наиболее подходящую систему управления базами данных для каждого конкретного микросервиса, учитывая его специфические требования и характер обрабатываемой информации;
- Оптимизация процессов масштабирования информационных хранилищ. Возможность независимого расширения и оптимизации баз данных для отдельных сервисов в соответствии с их индивидуальными потребностями в производительности и объеме хранения.
Потенциальные сложности:
- Обеспечение согласованности данных. Возникают определенные трудности в поддержании целостности и актуальности информации across различными микросервисами, особенно в условиях распределенной архитектуры.
- Координация информационных потоков и управление транзакциями. Требуется разработка и внедрение механизмов для эффективной синхронизации данных между микросервисами, а также реализация надежных протоколов управления распределенными транзакциями для обеспечения целостности бизнес-процессов.
Событийно-ориентированная архитектура (Event-Driven Architecture) — подход, который позволяет микросервисам взаимодействовать через события, которые публикуются и подписываются другими сервисами. К преимуществам можно отнести асинхронное взаимодействие между сервисами, повышенную гибкость и масштабируемость, возможность легко интегрировать новые сервисы.
В данной архитектуре есть посредник, брокер сообщений (Kafka, RabbitMQ). Очередь выступает гарантом, что сообщения между производителем (producer) и потребителем (consumer) не потеряются вследствии каких либо ошибок, и всегда дойдут до получателя. Кроме того сам производитель может не дожидаться ответа от других сервисов, т к очередь выступает гарантом доставки сообщений, что позволяет незамедлительно освободить ресурсы сервиса, и направить их на решения других задач.
Управление транзакциями и консистентностью данных (Saga Pattern) — используется для управления распределенными транзакциями и обеспечения консистентности данных в микросервисной архитектуре из-за ограничений, накладываемых теоремой CAP. В этом паттерне каждая транзакция состоит из нескольких шагов, каждый из которых является локальной транзакцией. В случае неудачи выполняются компенсирующие операции для отмены частично выполненных транзакций.
Существует два вида этого паттерна:
- Оркестрованный. Вся цепочка управляется центральным микросервисом (оркестратором), который посылает сообщения остальным сервисам для выполнения локальных транзакций или отправляет сообщение о необходимости компенсирующих транзакций. Минус данного подхода заключается в том, что при сбоях в данном сервисе, вся цепочка перестанет работать.
- Хореографический. Микросервисы общаются между собой напрямую, каждый сервис реагирует на изменения и выполняет транзакции или компенсации. Плюсом данного подхода можно выделить повышенную гибкость, минус же - сложная логика взаимодействия и тестирования сервисов.
Паттерны разделения монолитного приложения на микросервисы
Дробление по бизнес-возможностям — разделение системы на модули или сервисы, отражающие основные бизнес-функции предметной области. Фокусируясь на бизнес функциях, которые поддерживают основные цели и задачи компании, мы выделяем независимые изолированные модули, которые могут взять в разработку небольшие команды разработчиков. Главным условием данного паттерна является определение четких границ взаимодействия между модулями, посредствам API или событийной системы. В качестве примера можно привести приложение банка, оказывающее, различные финансовые услуги: управление счетами, обработка платежей, кредитование и пр. Каждая услуга должна быть реализована отдельным модулем или их набором, что позволит гибко разрабатывать приложение.
«Душитель» — постепенное переписывание существующего функционала на микросервисы, новые же функции сразу реализуются в них. Данная парадигма доступена вследствии использования фасада, через который разрабатывается в первую очередь и проксирует через себя все запросы как в монолит, так и в микросервисы. В качестве примера можно реализовать Gateway микросервис, который будет играть данную роль. Вышеупомянутый паттерн решает самую важную проблему, а именно постепенный переход от монолита к микросервисам.
Итог
Основные шаги перехода
- Оценка существующей архитектуры. Выявление критических проблем и потенциальных зон оптимизации;
- Фрагментация монолита. Сегментация цельного приложения на автономные микросервисы с четко определенными функциональными обязанностями;
- Интеграция API Gateway. Формирование унифицированной точки доступа для координации взаимодействий между микросервисами;
- Автоматизация рабочих процессов. Внедрение CI/CD методологий для автоматизированного тестирования и развертывания;
- Усиление защиты. Разработка и применение комплексных мер по обеспечению безопасности и управлению доступом;
- Внедрение систем наблюдения. Настройка инструментария для мониторинга и централизованного сбора логов.
Достигнутые преимущества после миграции
- Адаптивное масштабирование. Возможность независимого расширения отдельных микросервисов в соответствии с их нагрузкой;
- Ускорение цикла разработки. Автономность микросервисов позволяет командам работать параллельно, ускоряя выпуск обновлений;
- Повышение отказоустойчивости. Локализация сбоев в рамках отдельных микросервисов минимизирует влияние на общую работоспособность системы.
Практические рекомендации для успешного перехода
- Придерживайтесь поэтапного подхода. Осуществляйте декомпозицию монолита на микросервисы постепенно, с тщательным тестированием каждого этапа;
- Обеспечьте совместимость версий. В процессе миграции гарантируйте, что новые микросервисы сохраняют способность взаимодействовать с существующими компонентами системы;
- Приоритизируйте безопасность. Разработайте и имплементируйте комплексные стратегии для обеспечения защищенного взаимодействия между микросервисами;
- Выбирайте специализированные инструменты. Внедряйте технологические решения, оптимально подходящие для конкретных задач микросервисной архитектуры.