Вольный перевод https://nexo.sh/posts/microservices-for-startups/

Раннее разделение на микросервисы может снизить скорость работы вашей команды

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

Раннее внедрение микросервисов

Болевая точкаЧто в действительности происходитНегативные последствия
Сложность развертыванияВнесение изменений в 5 и более сервисов для реализации одной функции Увеличение времени релиза
Сложность настройки среды разработкиОбъемные Docker, сломанные скрипты, трюки для настройки под разные операционные системыМедленная адаптация новых разработчиков, частые поломки среды разработки
Дублирование CI/CDНесколько дублирующих процессовДополнительная работа на каждый сервис
Межсервисная связность“Несвязанные” сервисы связаны общим состояниемМедленные изменения, затраты на координацию
Сложный мониторингРаспределенная отладка, логирование, мониторингДолгая настройка
Фрагментированные тестыТесты разбросаны по сервисамХрупкие тесты, низкое доверие к тестам

Монолиты - не враги

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

Со временем в приложении часто появляются ненужные функции, код раздувается и становится запутанным, но монолиты в таких ситуациях проявляют свои лучшие качества - они продолжают работать. Монолит, даже структурно сложный, позволяет вашей команде сфокусироваться на том, что важно для стартапа:

Громадное преимущество монолита - это простота в развертывании. Обычно такие проекты построены вокруг проверенных фреймворков (например, Django для Python, ASP.Net для C#, Nest.js для Node.js, и т.д). Разрабатывая монолитное приложение вы получаете широкую поддержку сообщества, которое создавало и проектировало эти платформы для применения в первую очередь в монолитах.

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

Со временем оно превратилось в многофункциональный сервис, обрабатывающий сотни гигабайт документов и интегрированный во множество сторонних сервисов, работая на простом стеке PHP + Apache.

Команда опиралась на лучшие практики Laravel сообщества, и мы смогли значительно масштабировать приложение, по прежнему удовлетворяя потребности и ожидания бизнеса.

Нам никогда не приходилось разделять систему на микросервисы или внедрять более сложные инфраструктурные шаблоны. Простая архитектура позволила избежать многих проблем. Вспомните статью Basecamp про “Величественный монолит”, в которой объясняется почему простота сама по себе является суперсилой.

Монолиты считаются сложно масштабируемыми, но причиной этому часто является именно плохая внутренняя структура приложения, а не его монолитность.

Вывод: Хорошо структурированный монолит позволяет сосредоточиться на ценности продукта.

Но ведь микросервисы - это “лучшие практики”?

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

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

Вывод: Микросервисы - инструмент масштабирования, а не стартовый шаблон приложения.

Как именно микросервисы вредят (особенно на ранних стадиях)

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

Диаграмма: затраты на координацию росли линейно с количеством сервисов и экспонинцеально при подключении продуктового менеджера и при попытках вписаться в дедлайн.

Вот наиболее характерные анти-паттерны, появляющиеся на ранней стадии развития приложения при внедрении микросервисов

1. Проблема с разделением ответственности

Теория популяризует разделения бизнес-логики на домены: сервис пользователей, сервис продуктов, сервис заказов и т.д. Это идеи предметно-ориентированного проектирования (Domain-Driven Design) или чистой архитектуры (Clean Architecture), которые имеют смысл при масштабировании, но на ранней стадии разработки они усложняют стуктуру приложения, прежде чем сам программный продукт станет стабильным и верифицированным. В этоге вы получаете:

В одном проекте я наблюдал, как команда разработчиков формировала разные сервисы для управления пользователями, аутентификацей и авторизацей. Это приводило к проблемам в развертывании и координации для любого API, которые они разрабатывали.

На самом деле бизнес логика в общем случае не делится по границам сервисов. Преждевременное разделение делает систему более хрубкой и сложной для изменений.

Гораздо полезнее - искать и устранять конкретные проблемы с маштабированием приложения.

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

Вывод: Делите приложение на сервисы не на основании теории, а на основании анализа существующих проблем с масштабированием.

2. Неконтроллируемый рост количества репозиториев и сложности инфраструктуры

Во время работы над приложение обычно важные следующие вещи:

Работая с микросервисами, вы вынуждены внедрять это для каждого сервиса в отдельности. Пока приложение находится в единственном репозитории, вы можете упростить свою жизнь, используя централизованную конфигурацию CI/CD. Некоторые команды разделяют микросервисы в отдельные репозитории, что значительно усложняет поддержку согласованности кода и настроек.

Для команды из трех человек это непросто. Переключение контекста между репозиториями и инструментами увеличивает время разработки каждой функции.

Как улучшить ситуацию, используя единый репозиторий и единый язык программирования

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

Для проектов на Node.js я рекомендую использовать такие инструменты работы с единым репозиторием как nx или turborepo. Оба инструмента:

Эти инструменты экономят время, потраченное на написание связывающего кода и переработу оркестрации. Но тем неменее это все-таки компромисс:

Для микросервисов на основе GO неплохая идея - использовать единое рабочее пространство (в терминах GO) с заменой диркетив в go.mod. По мере масштабирования приложения модули всегда можно будет вынести в отдельные репозитории.

Вывод: единый репозиторий с объединенной инфраструктурой экономит ваше время, дает согласованный и адекватный код.

3. Проблемы с развертыванием на машинах разработчиков = проблемы со скроростью разработки

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

Проекты на ранней стадии развития зачастую страдают от:

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

Еще в одном проекте, который создавался разработчиком “в одни руки”, процесс настройки среды запуска микросервисов на основе Docker-контейнеров использовал локальную файловую систему. Конечно, это эффективно, когда ваш компьютер работает под управлением Linux.

Но подключение нового фронтэндера на старом ноутбуке с Windows превратилось в страшное дело. Им пришлось развернуть 10 контейнеров, чтобы просто запустить на компьютере фронтэндера пользовательский интерфейс. Ничего не работало: диски, сеть, совместимость контейнеров.

В итоге мы сделали прокси на базе Node.js для имитиации веб-сервера без контейнеров. Это не было элегантным решением, но позволило сотруднику начать работу.

Вывод: если ваше приложение развертывается только на единственной операционной системе, то вы - на расстоянии одного ноутбука от катастрофы.

Совет: В идеале, ваш проект должен запускаться всего лишь с помощью git clone && make. Если это не возможно, то поддерживайте актуальный README с инструкцией для Windows/macOS/Linux. В настоящее время существуют некоторые языки программирования, имеющие плохую поддержку Windows (например, OCaml), но современный широко распространенный стек разработки обычно хорошо работает в любой операционной системе. Ограничение одной операционной системой часто является симпотомом недостаточной инвестиции в DX.

4. Несовместимость технологий

Помимо архетиктуры, ваш технический стек также влияет на то, насколько болезненными становятся микросервисы: далеко не каждый язык хорошо вписывается в микросервисную архитектуру.

Важно выбрать правильный технический стек на самом раннем этапе. Если вам важна производительность, стоит обратить внимание на JVM и ее экосистему, а так же возможность развертывания для поддержки масштабирования и запуска в микросервисной архитектуре.  Если вам важна скорость разработки без масштабирования инфрастуктуры, то что-то вроде Python - неплохой выбор.

Зачастую команды поздно осознают, какие проблемы они получили, выбрав определенную технологию, и вынуждены переписывать бекенд на другой язык. Например, вот эта команда была вынуждена переписать систему с Python 2 на Go.

Однако, если вам действительно необходимо, вы можете объединить разные языки в одной системе с помощью таких протоколов как gRPC или с помощью асинхронного обмена сообщениями. Например, если вам необходимо дополнить свою систему функциями машинного обучения или ETL, то вам наверняка понадобится внедрить Python из-за его уникальных библиотек. Для этого нужны будут дополнительные разработчики с соответствующей компетенцией, иначе ваша небольшая команда завязнет в борьбе со сложностью объединения нескольких стеков разработки.

Вывод: Выбирайте технологию в соответствии с вашими ограничениями, а не с вашими амбициями.

5. Скрытая сложность: Коммуникации и мониторинг

Микросервисы требуют:

В монолите описать ошибку можно простой трассировкой стека вызовов. В распределенной системе появляются проблемы “почему сервис A падает, если сервис B  вдруг подвисает на 30 секунд?” И вы должны потратить время на построение адекватной системы отслеживания ошибок. Чтобы сделать это “правильно” необходимо определенным образом настроить ваши приложения, например интегрировать OpenTelemetry для поддержки трассировки, или использовать сервисы, предоставляемыми ваши облачным провайдером, например, AWS XRay, если вы используете сложную serverless-систему. Но для этого вам необходимо сместить фокус с разработки новых функций на создание сложной системы мониторинга инфраструктуры, которая позволит вам понять, действительно ли авше приложение корректно функционирует в продакшене.

Конечно, ситема мониторинга нужна и для монолита, но здесь ее построить гораздо проще с точки зрения количества сервисов, которые необходимо поддерживать в согласованном состоянии.

Совет: Поймите, что распределенность системы - не бесплатная опция. Она нужна для решения определенного класса инженерных задач.

Когда микросервисы действительно имеют смысл

Несмотря на упомянутые сложности с микросервисами, в некоторых случаях их применение действительно полезно:

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

Еще в одном проекте в нише недвижимости от прошлой команды нам остался код на Python, реализующий аналитику на основе данных в MS-SQL, и мы решили, что было бы слишком затратно реализовывать вокруг него приложение на Django. Код имел обособленные зависимости и был неплохо изолирован, таким образом мы оставили его в качестве отдельного приложения и только иногда меняли его, когда что-то работало не так, как ожидается. Это неплохо работало для нашей небольшой команды, потому что сервис аналитики редко требовал модификации и поддержки.

Вывод: Используйте микросервисы, когда рабочие процессы изолированы, а не только потому что они кажутся “чистыми”.

Практические советы для стартапов

Если вы работаете над первой версией приложения:

Самое главное: Оптимизируйте скорость разработки.

Скорость - это кислород вашего стартапа. Преждевременное разделение на микросервисы медленно высасывает кислород — до тех пор пока однажды вы не сможете дышать.

Вывод: Начните с простого, оставайтесь прагматичными, и разделяйте на микросервисы только того, когда это необходимо.

Если вы выберете микросервисную архитектуру

У меня были проекты, гдя микросервисы были внедрены слишком рано, и ниже рекомендации, что делать в таком случае:

Подводя итог: если вы все же решили использовать микросервисы, вам следуюет заранее понимать, какие затраты придется заплатить в виде дополнительного времени на разработку и обслуживание, чтобы сделать настройку работоспособной для каждого инженера в команде.

Вывод: Если осознанно усложняете систему, сделайте сложность управляемой.