
Вас не удивило, что проблема 1970-х — высокая сцепленность кода — дожила до 2010-го и способствовала изобретению микросервисов? Если так, то вы не удивитесь и узнав, что микросервисы тоже её не решили. Сегодня индустрия относится к ним скептически. За последние десять лет мы поняли, что они не стали панацеей. Архитекторы в мире IT — это не учёные, и даже не художники. Это шаманы. Удачно разбить систему на несцепленные части было сложно в 1970-е, сложно и сейчас.
При этом микросервисы привносят проблемы, которых не было в монолитных приложениях.
Проблемы
Отказ от транзакций
В 1980–90 годы на платформе x86 под базами данных понимали dBase и Paradox. В этих СУБД транзакций не было. В середине 1990-х появились серверы SQL, и программисты вздохнули свободно. При всей сложности прикладной разработки, любой программист БД твёрдо уверен в своих транзакциях. Это действительно фундамент. И от него вам придётся отказаться, потому что микросервисы не должны зависеть друг от друга и, конечно, не должны заглядывать в чужие базы.
Одним из решений в этой ситуации будет отказ от транзакций. Стоят ли микросервисы такой жертвы?
Сеть
Вызывая метод класса, вы точно знаете, что он будет выполнен и вернёт результат. Отправляя HTTP-запрос, вы не можете быть уверены ни в чём. Сеть непредсказуема, и вам приходится это учитывать. Может сломаться оборудование. Может оборваться провод. Может зависнуть один из ключевых сервисов — DNS или балансировщик нагрузки. Иногда вам будет сложно найти точку отказа. Компоненты, которые используют сломанный сервис, начнут записывать ошибки в свои логи — и, возможно, вам предстоит проверить их все.
Версионирование
C# — это язык со статической, довольно строгой типизацией. Мы, как разработчики, уверены в целостности наших проектов. Если в одном из методов появится новый параметр, то наша программа не скомпилируется, пока мы не добавим значение во все места вызова. Даже если над проектом работают независимые команды, компилятор обеспечивает соответствие контрактов. В мире микросервисов это не так. Хотя микросервисы не вызывают друг друга, они могут обмениваться сообщениями. Представьте, что в одном из микросервисов изменился формат сообщений. Мы получим ошибку, которую трудно обнаружить.
Нам приходится обеспечивать соответствие версий микросервисов друг другу. И не всегда эта работа может быть автоматизирована.
Сцепленность
Реальный мир непросто разделить на независимые контексты. Их границы размыты: непонятно, где заканчивается один микросервис и начинается другой. Сцепленность всей системы не снижается, и в конце разработки вы получаете тот же монолит, но уже без транзакций и статической типизации.
Похоже, мы в тупике. С одной стороны, у нас монолит, который сложно поддерживать, а с другой — микросервисы, в которых нет ни предсказуемости, ни транзакций.
Как быть? Существует ли срединный путь?
Решения
Сразу скажу, что готовых надёжных решений не существует. Есть подходы к решениям, которые вы можете примерить на свою ситуацию.
Транзакции
Транзакциям посвящена седьмая глава книги «Высоконагруженные приложения» Мартина Клеппмана. Подсмотрим несколько решений.
Отказаться от транзакций. Зачастую мы делаем программы, в которых речь не идёт о жизни и смерти. Бизнес может считать пропажу сообщений в мессенджере не слишком большой проблемой. Полезно обдумать, какие из ваших бизнес-операций требуют атомарности, а какие — нет.
Убираться. Идея заключается в том, что при отсутствии транзакций микросервисы вносят изменения каждый в свою базу, в расчёте на то, что ошибок не будет.
Если ошибка возникнет, то операция будет признана неудачной, но в некоторых базах останется мусор — записи, для которых нет соответствия в других базах. Чтобы избавиться от мусора, мы разрабатываем фоновое приложение, которое проверяет базы на соответствие друг другу и удаляет одинокие записи.Нарушить принцип «один сервис — одна база». Это неправильно, но, если вы рассмотрели все альтернативы и не нашли решения лучше, то можно сделать исключение. Впрочем, учитывая сильную связь между микросервисами, возможно, лучше их объединить.
Сцепленность
Обсудим способы обеспечения целостности на примере интернет-магазина. Одной из важнейших сущностей в нём является Заказ.
Оформление заказа происходит за несколько шагов. Сначала покупатель добавляет товары в корзину, потом выбирает способ доставки, потом оплачивает покупку, и так далее, пока курьер не доставит заказ покупателю.
Весь путь от начала и до конца называется бизнес-процессом. Мы могли бы реализовать весь бизнес-процесс в рамках одной программы, но это как раз и ведёт к высокой сцепленности.
Некоторые части нашего магазина будут меняться очень редко, а некоторые — постоянно. Например, способы доставки и способы оплаты — очень нестабильная часть программы, точно так же, как и скидки.
Неизменными в нашем случае является сам процесс и отдельные его шаги. Добавление товаров в корзину — неизменный шаг, который вряд ли придётся переписывать.
Неизменные части мы размещаем в основном модуле программы (в центральном сервисе, в разделяемом ядре), а изменяемые — в микросервисах.
Теперь мы можем распределить работу над сайтом между несколькими командами. Кто-то будет развивать ядро, кто-то — микросервис с выбором доставки, а кто-то — микросервис со скидками.