Иллюстрация @alvaro_sanchez Некоторое время все сходили с ума по микросервисам. Невозможно было открыть любимый новостной агрегатор и не увидеть, чтобы какая-то неизвестная вам доселе компания рассказывает о спасении своего инженерного отдела с помощью микросервисов. Возможно, вы даже сами работали в компании, которую захватил ажиотаж крохотных, магических маленьких сервисов, которые решат все проблемы большой, запущенной, полной легаси кодовой базы. Естественно, в реальности все оказалось совсем наоборот. Когда смотришь назад, на произошедшее, то зрение оказывается ближе к 100%, чем когда смотришь с надеждой в будущее. Я хочу рассказать о некоторых важных заблуждениях и подводных камнях движения за микросервисы с точки зрения человека, который работал в компании, убежденной в идее целительных свойств микросервисов. Я не хочу, чтобы выводом этой статьи для вас стало "микросервисы == плохо", но в идеале я хотел бы, чтобы вы задумались о проблемах когда будете решать, подходит ли вам микросервисная архитектура. Не существует идеального определения, но люди, которые продвигают эту область смогли описать неплохой набор требований к системе, по которым её можно было бы отнести к "микросервисной" архитектуре. Можно сказать, это — не монолит. На практике это означает, что микросервис работает лишь с небольшой, максимально ограниченной областью задач. Он выполняет минимум функций для достижения определенной цели в вашем стеке. Вот более конкретный пример: допустим, в банке есть "Login Service", и вы, конечно, не хотите, чтобы у этого сервиса был доступ к финансовым транзакциям ваших клиентов. Эту область вы вынесете в какой-нибудь “Transaction Service” (имейте ввиду — давать названия очень сложно). В дополнение, люди часто подразумевают, что микросервисы должны общаться с другими сервисами удаленно. Так как они являются самостоятельными процессами, и часто запущены далеко от других, обычно такие процессы общаются друг с другом по сети используя REST или какой-нибудь другой протокол RPC. Пока все кажется достаточно простым — нужно просто обернуть небольшие куски системы в какой-нибудь REST API, и пусть все общаются друг с другом по сети. По моему опыту, есть 5 "истин", в которые верят люди, и которые не всегда бывают истинными: «Не нужно добавлять сетевое ограничение чтобы оправдать написание лучшего кода». Правда жизни в том, что ни микросервисы, ни любой другой подход при моделировании технического стека не являются требованием для написания более чистого и поддерживаемого кода. Конечно, раз движимых частей меньше, то снижается возможность писать ленивый и непродуманный код. Но это как говорить, что кражи можно победить убрав товары с витрин магазинов. Вы не решили проблему, вы просто удалили много возможностей. Популярный подход — строить архитектуру таким образом, что логические "сервисы" владеют частями предметной области. Это похоже на концепцию микросервисов, потому что зависимости, управляющие системой, будут явными. И бизнес-логика не будет влезать в разные углы. К тому же, такой подход не навлекает на себя чрезмерное использование сети и возможные проблемы, связанные с этим. Еще одно преимущество этого подхода в том, что оно напоминает сервис-ориентированную архитектуру (Service Oriented Architecture), построенную на базе микросервисов: если вы решите перейти к микросервисам, то большая часть работы уже выполнена, и у вас скорее всего уже есть неплохое понимание предметной области для правильного разделения ее частей. Настоящий SOA начинается с кода, и с течением времени продолжается на уровне физической топологии стека. «Распределенные транзакции никогда не легче» Снаружи может казаться иначе, но большинство предметных областей (особенно в новых компаниях, которым нужно создавать прототипы, делать пивоты и в целом переопределять саму область много раз) невозможно разделить на аккуратные, четкие коробочки. Зачастую произвольной части системы требуется получить данные о другой части чтобы работать корректно. Все усложняется еще сильнее, когда она делегирует операцию записи данных другой части, за пределами своей предметной области. Когда вы вышли за пределы своей зоны влияния, и вам нужно задействовать другие части для хранения или изменения данных, то вы попали в страну распределенных транзакций. Когда в рамках одного запроса задействовано несколько удаленных сервисов, сложность сильно возрастает. Можно ли обращаться к ним параллельно или нужно обращаться последовательно? Известны ли вам все возможные ошибки заранее (на уровне приложения и на уровне сети), которые могут возникнуть в любой момент, на любом участке цепи, и что эти ошибки будут означать для самого запроса? Зачастую, каждой из распределенных транзакций требуется свой подход к обработке ошибок. И понять все ошибки и понять, как их решать — это очень, очень большая работа. «Можно сильно улучшить производительность монолита если добавить немного дисциплины» Это заблуждение сложно опровергнуть, потому что в реальности мы часто можем ускорить отдельную систему, если уменьшим количество задач или количество загружаемых зависимостей, и так далее. Но в целом, это не системное доказательство. Я не сомневаюсь, что если вы перешли на микросервисы, то изолированный в этих сервисах код мог ускориться, но не нужно забывать про появившиеся задержки из-за сетевых запросов. Сеть никогда не бывает такой же быстрой, как внутренняя коммуникация, то иногда бывает «достаточно быстрой». К тому же, многие истории об увеличении производительности на самом деле связаны с преимуществами нового языка или целого технологического стека, а не просто архитектурой микросервисов. Если переписать старое Ruby on Rails, Django или NodeJS приложение на новом языке вроде Scala и Go (два популярных выбора для микросервисной архитектуры), то производительность улучшится хотя бы из-за улучшений производительности новых технологий в целом. Но этим языкам вообще-то без разницы, если вы будете называть их процессы "микро". Они работают быстрее ввиду простых факторов вроде компиляции. Также, в большинстве приложений в стартапах чистая производительность процессора и памяти почти никогда не является проблемой. Обычно проблема в I/O, а дополнительные сетевые вызовы только увеличивают I/O. Когда много инженеров работают в изолированных кодовых базах, то возникает синдром «это не моя проблема». Может показаться, что когда маленькие команды работают над маленькими частями головоломки, то все будет проще. Но в итоге такая конфигурация может привести к проблемам. Самая большая проблема в необходимости запускать постоянно растущее количество сервисов для любого, даже самого маленького изменения. То есть нужно вложить время и усилия чтобы построить и поддерживать систему, где каждый инженер может запускать все локально. Такие штуки, как Докер могут упростить этот момент, но кому-то все же придется поддерживать конфигурацию на протяжении жизни проекта. К тому же, это усложняет написание тестов. Чтобы написать нормальный набор интеграционных тестов, нужно понимать все сервисы, связанные с конкретной операцией, принять во внимание все возможные ошибки и так далее. На понимание системы будет уходить больше времени, чем на разработку системы. И хотя я никогда не скажу инженеру, что понимать систему — это трата времени, я все же хочу предостеречь его от преждевременного добавления сложности. И, наконец, такая конфигурация создает социальные проблемы. Баги могут жить одновременно в нескольких сервисах и требовать изменений на уровне нескольких команд. Им нужно синхронизироваться и координировать усилия. К тому же, люди могут терять чувство ответственности и стараться спихнуть как можно больше проблем другой команде. Когда инженеры работают вместе, над одной кодовой базой, то понимание системы растет одновременно с пониманием друг друга. Они с большей вероятностью будут работать над решением проблем вместе, вместо того, чтобы быть королями и королевами маленьких изолированных княжеств. «Микросервис можно масштабировать вширь также, как и монолит» Упаковать сервисы в отдельные юниты и масштабировать их с помощью, скажем, Докера — это хороший подход для горизонтального масштабирования, без сомнений. Однако, это можно сделать не только с микросервисами. Подход работает и в случае с монолитными приложениями. Можно сделать логические кластеры монолита, которые обрабатывают части трафика. Например, входящие API-запросы, фронтэнд панели управления и фоновые задачи могут находиться в одной кодовой базе, но не обязательно на каждой машине обрабатывать все три типа запросов. Преимущество здесь такое же, как в случае с микросервисами: отдельные кластеры можно настраивать в зависимости от нагрузки, а также масштабировать их по отдельности, реагируя на скачки трафика. Микросервисная архитектура с самого начала толкает к такому подходу, но ничего не мешает вам применить такой же метод для масштабирования монолитного стека. «Когда вы, как инженерная организация, будете готовы» Я хочу закончить эту статью обсуждением того, когда же наступит правильное время перейти к микросервисам (или, если вы уже начали, как понять, было ли это правильным моментом). Самая главная вещь на пути к хорошей, работающей микросервисной архитектуре — это простое понимание своей предметной области. Если вы не понимаете ее, или все еще пытаетесь понять, то микросервисы принесут больше проблем, чем решений. Но если у вас уже есть глубокое понимание, то вам знакомы границы и зависимости, и микросервисный подходом может быть правильным шагом. Еще один важный момент это ваше рабочее окружение, особенно, как оно будет выглядеть в контексте распределенных транзакций. Если вам знакомы пути каждой категории запросов в системе, и вы понимаете где, как и зачем эти пути могут сломаться, тогда вы сможете построить распределенную модель для обработки запросов. Кроме понимания окружения есть тема мониторинга окружения. Она выходит за пределы дискуссии "микросервис vs. монолит", но мониторинг должен быть в основе инженерных усилий. Вам скорее всего понадобится много информации о разных частях системы чтобы понять, почему одна из частей ведет себя плохо или даже генерирует ошибки. Если у вас налаженная система мониторинга составных частей системы, тогда вы сможете понимать поведение системы при горизонтальном масштабировании. И, наконец, если вы на самом деле продемонстрируете пользу своей инженерной организации и бизнесу в целом, тогда движение к микросервисам поможет расти, масштабироваться и зарабатывать деньги. Конечно, строить интересные системы и пробовать новые штуки — это круто, но в итоге у любой компании есть самый важный показатель. Если приходится откладывать релиз новой фичи, которая принесет компании деньги, из-за поста в каком-то блоге про "монолиты это плохо", то придется оправдывать это решение. Иногда это того стоит. Иногда — нет. Знать, когда нужно настоять и уделить время поможет вашей репутации в долгосрочной перспективе. Надеюсь, теперь у вас появился набор условий и вопросов, которые нужно разрешить если кто-то предложит микросервисный подход. Как я говорил в начале, моей целью не было доказать, что "микросервисы это плохо". Но спрыгивать на микросервисы не подумав обо всех возможностях деталях и вопросах — это напрашиваться на проблемы в будущем. Если спрашивать моего совета, то я бы смотрел в сторону "внутренних" сервисов на основе чистых, хорошо определенных модулей в коде. Их уже можно будет вынести в настоящие сервисы в будущем, если появится такая необходимость. Этот подход — не единственный возможный, и уж точно не панацея от плохого кода. Но он поможет вам продвигаться вперед быстрее, чем если вы закопаетесь в микросервисы раньше нужного.