Разбираемся в проектировании микросервисов. Основные паттерны (Часть 1)
Привет, Хабр!
Меня зовут Сергей Громов, я работаю в IT уже 30 лет. Прошел путь от программиста на Assembler и преподавателя до ведущего системного аналитика. Последние 17 лет занимаюсь, собственно, аналитикой, специализируюсь на системном анализе, участвую в сложных проектах как аналитик и архитектор, а также разрабатываю обучающие курсы для молодых специалистов.
В этой статье мы разберемся с некоторыми основами проектирования микросервисов. Моя цель — объяснить принципы проектирования начинающим и тем, кто работает с уже готовой микросервисной архитектурой. Порой новичкам архитектура сервиса кажется неочевидной. На самом деле, все не так страшно. Есть определенные принципы, которые помогают в этом разобраться.
Сейчас мы будем исходить из того, что микросервисная архитектура — это уже решенный вопрос. Наша задача — понять, как эти микросервисы устроены, и научиться проектировать их самостоятельно. Это поможет при знакомстве с новым проектом разобраться, что к чему. Мы выясним, как анализировать структуру сервиса, чтобы понимать логику построения. Давайте поговорим сегодня о паттернах.
Итак, паттерны проектирования микросервисов. Их основная цель — дать нам проверенные решения для задач, которые возникают при разработке микросервисной архитектуры. Они помогают организовать взаимодействие микросервисов друг с другом и с клиентскими приложениями, спроектировать работу с базами данных и обеспечить отказоустойчивость системы.
Паттерны декомпозиции на микросервисы
Как разделить приложение на микросервисы? Рассмотрим два основных подхода:
Шаблон «Разбиение по бизнес-возможностям» (Decompose By Business Capability). Суть в том, чтобы выделить главные бизнес-возможности приложения и создать отдельный микросервис для каждой из них. Бизнес-возможности — это функции, доступные пользователям при работе с приложением. Например, такими возможностями могут быть «Управление заявкой на кредит», «Управление продуктами», «Управление платежами» и т.д. Каждый из этих блоков можно реализовать в виде отдельного микросервиса.
Шаблон «Разбиение по поддоменам» (Domain-Driven Design, DDD). DDD предлагает другой подход к декомпозиции. Вся предметная область (домен) разбивается на поддомены. У каждого поддомена своя модель данных, ее область действия называется ограниченным контекстом (Bounded Context). Микросервисы разрабатываются внутри этих ограниченных контекстов. Главная задача при использовании DDD — правильно определить поддомены и границы между ними, так мы обеспечим максимальную независимость и избежим сильной связанности между сервисами. Соответственно, разрабатывать и поддерживать все это нам будет проще.
После того, как мы определились с принципами декомпозиции, надо разобраться, как перейти к микросервисной архитектуре из существующей системы. Особенно это актуально при работе с монолитными приложениями. Тут нам помогут специальные паттерны рефакторинга.
Паттерны рефакторинга для перехода на микросервисы
Шаблон «Душитель» (Strangler). Он поможет постепенно вытеснить монолитное приложение, заменив его функциональность новыми микросервисами, которые разрабатываются и разворачиваются параллельно с существующим. Постепенно они берут на себя все больше и больше функций, а монолит со временем атрофируется и удаляется. Там мы уменьшим риски, связанные с резким переходом на новую архитектуру, и сможем поэтапно внедрить микросервисы. Но важно правильно определить границы между микросервисами (используем принципы DDD и разбиения по бизнес-возможностям), чтобы обеспечить их независимость и эффективность.
Шаблон «Уровень защиты от повреждений» (Anti-Corruption Layer). Этот паттерн нужен, чтобы изолировать различные подсистемы от влияния новой микросервисной архитектуры. Между подсистемами появляется промежуточный слой — уровень защиты от повреждений. Он преобразует данные и запросы между ними. В итоге одна система остается неизменной, а другой системе не надо адаптироваться к устаревшему коду, она может использовать современные технологии и подходы. Этот шаблон действует как переводчик между ними.
Что мы теперь знаем? Декомпозиция приложения и стратегия миграции — это важно. Но не менее важно правильно организовать управление данными в микросервисной архитектуре. От того, как микросервисы взаимодействуют с данными, зависит их автономность, производительность и масштабируемость.
Паттерны управления данными в микросервисной архитектуре
Шаблон «База данных на сервис» (Database Per Service). Этот паттерн предлагает дать каждому микросервису свое собственное хранилище данных. Так мы сможем избежать сильных зависимостей между сервисами на уровне данных и обеспечить их автономность. Обращаю внимание, что речь идет о логическом разделении данных. Физически микросервисы могут использовать одну и ту же базу данных, но в ней они должны работать с отдельными схемами, коллекциями или таблицами. Этот шаблон дает большую автономность микросервисам и уменьшает связь между командами разработки, которые занимаются отдельными сервисами. Например, сервис управления клиентами получит доступ только к таблице clients, а сервис управления товарами — только к таблице products, даже если физически обе таблицы лежат в одной базе.
Шаблон «Разделяемая база данных» (Shared Database). По сути, это антипаттерн, он разрешает использовать одну базу данных для нескольких микросервисов. Это, конечно, противоречит принципу автономности микросервисов и может привести к сильной связанности между ними. Такой шаблон подойдет только на начальных этапах миграции или в очень маленьких проектах, где все микросервисы (не больше 2–3 штук) делает одна команда. Но проект растет, микросервисов становится больше, и тогда использовать разделяемую базу уже проблематично, это может снизить производительность, усложнить разработку и масштабирование. И в результате мы получим не микросервисную архитектуру, а распределенный монолит. Поэтому в большинстве случаев нужно стремиться к паттерну «База данных на сервис», он даст максимальную автономность и гибкость микросервисной архитектуры.
Шаблон «API-композиция» (API Composition). Этот паттерн для того, чтобы получать данные из нескольких микросервисов, у каждого из которых своя база данных (вспоминаем паттерн Database Per Service). API-композиция предполагает отдельный API, который обращается к необходимым микросервисам, получает от них данные, собирает их и обрабатывает. Кстати, этот паттерн можно рассматривать как частный случай использования паттерна API Gateway. API-композиция позволяет клиентским приложениям получать необходимые данные из разных источников через единую точку входа. Соответственно, упрощается разработка клиентских приложений и повышается эффективность работы с данными.
Шаблон «Разделение команд и запросов» (Command Query Responsibility Segregation, CQRS). Иногда бывает важно обеспечить высокую производительность базы данных как на чтение, так и на изменение данных, а сделать это очень сложно. Тут предлагается разделить операции изменения данных (Command) и операции чтения данных (Query). CQRS существует в двух формах: простой и расширенной. В простой форме CQRS используются различные модели ORM (Object-Relational Mapping) для чтения и записи данных, но при этом сохраняется общее хранилище.
Шаблон «Выполнение распределенных транзакций» (Saga). Этот паттерн предлагает надежный способ управления распределенными транзакциями, обеспечивая согласованность данных без нарушения автономности сервисов. Здесь каждая локальная транзакция обновляет данные в хранилище отдельного микросервиса и публикует событие или сообщение. Это событие запускает следующую локальную транзакцию в цепочке. Если одна из локальных транзакций завершается с ошибкой, запускается серия компенсирующих транзакций, которые отменяют изменения, внесенные предыдущими транзакциями.
Есть два подхода к реализации Saga:
«Оркестровка» (Orchestration). Это централизованная координация, при которой отдельный компонент (оркестратор) сообщает микросервисам, какие действия необходимо выполнить.
«Хореография» (Choreography): А это децентрализованная координация, при которой каждый микросервис «подслушивает» события или сообщения других микросервисов и решает, какие действия надо предпринять.
И в заключение стоит помнить главное: универсального рецепта нет, все зависит от специфики проекта и его требований, а порой выбор паттерна — это процесс очень творческий. При этом от его правильности зависит успех всего проекта. Описанные выше паттерны — отличная отправная точка для проектирования эффективных и надежных микросервисных систем.
Что ж, надеюсь, эта статья была вам полезна. Другие популярные паттерны разберем во 2й части. Спасибо за внимание!