> For the complete documentation index, see [llms.txt](https://folko.gitbook.io/podgotovka-k-sobesedovaniyu/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://folko.gitbook.io/podgotovka-k-sobesedovaniyu/arkhitekturnye-voprosy/modulnost-vs-mikroservisy.md).

# Модульность VS Микросервисы

## Что такое модуль <a href="#chto-takoe-modul" id="chto-takoe-modul"></a>

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

Хотя формально такое приложение можно назвать монолитом, у этого подхода намного больше общего с микросервисами, поэтому и сравнивать его имеет смысл именно с микросервисным подходом. Как и микросервис, каждый модуль:<br>

* Должен иметь тщательно спроектированное и документированное API, и только оно должно быть доступно другому коду, выполняющемся в этом же приложении (контроль этого ограничения требует поддержки на уровне языка и/или инструментов разработки).
* Должен выполняться независимо от кода других модулей и иметь возможность эффективно взаимодействовать с API других модулей без использования сетевых протоколов (это требует поддержки на уровне языка: нити/горутины/акторы, каналы/сообщения, etc.).
* Должен использовать собственное хранилище данных (если оно ему нужно).
* Может находиться в отдельном репо или монорепо приложения.
* Может использоваться разными приложениями.
* Может предоставлять доступ к своему API по сети (только модулю это делать не обязательно, да и доступ может быть не полным, как внутри приложения, а частичным).

В отличие от микросервисов, модуль:

* Обычно нельзя деплоить независимо (если только язык не поддерживает горячую замену части кода в запущенном приложении).
* Нельзя перезапустить независимо от всего приложения.
* Нельзя масштабировать независимо от других модулей.
* Не может помешать доступу к "своим" файлам из других модулей, если они решат нарушить изоляцию между модулями. Ни язык ни инструменты такое нарушение предотвратить не смогут, так что здесь придётся полагаться исключительно на дисциплину самих разработчиков и ревью кода.
* Возможно, может без ограничений использовать библиотеки, общие с другими модулями — но тут я не уверен. Негативные последствия этого будут не такие значительные, как для обычных микросервисов, но всё-таки будут. Особенно если модуль будет в будущем запущен как полноценный микросервис (а этому, по большому счёту, пока что ничего не мешает — модули довольно легко превращаются в отдельные микросервисы при необходимости).

В отличие от обычных библиотек, модуль:

* Не должен разделять с использующим его API кодом никаких общих данных — все данные должны передаваться через API либо в виде копии, либо, если данных очень много и к ним нужен доступ только на чтение, в виде неизменяемой (immutable) структуры данных (требует поддержки на уровне языка).
* Не предоставляет функций, которые могут вызывать другие модули — т.е. не выполняет никакого своего кода в чужом потоке выполнения (нити, горутине, etc.), таким образом изолируя от вызывающего кода даже исключения, которые могут возникать в коде модуля.<br>
  * У модуля есть публичная функция инициализации и запуска модуля, которая вызывается при запуске приложения, но она не предназначена для (повторного) вызова из других модулей.
* Имеет собственные, полностью изолированные от других модулей:<br>
  * Конфигурацию (передаваемую ему при запуске приложения).\
    &#x20;• Она может включать общие для всех модулей настройки логирования.
  * Хранилище данных (если оно ему нужно).\
    &#x20;• Поддержка версионирования схемы этих данных и миграций при обновлениях — так же ответственность модуля, хотя запуск миграций данных каждого модуля может быть частью общего для всех модулей процесса деплоя приложения.

В большинстве случаев модули не нуждаются в каком-либо реестре сервисов — вместо регистрации себя и поиска других модулей они получают необходимые им интерфейсы других модулей при запуске, когда запускающееся приложение вызывает функцию инициализации каждого модуля (в порядке, определяемом зависимостями между модулями). В качестве побочного эффекта это позволит сразу обнаружить циклические зависимости между модулями, если они появятся (и, по возможности, изменить архитектуру так, чтобы от них избавиться).<br>

## Где нужны модули <a href="#gde-nuzhny-moduli" id="gde-nuzhny-moduli"></a>

Есть константная добавленная сложность ([accidental complexity](https://en.wikipedia.org/wiki/Accidental_complexity)), одинаковая в каждом микросервисе (регистрация/обнаружение сервисов, подключение и переподключение к ним, авторизация между сервисами, (де)маршалинг и шифрование трафика, использование прерывателей зацикленных запросов, реализация трассировки запросов, etc.). Есть аналогичная константная добавленная операционная сложность (необходимость автоматизации тестирования и выката, реализация детального мониторинга, агрегация логов, использование служебных сервисов для регистрации и поиска сервисов, для хранения конфигурации сервисов, для аудита, etc.). С этим можно смириться, потому что реализовать всё это можно один раз, по мере роста количества микросервисов эта сложность не растёт, а преимущества микросервисов с лихвой компенсируют эти затраты.<br>

Но есть сложность, зависящая от бизнес-логики конкретного приложения и увеличивающаяся по мере развития приложения, от которой хотелось бы избавиться хотя бы в тех приложениях, которым не требуется возможность масштабирования и высокая доступность (или хотя бы той части кода таких приложений, в которой нет явной необходимости взаимодействовать с внешними сервисами):

* Необходимость разделять идемпотентные и не идемпотентные запросы.
* Для всех запросов на клиенте:<br>
  * Необходимость использовать таймауты (которые могут быть разными для разных запросов).
  * Необходимость использовать больше асинхронности для ускорения работы.
  * Больше ситуаций eventual consistency и вызванных этим сложностей.\
    &#x20;• В т.ч. необходимость иногда кешировать данные других сервисов.
  * Необходимость повторять некоторые запросы:\
    &#x20;• Используя ограничение по количеству или времени повторов.\
    &#x20;• Используя задержки между повторами.
* Дополнительно для не идемпотентных запросов на клиенте:<br>
  * Необходимость реализовать возможность определения дубликатов при повторе запросов.
* Дополнительно для не идемпотентных запросов на сервере:<br>
  * Необходимость определять и обрабатывать дубликаты запросов (по-разному, в зависимости от бизнес-логики каждого запроса).
* В некоторых случаях необходимость выполнять запросы в том же порядке, в\
  &#x20;котором они были отправлены.

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

* Падение модуля приводит к падению всего приложения.
* Уменьшается скорость сборки и тестирования.<br>
  * Не уверен, что скорость сборки это реальная проблема — есть много способов её ускорения.
  * В принципе возможно при тестировании ветки/PR запускать только тесты изменившегося модуля, а тесты всего проекта выполнять только перед деплоем — это в достаточной степени должно нивелировать эту проблему.
* Уменьшается скорость запуска (разогрев кешей, etc.).<br>
  * Здесь проблема не столько в реальном замедлении, сколько в том, что когда сервисов много время запуска "размазано" по нескольким сервисам, которые (пере)запускаются независимо друг от друга. Тем не менее, видимый эффект более медленного запуска этот факт не отменяет.

Так же у модульного подхода появляются новые достоинства:<br>

* Увеличивается скорость взаимодействия между модулями-сервисами, что и позволяет значительно уменьшить необходимость в асинхронности, eventual consistency и кешировании.
* Пока у модуля нет внешнего (сетевого) API его API намного проще изменять и деплоить эти изменения атомарно с деплоем использующих этот модуль клиентов (других модулей этого же приложения).
* Совместную работу модулей проще тестировать, чем группу микросервисов.
* Проще использовать монорепо (если хочется), хоть это и не обязательно.<br>
  * Монорепо упрощает рефакторинг и изменение API, по крайней мере пока есть гарантия что у модуля нет сетевого интерфейса и внешних клиентов.

## Резюме <a href="#rezyume" id="rezyume"></a>

В общем и целом подход выглядит достаточно соблазнительным — мы получаем возможность писать монолитное приложение в виде кучки по-настоящему хорошо изолированных частей (причём контролировать это будет по большей части язык и/или инструменты, а не внутренняя дисциплина), разрабатываемых в микросервисном стиле (в т.ч. разными командами в разных репо), которые "лёгким движением руки" могут превратиться в настоящие микросервисы если в этом возникнет реальная необходимость… А пока её нет — мы можем использовать обмен сообщениями между модулями внутри приложения как простую и очень быструю замену настоящего RPC, избегая сложностей асинхронности, eventual consistency и обработки сетевых ошибок.

Источник:

<https://habr.com/ru/post/340898/>
