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