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

Можно? Наверное, но лучше сначала рассмотреть принципы для организации доступа к данным.

Принципы

Мы будем рассматривать и сравнивать между собой два принципа:

  • Данными владеет только владелец.

  • Локальность данных.

Данными владеет только владелец

Принцип «данными владеет только владелец» означает, что тот, кто создает данные, тот ими и владеет и если кто-то хочет эти данные получить, он должен вызвать владельца (например, через web API) и получить то, что ему нужно. По существу, это доведенное до предела правило «один источник правды». Это очень важное и ценное правило.

Данный принцип выглядит естественно, и позволяет строить красивые диаграммы микросервисной системы.

Однако, этот принцип в пределе приводит к построению хрупких систем. Представьте себе, что Ваш веб-сервер вызывает сервис А для получения необходимых данных. Сервис А владеет некоторой информацией (своей информацией), но, чтобы выполнить запрос BFF, ему надо информацию обогатить. Он делает запрос к сервису Б для обогащения. Цикл может повториться вовлечением сервиса В (Б -> В) и так далее. Это приводит к замедлениям из-за сетевых вызовов и хрупкости – достаточно выйти из строя сервису Ё, чтобы веб-сервер не получил свои данные и не смог выполнить запрос пользователя.

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

Локальность данных

Как показано выше, принцип владения в абсолюте непрактичен. Принцип владения должен пониматься как принцип «новую правду генерирует только один компонент – ее владелец», но потреблять правду (копировать себе) имеют право все, кому эта правда нужна. Таким образом, источник правды генерирует её, сохраняет себе и публикует её где-то (например, в топике Kafka), а все потребители подписываются на топик и копируют (возможно, с фильтрацией и обработкой) к себе, например, в свои базы данных.

Если это мысленно довести до предела, никакого API в сервисе быть не должно, так как есть информация, подписывайся и обрабатывай. Контракт тут – структура json в топике, а не вызовы API.

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

Разумеется, на практике довести до предела эту концепцию невозможно, и API всё равно существуют вместе с «топиками». Такая гибридная схема (немножко API + топик с объявленным контрактом) намного лучше и гибче, чем чистый API без топика.

Дублирование кода

Представим себе, что у нас работает сервис по схеме «нет никакого API, только топик и контракт на него». Добавим в эту картину два других сервиса-потребителя (и две команды разработки оных), которые успешно подписаны на топик, сохраняют данные к себе и обрабатывают их каким-то образом. Допустим, что обработка в каждом потребителе многоэтапная, и, так получилось, что один или несколько этапов обработки одинаковый.

Зачастую, одинаковая обработка данных вполне обыденная ситуация, и для каждого вида информации существуют «естественные обработки». Тривиальный (но плохой) пример – фильтрация по какому-либо статусу или полю. Бывают и более «жирные» случаи, которые как раз нас и интересуют.

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

Выхода можно предложить два:

  • Давайте просто создадим API в сервисе-источнике, напишем там код один раз, и пусть обе команды вызывают этот API, получают обработанные данные и уже их складывают себе.

  • Давайте просто заведем второй топик в сервисе-источнике, куда будут складываться обработанные данные и пусть обе команды подписываются уже на него, вместо топика «сырой» правды.

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

Также есть философская проблема дизайна: почему модифицируется поставщик, если появляется потребитель с новыми потребностями обработки? Может лучше модифицировать потребителя (и вернуться к риску дублирования кода)?

Ответ кроется в dev time.

Поставка данных и кода

Итак, мы имеем сервис-источник правды с топиком «правды». Мы имеем несколько потребителей, код которых содержит какую-то «естественную» обработку правды и эта обработка одна и та же в разных потребителях, но код написан свой,  так как сервисы независимы. Также, наш контракт на топик уже менялся, то есть часть сообщений в топике в старом формате v1, а те, что посвежее – в формате v2. Оба потребителя вынуждены были независимо делать поддержку и v1 и  v2. Что можно тут оптимизировать?

Идея заключается в том, что команда-разработчик сервиса «правды» должна не только создавать топик, публиковать контракт на него, но и распространять библиотеку (например, nuget пакет для .NET) для работы с этим топиком. Можно назвать эту библиотеку – SDK.

Что же можно упаковать в эту библиотеку:

  • Документацию на топик, контракт и любые другие описания.

  • Код, который позволяет подписаться на топик и сохранять данные из него в БД сервиса-потребителя.

  • Код, который знаком и умеет работать с разными версиями контракта данных (v1, v2…).

  • Общие (естественные) алгоритмы обработки информации из топика.

Как выглядит работа с точки зрения программиста команды-потребителя:

  • Осознана необходимость получить информацию («правду») из сервиса-источника.

  • Подключается библиотека общего кода от команды-источника, настраиваются реквизиты, задействуются необходимые естественные алгоритмы.

  • При запуске сервиса-потребителя, выполняется созданием структур в целевой БД, подписка на топик, и копирование информации в БД одновременно с обработкой естественными алгоритмами.

  • По мере выхода новых версий библиотеки он обновляется, чтобы уметь обрабатывать новые версии контракта.

Этот подход позволяет сэкономить:

  • На бойлерплейте типа «создать структуры в целевой БД», «подписаться», «скопировать в целевую БД».

  • На написании естественных алгоритмов обработки.

  • На когнитивной нагрузке команды-потребителя от версионирования контракта и на миграции структур в целевой БД по мере изменения контракта. 

Окончательный вид

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

  • Сам код сервиса, его логика, его БД.

  • Минимальное API сервиса (в пределе – отсутствующее).

  • Что-то, позволяющее подписываться на новую правду (топик Kafka, RabbitMQ, таблица в БД и т.п.). Здесь же – описание контракта данных.

  • Общая библиотека с описанием, подпиской, обработкой (версий) контракта и естественными алгоритмами обработки правды, которые могут быть задействованы потребителями.

Недостатки

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

  • Сложность. Вместо одного АПИ, появляются топики, библиотеки, естественные обработки (которые надо выявить) и т.п.

  • Лаги в процессе передачи данных. Пока в топик сохранится, пока прочитается, сохранится в целевую БД и станет доступна для бизнес-логики потребителя.

Сложность – это большой недостаток, и надо оценивать pro и cons. Что важнее – отсутствие цепочек вызовов (даже в отдаленном будущем) или простота системы?

Лаги тоже неприятная проблема, особенно, если учесть, что подписки, как и вызовы API, тоже выстраиваются в цепочки!  Сервис А опубликовал данные, сервис Б принял их, обработал, обогатил, опубликовал, сервис В принял их и ситуация повторяется и т.п. Так стоила ли овчинка выделки? Вернемся к простому API?

Нет, тут другая задержка: не задержка обработки запроса пользователя, а задержка доступа до новых(!) данных. Это – очень разные вещи. Очень много систем на практике толерантны к задержке новых данных. При этом очень мало систем толерантны к тормозам и хрупкости в обработке запросов от пользователя.

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

Проектируйте сбалансировано, друзья.

Комментарии (12)


  1. LeoKudrik
    16.03.2026 11:53

    так как это положит почти всю систему. А это уже свойство монолита

    Давайте так - это свойство Вашего монолита

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

    Нет у микросервисной архитектуры никакой устойчивости, если вы её сами не реализуете)

    Вы сейчас переизобрели event-driven "конвеер" типа ETL.


    1. lsoft Автор
      16.03.2026 11:53

      Свойство монолита (любого, не только моего) - если падает, то падает всё. Неотъемлемое свойство монолита. Речь была про это, возможно, выражено не полностью ясно.


      1. vadimr
        16.03.2026 11:53

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

        Согласен с коллегой выше, тут дело не такое простое.


        1. lsoft Автор
          16.03.2026 11:53

          Микросервисы используются не потому, что они проще, тут не о чем спорить, микросервисы намного сложнее. Это очевидно. Они используются для изоляции, горизонтального масштабирования, устойчивости к частичным отказам (и вообще - к существованию такого явления) и т.д. и т.п.

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


          1. LeoKudrik
            16.03.2026 11:53

            Есть смысл! Потому что вы произносите, мягко скажем, сомнительные тезисы и чаще всего этими тезисами аргументируете переход на микросервисные/распределённые архитектуры, что удорожает разработку в разы!

            Вам сразу зачем то понадобилась изоляция, почему-то монолиты перестали масштабироваться, а отказы у микросервисов вообще пропали как явление)


            1. lsoft Автор
              16.03.2026 11:53

              Статья совсем не о перехода на микросервисы. Но не будем спорить. Каждому своё. Удачи!


      1. LeoKudrik
        16.03.2026 11:53

        Если у вас в монолите, по какой-то причине, падает поток/корутина, то это не значит, что у вас должно упасть всё! Это значит, что поток не был покрыт try ... except (python) и не были обработаны все критические участки кода на ошибки.

        По такой же причине у вас может упасть любой микро/макро/наносервис!

        Так что не надо переваливать ответственность с кривых рук на архитектуру)

        UPD Более того, вы все так же можете перезапустить этот поток/корутину, если у вас это продумано и над изоляциями предусмотрены обёртки, которые, в случае падения критически важного потока/корутины, перезапускают её и восстанавливают стейт.


      1. Dhwtj
        16.03.2026 11:53

        Свойство монолита (любого, не только моего) - если падает, то падает всё. Неотъемлемое свойство монолита

        Нет. Ничего не мешает создать сервис в монолите и супервайзер для него. Падать всё может только через oom или какие-то общие системные ресурсы, но это скажем так зависит от технологического стека и много чего. Часто это влияет на устойчивость довольно мало и такие монолиты живут кварталами аптайма, опыт есть.


        1. vadimr
          16.03.2026 11:53

          Тут ещё, по большому счёту, вопрос терминологии, являются ли сервисы в монолите микросервисами или монолитом.


          1. Dhwtj
            16.03.2026 11:53

            В ИТ вообще нет четких определений нигде, это не математика.

            Но если сослаться на признанных авторов книги «Фундаментальный подход к программной архитектуре» авторов Марка Ричардса и Нила Форда, то микросервис это логическое И, одновременно несколько факторов:

            • Распределённая архитектура, то есть независимый деплой

            • По одной бд на микросервис (если нет, то это не микросервис, а service based architecture)

            • Вероятно что-то ещё, не важно сейчас

            В нашем случае оба признака не выполнены.


  1. vadimr
    16.03.2026 11:53

    Многие соображения очень справедливы, однако надо иметь в виду, что основная проблема микросервисной архитектуры в её традиционной реализации проистекает непосредственно из принципов OOP/OOD, а именно речь идёт о дублировании исполняемого кода в результате наследования. Разные микросервисы будут реализовывать одинаковые алгоритмы обработки разным исполняемым кодом (в лучшем случае отнаследованным от общего прототипа, а в худшем – реализованным независимо, как вы приводите в пример). Потом даже в этой общенаследной реализации в большом проекте неизбежно разойдутся версии фреймвока в разных микросервисах, и через несколько лет уже будет не разобраться, что там откуда берётся и как это всё вместе работает. В пределе это приводит к тому, что разные микросервисы даже скомпилировать в одном окружении уже не получается.

    Для решения этой проблемы микросервис должен представлять собой не реализацию, а только интерфейс, обрабатываемый одним универсальным ядром. Как, например, программ на Питоне много, а интерпретатор у них один (тут некоторые, правда, тоже скептически улыбнутся и вспомнят про env). В более практичном случае этот подход приводит к DSL. В общем, это переход от "тяжёлого" разделения функций программных компонентов к "легковесному".


  1. l1onsun
    16.03.2026 11:53

    По моему, статья странно построена:

    Есть какие-то ограничения, которые сразу принимаются как аксиомы - Что у нас (обоснованно) выбрана микросервисная архитектура - Что мы должны соблюдать принципы локальности/владения. И из таких предпосылок уже следует куча сложностей, которые мы героически решаем.

    Обычно интересно как раз понять изначальную проблему откуда вытекают ограничения. Чтобы понимать потом, что эти сложности оправданы.

    Иначе, вне контекста, хочется предложить выбрать (оправданно) монолитную архитектуру. И избежать кучи проблем =)