Всем привет!
Поговорим про микросервисы. Статей будет несколько, от плюсов и минусов до саги и прочих паттернов.
Для начала что такое микросервис. Я дам определение в виде набора обязательных признаков. Обязательны все из них.
1) одна команда
2) отдельная кодовая база
3) отдельная БД
4) отдельный pipeline
5) отдельный релизный цикл
6) небольшой объем кода
7) взаимодействие с другими сервисами через межпроцессное API. REST, gRPC, GraphQL, Kafka - это то, что сейчас на слуху, на самом деле технологий больше.
Для начала рассмотрим плюсы:
1) один сервис разрабатывает одна команда. При этом одна команда может делать несколько приложений, но главное здесь - у одного приложения один хозяин, определяющий вектор его развития. Такие ключевые решения, как: язык, на котором написан код, используемые библиотеки, утилита для сборки и архитектура в конце концов) Проще договорится, меньше согласований и зависимостей от внешних людей или команд. Этого же результата помогает достичь отдельный релизный цикл. Хотя в случае enterprise микросервисов зависимостей не избежать и это боль( Еще одно замечание - микросервис может релизиться независимо при условии использования фича-тогглов https://martinfowler.com/articles/feature-toggles.html и поддержки совместимости API. Альтернатива фича тогглов - писать код так, чтобы его неготовый кусок невозможно было вызывать снаружи до момента полной готовности вас и смежников.
2) МИКРОсервис означает, что кода не должно быть много. Сколько тысяч строк - явно не миллион и не сто тысяч, скорее несколько тысяч. А раз кода не много - в него проще погрузится. Меньше кривая обучения для новых членов команды. А если ты в целом понимаешь как работает приложение - с большей вероятностью доработки и рефакторинг будут сохранять его исходную архитектуру или менять ее единообразно. Как результат сервис будет оставаться понятным и далее.
3) проект быстрее компилируется и запускается, быстрее прогоняются тесты - быстрее обратная связь, быстрее процесс разработки. Проще делать маленькие Pull Request, ревьюверы меньше матерятся, т.к. не видят 100+ файлов в Pull Request, проще слияния. Вообще писать код нужно так, чтобы merge конфликтов не было, но это идеал, к которому надо стремится)
4) DevOps инструменты и практики будут работать "из коробки". На проектах с сотнями тысяч строк pipeline будет падать. И падать достаточно часто. SonarQube не может определить дельту Pull Request и падает. Checkmarx нужно дорабатывать у вендора, чтобы не падал. Джобы сборки оптимизировать, параллелить, чтобы добиться приемлемого времени сборки. Интеграционные тесты падают, т.к. контекст, необходимый для поднятия приложения, постоянно растет. База становится такой сложной, что DBA наотрез отказываются от Liquibase и накатывают скрипты руками пошагово. Число интеграций такое, что набор примочных тестов запущенный автоматически с большой вероятностью упадет и встанет вопрос - а точно нам нужны автотесты? НТшники работают по ночам. Bitbucket дико тормозит, когда размер репозитория превышает 2 Тб, а это вполне реально - ведь хранится вся история изменений. Да и в принципе по статистике чем дольше сборка и deploy, тем больше вероятность, что на jenkins slave кончится место или память и сборка упадет. И это все - только примеры из моего опыта. А суть в следующем: инструменты DevOps имеют свои ограничения по объему данных и нагрузке, и нужно либо допиливать их напильником, либо не делать таких больших монолитов. Второй путь проще)
5) если микросервис не оправдал ожиданий - его можно выкинуть и переписать. Я не говорю, что это типовая практика, я говорю, что такая опция есть. Решение принимается на уровне команды\отдела\кластера, а не вице-президента по ИТ в случае монолита)
Резюмирую - если кода меньше, его проще выводить в ПРОМ, поддерживать и если надо выкинуть.
О минусах - в следующем посте.
#microservices #сравнение
Поговорим про микросервисы. Статей будет несколько, от плюсов и минусов до саги и прочих паттернов.
Для начала что такое микросервис. Я дам определение в виде набора обязательных признаков. Обязательны все из них.
1) одна команда
2) отдельная кодовая база
3) отдельная БД
4) отдельный pipeline
5) отдельный релизный цикл
6) небольшой объем кода
7) взаимодействие с другими сервисами через межпроцессное API. REST, gRPC, GraphQL, Kafka - это то, что сейчас на слуху, на самом деле технологий больше.
Для начала рассмотрим плюсы:
1) один сервис разрабатывает одна команда. При этом одна команда может делать несколько приложений, но главное здесь - у одного приложения один хозяин, определяющий вектор его развития. Такие ключевые решения, как: язык, на котором написан код, используемые библиотеки, утилита для сборки и архитектура в конце концов) Проще договорится, меньше согласований и зависимостей от внешних людей или команд. Этого же результата помогает достичь отдельный релизный цикл. Хотя в случае enterprise микросервисов зависимостей не избежать и это боль( Еще одно замечание - микросервис может релизиться независимо при условии использования фича-тогглов https://martinfowler.com/articles/feature-toggles.html и поддержки совместимости API. Альтернатива фича тогглов - писать код так, чтобы его неготовый кусок невозможно было вызывать снаружи до момента полной готовности вас и смежников.
2) МИКРОсервис означает, что кода не должно быть много. Сколько тысяч строк - явно не миллион и не сто тысяч, скорее несколько тысяч. А раз кода не много - в него проще погрузится. Меньше кривая обучения для новых членов команды. А если ты в целом понимаешь как работает приложение - с большей вероятностью доработки и рефакторинг будут сохранять его исходную архитектуру или менять ее единообразно. Как результат сервис будет оставаться понятным и далее.
3) проект быстрее компилируется и запускается, быстрее прогоняются тесты - быстрее обратная связь, быстрее процесс разработки. Проще делать маленькие Pull Request, ревьюверы меньше матерятся, т.к. не видят 100+ файлов в Pull Request, проще слияния. Вообще писать код нужно так, чтобы merge конфликтов не было, но это идеал, к которому надо стремится)
4) DevOps инструменты и практики будут работать "из коробки". На проектах с сотнями тысяч строк pipeline будет падать. И падать достаточно часто. SonarQube не может определить дельту Pull Request и падает. Checkmarx нужно дорабатывать у вендора, чтобы не падал. Джобы сборки оптимизировать, параллелить, чтобы добиться приемлемого времени сборки. Интеграционные тесты падают, т.к. контекст, необходимый для поднятия приложения, постоянно растет. База становится такой сложной, что DBA наотрез отказываются от Liquibase и накатывают скрипты руками пошагово. Число интеграций такое, что набор примочных тестов запущенный автоматически с большой вероятностью упадет и встанет вопрос - а точно нам нужны автотесты? НТшники работают по ночам. Bitbucket дико тормозит, когда размер репозитория превышает 2 Тб, а это вполне реально - ведь хранится вся история изменений. Да и в принципе по статистике чем дольше сборка и deploy, тем больше вероятность, что на jenkins slave кончится место или память и сборка упадет. И это все - только примеры из моего опыта. А суть в следующем: инструменты DevOps имеют свои ограничения по объему данных и нагрузке, и нужно либо допиливать их напильником, либо не делать таких больших монолитов. Второй путь проще)
5) если микросервис не оправдал ожиданий - его можно выкинуть и переписать. Я не говорю, что это типовая практика, я говорю, что такая опция есть. Решение принимается на уровне команды\отдела\кластера, а не вице-президента по ИТ в случае монолита)
Резюмирую - если кода меньше, его проще выводить в ПРОМ, поддерживать и если надо выкинуть.
О минусах - в следующем посте.
#microservices #сравнение
martinfowler.com
Feature Toggles (aka Feature Flags)
Feature Flags can be categorized into several buckets; manage each appropriately. Smart implementation can help constrain complexity.
Всем привет!
Пару заблуждений про микросервисы.
1) Облачные технологии (k8s) не равно микросервисам. Да, если вы начинаете проект с микросервисами с нуля и есть возможность развернуть его в облаке - так и нужно делать.
Облако упрощает развертывание и масштабирование микросервисов, добавляет отказоустойчивость. Но микросервисы - это архитектура. А k8s - технологии.
Аналогия - REST обычно используют с JSON, для того же Spring RestController это настройка по умолчанию. Но никто не мешает в REST API возвращать XML.
Как я писал ранее: по большому счету микросервис - это про сознательное ограничение объема кода\функционала и связанные с этим плюшки.
2) трушный микросервис не обязан реализовывать строго одну операцию. Как именно выделять микросервисы - отдельная тема. Базовые варианты: по функционалу, по архитектурному слою - например, слой хранения - или по требованиям к масштабированию.
Но в любом из этих случаев в API, которое выставляет сервис, может быть несколько логически связанных методов. Ключевое слове здесь - логически связанных.
Если я что-то упустил, есть вопросы или возражения - пишите.
#microservices
Пару заблуждений про микросервисы.
1) Облачные технологии (k8s) не равно микросервисам. Да, если вы начинаете проект с микросервисами с нуля и есть возможность развернуть его в облаке - так и нужно делать.
Облако упрощает развертывание и масштабирование микросервисов, добавляет отказоустойчивость. Но микросервисы - это архитектура. А k8s - технологии.
Аналогия - REST обычно используют с JSON, для того же Spring RestController это настройка по умолчанию. Но никто не мешает в REST API возвращать XML.
Как я писал ранее: по большому счету микросервис - это про сознательное ограничение объема кода\функционала и связанные с этим плюшки.
2) трушный микросервис не обязан реализовывать строго одну операцию. Как именно выделять микросервисы - отдельная тема. Базовые варианты: по функционалу, по архитектурному слою - например, слой хранения - или по требованиям к масштабированию.
Но в любом из этих случаев в API, которое выставляет сервис, может быть несколько логически связанных методов. Ключевое слове здесь - логически связанных.
Если я что-то упустил, есть вопросы или возражения - пишите.
#microservices
Всем привет!
И снова микросервисы.
Из предыдущих постов видно, что разработчикам и команде в целом проще работать с микросервисами. Компании, которой принадлежит продукт, тоже, т.к. снижаются риски. Неужели так все радужно? Не совсем)
И основные трудности ложатся на плечи сопровождения и третьей линии поддержки. Причина проста - увеличивается число единиц деплоймента, увеличивается число интеграций и, следовательно, зависимостей между сервисами. Сложнее разворачивать, т.к. надо учитывать зависимости, сложнее разбирать ошибки, т.к. недостаточно посмотреть логи одного приложения как это было с монолитом. Также увеличивается риск каскадных сбоев, когда из-за сбоя одного ключевого сервиса "подвисают" его входящие сетевые соединения, переполняются пулы потоков и все падает большое количество серверов.
Что тут можно сделать?
1) с увеличением числа единиц деплоймента сделать ничего нельзя, но упросить сам процесс деплоя позволяет k8s
2) проблему сложных зависимостей между микросервисами надо решать через поддержку совместимости и версионирование API и фича тогглы. Т.е. поставщик данных при любых изменениях версионирует API какое-то время сохраняя старую версию, а потребитель включает новую фичу для клиентов по рубильнику по готовности поставщика. Это позволит избежать синхронных внедрений нескольких сервисов. Синхронные внедрения - "зло", я об этом уже писал: https://t.me/javaKotlinDevOps/24 Решение звучит просто, а реализуется исходя из моего опыта сложно) Как его добиться? Только дисциплиной, серебряной пули нет. Ну и учитывая версионирование API при проектировании сервиса, а также достаточным набором модульных и приемочных тестов чтобы не поломать API. Как определить достаточность? Очень просто: если есть чувство уверенности, что раз тесты прошли, то API не сломали - значит набор достаточен))) Для того, чтобы потребитель и поставщик одинаково представляли себе новую версию API она обязательно должна храниться в системе контроля версий (VCS). А убедиться в совместимости позволяет технология Consumer Driven Contract (CDC) https://martinfowler.com/articles/consumerDrivenContracts.html Самые известные фреймворки ее реализующие - Spring Cloud Contract и Pact.
3) проблему разбора багов и инцидентов помогают решить централизованный сбор логов и метрик, а главное - распределенная трассировка. Это технология для отслеживания всей цепочки запросов, включая список сервисов, через который прошел запрос, тайминги и информацию о возникающих ошибках. Для трассировки есть стандарт - OpenTelemetry https://opentelemetry.io/docs/concepts/what-is-opentelemetry/. Вот неплохое видео, описывающее архитектуру трассировки в целом и сравнивающее две его самые известные реализации - Zipkin и Jaeger. https://youtu.be/6PiThk3QHWw?t=4191 См. вторую часть видео, ссылка указывает на нее. А вот статья, описывающая проблемы текущих решений - https://habr.com/ru/company/flant/blog/460365/
4) решить проблему каскадных сбоев позволяют ограниченные и согласованные таймауты и реализация circuit breakers https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker Согласованность таймаутов можно обеспечить в ручном режиме, а можно передачей значений таймаута в API от клиента к серверу. Надо сказать, что данные паттерны важны и для монолита, но для микросервисов из-за увеличения числа интеграций их сложнее контролировать. Паттерны можно реализовать на клиенте, а можно с помощью Service Mesh, например, Istio - https://istio.io/latest/docs/concepts/traffic-management/#network-resilience-and-testing Последний вариант дает возможность централизованного контроля за настройкой таймаутов и circuit breaker.
#microservices #сравнение
И снова микросервисы.
Из предыдущих постов видно, что разработчикам и команде в целом проще работать с микросервисами. Компании, которой принадлежит продукт, тоже, т.к. снижаются риски. Неужели так все радужно? Не совсем)
И основные трудности ложатся на плечи сопровождения и третьей линии поддержки. Причина проста - увеличивается число единиц деплоймента, увеличивается число интеграций и, следовательно, зависимостей между сервисами. Сложнее разворачивать, т.к. надо учитывать зависимости, сложнее разбирать ошибки, т.к. недостаточно посмотреть логи одного приложения как это было с монолитом. Также увеличивается риск каскадных сбоев, когда из-за сбоя одного ключевого сервиса "подвисают" его входящие сетевые соединения, переполняются пулы потоков и все падает большое количество серверов.
Что тут можно сделать?
1) с увеличением числа единиц деплоймента сделать ничего нельзя, но упросить сам процесс деплоя позволяет k8s
2) проблему сложных зависимостей между микросервисами надо решать через поддержку совместимости и версионирование API и фича тогглы. Т.е. поставщик данных при любых изменениях версионирует API какое-то время сохраняя старую версию, а потребитель включает новую фичу для клиентов по рубильнику по готовности поставщика. Это позволит избежать синхронных внедрений нескольких сервисов. Синхронные внедрения - "зло", я об этом уже писал: https://t.me/javaKotlinDevOps/24 Решение звучит просто, а реализуется исходя из моего опыта сложно) Как его добиться? Только дисциплиной, серебряной пули нет. Ну и учитывая версионирование API при проектировании сервиса, а также достаточным набором модульных и приемочных тестов чтобы не поломать API. Как определить достаточность? Очень просто: если есть чувство уверенности, что раз тесты прошли, то API не сломали - значит набор достаточен))) Для того, чтобы потребитель и поставщик одинаково представляли себе новую версию API она обязательно должна храниться в системе контроля версий (VCS). А убедиться в совместимости позволяет технология Consumer Driven Contract (CDC) https://martinfowler.com/articles/consumerDrivenContracts.html Самые известные фреймворки ее реализующие - Spring Cloud Contract и Pact.
3) проблему разбора багов и инцидентов помогают решить централизованный сбор логов и метрик, а главное - распределенная трассировка. Это технология для отслеживания всей цепочки запросов, включая список сервисов, через который прошел запрос, тайминги и информацию о возникающих ошибках. Для трассировки есть стандарт - OpenTelemetry https://opentelemetry.io/docs/concepts/what-is-opentelemetry/. Вот неплохое видео, описывающее архитектуру трассировки в целом и сравнивающее две его самые известные реализации - Zipkin и Jaeger. https://youtu.be/6PiThk3QHWw?t=4191 См. вторую часть видео, ссылка указывает на нее. А вот статья, описывающая проблемы текущих решений - https://habr.com/ru/company/flant/blog/460365/
4) решить проблему каскадных сбоев позволяют ограниченные и согласованные таймауты и реализация circuit breakers https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker Согласованность таймаутов можно обеспечить в ручном режиме, а можно передачей значений таймаута в API от клиента к серверу. Надо сказать, что данные паттерны важны и для монолита, но для микросервисов из-за увеличения числа интеграций их сложнее контролировать. Паттерны можно реализовать на клиенте, а можно с помощью Service Mesh, например, Istio - https://istio.io/latest/docs/concepts/traffic-management/#network-resilience-and-testing Последний вариант дает возможность централизованного контроля за настройкой таймаутов и circuit breaker.
#microservices #сравнение
Telegram
(java || kotlin) && devOps
Всем привет!
Современная разработка - это микросервисы, микросервисы - это много внешних взаимодействий, а внешние взаимодействия - это API.
Каким должно быть хорошее API?
1) API должно быть версионировано. Все течет, все меняется: бизнес-требования, новые…
Современная разработка - это микросервисы, микросервисы - это много внешних взаимодействий, а внешние взаимодействия - это API.
Каким должно быть хорошее API?
1) API должно быть версионировано. Все течет, все меняется: бизнес-требования, новые…
Всем привет!
Всегда ли нужно распиливать систему на микросервисы? Не всегда.
Я вижу как минимум один кейс.
Если выполняется хотя бы одно условие:
1) у вас одна команда
2) сроки ограничены
3) деньги ограничены
3) речь про стартап
4) создается приложение для проверки гипотезы
- стоит рассмотреть вариант с монолитом.
Плюсы - потребуется меньше железа, проще настраивать пайплайн и инфраструктуру. В простейшем случае можно весь ПРОМ развернуть на одном сервере. В случае стартапа или проверки гипотезы когда ещё не ясно как правильно разделить систему на независимые контексты ещё не понятно - в случае монолита ее проще рефакторить. Пока он маленький)
Может возникнуть вопрос - а как дальше масштабировать? Ответ - делите систему на уровне модулей Maven\Gradle, контролируйте зависимости между уровнями приложения, избегайте циклических ссылок. Для всего этого есть утилиты, напишу о них далее. Ну и конечно нужен набор приемочных тестов, его можно реализовать в период разделения на микросервисы, запуская вначале на "монолите", потом на микросервисах. С точки зрения БД - храните данные в разных схемах или хотя бы таблицах. Первый вариант лучше и одновременно хуже тем, что между схемами невозможны транзакции. Проектирование с использованием DDD - Domain driven development - в частности концепция агрегата, тоже хороший инструмент для создания разделяемого монолита. Тогда из этих модулей можно будет в будущем сделать микросервисы. Да, в любом случае при разделении потребуется дополнительная работа, но зато значительно увеличивается время вывода в ПРОМ и получение обратной связи. А дополнительную работу предлагаю рассматривать как рефакторинг.
Какие риски у этого подхода - по большому счету один. Если маленький монолит, который задумывался временным решением, превратится в большой и станет постоянным. Тогда в какой-то момент технологии устареют, части монолита срастутся сильнее и возникнет вопрос - а стоит ли его вообще разделять? Или проще переписать заново)))
И главное, что стоит запомнить - у каждого микросервиса должна быть веская причина существования. Если есть сомнения - нужен ли еще микросервис, не надо его выделять. Микросервис - далека не бесплатная штука в плане разработки и поддержки
#microservices #сравнение
Всегда ли нужно распиливать систему на микросервисы? Не всегда.
Я вижу как минимум один кейс.
Если выполняется хотя бы одно условие:
1) у вас одна команда
2) сроки ограничены
3) деньги ограничены
3) речь про стартап
4) создается приложение для проверки гипотезы
- стоит рассмотреть вариант с монолитом.
Плюсы - потребуется меньше железа, проще настраивать пайплайн и инфраструктуру. В простейшем случае можно весь ПРОМ развернуть на одном сервере. В случае стартапа или проверки гипотезы когда ещё не ясно как правильно разделить систему на независимые контексты ещё не понятно - в случае монолита ее проще рефакторить. Пока он маленький)
Может возникнуть вопрос - а как дальше масштабировать? Ответ - делите систему на уровне модулей Maven\Gradle, контролируйте зависимости между уровнями приложения, избегайте циклических ссылок. Для всего этого есть утилиты, напишу о них далее. Ну и конечно нужен набор приемочных тестов, его можно реализовать в период разделения на микросервисы, запуская вначале на "монолите", потом на микросервисах. С точки зрения БД - храните данные в разных схемах или хотя бы таблицах. Первый вариант лучше и одновременно хуже тем, что между схемами невозможны транзакции. Проектирование с использованием DDD - Domain driven development - в частности концепция агрегата, тоже хороший инструмент для создания разделяемого монолита. Тогда из этих модулей можно будет в будущем сделать микросервисы. Да, в любом случае при разделении потребуется дополнительная работа, но зато значительно увеличивается время вывода в ПРОМ и получение обратной связи. А дополнительную работу предлагаю рассматривать как рефакторинг.
Какие риски у этого подхода - по большому счету один. Если маленький монолит, который задумывался временным решением, превратится в большой и станет постоянным. Тогда в какой-то момент технологии устареют, части монолита срастутся сильнее и возникнет вопрос - а стоит ли его вообще разделять? Или проще переписать заново)))
И главное, что стоит запомнить - у каждого микросервиса должна быть веская причина существования. Если есть сомнения - нужен ли еще микросервис, не надо его выделять. Микросервис - далека не бесплатная штука в плане разработки и поддержки
#microservices #сравнение
Всем привет!
Поговорим про разделение функционала между микросервисами.
Для начала я бы разделил два случая - распил монолита или создание нового приложения.
При распиле есть несколько путей, которые можно комбинировать:
1) отделять в первую очередь менее значительный функционал. Так можно будет набить шишки, настроить DevOps, инфраструктуру, а потом уже переходить к чему-то серьёзному
2) отделять функционал, который легче выделить. Например, он уже компилируется в отдельный war/ear, хотя и имеет общий релизный цикл с монолитом. Или он правильно спроектирован https://t.me/javaKotlinDevOps/59 и его отделение требует минимальных усилий.
3) делать новые фичи на микросервисах, закрывая монолит API Gateway для единообразия API.
Все это называется шаблоном Душитель https://microservices.io/patterns/refactoring/strangler-application.html Душим мы как вы понимаете монолит)
Если же мы делаем новый функционал - тоже есть разные варианты, размещу их в порядке приоритета
1) разделение по бизнес фичам. Где фича - это агрегат в терминах DDD. Или ресурс в REST. Как я уже говорил ранее - не операция, это слишком мелко https://t.me/javaKotlinDevOps/59
2) разделение по слоям. Например, можно выделить в отдельный сервис сохранение данных. Или шлюз к смежной системе
3) разделение по планируемой нагрузке. Я бы его применял как дополнительный фактор к первым двум. Более нагруженные сервисы выделяем отдельно.
Еще один важный фактор: возможность переиспользования сервиса. Имеет значение и при распиле монолита, и при разработке новых фичей. Пример: отправка уведомлений пользователю по-хорошему стандартизирована и такой сервис смогут переиспользовать все создаваемые микросервисы. Способствуем выполнению принципа DRY - https://ru.wikipedia.org/wiki/Don’t_repeat_yourself
И напоследок хотел бы напомнить простую истину - как нет серебряной пули или единственно верной архитектуры, так нет канонического способа разделения на микросервисы)
#microservices #сравнение
Поговорим про разделение функционала между микросервисами.
Для начала я бы разделил два случая - распил монолита или создание нового приложения.
При распиле есть несколько путей, которые можно комбинировать:
1) отделять в первую очередь менее значительный функционал. Так можно будет набить шишки, настроить DevOps, инфраструктуру, а потом уже переходить к чему-то серьёзному
2) отделять функционал, который легче выделить. Например, он уже компилируется в отдельный war/ear, хотя и имеет общий релизный цикл с монолитом. Или он правильно спроектирован https://t.me/javaKotlinDevOps/59 и его отделение требует минимальных усилий.
3) делать новые фичи на микросервисах, закрывая монолит API Gateway для единообразия API.
Все это называется шаблоном Душитель https://microservices.io/patterns/refactoring/strangler-application.html Душим мы как вы понимаете монолит)
Если же мы делаем новый функционал - тоже есть разные варианты, размещу их в порядке приоритета
1) разделение по бизнес фичам. Где фича - это агрегат в терминах DDD. Или ресурс в REST. Как я уже говорил ранее - не операция, это слишком мелко https://t.me/javaKotlinDevOps/59
2) разделение по слоям. Например, можно выделить в отдельный сервис сохранение данных. Или шлюз к смежной системе
3) разделение по планируемой нагрузке. Я бы его применял как дополнительный фактор к первым двум. Более нагруженные сервисы выделяем отдельно.
Еще один важный фактор: возможность переиспользования сервиса. Имеет значение и при распиле монолита, и при разработке новых фичей. Пример: отправка уведомлений пользователю по-хорошему стандартизирована и такой сервис смогут переиспользовать все создаваемые микросервисы. Способствуем выполнению принципа DRY - https://ru.wikipedia.org/wiki/Don’t_repeat_yourself
И напоследок хотел бы напомнить простую истину - как нет серебряной пули или единственно верной архитектуры, так нет канонического способа разделения на микросервисы)
#microservices #сравнение
Telegram
(java || kotlin) && devOps
Всем привет!
Всегда ли нужно распиливать систему на микросервисы? Не всегда.
Я вижу как минимум один кейс.
Если выполняется хотя бы одно условие:
1) у вас одна команда
2) сроки ограничены
3) деньги ограничены
3) речь про стартап
4) создается приложение…
Всегда ли нужно распиливать систему на микросервисы? Не всегда.
Я вижу как минимум один кейс.
Если выполняется хотя бы одно условие:
1) у вас одна команда
2) сроки ограничены
3) деньги ограничены
3) речь про стартап
4) создается приложение…
Всем привет!
На какие грабли можно наступить при распиле монолита на микросервисы?
Небольшой дисклеймер: я подразумеваю, что внедряются микросервисы + Agile, т.к по определению у микросервиса должен быть владелец - команда.
Примеры взяты из жизни)
1) Как я уже говорил https://t.me/javaKotlinDevOps/60 - начинать распил лучше с каких-то небольших частей существующего монолита или новых фичей для этого монолита. Тут важен вот какой момент - скорее всего монолит выполняет некую критическую функцию для компании и, следовательно, для него существуют некие требования. Например, 99,99-ая доступность. И первые "тренировочные" микросервисы не стоит делать для другого сервиса=приложения=монолита, например, потому, что у него более лояльные пользователи или там проще внедрение. Если так сделать, то при переходе от простого монолита к сложному появится куча неожиданных требований по надежности, масштабируемости, ИБ, удобству сопровождения, и архитектуру придется переделывать.
2) должна быть зафиксированная и согласованная со всеми заинтересованными лицами архитектура.
Почему это критично - потому что архитектура - это основа. Можно конечно создавать архитектуру параллельно разработке, по принципу ввяжемся в бой, а там разберемся. Но это приведет к переписыванию микросервисов, к микросервисам-фантомам, которые на бумаге есть, а в жизни оказались не нужны, и к их антиподам, про которых просто забыли и конечно же к проблемам на всех стендах из-за меняющегося API. В конце концов при таком подходе слова архитектура и архитектор по отношению к новой микросервисной платформе станут ругательными)
3) нужно время на обучение разработчиков, тестировщиков и сопровождения. Обучение может быть и должно быть на практике - на реальных сервисах, но оно должно быть учтено в roadmap перехода. Сроки перехода должны быть согласованы командами разработки и сопровождения, а не спущены сверху. Звучит очевидно, но по моему опыту не для всех)
4) нужно изменение релизных и контролирующих политик в компании, иначе Lead Time и сложность вывода в ПРОМ для микросервисов останется на уровне монолита. Это сложный момент, т.к. люди консервативны, они привыкают к правилам, и не всегда осознают, что цена ошибки в монолите и в микросервисе разная. Особенно консервативно сопровождение и DBA, работа у них такая, цена ошибки высока) С другой стороны, конечно же микросервисная система должна быть спроектирована так, чтобы падение одного сервиса не приводило к каскадным падениям других. Это снова про критичное влияние архитектуры.
Еще важный момент - в монолите процесс согласования был централизован, им занимались специально обученные люди. С микросервисами релизный процесс попадает в Agile команды. А правило 7 +-2 действует не только для людей, а и для команд. Ну и большое число внешних взаимодействий противоречит Agile-у.
5) к началу разработки нужен работающий и простой DevOps. Разрабочики монолита как правило занимаются только разработкой и если заставить их учиться DevOps - потеряем время. Даже если набирать новых людей - все равно не каждый разработчик готов разрабатывать пайплайны. Т.е. должна быть минимальная конфигурация pipeline в декларативном стиле и работающие CI и CD pipeline. Опять же релизы микросервисов происходят намного чаще, для них это важно.
Также не должно быть невнятных инструкций и десятков настроечных файлов, необходимых для работы pipeline. Конфигурация - это не код, писать его не интересно, разбираться в ней тем более. И это не DevOps на самом деле) Должны быть - генераторы конфигураций, принцип convention over configuration.
6) микросервисам нужно больше инфраструктуры по сравнению с монолитом: сервера, репозитории - и больше доступов. Если в монолите как правило получением инфраструктуры занималась выделенная команда(ы), то при микросервисной архитекторе этим занимается каждая команда. Следовательно, процесс выдачи должен быть максимально автоматизирован и упрощен. Снова работает правило 7+-2. Разработчики должны заниматься тем, что у них лучше всего получается, а не заявками.
#microservices #fuckup #conv_over_conf
На какие грабли можно наступить при распиле монолита на микросервисы?
Небольшой дисклеймер: я подразумеваю, что внедряются микросервисы + Agile, т.к по определению у микросервиса должен быть владелец - команда.
Примеры взяты из жизни)
1) Как я уже говорил https://t.me/javaKotlinDevOps/60 - начинать распил лучше с каких-то небольших частей существующего монолита или новых фичей для этого монолита. Тут важен вот какой момент - скорее всего монолит выполняет некую критическую функцию для компании и, следовательно, для него существуют некие требования. Например, 99,99-ая доступность. И первые "тренировочные" микросервисы не стоит делать для другого сервиса=приложения=монолита, например, потому, что у него более лояльные пользователи или там проще внедрение. Если так сделать, то при переходе от простого монолита к сложному появится куча неожиданных требований по надежности, масштабируемости, ИБ, удобству сопровождения, и архитектуру придется переделывать.
2) должна быть зафиксированная и согласованная со всеми заинтересованными лицами архитектура.
Почему это критично - потому что архитектура - это основа. Можно конечно создавать архитектуру параллельно разработке, по принципу ввяжемся в бой, а там разберемся. Но это приведет к переписыванию микросервисов, к микросервисам-фантомам, которые на бумаге есть, а в жизни оказались не нужны, и к их антиподам, про которых просто забыли и конечно же к проблемам на всех стендах из-за меняющегося API. В конце концов при таком подходе слова архитектура и архитектор по отношению к новой микросервисной платформе станут ругательными)
3) нужно время на обучение разработчиков, тестировщиков и сопровождения. Обучение может быть и должно быть на практике - на реальных сервисах, но оно должно быть учтено в roadmap перехода. Сроки перехода должны быть согласованы командами разработки и сопровождения, а не спущены сверху. Звучит очевидно, но по моему опыту не для всех)
4) нужно изменение релизных и контролирующих политик в компании, иначе Lead Time и сложность вывода в ПРОМ для микросервисов останется на уровне монолита. Это сложный момент, т.к. люди консервативны, они привыкают к правилам, и не всегда осознают, что цена ошибки в монолите и в микросервисе разная. Особенно консервативно сопровождение и DBA, работа у них такая, цена ошибки высока) С другой стороны, конечно же микросервисная система должна быть спроектирована так, чтобы падение одного сервиса не приводило к каскадным падениям других. Это снова про критичное влияние архитектуры.
Еще важный момент - в монолите процесс согласования был централизован, им занимались специально обученные люди. С микросервисами релизный процесс попадает в Agile команды. А правило 7 +-2 действует не только для людей, а и для команд. Ну и большое число внешних взаимодействий противоречит Agile-у.
5) к началу разработки нужен работающий и простой DevOps. Разрабочики монолита как правило занимаются только разработкой и если заставить их учиться DevOps - потеряем время. Даже если набирать новых людей - все равно не каждый разработчик готов разрабатывать пайплайны. Т.е. должна быть минимальная конфигурация pipeline в декларативном стиле и работающие CI и CD pipeline. Опять же релизы микросервисов происходят намного чаще, для них это важно.
Также не должно быть невнятных инструкций и десятков настроечных файлов, необходимых для работы pipeline. Конфигурация - это не код, писать его не интересно, разбираться в ней тем более. И это не DevOps на самом деле) Должны быть - генераторы конфигураций, принцип convention over configuration.
6) микросервисам нужно больше инфраструктуры по сравнению с монолитом: сервера, репозитории - и больше доступов. Если в монолите как правило получением инфраструктуры занималась выделенная команда(ы), то при микросервисной архитекторе этим занимается каждая команда. Следовательно, процесс выдачи должен быть максимально автоматизирован и упрощен. Снова работает правило 7+-2. Разработчики должны заниматься тем, что у них лучше всего получается, а не заявками.
#microservices #fuckup #conv_over_conf
Telegram
(java || kotlin) && devOps
Всем привет!
Поговорим про разделение функционала между микросервисами.
Для начала я бы разделил два случая - распил монолита или создание нового приложения.
При распиле есть несколько путей, которые можно комбинировать:
1) отделять в первую очередь менее…
Поговорим про разделение функционала между микросервисами.
Для начала я бы разделил два случая - распил монолита или создание нового приложения.
При распиле есть несколько путей, которые можно комбинировать:
1) отделять в первую очередь менее…
Всем привет!
Вспомнил еще пару косяков при переходе на микросервисы.
7) не стоит совмещать переход на микросервисы с внедрением новой, необкатанной технологии. Новой не в плане того, что разрабы еще не работали к примеру с k8s. А например такой: использовать какой-нибудь новый "более лучший" аналог Istio или Helm. Или решить отказаться от СУБД в пользу перспективного noSQL решения. Микросервисная система приносит с собой новые технологии, меняются интеграции, DevOps, инфраструктура, скорее всего будет больно при интеграции микросервисов и legacy, не стоит добавлять в процесс еще немного хаоса) В принципе очевидная вещь, но я видел эти грабли в действии)
8) в монолите разработчики ограничены по стеку технологий и по способам реализации фичей и это понятно. Энтропия растет, монолит имеет тенденцию становится "большим куском грязи", поэтому если разрешать каждому добавлять свою любимую библиотеку или делать однотипные вещи по разному - оно будет долго собираться, конфликтовать в runtime, а изучить такой код с нуля станет очень сложно. А одно из преимуществ микросервисов в том, что их можно писать на разных технологических стеках. С другой стороны есть набор эксплуатационных требований, требований ИБ и требований по надежности и отказоустойчивости. Часть из них можно и нужно реализовать снаружи - в k8s или Service Mesh, часть - только внутри. Есть соблазн ту часть, что внутри, вынести в платформенный слой. Это допустимо, хотя и не обязательно - можно просто выставить необходимое платформенное API. Главное помнить, что этот слой должен быть тонким, чтобы его изучение и подключение не стало занимать больше времени, чем разработка фичи)
9) при переходе на микросервисы с большой вероятностью возникнут конфликты между командами разработки монолита и микросервисов. Как их разрешить - вопрос сложный. Как вариант - активно вовлекать монолитные команды в разработку микросервисов. Тут правда возникнет вопрос - а кто будет пилить бизнес-фичи на время перехода? Идеальный кейс - когда команда по очереди занимается и legacy, и микросервисами.
10) если речь про кровавый enterprise, то рядом с монолитами обычно живет ESB - Enterprise Service Bus, по которой монолиты общаются между собой. Может возникнуть идея сделать на основе k8s прямую замену ESB - как единую точку контроля и стандартизации интеграций. Если контроль делать через единый pipeline или проверку правил внутри k8s - ок. А если попытаться отдать разработку интеграций в ту же команду, что занималась ESB - то мы снова получим ESB как единую точку замедления разработки) Важно то, что задачи у k8s и ESB разные, и эту мысль нужно до всех доносить. У k8s - маршрутизация, автоматическое масштабирование, отказоустойчивость, наблюдаемость (observability) и прозрачное шифрование. У ESB - маршрутизация, стандартизация API и конвертация данных. Общая только маршрутизация. Т.е ESB это больше про унификацию бизнес логики, а облако и Service Mesh - про упрощение поддержки микросервисов.
#microservices #fails
Вспомнил еще пару косяков при переходе на микросервисы.
7) не стоит совмещать переход на микросервисы с внедрением новой, необкатанной технологии. Новой не в плане того, что разрабы еще не работали к примеру с k8s. А например такой: использовать какой-нибудь новый "более лучший" аналог Istio или Helm. Или решить отказаться от СУБД в пользу перспективного noSQL решения. Микросервисная система приносит с собой новые технологии, меняются интеграции, DevOps, инфраструктура, скорее всего будет больно при интеграции микросервисов и legacy, не стоит добавлять в процесс еще немного хаоса) В принципе очевидная вещь, но я видел эти грабли в действии)
8) в монолите разработчики ограничены по стеку технологий и по способам реализации фичей и это понятно. Энтропия растет, монолит имеет тенденцию становится "большим куском грязи", поэтому если разрешать каждому добавлять свою любимую библиотеку или делать однотипные вещи по разному - оно будет долго собираться, конфликтовать в runtime, а изучить такой код с нуля станет очень сложно. А одно из преимуществ микросервисов в том, что их можно писать на разных технологических стеках. С другой стороны есть набор эксплуатационных требований, требований ИБ и требований по надежности и отказоустойчивости. Часть из них можно и нужно реализовать снаружи - в k8s или Service Mesh, часть - только внутри. Есть соблазн ту часть, что внутри, вынести в платформенный слой. Это допустимо, хотя и не обязательно - можно просто выставить необходимое платформенное API. Главное помнить, что этот слой должен быть тонким, чтобы его изучение и подключение не стало занимать больше времени, чем разработка фичи)
9) при переходе на микросервисы с большой вероятностью возникнут конфликты между командами разработки монолита и микросервисов. Как их разрешить - вопрос сложный. Как вариант - активно вовлекать монолитные команды в разработку микросервисов. Тут правда возникнет вопрос - а кто будет пилить бизнес-фичи на время перехода? Идеальный кейс - когда команда по очереди занимается и legacy, и микросервисами.
10) если речь про кровавый enterprise, то рядом с монолитами обычно живет ESB - Enterprise Service Bus, по которой монолиты общаются между собой. Может возникнуть идея сделать на основе k8s прямую замену ESB - как единую точку контроля и стандартизации интеграций. Если контроль делать через единый pipeline или проверку правил внутри k8s - ок. А если попытаться отдать разработку интеграций в ту же команду, что занималась ESB - то мы снова получим ESB как единую точку замедления разработки) Важно то, что задачи у k8s и ESB разные, и эту мысль нужно до всех доносить. У k8s - маршрутизация, автоматическое масштабирование, отказоустойчивость, наблюдаемость (observability) и прозрачное шифрование. У ESB - маршрутизация, стандартизация API и конвертация данных. Общая только маршрутизация. Т.е ESB это больше про унификацию бизнес логики, а облако и Service Mesh - про упрощение поддержки микросервисов.
#microservices #fails
Всем привет!
В разработке сейчас много хайповых понятий, те же микросервисы, Service Mesh, GitOps, Serverless. Обо всем этом как-нибудь напишу, а сегодня пару мыслей про Cloud Native.
Если подумать, то для того, чтобы сделать native приложение для облака, нужно не так уж много.
1) k8s должен как-то понимать, что приложение поднялось и может принимать траффик. Для этого облачный сервис должен реализовать probes, они же healthchecks.
https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
Вообще их три, но в случае простейшего приложения хватит одной - liveness. Остальные две нужны если приложение долго стартует - startup или может быть недоступно в процессе работы - readyness.
2) облачное приложение не должно долго стартовать. Причина: k8s при добавлении новых нод или подов, или изменения их настроек Anti-affinity (возможности совместной работы на одной ноде) в любой момент может погасить под и поднять его на другом сервере. Да, можно указать startup probe чтобы траффик не шел на долго стартующее приложение. Можно указать maxUnavailable https://kubernetes.io/docs/tasks/run-application/configure-pdb/ для того, чтобы k8s оставлял запас подов при изменении их численности. Но все это обходные пути. Если вспомнить про Spring Boot, то я бы сказал что ему есть куда расти в плане оптимизации, не зря сейчас растет популярность его альтернатив, стартующих существенно быстрее: Quarkus, Micronaut, Helidon и использования native images.
3) cloud native приложение не должно зависеть от локального состояния - кэширования данных локально. Все критичные для работы данные должны хранится или в storage, или на клиенте. Причина все та же - k8s в любой момент может поднять под на другом сервере, локальный кэш при этом теряется.
4) для cloud native приложения крайне рекомендуется отбрасывать метрики и поддерживать distributed tracing - https://opentelemetry.io/docs/concepts/what-is-opentelemetry/. Причина: перенос в облако упрощает разработку, как правило идет рука об руку с микросервисами, следовательно, сервисов становится существенно больше, следовательно, должны быть инструменты для отслеживания их состояния и более точного понимания, где проблема.
Что на мой взгляд не относится к критичным требованиям для Cloud native приложений.
1) поддержка Docker. В Docker можно засунуть практически любое приложение, есть куча официальных образов. Даже IBM Websphere и WildFly, хотя использование их в облаке выглядит странным) Вижу проблему только с Windows native приложениями, но их остается не так уже много
2) поддержка внутри приложения cloud \ fault tolerance функций: circuit breakers, retries, timeouts, service discovery. Например, этот функционал реализуется в Spring Cloud библиотеке. Почему так? Потому что в связке k8s + service mesh все эти функции можно реализовать на уровне облака. Не хочу сказать, что Spring Cloud и его аналоги не нужны, но точно не обязательны для облака.
3) использование REST API. С одной стороны для HTTP траффика k8s + service mesh дает больше возможностей по маршрутизации, но с другой стороны tcp трафик тоже маршрутизируется.
#cloud_native #microservices #spring_boot #tracing
В разработке сейчас много хайповых понятий, те же микросервисы, Service Mesh, GitOps, Serverless. Обо всем этом как-нибудь напишу, а сегодня пару мыслей про Cloud Native.
Если подумать, то для того, чтобы сделать native приложение для облака, нужно не так уж много.
1) k8s должен как-то понимать, что приложение поднялось и может принимать траффик. Для этого облачный сервис должен реализовать probes, они же healthchecks.
https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
Вообще их три, но в случае простейшего приложения хватит одной - liveness. Остальные две нужны если приложение долго стартует - startup или может быть недоступно в процессе работы - readyness.
2) облачное приложение не должно долго стартовать. Причина: k8s при добавлении новых нод или подов, или изменения их настроек Anti-affinity (возможности совместной работы на одной ноде) в любой момент может погасить под и поднять его на другом сервере. Да, можно указать startup probe чтобы траффик не шел на долго стартующее приложение. Можно указать maxUnavailable https://kubernetes.io/docs/tasks/run-application/configure-pdb/ для того, чтобы k8s оставлял запас подов при изменении их численности. Но все это обходные пути. Если вспомнить про Spring Boot, то я бы сказал что ему есть куда расти в плане оптимизации, не зря сейчас растет популярность его альтернатив, стартующих существенно быстрее: Quarkus, Micronaut, Helidon и использования native images.
3) cloud native приложение не должно зависеть от локального состояния - кэширования данных локально. Все критичные для работы данные должны хранится или в storage, или на клиенте. Причина все та же - k8s в любой момент может поднять под на другом сервере, локальный кэш при этом теряется.
4) для cloud native приложения крайне рекомендуется отбрасывать метрики и поддерживать distributed tracing - https://opentelemetry.io/docs/concepts/what-is-opentelemetry/. Причина: перенос в облако упрощает разработку, как правило идет рука об руку с микросервисами, следовательно, сервисов становится существенно больше, следовательно, должны быть инструменты для отслеживания их состояния и более точного понимания, где проблема.
Что на мой взгляд не относится к критичным требованиям для Cloud native приложений.
1) поддержка Docker. В Docker можно засунуть практически любое приложение, есть куча официальных образов. Даже IBM Websphere и WildFly, хотя использование их в облаке выглядит странным) Вижу проблему только с Windows native приложениями, но их остается не так уже много
2) поддержка внутри приложения cloud \ fault tolerance функций: circuit breakers, retries, timeouts, service discovery. Например, этот функционал реализуется в Spring Cloud библиотеке. Почему так? Потому что в связке k8s + service mesh все эти функции можно реализовать на уровне облака. Не хочу сказать, что Spring Cloud и его аналоги не нужны, но точно не обязательны для облака.
3) использование REST API. С одной стороны для HTTP траффика k8s + service mesh дает больше возможностей по маршрутизации, но с другой стороны tcp трафик тоже маршрутизируется.
#cloud_native #microservices #spring_boot #tracing
Kubernetes
Configure Liveness, Readiness and Startup Probes
This page shows how to configure liveness, readiness and startup probes for containers.
For more information about probes, see Liveness, Readiness and Startup Probes
The kubelet uses liveness probes to know when to restart a container. For example, liveness…
For more information about probes, see Liveness, Readiness and Startup Probes
The kubelet uses liveness probes to know when to restart a container. For example, liveness…
Всем привет!
Хочу продолжить серию постов про #microservices
Я уже писал про их плюсы и минусы. Одним из главных минусов является увеличенная сложность развертывания и поддержки, а также накладные расходы на сетевое взаимодействие. Как ответ на эту сложность может возникнуть идея - а давайте сделаем несколько независимых модулей в одном микросервисе, а потом при необходимости их разделим. Ключевое слово здесь - независимый. Идея на самом деле здравая. "Модулем" здесь может быть модуль Maven\Gradle или даже пакет. Но есть одна проблема: если не следить за связями между "модулями" - они со временем становятся связанными и получается тот самый спагетти код))) А тогда выделение нового микросервиса превратится в распутывание клубка зависимостей. Значит нужна проверка границ "модулей". Лучший способ сделать надежную постоянно выполняемую проверку - это написать unit тест. И запускать его на prcheck и сборке конечно же. Но любой тест должен быть антихрупким - т.е. при изменениях в коде оставаться актуальным. В нашем случае - в случае добавлении\изменении\удалении "модулей" в проекте.
К чему я веду: есть технология, решающая эту проблему - Spring Modulith https://spring.io/projects/spring-modulith
А вот статья, описывающая предпосылки его появления более подробно и способ его использования: https://habr.com/ru/articles/701984/
Мне нравится.
Зависимость от Spring на мой взгляд не является большим минусом. Требование объявить все пакеты в одном модулей Maven\Gradle - минус чуть пожирнее, но на мой взгляд тоже не критично. И сборка в этом случае будет быстрее.
#spring #microservices
Хочу продолжить серию постов про #microservices
Я уже писал про их плюсы и минусы. Одним из главных минусов является увеличенная сложность развертывания и поддержки, а также накладные расходы на сетевое взаимодействие. Как ответ на эту сложность может возникнуть идея - а давайте сделаем несколько независимых модулей в одном микросервисе, а потом при необходимости их разделим. Ключевое слово здесь - независимый. Идея на самом деле здравая. "Модулем" здесь может быть модуль Maven\Gradle или даже пакет. Но есть одна проблема: если не следить за связями между "модулями" - они со временем становятся связанными и получается тот самый спагетти код))) А тогда выделение нового микросервиса превратится в распутывание клубка зависимостей. Значит нужна проверка границ "модулей". Лучший способ сделать надежную постоянно выполняемую проверку - это написать unit тест. И запускать его на prcheck и сборке конечно же. Но любой тест должен быть антихрупким - т.е. при изменениях в коде оставаться актуальным. В нашем случае - в случае добавлении\изменении\удалении "модулей" в проекте.
К чему я веду: есть технология, решающая эту проблему - Spring Modulith https://spring.io/projects/spring-modulith
А вот статья, описывающая предпосылки его появления более подробно и способ его использования: https://habr.com/ru/articles/701984/
Мне нравится.
Зависимость от Spring на мой взгляд не является большим минусом. Требование объявить все пакеты в одном модулей Maven\Gradle - минус чуть пожирнее, но на мой взгляд тоже не критично. И сборка в этом случае будет быстрее.
#spring #microservices
Spring Modulith
Level up your Java code and explore what Spring can do for you.
Всем привет!
Продолжая тему рефакторинга. Основное предусловие для начала рефакторинга - это наличие хорошего тестового покрытия. Т.к. мы не меняем бизнес функционал, а улучшаем код либо для повышения производительности, либо для его упрощения. Но при этом нужно гарантировать, что бизнес функционал не сломался, т.е. не появились регрессионные баги. Ведь рефакторинг, в отличие от новой фичи, может затронуть все приложение. Соответственно, баги могут появиться в любом месте, и без тестового покрытия - это большие риски.
Можно рассмотреть похожий кейс. У нас есть монолит, мы хотим распилить его на микросервисы. Это тоже своего рода рефакторинг, только на уровне архитектурном уровне. И тоже аналогичное условие для его начала - наличие достаточного набора тестов. В данном случае повышается важность интеграционных тестов.
Важный момент: в процессе подготовки к разбиению монолита или серьёзному рефакторингу может возникнуть вопрос-предложение - а давайте все выкинем и напишем заново. Так вот - одним из базовых критериев для ответа на этот вопрос также является покрытие тестами. Очевидно, не единственным, но важным. Другие критерии - объем техдолга, соответствие текущей архитектуры и целевой.
Еще кейс - разработчик «боится» рефакторить код, т.к. он слишком сложный или затрагивает слишком много зависимостей. С тестами решиться на рефакторинг намного проще.
Вывод простой - пишите тесты, это страховка при рефакторинге)
#refactoring #unittests #microservices
Продолжая тему рефакторинга. Основное предусловие для начала рефакторинга - это наличие хорошего тестового покрытия. Т.к. мы не меняем бизнес функционал, а улучшаем код либо для повышения производительности, либо для его упрощения. Но при этом нужно гарантировать, что бизнес функционал не сломался, т.е. не появились регрессионные баги. Ведь рефакторинг, в отличие от новой фичи, может затронуть все приложение. Соответственно, баги могут появиться в любом месте, и без тестового покрытия - это большие риски.
Можно рассмотреть похожий кейс. У нас есть монолит, мы хотим распилить его на микросервисы. Это тоже своего рода рефакторинг, только на уровне архитектурном уровне. И тоже аналогичное условие для его начала - наличие достаточного набора тестов. В данном случае повышается важность интеграционных тестов.
Важный момент: в процессе подготовки к разбиению монолита или серьёзному рефакторингу может возникнуть вопрос-предложение - а давайте все выкинем и напишем заново. Так вот - одним из базовых критериев для ответа на этот вопрос также является покрытие тестами. Очевидно, не единственным, но важным. Другие критерии - объем техдолга, соответствие текущей архитектуры и целевой.
Еще кейс - разработчик «боится» рефакторить код, т.к. он слишком сложный или затрагивает слишком много зависимостей. С тестами решиться на рефакторинг намного проще.
Вывод простой - пишите тесты, это страховка при рефакторинге)
#refactoring #unittests #microservices
Всем привет!
Сегодняшний пост начну издалека. Распределенные системы обмениваются сообщениями. Каждое сообщение можно рассматривать как событие, требующее какой-то обработки и передачи дальше - в другую подобную систему или для хранения в БД. Т.об. мы получаем распределенную цепочку микросервисов, через которые проходит событие. Существуют т.наз. семантики доставки сообщений:
- at-most-once
- at-least-once
- exactly-once
at-most-once - максимум один раз, т.е. 0 или 1. Т.е. есть вероятность потерять и не обработать сообщение. Подходит для случаев, когда поток сообщений большой, используется для сбора статистики и потеря одного сообщения не критична. Например - статистика по кликам на сайте.
at-least-once - минимум один раз, т.е. 1 или более. Т.е. есть вероятность повторно отправить какое-то сообщение и, соответственно, обработать его дважды. Подходит для систем, где важно знать последнее значение. Пример: стоимость акции на сайте брокера. Или для систем, идемпотентно обрабатывающих входящие сообщения - https://habr.com/ru/companies/domclick/articles/779872/
exactly-once - строго один раз. Идеальный случай.
Да, система может поддерживать сразу несколько семантик, т.е. по сути иметь несколько режимов работы.
Самый интересный и сложный - это конечно exactly-once. Как с ним обстоят дела?
Например, его поддерживает Kafka - самая распространённая система потоковой передачи сообщений - https://docs.confluent.io/kafka/design/delivery-semantics.html
А также самые распространенные системы потоковой обработки данных:
Kafka Streams - https://kafka.apache.org/33/documentation/streams/core-concepts
Flink - https://flink.apache.org/2018/02/28/an-overview-of-end-to-end-exactly-once-processing-in-apache-flink-with-apache-kafka-too/
Spark - https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html
Кажется, что все хорошо. Но не совсем)
Если прочитать внимательнее, что пишут, например, создатели Kafka, то выяснится что exactly-once гарантируется на участке Producer - Kafka, но далее все зависит от того, как организована работа в Consumer-е. Вот неплохая переводная статья на эту тему от одного из создателей Kafka: https://habr.com/ru/companies/badoo/articles/333046/ // в статье detected american style самореклама, но все равно она неплохая)))
Создатели Flink тоже говорят, что
а) мы даем механизм для exactly-once - в случае сбоя мы откатим ваш обрабатывающий процесс на конкретное состояние (checkpoint), и вы четко будете знать, что это за состояние - его метку времени, что было до и после него, но
б) что делать уже обработанными записями, находящимися после восстановленного состояния - разбирайтесь сами
в) возможность вернуться к сообщениям, на которые указывает checkpoint - тоже на стороне разработчика. В случае Kafka это чаще всего элементарно - сообщения не удаляются сразу после чтения из топика, а если это MQ или сетевой сокет...
г) а еще можно рассмотреть кейс, если кто-то обновил хранилище, где хранится состояние
д) или если в функции потоковой обработки используются какие-то внешние вызовы, которые сломаются на повторах после отката...
Но по большому счету это частности. Основная проблема - системы типа Kafka или Flink могут обеспечить exactly-once на какой-то небольшой части вашей микросервисной системы. Как обеспечить ее на всей системе - в любом случае задача архитектора и разработчика.
Подсказка: наличие operationId (traceId), идемпотентность, транзакции там где это возможно, докаты и наконец админка для ручного разбора инцидентов если не помогли все предыдущие варианты.
#streaming #kafka #flink #arch #microservices #exactly_once
Сегодняшний пост начну издалека. Распределенные системы обмениваются сообщениями. Каждое сообщение можно рассматривать как событие, требующее какой-то обработки и передачи дальше - в другую подобную систему или для хранения в БД. Т.об. мы получаем распределенную цепочку микросервисов, через которые проходит событие. Существуют т.наз. семантики доставки сообщений:
- at-most-once
- at-least-once
- exactly-once
at-most-once - максимум один раз, т.е. 0 или 1. Т.е. есть вероятность потерять и не обработать сообщение. Подходит для случаев, когда поток сообщений большой, используется для сбора статистики и потеря одного сообщения не критична. Например - статистика по кликам на сайте.
at-least-once - минимум один раз, т.е. 1 или более. Т.е. есть вероятность повторно отправить какое-то сообщение и, соответственно, обработать его дважды. Подходит для систем, где важно знать последнее значение. Пример: стоимость акции на сайте брокера. Или для систем, идемпотентно обрабатывающих входящие сообщения - https://habr.com/ru/companies/domclick/articles/779872/
exactly-once - строго один раз. Идеальный случай.
Да, система может поддерживать сразу несколько семантик, т.е. по сути иметь несколько режимов работы.
Самый интересный и сложный - это конечно exactly-once. Как с ним обстоят дела?
Например, его поддерживает Kafka - самая распространённая система потоковой передачи сообщений - https://docs.confluent.io/kafka/design/delivery-semantics.html
А также самые распространенные системы потоковой обработки данных:
Kafka Streams - https://kafka.apache.org/33/documentation/streams/core-concepts
Flink - https://flink.apache.org/2018/02/28/an-overview-of-end-to-end-exactly-once-processing-in-apache-flink-with-apache-kafka-too/
Spark - https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html
Кажется, что все хорошо. Но не совсем)
Если прочитать внимательнее, что пишут, например, создатели Kafka, то выяснится что exactly-once гарантируется на участке Producer - Kafka, но далее все зависит от того, как организована работа в Consumer-е. Вот неплохая переводная статья на эту тему от одного из создателей Kafka: https://habr.com/ru/companies/badoo/articles/333046/ // в статье detected american style самореклама, но все равно она неплохая)))
Создатели Flink тоже говорят, что
а) мы даем механизм для exactly-once - в случае сбоя мы откатим ваш обрабатывающий процесс на конкретное состояние (checkpoint), и вы четко будете знать, что это за состояние - его метку времени, что было до и после него, но
б) что делать уже обработанными записями, находящимися после восстановленного состояния - разбирайтесь сами
в) возможность вернуться к сообщениям, на которые указывает checkpoint - тоже на стороне разработчика. В случае Kafka это чаще всего элементарно - сообщения не удаляются сразу после чтения из топика, а если это MQ или сетевой сокет...
г) а еще можно рассмотреть кейс, если кто-то обновил хранилище, где хранится состояние
д) или если в функции потоковой обработки используются какие-то внешние вызовы, которые сломаются на повторах после отката...
Но по большому счету это частности. Основная проблема - системы типа Kafka или Flink могут обеспечить exactly-once на какой-то небольшой части вашей микросервисной системы. Как обеспечить ее на всей системе - в любом случае задача архитектора и разработчика.
Подсказка: наличие operationId (traceId), идемпотентность, транзакции там где это возможно, докаты и наконец админка для ручного разбора инцидентов если не помогли все предыдущие варианты.
#streaming #kafka #flink #arch #microservices #exactly_once
Хабр
Идемпотентность: больше, чем кажется
Друзья, всем привет! Идемпотентность в проектировании API — не просто формальность. Это свойство, часто рассматриваемое как способ получения одинакового ответа на повторяющийся запрос, на самом деле...
Всем привет!
Микросервисная архитектура, которая становится все более популярной, требует хранения кода каждого сервиса в отдельном репозитории. На всякий пожарный - про остальные требования к микросервисам можно почитать тут https://t.me/javaKotlinDevOps/55
Бонусом мы получаем "штатную" работы IDE, pipeline, механизмов код-ревью. Микросервис подразумевает небольшой объём кода, т.е. все инструменты будут отрабатывать достаточно быстро, с меньшей вероятностью тормозить или падать, меньше вероятность конфликтов merge и т.д.
Зачем же тогда могут понадобится гигантские монорепы?
А ведь примеров хватает:
Яндекс https://habr.com/ru/companies/yandex/articles/469021/
Гугл https://qeunit.com/blog/how-google-does-monorepo/
Microsoft Windows https://habr.com/ru/articles/795635/
И даже Юла: https://habr.com/ru/companies/oleg-bunin/articles/531632/
Отдельно я бы выделил три типовых кейса, не относящиеся к микросервисам:
1) собственно монолит. И причина понятна - странно части монолита собирать из разных репозиториев. Релизный цикл, pipeline все равно же общие
2) игры. Тоже по сути отдельный вид монолита - огромные размеры репо, особенность: основной объем занимают бинарные данные: текстуры, картинки, видео...
3) большие мобильные приложения, SuperApp или стремящиеся к ним. Тоже по сути отдельный случай монолита.
Но в списке выше есть и микросервисы, зачем им лезть в монорепы? Суммируя, можно выделить такие причины:
1) проще контроль над своевременностью обновления библиотек
2) возможность одним PR занести кросс-сервисную фичу. Кстати, полезная штука
3) обучение новичков и обмен знаниями на примерах из других сервисов. Кажется, что это можно сделать и с разными репо, но в монорепе, конечно, удобнее
4) облегчение работы с общим кодом - не надо подключать новые модули явно в каждый сервис, просто работаем с тем, что есть в develop. Опять же все ошибки при выпуске новой версии общего кода ловятся быстрее, т.к. все тесты в одном месте. Конечно тесты, а точнее достаточное тестовое покрытие, должно быть) Рефакторинг, застрагивающий все части системы, как правило связанный с изменением общего API, также проводить проще
5) легче обеспечить унификацию требований к коду. Тоже - можно решить на уровне pipeline и commit hook, но в одном репо проще
6) т.к. модули в монорепо скорее всего работают вместе, а иначе зачем их код хранится вместе - с монорепо можно развернуть тестовый стенд проще, чем с отдельными микросервисами
Что касается минусов - про то как их побороть, можно глянуть статьи выше. Особенно полезна статья про Microsoft, содержащая технические советы по обслуживанию репо на уровне git-а. Часть я знал и ранее, и даже использовал, часть для меня стало сюрпризом. Что особенно важно - все доработки из своего форка git Microsoft портировала в master git. Вот краткий список советов:
1) sparse checkout - выкачивание только нужных папок
2) shallow copy - выкачивание ограниченного числа версии из истории изменений, как правило, только последней
3) клонирование без blob (--filter=blob: none) В blob хранится содержимое файлов. Ясно, что без содержимого файлов разработка невозможна, но в этом режиме git во-первых подкачивает blob для последней версии, а во-вторых - будет подкачивать необходимые ему файлы по требованию
4) также может быть полезно клонирование без checkout, опция --no-checkout, т.е. без копирования собственно файлов в рабочий каталог. Так мы сможем избежать случайного копирования всех файлов до того, как будет проведена фильтрация
5) запуск git maintenance в фоне - обновление графа коммитов, ускоряет выполнение некоторых команд git
6) фоновый монитор файловой системы, настройка git config core.fsmonitor true. Слушает уведомления об изменений файлов в каталоге с исходниками, поэтому git status отрабатывает быстрее.
Более того, в состав Git есть отдельная утилита scalar, из коробки делающая многое из перечисленных выше пунктов.
Итоги: если можно обойтись без монорепо - так и нужно делать. Но если все же решили его использовать - боль можно облегчить)
#git #microservices #monorepo
Микросервисная архитектура, которая становится все более популярной, требует хранения кода каждого сервиса в отдельном репозитории. На всякий пожарный - про остальные требования к микросервисам можно почитать тут https://t.me/javaKotlinDevOps/55
Бонусом мы получаем "штатную" работы IDE, pipeline, механизмов код-ревью. Микросервис подразумевает небольшой объём кода, т.е. все инструменты будут отрабатывать достаточно быстро, с меньшей вероятностью тормозить или падать, меньше вероятность конфликтов merge и т.д.
Зачем же тогда могут понадобится гигантские монорепы?
А ведь примеров хватает:
Яндекс https://habr.com/ru/companies/yandex/articles/469021/
Гугл https://qeunit.com/blog/how-google-does-monorepo/
Microsoft Windows https://habr.com/ru/articles/795635/
И даже Юла: https://habr.com/ru/companies/oleg-bunin/articles/531632/
Отдельно я бы выделил три типовых кейса, не относящиеся к микросервисам:
1) собственно монолит. И причина понятна - странно части монолита собирать из разных репозиториев. Релизный цикл, pipeline все равно же общие
2) игры. Тоже по сути отдельный вид монолита - огромные размеры репо, особенность: основной объем занимают бинарные данные: текстуры, картинки, видео...
3) большие мобильные приложения, SuperApp или стремящиеся к ним. Тоже по сути отдельный случай монолита.
Но в списке выше есть и микросервисы, зачем им лезть в монорепы? Суммируя, можно выделить такие причины:
1) проще контроль над своевременностью обновления библиотек
2) возможность одним PR занести кросс-сервисную фичу. Кстати, полезная штука
3) обучение новичков и обмен знаниями на примерах из других сервисов. Кажется, что это можно сделать и с разными репо, но в монорепе, конечно, удобнее
4) облегчение работы с общим кодом - не надо подключать новые модули явно в каждый сервис, просто работаем с тем, что есть в develop. Опять же все ошибки при выпуске новой версии общего кода ловятся быстрее, т.к. все тесты в одном месте. Конечно тесты, а точнее достаточное тестовое покрытие, должно быть) Рефакторинг, застрагивающий все части системы, как правило связанный с изменением общего API, также проводить проще
5) легче обеспечить унификацию требований к коду. Тоже - можно решить на уровне pipeline и commit hook, но в одном репо проще
6) т.к. модули в монорепо скорее всего работают вместе, а иначе зачем их код хранится вместе - с монорепо можно развернуть тестовый стенд проще, чем с отдельными микросервисами
Что касается минусов - про то как их побороть, можно глянуть статьи выше. Особенно полезна статья про Microsoft, содержащая технические советы по обслуживанию репо на уровне git-а. Часть я знал и ранее, и даже использовал, часть для меня стало сюрпризом. Что особенно важно - все доработки из своего форка git Microsoft портировала в master git. Вот краткий список советов:
1) sparse checkout - выкачивание только нужных папок
2) shallow copy - выкачивание ограниченного числа версии из истории изменений, как правило, только последней
3) клонирование без blob (--filter=blob: none) В blob хранится содержимое файлов. Ясно, что без содержимого файлов разработка невозможна, но в этом режиме git во-первых подкачивает blob для последней версии, а во-вторых - будет подкачивать необходимые ему файлы по требованию
4) также может быть полезно клонирование без checkout, опция --no-checkout, т.е. без копирования собственно файлов в рабочий каталог. Так мы сможем избежать случайного копирования всех файлов до того, как будет проведена фильтрация
5) запуск git maintenance в фоне - обновление графа коммитов, ускоряет выполнение некоторых команд git
6) фоновый монитор файловой системы, настройка git config core.fsmonitor true. Слушает уведомления об изменений файлов в каталоге с исходниками, поэтому git status отрабатывает быстрее.
Более того, в состав Git есть отдельная утилита scalar, из коробки делающая многое из перечисленных выше пунктов.
Итоги: если можно обойтись без монорепо - так и нужно делать. Но если все же решили его использовать - боль можно облегчить)
#git #microservices #monorepo
Telegram
(java || kotlin) && devOps
Всем привет!
Поговорим про микросервисы. Статей будет несколько, от плюсов и минусов до саги и прочих паттернов.
Для начала что такое микросервис. Я дам определение в виде набора обязательных признаков. Обязательны все из них.
1) одна команда
2) отдельная…
Поговорим про микросервисы. Статей будет несколько, от плюсов и минусов до саги и прочих паттернов.
Для начала что такое микросервис. Я дам определение в виде набора обязательных признаков. Обязательны все из них.
1) одна команда
2) отдельная…
Всем привет!
Сегодня будет пост про паттерн Saga.
Saga - это способ осуществить распределённую транзакцию. Обычная транзакция осуществляется в рамках одной сущности, как правило базы данных. Распределённая - между несколькими. Проблема здесь в том, что для одной системы - реляционной БД или кластера Kafka - можно воспользоваться встроенным механизмом транзакций, для распределённой - нет.
В общем случае распределённые транзакции могут понадобиться и для операций между несколькими монолитными приложениями, но наиболее актуальны они стали при переходе на микросервисы, т.к. при этом расширились границы существующих бизнес транзакций.
Возможно кто-то слышал в применении к распределённым транзакциям и Java такие аббревиатуры как XA или JTA. Это стандарт Java EE (Jakarta EE) для осуществления распределённых транзакций между JDBC и JMS источниками. Существует давно, есть работающие реализации - https://samolisov.blogspot.com/2011/02/xa-jta-javase-spring-atomikos-2.html
Что же с ним не так, раз понадобился новый патерн?
1) т.к. в JTA появляется новая сущность - координатор транзакций - и сам процесс двухфазный - подготовка и фиксация транзакции - то это приводит к накладным расходам на сетевые вызовы и увеличению задержек (latency)
2) JTA стандарт ограничивает нас JRE совместимыми языками. Более того, не все поддерживают JDBC - см. noSQL хранилища - и JMS - см. Kafka. Причём последняя не стала добавлять поддержку JMS/JTA принципиально https://docs.confluent.io/platform/current/clients/kafka-jms-client/index.html
3) диспетчер транзакций - это ещё одна точка отказа. Пусть их и так много - для транзакции из 3 фаз это минимум 9 = (сервис + хранилище + сеть) х 3, и это не учитывая датацентры, СХД... И кажется, что добавление ещё одной сильно ситуацию не ухудшит. Но эта диспетчер - это централизованная (единая) точка отказа, при сбое диспетчера придётся повторять всю транзакцию с начала.
Но вернёмся к саге. Во-первых у нее есть 2 варианта реализации - оркестрация и хореография. Оркестрация - транзакция управляется из одного микросервиса, хореография - нет единой точки управления, просто идёт обмен сообщениями между микросервисами. Оркестрацией проще управлять и тестировать, хореография более надёжна, т.к. нет единой точки отказа.
Вот тут неплохое описание отличий https://learn.microsoft.com/ru-ru/azure/architecture/reference-architectures/saga/saga
Во-вторых: сага - это не стандарт или библиотека, это архитектурный патерн - реализацию нужно будет писать самому.
Суть саги - одну большую транзакцию мы делим на ряд локальных транзакций, в рамках которых обеспечивается строгая тразакционность. Плюс все локальные транзакции мы упорядочиваем таким образом, что вначале идут компенсируемые транзакции, а потом - повторяемые. Первые в случае сбоя мы компенсируем - т.е. откатываем, вторые - докатываем. Соответственно, в середине есть поворотная (pivot) локальная транзакция, после успешного выполнения которой все последующие транзакции мы обязаны докатить.
To be continued...
#patterns #microservices #saga
Сегодня будет пост про паттерн Saga.
Saga - это способ осуществить распределённую транзакцию. Обычная транзакция осуществляется в рамках одной сущности, как правило базы данных. Распределённая - между несколькими. Проблема здесь в том, что для одной системы - реляционной БД или кластера Kafka - можно воспользоваться встроенным механизмом транзакций, для распределённой - нет.
В общем случае распределённые транзакции могут понадобиться и для операций между несколькими монолитными приложениями, но наиболее актуальны они стали при переходе на микросервисы, т.к. при этом расширились границы существующих бизнес транзакций.
Возможно кто-то слышал в применении к распределённым транзакциям и Java такие аббревиатуры как XA или JTA. Это стандарт Java EE (Jakarta EE) для осуществления распределённых транзакций между JDBC и JMS источниками. Существует давно, есть работающие реализации - https://samolisov.blogspot.com/2011/02/xa-jta-javase-spring-atomikos-2.html
Что же с ним не так, раз понадобился новый патерн?
1) т.к. в JTA появляется новая сущность - координатор транзакций - и сам процесс двухфазный - подготовка и фиксация транзакции - то это приводит к накладным расходам на сетевые вызовы и увеличению задержек (latency)
2) JTA стандарт ограничивает нас JRE совместимыми языками. Более того, не все поддерживают JDBC - см. noSQL хранилища - и JMS - см. Kafka. Причём последняя не стала добавлять поддержку JMS/JTA принципиально https://docs.confluent.io/platform/current/clients/kafka-jms-client/index.html
3) диспетчер транзакций - это ещё одна точка отказа. Пусть их и так много - для транзакции из 3 фаз это минимум 9 = (сервис + хранилище + сеть) х 3, и это не учитывая датацентры, СХД... И кажется, что добавление ещё одной сильно ситуацию не ухудшит. Но эта диспетчер - это централизованная (единая) точка отказа, при сбое диспетчера придётся повторять всю транзакцию с начала.
Но вернёмся к саге. Во-первых у нее есть 2 варианта реализации - оркестрация и хореография. Оркестрация - транзакция управляется из одного микросервиса, хореография - нет единой точки управления, просто идёт обмен сообщениями между микросервисами. Оркестрацией проще управлять и тестировать, хореография более надёжна, т.к. нет единой точки отказа.
Вот тут неплохое описание отличий https://learn.microsoft.com/ru-ru/azure/architecture/reference-architectures/saga/saga
Во-вторых: сага - это не стандарт или библиотека, это архитектурный патерн - реализацию нужно будет писать самому.
Суть саги - одну большую транзакцию мы делим на ряд локальных транзакций, в рамках которых обеспечивается строгая тразакционность. Плюс все локальные транзакции мы упорядочиваем таким образом, что вначале идут компенсируемые транзакции, а потом - повторяемые. Первые в случае сбоя мы компенсируем - т.е. откатываем, вторые - докатываем. Соответственно, в середине есть поворотная (pivot) локальная транзакция, после успешного выполнения которой все последующие транзакции мы обязаны докатить.
To be continued...
#patterns #microservices #saga
Blogspot
Распределенные транзакции (XA) с помощью JTA в JavaSE (на примере Spring + Atomikos)
При интеграции приложений в единую информационную систему наиболее остро встает проблема обеспечения целостности и непротиворечивости данны...
Всем привет!
Продолжение про сагу.
Когда мы говорим про транзакции, сначала всплывает аббревиатура ACID. Транзакции должны обеспечивать принципы ACID. Посмотрим что тут у нас с сагой.
A - атомарность: или все выполняется, или все откатывается. Собственно атормарность есть в определении паттерна, см. выше. Единственное отличие - у нас нет волшебного rollback на всю распределённую транзакцию, бизнес логику отката придётся писать руками.
C - консистентность данных. Сага обеспечивает т.наз. eventually consistentcy - конечную согласованность. Т.е. данные будут согласованы только после окончания распределённой транзакции. В течение транзакции данные в разных микросервисах могут расходится. Транзакция в БД может обеспечить строгую согласованность изменяемых данных с нужным уровнем изоляции. Поэтому переходим к
I - изоляции изменений внутри транзакции от других операций. Сага не обеспечивает ее совсем, что с этим можно сделать описано в статье про это патерн от Microsoft по ссылке выше. Важный момент - в отличие от транзакции в БД, которая как правило длится миллисекунды, распределённая транзакция - это секунды, может даже десятки секунд. Несогласованность данных в течение этого времени из-за отсутствия изоляции нужно иметь в виду. В дополнение к описанным в статье по ссылке способом скажу ещё один - завершать транзакцию как можно быстрее и игнорировать несогласованность данных) Пример: клиент вряд ли будет жаловаться в службу поддержки, если после отмены заказа деньги и бонусы вернутся на счёт в течение минуты. И скорее всего будет - если это не будет сделано через час.
D - надёжность хранения данных, к саге отношения напрямую не имеет, обеспечивается используемыми хранилищами.
Т.к. в итоге мы получили ACD, причем неполноценный, то для распределенных транзакций придумали новую аббревиатуру - Basically Available, Soft-state, Eventually consistent - https://ru.m.wikipedia.org/wiki/Теорема_CAP#BASE-архитектура
Ещё один интересный момент про сагу: определение последовательности шагов - локальных транзакций. Единственно верной схемы нет, но есть рекомендации. Первая - fail fast. Т.е. если есть локальная транзакция, которая упадёт с большей вероятностью - ее нужно ставить вначале. Пример: резерв билета или товара. Вторая - если какая-то локальная транзакция проводит к критичной для клиента несогласованность данных - ее нужно выполнять как можно позже. Что делать, если эти рекомендации противоречат друг другу - зависит от сценария, но в целом я бы выбрал уменьшение времени неконсистентности.
Еще интересный момент касается саги в виде оркестрации. Т.к. ее главный плюс - сделать простой и понятной бизнес логику саги, то самая очевидная ее реализация вот такая:
class OrderSaga {
SagaResult execute() {
// шаг 1
// шаг 2
// ...
}
}
Назовём этот подход Transaction Script, есть такой Паттерн организации бизнес логики.
Просто - да. Но если процесс сложный, каждый шаг тоже, то мы ухудшим читаемость кода, получим замечание SonarQube про длину метода да и нарушим S из SOLID, принцип единой ответственности. Что делать? Использовать event driven подход:
class OrderSaga {
PrepareEvent start(...) {..}
ReserveEvent makeReservation(...) {...}
// ...
}
При необходимости обработку событий можно разнести в разные классы. Чтобы было понимание как работает процесс нужно написать пару модульных тестов - позитивный и негативные сценарии, ведь тесты в идеале - лучшая документация к коду. Ещё один плюс - в событийной стиле легко сделать весь процесс неблокирующим, например, через адаптер отправляя и принимая все события в Kafka. Да, есть ещё БД, запись в БД в эту парадигме - это такое же событие. В этом случае стоит посмотреть в сторону R2DBC https://www.baeldung.com/r2dbc Для REST endpoint и client есть Spring WebFlux.
К слову, Transaction script тоже может обеспечить неблокирующее выполнение, но только в языках программирования с async await: c#, python, rust https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/operators/await
To be continued...
#patterns #saga #microservices #acid #arch_compromises
Продолжение про сагу.
Когда мы говорим про транзакции, сначала всплывает аббревиатура ACID. Транзакции должны обеспечивать принципы ACID. Посмотрим что тут у нас с сагой.
A - атомарность: или все выполняется, или все откатывается. Собственно атормарность есть в определении паттерна, см. выше. Единственное отличие - у нас нет волшебного rollback на всю распределённую транзакцию, бизнес логику отката придётся писать руками.
C - консистентность данных. Сага обеспечивает т.наз. eventually consistentcy - конечную согласованность. Т.е. данные будут согласованы только после окончания распределённой транзакции. В течение транзакции данные в разных микросервисах могут расходится. Транзакция в БД может обеспечить строгую согласованность изменяемых данных с нужным уровнем изоляции. Поэтому переходим к
I - изоляции изменений внутри транзакции от других операций. Сага не обеспечивает ее совсем, что с этим можно сделать описано в статье про это патерн от Microsoft по ссылке выше. Важный момент - в отличие от транзакции в БД, которая как правило длится миллисекунды, распределённая транзакция - это секунды, может даже десятки секунд. Несогласованность данных в течение этого времени из-за отсутствия изоляции нужно иметь в виду. В дополнение к описанным в статье по ссылке способом скажу ещё один - завершать транзакцию как можно быстрее и игнорировать несогласованность данных) Пример: клиент вряд ли будет жаловаться в службу поддержки, если после отмены заказа деньги и бонусы вернутся на счёт в течение минуты. И скорее всего будет - если это не будет сделано через час.
D - надёжность хранения данных, к саге отношения напрямую не имеет, обеспечивается используемыми хранилищами.
Т.к. в итоге мы получили ACD, причем неполноценный, то для распределенных транзакций придумали новую аббревиатуру - Basically Available, Soft-state, Eventually consistent - https://ru.m.wikipedia.org/wiki/Теорема_CAP#BASE-архитектура
Ещё один интересный момент про сагу: определение последовательности шагов - локальных транзакций. Единственно верной схемы нет, но есть рекомендации. Первая - fail fast. Т.е. если есть локальная транзакция, которая упадёт с большей вероятностью - ее нужно ставить вначале. Пример: резерв билета или товара. Вторая - если какая-то локальная транзакция проводит к критичной для клиента несогласованность данных - ее нужно выполнять как можно позже. Что делать, если эти рекомендации противоречат друг другу - зависит от сценария, но в целом я бы выбрал уменьшение времени неконсистентности.
Еще интересный момент касается саги в виде оркестрации. Т.к. ее главный плюс - сделать простой и понятной бизнес логику саги, то самая очевидная ее реализация вот такая:
class OrderSaga {
SagaResult execute() {
// шаг 1
// шаг 2
// ...
}
}
Назовём этот подход Transaction Script, есть такой Паттерн организации бизнес логики.
Просто - да. Но если процесс сложный, каждый шаг тоже, то мы ухудшим читаемость кода, получим замечание SonarQube про длину метода да и нарушим S из SOLID, принцип единой ответственности. Что делать? Использовать event driven подход:
class OrderSaga {
PrepareEvent start(...) {..}
ReserveEvent makeReservation(...) {...}
// ...
}
При необходимости обработку событий можно разнести в разные классы. Чтобы было понимание как работает процесс нужно написать пару модульных тестов - позитивный и негативные сценарии, ведь тесты в идеале - лучшая документация к коду. Ещё один плюс - в событийной стиле легко сделать весь процесс неблокирующим, например, через адаптер отправляя и принимая все события в Kafka. Да, есть ещё БД, запись в БД в эту парадигме - это такое же событие. В этом случае стоит посмотреть в сторону R2DBC https://www.baeldung.com/r2dbc Для REST endpoint и client есть Spring WebFlux.
К слову, Transaction script тоже может обеспечить неблокирующее выполнение, но только в языках программирования с async await: c#, python, rust https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/operators/await
To be continued...
#patterns #saga #microservices #acid #arch_compromises
Baeldung
R2DBC – Reactive Relational Database Connectivity | Baeldung
A quick and practical overview of R2DBC - reactive database connectivity.
Всем привет!
Этим постом завершается серия по паттерну Сага.
В предыдущем посте забыл упомянуть 3-й и 4-й способ реализации Саги.
Третий - если вы используете BPMN движок, например, Camunda, то он отлично подходит для оркестратора Саги. Более того, использовать BPMN как оркестратор - лучшая идея, чем использовать его как среду для low-code разработки. Ну не верю я в low-code, не сталкивался с работающими кейсами) Главные плюсы BPMN в данном - случае готовая state machine и визуализация Саги. К слову сама Camunda поставила этот use case на первое место в списке https://camunda.com/solutions/microservices-orchestration/ что как бы намекает. На всякий случай: Camunda - это самый распространенный BPMN движок, собственно движок - opensource, платить нужно только за UI консоль.
Аналогично - если вы уже используете Apache Camel - он тоже умеет в сагу, https://camel.apache.org/components/4.4.x/eips/saga-eip.html
Тут встает вопрос - стоит ли внедрять данные инструменты только ради Саги? Базовый ответ нет, идеальный кейс: если какой-то из этих компонент уже у вас используется - логично реализовать оркестрацию с его помощью. Я бы внедрял, если бы были какие-то еще плюсы от использования, кроме собственно реализации паттерна.
Еще важный момент при реализации оркестратора - stateless или statefull? Да, любая бизнес операция имеет как минимум ID и состояние, которые нужно хранить. Но необязательно это делать в классе Саги. Особенно используя event driven подход, можно просто передавать все не необходимые данные в событиях\командах. Напомню, при этом сохранение состояния операции в БД - это тоже событие. Плюс такого подхода - не нужно думать о букве D из ACID, т.е. персистентности, для данных, хранимых в оркестраторе. А где персистентность, там и кэширование, т.к. обращение к БД - дорого. И восстановление данных из БД при сбоях. Поэтому если вы все же решили хранить состояние операции в коде - я бы рекомендовал не изобретать велосипед, а воспользоваться готовым фреймворком. Два из них я уже упомянул выше, но они достаточно "тяжелые". Вот еще несколько, заточенных собственно под паттерн Сага и под DDD, который в общем-то тесно связан с сагой. Ведь если мы делим систему на ограниченный контексты, Bounded Context, то их данные лежат в разных БД, а следовательно возникает распределенная транзакция...
1) Axios https://docs.axoniq.io/reference-guide/v/3.1/part-ii-domain-logic/sagas
2) Eventuate Tram Saga https://eventuate.io/docs/manual/eventuate-tram/latest/getting-started-eventuate-tram-sagas.html
3) Seata https://www.seata.io/docs/user/mode/saga
Фреймворк помогает нам с:
а) персистентностью
б) кэшированием
в) созданием экземпляра саги для конкретной бизнес-операции
г) удобной работой с параметрами операции
При этом он не отменяет написания кода оркестрации и компенсирующих действий.
На этом пожалуй все.
Хотя нет. Остается вопрос - как же лучше реализовать Сагу? Ответ - лучше сделать свой ограниченный контекст = микросервис таким, чтобы Сага была не нужна)
А если серьезно.
1) постарайтесь использовать только локальные транзакции
2) если это не возможно, и у вас 2-4 шага - используйте хореографию
3) если шагов от 4+ и сервис создаётся с нуля - используйте оркестратор, для начала самописный, stateless event driven
4) у вас уже используется Camunda или Camel - делайте оркестратор на их основе
5) если вас нужен state - используйте фреймворки из последнего списка, например, Axios
6) если нужна сага и state machine - Camunda или Seata
#saga #microservices #ddd #patterns
Этим постом завершается серия по паттерну Сага.
В предыдущем посте забыл упомянуть 3-й и 4-й способ реализации Саги.
Третий - если вы используете BPMN движок, например, Camunda, то он отлично подходит для оркестратора Саги. Более того, использовать BPMN как оркестратор - лучшая идея, чем использовать его как среду для low-code разработки. Ну не верю я в low-code, не сталкивался с работающими кейсами) Главные плюсы BPMN в данном - случае готовая state machine и визуализация Саги. К слову сама Camunda поставила этот use case на первое место в списке https://camunda.com/solutions/microservices-orchestration/ что как бы намекает. На всякий случай: Camunda - это самый распространенный BPMN движок, собственно движок - opensource, платить нужно только за UI консоль.
Аналогично - если вы уже используете Apache Camel - он тоже умеет в сагу, https://camel.apache.org/components/4.4.x/eips/saga-eip.html
Тут встает вопрос - стоит ли внедрять данные инструменты только ради Саги? Базовый ответ нет, идеальный кейс: если какой-то из этих компонент уже у вас используется - логично реализовать оркестрацию с его помощью. Я бы внедрял, если бы были какие-то еще плюсы от использования, кроме собственно реализации паттерна.
Еще важный момент при реализации оркестратора - stateless или statefull? Да, любая бизнес операция имеет как минимум ID и состояние, которые нужно хранить. Но необязательно это делать в классе Саги. Особенно используя event driven подход, можно просто передавать все не необходимые данные в событиях\командах. Напомню, при этом сохранение состояния операции в БД - это тоже событие. Плюс такого подхода - не нужно думать о букве D из ACID, т.е. персистентности, для данных, хранимых в оркестраторе. А где персистентность, там и кэширование, т.к. обращение к БД - дорого. И восстановление данных из БД при сбоях. Поэтому если вы все же решили хранить состояние операции в коде - я бы рекомендовал не изобретать велосипед, а воспользоваться готовым фреймворком. Два из них я уже упомянул выше, но они достаточно "тяжелые". Вот еще несколько, заточенных собственно под паттерн Сага и под DDD, который в общем-то тесно связан с сагой. Ведь если мы делим систему на ограниченный контексты, Bounded Context, то их данные лежат в разных БД, а следовательно возникает распределенная транзакция...
1) Axios https://docs.axoniq.io/reference-guide/v/3.1/part-ii-domain-logic/sagas
2) Eventuate Tram Saga https://eventuate.io/docs/manual/eventuate-tram/latest/getting-started-eventuate-tram-sagas.html
3) Seata https://www.seata.io/docs/user/mode/saga
Фреймворк помогает нам с:
а) персистентностью
б) кэшированием
в) созданием экземпляра саги для конкретной бизнес-операции
г) удобной работой с параметрами операции
При этом он не отменяет написания кода оркестрации и компенсирующих действий.
На этом пожалуй все.
Хотя нет. Остается вопрос - как же лучше реализовать Сагу? Ответ - лучше сделать свой ограниченный контекст = микросервис таким, чтобы Сага была не нужна)
А если серьезно.
1) постарайтесь использовать только локальные транзакции
2) если это не возможно, и у вас 2-4 шага - используйте хореографию
3) если шагов от 4+ и сервис создаётся с нуля - используйте оркестратор, для начала самописный, stateless event driven
4) у вас уже используется Camunda или Camel - делайте оркестратор на их основе
5) если вас нужен state - используйте фреймворки из последнего списка, например, Axios
6) если нужна сага и state machine - Camunda или Seata
#saga #microservices #ddd #patterns
Camunda
Orchestrate Microservices | Camunda
Overcome challenges of microservices orchestration with Camunda. Get speed, scale, and resiliency without compromising microservice autonomy. Learn how.
Всем привет!
При проектировании системы применяя микросервисный подход всегда появляется главный вопрос - как делить?
Сделаешь слишком крупно - получишь маленький монолит. Это как правило всем понятно, т.к. от монолита мы пытаемся уйти создавая микросервисы.
Но есть и другая крайность - слишком мелкое деление. Уже немного писал об этом https://t.me/javaKotlinDevOps/57
Сейчас же хочу проиллюстрировать эту крайность примером.
Предположим у нас есть некая система, представляющая клиентам CRUD REST API. Create, Read, Update, Delete методы. И еще List, который сильно отличается от Read поэтому должен быть выделен отдельно - pagination, сортировка, кэширование...
Можно применить назовем его "наивный" подход к микросервисам и сделать 5 микросервисов по числу методов API. Точнее даже "миллисервисов")
Что получим?
Вспоминаем, что у каждого микросервиса должна быть своя БД.
Это значит что от микросервисов Create и Delete зависят все остальные, т.к. им нужно будет обновить свою копию данных. Это может быть event driven подход с Kafka, CQRS или что-то другое, но в любом случае это зависимость.
От микросервиса Update зависят Read и List.
А если структура данных меняется?
И это зависимости "из коробки" на сферическом CRUD в вакууме. В реальном кейсе по мере развития системы число зависимостей будет больше. Что получилось? Получился распределённый "ком грязи". Такой же "ком грязи", как в старом неподдерживаемом монолите, от которого мы уходили, только хуже. Там хоть БД одна была и интеграций сильно меньше.
Можно попробовать вынести все взаимодействие с БД в отдельный микросервис Storage, но тогда мы нарушаем Single Responsibility - за ту же операцию Create отвечает и микросервис Create, и микросервис Storage. И Create скорее всего станет слишком простым для отдельного микросервиса.
Пример специально взят простой, в реальности выбор может быть сложнее. Зато на этом примере хорошо видны недостатки "миллисервисов".
P.S. За идею примера спасибо все из той же книжке по DDD, расскажу о ней в ближайшее время.
#microservices #rest #arch_compromises
При проектировании системы применяя микросервисный подход всегда появляется главный вопрос - как делить?
Сделаешь слишком крупно - получишь маленький монолит. Это как правило всем понятно, т.к. от монолита мы пытаемся уйти создавая микросервисы.
Но есть и другая крайность - слишком мелкое деление. Уже немного писал об этом https://t.me/javaKotlinDevOps/57
Сейчас же хочу проиллюстрировать эту крайность примером.
Предположим у нас есть некая система, представляющая клиентам CRUD REST API. Create, Read, Update, Delete методы. И еще List, который сильно отличается от Read поэтому должен быть выделен отдельно - pagination, сортировка, кэширование...
Можно применить назовем его "наивный" подход к микросервисам и сделать 5 микросервисов по числу методов API. Точнее даже "миллисервисов")
Что получим?
Вспоминаем, что у каждого микросервиса должна быть своя БД.
Это значит что от микросервисов Create и Delete зависят все остальные, т.к. им нужно будет обновить свою копию данных. Это может быть event driven подход с Kafka, CQRS или что-то другое, но в любом случае это зависимость.
От микросервиса Update зависят Read и List.
А если структура данных меняется?
И это зависимости "из коробки" на сферическом CRUD в вакууме. В реальном кейсе по мере развития системы число зависимостей будет больше. Что получилось? Получился распределённый "ком грязи". Такой же "ком грязи", как в старом неподдерживаемом монолите, от которого мы уходили, только хуже. Там хоть БД одна была и интеграций сильно меньше.
Можно попробовать вынести все взаимодействие с БД в отдельный микросервис Storage, но тогда мы нарушаем Single Responsibility - за ту же операцию Create отвечает и микросервис Create, и микросервис Storage. И Create скорее всего станет слишком простым для отдельного микросервиса.
Пример специально взят простой, в реальности выбор может быть сложнее. Зато на этом примере хорошо видны недостатки "миллисервисов".
P.S. За идею примера спасибо все из той же книжке по DDD, расскажу о ней в ближайшее время.
#microservices #rest #arch_compromises
Telegram
(java || kotlin) && devOps
Всем привет!
Пару заблуждений про микросервисы.
1) Облачные технологии (k8s) не равно микросервисам. Да, если вы начинаете проект с микросервисами с нуля и есть возможность развернуть его в облаке - так и нужно делать.
Облако упрощает развертывание и масштабирование…
Пару заблуждений про микросервисы.
1) Облачные технологии (k8s) не равно микросервисам. Да, если вы начинаете проект с микросервисами с нуля и есть возможность развернуть его в облаке - так и нужно делать.
Облако упрощает развертывание и масштабирование…
Всем привет!
Поговорим снова о микросервисах. Я уже писал, почему не стоит делать слишком мелкие микросервисы https://t.me/javaKotlinDevOps/305
Но встает закономерный вопрос - "сколько вешать в граммах", в смысле - а какого размера должны быть микросервисы?
Обозначим нижний и верхний предел, а для этого придется вспомнить DDD.
Для начала рассмотрим понятие ограниченного контекста (bounded context). Это связанный набор сущностей из реального мира, для наименования которых используется "единый язык" (ubiquitous language) - непротиворечивый набор терминов. Эти сущности описываются в аналитике, тест-кейсах и превращаются в классы в нашем сервисе и в таблицы в БД. Контекстом как правило занимается одна команда - так проще всего поддерживать "единый язык". И за микросервис тоже должна отвечать одна команда. Т.е. ограниченный контекст - это отличный кандидат на микросервис. Но при этом у одной команды может быть несколько микросервисов. И контекст может быть достаточно большим. Т.е. у нас есть верхняя граница микросервиса.
Теперь рассмотрим понятие агрегата - группу сущностей, имеющую уникальный идентификатор, изменение которой производится атомарно. Т.е. агрегат - граница транзакции в БД. А т.к. возможность делегировать управление транзакцией СУБД - это очень крутая штука, то разделять агрегат между разными БД не стоит. При этом один микросервис = одна БД. Поэтому агрегат - нижняя граница микросервиса.
#microservices #ddd
Поговорим снова о микросервисах. Я уже писал, почему не стоит делать слишком мелкие микросервисы https://t.me/javaKotlinDevOps/305
Но встает закономерный вопрос - "сколько вешать в граммах", в смысле - а какого размера должны быть микросервисы?
Обозначим нижний и верхний предел, а для этого придется вспомнить DDD.
Для начала рассмотрим понятие ограниченного контекста (bounded context). Это связанный набор сущностей из реального мира, для наименования которых используется "единый язык" (ubiquitous language) - непротиворечивый набор терминов. Эти сущности описываются в аналитике, тест-кейсах и превращаются в классы в нашем сервисе и в таблицы в БД. Контекстом как правило занимается одна команда - так проще всего поддерживать "единый язык". И за микросервис тоже должна отвечать одна команда. Т.е. ограниченный контекст - это отличный кандидат на микросервис. Но при этом у одной команды может быть несколько микросервисов. И контекст может быть достаточно большим. Т.е. у нас есть верхняя граница микросервиса.
Теперь рассмотрим понятие агрегата - группу сущностей, имеющую уникальный идентификатор, изменение которой производится атомарно. Т.е. агрегат - граница транзакции в БД. А т.к. возможность делегировать управление транзакцией СУБД - это очень крутая штука, то разделять агрегат между разными БД не стоит. При этом один микросервис = одна БД. Поэтому агрегат - нижняя граница микросервиса.
#microservices #ddd
Telegram
(java || kotlin) && devOps
Всем привет!
При проектировании системы применяя микросервисный подход всегда появляется главный вопрос - как делить?
Сделаешь слишком крупно - получишь маленький монолит. Это как правило всем понятно, т.к. от монолита мы пытаемся уйти создавая микросервисы.…
При проектировании системы применяя микросервисный подход всегда появляется главный вопрос - как делить?
Сделаешь слишком крупно - получишь маленький монолит. Это как правило всем понятно, т.к. от монолита мы пытаемся уйти создавая микросервисы.…
Всем привет!
Случайно наткнулся на старую статью - 2015 год - про переход с legacy на Service Oriented Architecture ака SOA.
И хочу сказать, что это хороший пример развития истории по спирали)
Что в статье актуально?
Заменяем слово SOA на микросервисы, и в целом все, что касается преимуществ микросервисной архитектуры и стратегии перехода на нее - актуально. Микросервисы = SOA 2.0 )))
REST оставляем, SOAP+XML заменяем на gRPC\GraphQL для тех случаев, когда требуется большая производительность и гибкость соответственно по сравнению с REST. К слову, недостаток производительности и гибкости - это основные проблемы SOAP. Ремарка - знаю места, где SOAP еще жив (интеграция с госорганами), но он в любом случае вымирает.
ESB, трудности реализации асинхронного взаимодействия - все эти задачи взяла на себя Kafka. Прорывной инструмент - быстрый, надежный (обеспечивает дешевую персистентность), opensource, простой с точки зрения разработчика. В т.ч. потому, что нет необходимости разрабатывать логику маппинга сообщений на брокере. Да, он реализует только одну из двух основных моделей асинхронного взаимодействия - Publisher-Subscriber - и не реализует Message Queue. Но понятно, что топиками можно пользоваться как заменой очередей, и в большинстве случаев проблем при этом не будет.
Облачные решения - за 10 лет из вызова превратились в новую реальность)
А вызов сейчас - внедрение AI. Как-то так)
#microservices #ai #cloud #kafka #rest
Случайно наткнулся на старую статью - 2015 год - про переход с legacy на Service Oriented Architecture ака SOA.
И хочу сказать, что это хороший пример развития истории по спирали)
Что в статье актуально?
Заменяем слово SOA на микросервисы, и в целом все, что касается преимуществ микросервисной архитектуры и стратегии перехода на нее - актуально. Микросервисы = SOA 2.0 )))
REST оставляем, SOAP+XML заменяем на gRPC\GraphQL для тех случаев, когда требуется большая производительность и гибкость соответственно по сравнению с REST. К слову, недостаток производительности и гибкости - это основные проблемы SOAP. Ремарка - знаю места, где SOAP еще жив (интеграция с госорганами), но он в любом случае вымирает.
ESB, трудности реализации асинхронного взаимодействия - все эти задачи взяла на себя Kafka. Прорывной инструмент - быстрый, надежный (обеспечивает дешевую персистентность), opensource, простой с точки зрения разработчика. В т.ч. потому, что нет необходимости разрабатывать логику маппинга сообщений на брокере. Да, он реализует только одну из двух основных моделей асинхронного взаимодействия - Publisher-Subscriber - и не реализует Message Queue. Но понятно, что топиками можно пользоваться как заменой очередей, и в большинстве случаев проблем при этом не будет.
Облачные решения - за 10 лет из вызова превратились в новую реальность)
А вызов сейчас - внедрение AI. Как-то так)
#microservices #ai #cloud #kafka #rest