Привет, Хабр!
Меня зовут Сергей Громов, я работаю в 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й части. Спасибо за внимание!
Комментарии (8)
Fardeadok
05.12.2024 18:11Очередной список паттернов) Ответьте: проверка данных должна быть до микросервиса или внутри?
mrobespierre
05.12.2024 18:11Начиналось всё хорошо, а потом сага - паттерн (а не антипаттерн). Если мы вовлекаем 2-3-5-10 микросервисов в одну бизнес-операцию, это значит мы неверно их разделили. Это примерно как держать фамилию клиента в одном постгресе, его отчество в другом, а фамилию в третьем. Разумеется, если человек решит поменять ФИО (всё сразу) нам нужна будет сага. Но в действительности нам лучше бы держать изменяемые вместе данные - вместе, ваш кэп.
grom62 Автор
05.12.2024 18:11Если вы считаете, что нужно держать изменяемые данные вместе, то ваш путь - монолит. В MSA так не работает, держать все данные в одном месте нонсенс, например, я не смогу при проведении банковского платежа (одна бизнес операция) смешать все в кучу, уменьшение лимитов, расчет комиссии, хранение истории. И т.к. это разные сервисы, то сразу возникает распределенная транзакция и проблема согласованности данных. А как ее решать, сага или не сага, тут уж каждый решает сам, не нравится сага, придумываем свое решие, и возможно потом появится новый потерн.
kivan_mih
05.12.2024 18:11Опыт автора и академический тон статьи должны как будто убедить нас, что всё написанное нужно всем. Пользуйтесь и всё у вас будет хорошо. Если прочитав эту статью вы уже идёте душить своего монолита и разделять базу данных, не торопитесь.
Для начала то о чём забыл написать автор (преклонный возраст или нежелание портить благостную картинку) - КАЖДЫЙ пункт этого набора имеет свои недостатки. Например. Вот, руководствуясь Decompose By Business Capability и Database Per Service вы вынесли покупки пользователей в одну базу, а профили пользователей в другую. Вдруг к вам прибегает окрыленное руководство и говорит - ребята срочно, у нас есть бизнес идея, для проверки нам нужно сгруппировать покупки по городам пользователей и посчитать их количество. Адрес пользователя в базе профилей, покупки в базе покупок, простой join не сделать. Как быть? В этот момент обычно приходят умные дяди с 30 летним опытом и говорят, что нам нужно data strategy, data warehouse, etl и т.д. Получается, что для того, чтобы сделать простой аналитический join в мире микросервисов вам теперь нужно иметь дополнительный тяжелый, дорогостоящий, требующий обслуживания слой. Иногда это неизбежно, но мне кажется в 90% случаев этого можно избежать.
Или же паттерн Сага. Вот вы делаете транзакцию и просходит ошибка, нужно откатиться. Ничего страшного, говорит Сергей Громов, достаточно запустить серию(sic!) компенсирующих транзакций и все вернется на свои места. Но вот в этой серии из 10 компенсирующих транзакций, восьмая упала с эксепшеном. Что теперь? Компенсируем компенсирующие транзакции? Или просто забиваем, оставляем неконсистентный стейт и называем это progressive eventual consistency?
Я уже не говорю про курьезы и перегибы, когда на 15 человек команды в компании написана сотня микросервисов. Time2market и delivery speed падают практически до 0.
Сергей как мне кажется начал не с того. Главный вопрос современности, не как удушить ваш монолит, а в каком случае вам вообще стоит использовать микросервисы и для чего они нужны. Из своего 18 летнего опыта могу сказать, что единственное, когда микросервисы действительно помогают, а не мешают - это когда у вас МНОГО разработчиков. Как в Гугле, Яндексе и прочих Майкрософтах.
Если же у вас весь отдел разработки до 50 человек, я бы порекомендовал использовать модульный монолит и сэкономить огромное количество времени и сил. Как справедливо замечает Сергей, в случае необходимости потом его всегда можно "удушить".
grom62 Автор
05.12.2024 18:11Я с вами практически полностью согласен (к сожаления не смог вас идентифицировать). И действительно "Главный вопрос современности, не как удушить ваш монолит, а в каком случае вам вообще стоит использовать микросервисы и для чего они нужны ", но данная статья ни в коем случае не была направлена на выбор архитектур, да и рассчитана немного на другую аудиторию, на тех молодых специалистов кому (бородатые дядьки) сказали "делаем MSA", и тут зачастую начинается изобретение велосипеда.
А по поводу выбора архитектуры, особенно MSA, на эту тему надо делать отдельную статью, но размещать ее надо будет в разделе "Философия" т.к. тут сколько люде, столько и мнений. Я сам считаю, что к выбору MSA надо подходить крайне осторожно и продуманно, т.к. недостатков и сложностей у этой архитектуры очень много. Скажу больше, лично я считаю, что часть того, что принято считать достоинствами MSA, является ее недостатками. :-) И зачастую переход на микросервисы продиктован "модой", а не реальной необходимостью.
Но повторюсь это отдельная тема. И нам все равно не никуда не деться от микросервисов (по крайней мере в ближайшем будущем) и значит нужно уметь их проектировать и понимать как они работают.
gerashenko
05.12.2024 18:11А нельзя для аналитики взять ещё один сервис и дать ему доступ к репликам нужных бд? И сервис отдельный и статистика тянется с бд которых не жалко.
cmyser
05.12.2024 18:11Статья - краткая выжимка из книги Сэма "Создание микросервисов" ( и откровенно говоря бесполезная )
vic_1
Есть прекрасный сайт где все это собрано https://microservices.io/index.html и книга от того же перца
Microservices Patterns: With Examples in Java есть вроде и на русском, от неё мне спать не хотелось, увлекательное чтиво