Микросервисы, скалы и гигантские приложения

Монолитная архитектура — это как?
9 минут

Как работает мозг разработчика? Очень просто и логично. Например, когда команда разрабов садится за написание новой программы, чаще всего естественным образом выбирается самый простой способ организации кода. А именно — свалить все функции и фичи в одну большую кучу.

Нет, конечно, в коде будет определенная структура. Функции и классы будут аккуратно разбиты на файлы и пакеты, и все это (возможно) будет разложено по папочкам. В итоге на выходе получится набор функций, красиво и логично распиханный по сотням текстовых файлов. И код из одного файла легко может вызывать код из другого файла, без каких-либо ограничений.

Такую структуру очень удобно использовать — написал компонент, закинул его в нужную папку и дальше повторно используешь в других частях системы. Живое воплощение инженерного принципа DRY — don’t repeat yourself — «не пиши код для решения задачи дважды, используй уже написанное».

Такая организация кода (когда все в одном месте) называется «монолитной архитектурой». Конечно, в этой монолитной архитектуре есть свои неоднородности в виде пакетов, файлов и модулей. Но в целом такой код — это огромная скала, состоящая из прожилок, кристалликов и других включений. В скале видно некоторую внутреннюю структуру — но это все же скала. Монолит.

Типичные проблемы с монолитами

Стукнешь здесь — треснет там. Любой первобытный человек знает — стукнув по скале или отпилив кусок, можно вызвать трещины в совершенно другой части этой самой скалы.

А мы с вами знаем, что, внеся изменения в одном месте монолитной программы, можно вызвать повреждения в других ее частях. Все дело в том, что компоненты в монолите могут иметь очень сложные и неочевидные взаимосвязи. Классы, вызывающие функции, получающие на вход другие классы, порождающие новые классы, которые должны быть упакованы в функции — ну… вы поняли.

Мы все стараемся так не делать, но иногда так получается. Например, если нужно срочно внести в код незапланированные изменения в бизнес-процессах и технических заданиях. Именно так классы и функции в коде обрастают многочисленными и неочевидными связями, которые легко можно сломать, даже не узнав об этом.

Сложно обрабатывать. Монолитные куски скал большие, тяжелые, твердые, трескаются от любого неловкого движения.

Такая же история с монолитными программами. Попытка изменить что-то в коде может занять массу времени и иметь далеко идущие последствия.

Например, функция, которую вы меняете, может использоваться в ряде других функциональных блоков. И даже если связи между этими блоками вполне очевидны, вам обязательно нужно не забыть проверить и изменить все зависимые блоки. А если с этими блоками связаны еще какие-то – вам придется идти дальше по цепочке, проверять, вносить изменения, проверять, вносить изменения.

Когда упали — трудно поднять. О да, куски скал падают с грохотом, их тяжело поднимать.

Монолитные приложения тоже падают с грохотом, зачастую парализуя все бизнес-процессы в компании. Даже если ошибка была в коде маленькой и незначительной операции, которая нужна 0.01% пользователей, — в случае сбоя может прилечь вся ваша система.

Нужен спец. Ещё бы, не каждый простой охотник на мамонта может выточить из скалы лицо вождя (племени).

С монолитным кодом — та же история. Из-за своей сложности монолитные приложения требуют от инженера глубокого понимания внутреннего устройства кода. Это понимание обретается либо в ходе долгой работы над проектом, либо путем чтения документации к проекту. А документация часто бывает неполной, ошибочной и не отражающей всей нужной информации о системе.

Вот и выходит, что с монолитной программой хорошо могут работать только те, кто провел за этим кодом много времени. Новички же потратят кучу времени на выяснение того, как программа работает. И не факт, что разберутся. А теперь представьте, что всех ваших ключевых опытных разработчиков схантил конкурент! Это может полностью парализовать разработку кода на большой срок. Или даже убить ваш бизнес.

Разделяй и властвуй

Один из подходов, решающий все эти проблемы (а заодно ускоряющий, удешевляющий и упрощающий разработку в целом) — это микросервисы.

Микросервис — это небольшая программа, запущенная на своем сервере и работающая только над одним типом задач. Например, микросервис склада, который предоставляет другим программам данные о запасах товаров в вашем магазине. Или микросервис авторизации, который принимает пользовательские ключи и пароли, а потом решает, давать ли юзеру доступ в систему или нет. Или микросервис отправки СМСок юзерам.

Любую сложную систему можно разбить на множество мелких сервисов, общающихся друг с другом. Разговаривать сервисы могут любым удобным способом: кидая друг другу сырые TCP-пакеты, вызывая методы по HTTP или используя умную и удобную систему доставки сообщений вроде RabbitMQ.

Почему микросервисы — это классно?

Минимум кода. Микросервисы должны быть действительно “микро”. Программного кода в них должно быть реально мало. В идеале ровно столько, чтобы пара программистов могла написать такую функциональность с нуля за 2-7 рабочих дней.

В таком объеме кода любой специалист сможет разобраться за несколько часов и при необходимости полностью переписать за короткий срок. Увольнение важного и опытного сотрудника не затормозит ваш бизнес — другие разработчики легко во всем разберутся.

На лицо понятные, сложные внутри. Все детали реализации сервиса убираются под капот. Снаружи торчит только простой и удобный интерфейс. Например, ваш сервис доставки СМСок умеет отправлять сообщения через 15 каналов связи, балансируя между ними нагрузку, но все эти тонкости не видны снаружи — на вход сервиса поступают лишь номера телефонов и тексты сообщений.

Как бонус такой подход дает возможность писать сервисы на любом языке программирования, удобном команде. Допустим, вы наняли специалиста, пишущего на странном языке Rust. Вам не нужно переучивать его работать, допустим, с языком Go, он просто может написать нужную логику на Rust, упаковать ее в микросервис и дать другим членам команды доступ к фиче.

Гибко и надежно. Допустим, ваше приложение — магазин, состоящий из набора сервисов (склад, обработка платежей, корзина, пользователи, витрина с товарами). И вот в середине рабочего дня у вас отваливается интеграция с платежным шлюзом. В случае с монолитной архитектурой весь ваш магазин может прилечь (а может и нет, но этого вы не узнаете этого до момента самого сбоя).

В случае микросервиса ваш бизнес сможет показывать клиентам товары и формировать корзины с заказами. А в момент оплаты пользователь будет видеть сообщение “Вы сможете оплатить заказ позже”. Как только сервис оплаты оживет, вы уведомите клиентов об этом и часть из них вернется и оплатит заказ. Пусть это не 100% вашей нормальной выручки, но и не 100% убытка, как было бы в случае полного отказа монолитной системы.

А еще вы можете держать две версии микросервиса оплаты, работающих с разными процессинговыми центрами. В случае отказа одного сервиса, вы отправляете все запросы на резервный — и у вас все хорошо.

Легко чинить. Разработка становится прозрачнее, снижаются её сложность и стоимость. Микросервисную архитектуру легко чинить — вы всегда точно знаете, что и где сломалось. Нет данных о запасах товаров – чиним склад. Не пришла смска – чиним смски.

Кода в сервисе мало, поэтому найти проблему всегда легко. А еще такая архитектура — эталон подхода DRY (не пиши код дважды).

За все надо платить

Как известно, у всего есть цена. И у удобств микросервисной архитектуры она, разумеется, тоже есть.

  • Возникают накладные расходы на коммуникации между компонентами. Если в монолитном приложении модули могут общаться через общие переменные в памяти, то микросервисы шлют друг другу запросы по сети. Пересылка данных вызывает небольшие задержки, которые могут быть критичны для приложений, где важна высокая скорость.
  • Усложняется управление инфраструктурой. Каждый сервис принято запускать на своем отдельном сервере. Это потребует от вас вложений в автоматизацию работы с серверами и надежного провайдера облачных мощностей. Стоимость развертывания облачных машин крайне низка, их можно вводить в строй сотнями и тысячами буквально за секунды (конечно, если ваш облачный оператор действительно знает свое дело) — а это именно то, что жизненно необходимо для хорошей микросервисной архитектуры. Сделал новую версию микросервиса — сразу создал под него сервер.
  • Требуется грамотное техническое руководство, которое понимает, что и как нужно выделять в микросервисы и как организовывать коммуникации. Специалисты с такими навыками не редкость, но и не такая уж частая находка на рынке айтишного персонала.

Как распилить монолит

План по переходу с монолитной архитектуры на микросервисы выглядит так:

  1. Разбиваем монолит на простые и понятные функции.
  2. Создаем задачи для отдельных команд разработки.
  3. Пишем сервисы.
  4. Постепенно заменяем монолитные куски кода на вызовы изолированных сервисов.
  5. Разворачиваем сервисы на изолированных облачных серверах.
  6. Грамотно рулим парком машин и сервисов и следим за их состоянием.

Ура, у нас в руках относительно дешевая, отказоустойчивая, легкая в масштабировании и рефакторинге система. Profit!

Иллюстрация в шапке: https://www.thoughtco.com/debitage-waste-flakes-stone-tool-processing-170697

Group 40Group 44Group 43Group 46Group 41Group 27Group 42Group 39