РозробкаБудь-який рівень

Моноліт

Моноліт — це один застосунок, який збирають і деплоять як єдине ціле. Це слово вживають як образу, і дарма: моноліт зі справжніми межами модулів — найдешевша архітектура, яка працює, і правильний вибір за замовчуванням для більшості систем. Монолітам шкодить не розмір, а відсутність внутрішніх меж — а це інша проблема з іншим лікуванням.

Одиниця деплою
Один артефакт
Ціна виклику
Мікросекунди
Режим відмови
Розмиті межі

Що це слово означає насправді

Моноліт — це застосунок, чий код збирають, тестують і деплоять як одне ціле. Це все визначення. Воно не каже нічого про розмір кодової бази, кількість людей на ній, якість організації коду чи взагалі про те, добрий він чи ні.

Плутанина походить від другого значення, що приліпилося до слова близько 2014 року: «велика грудка бруду», де все викликає все, жодну частину неможливо зрозуміти окремо, а зміна в рахунках ламає сторінку входу. Це реальний і поширений стан. Просто його спричиняє не єдина одиниця деплою, і поділ деплою його не лікує.

Моноліт — це одна одиниця деплою: модулі викликають одне одного всередині процесу, і всі вони мають спільну базу даних. Червона пунктирна лінія — це обхід, що спричиняє гниття: один модуль читає таблиці іншого напряму, замість іти через його інтерфейс. Моноліт із дотриманими межами лишається робочим необмежено довго; без них він стає системою, у якій жодна зміна не є локальною.

Версія, до якої варто прагнути, має назву: модульний моноліт. Усередині однієї одиниці деплою код поділений на модулі з явними інтерфейсами — замовлення, білінг, каталог, користувачі. Модуль може викликати інший лише через його опублікований інтерфейс і ніколи не лізе в його дані. Деплой лишається простим; внутрішня структура така сама, як у набору сервісів, тільки без мережі.

Чому це правильний вибір за замовчуванням

Аргумент на користь монолітного старту — не ностальгія. Він у тому, що єдиний процес усуває цілу категорію проблем, які інакше довелося б розв’язати ще до написання бізнес-логіки.

  • Виклик між модулями — це виклик функції: мікросекунди, без серіалізації, без таймауту, без ретраю, і він або стався, або ні.
  • Транзакція між двома модулями — це одна транзакція бази даних. Без саг, без компенсаційних дій, без потреби пояснювати бізнесу консистентність із часом.
  • Пересунути межу — це рефакторинг, який перевіряє компілятор. У розподіленій системі та сама зміна — це міграція через два репозиторії, два деплої й питання версіонування.
  • Стектрейс покриває весь запит. Дебаг — це читання, а не звірка трейсів між чотирма сервісами.
  • Одна річ, яку треба запустити локально. Новий інженер стає продуктивним першого дня, а не другого тижня.

Глибший аргумент, який Фаулер подав під назвою «спершу моноліт», — про знання. Межі сервісів дорого пересувати, а помиляються в них тоді, коли ще не розуміють предметної області, — а на старті продукту ви її не розумієте. Моноліт дозволяє дешево рухати межі, поки ви ще вчитеся, де їм бути, і відкладає дороге зобов’язання до появи доказів.

Як утримати межі

Межі в моноліті нічим не забезпечені, доки ви цього не зробите. Ніщо не заважає розробнику о другій ночі заімпортувати клас з іншого модуля, і ніщо не скаже йому, що так не можна. У розподіленій системі межу тримає мережа тим, що вона незручна; у моноліті цю незручність треба створити свідомо.

МеханізмЩо запобігаєЦіна
Модуль як одиниця збіркиІмпорту внутрішнього — іншим видно лише те, що модуль експортує.Повільніший граф збірки
Архітектурні тестиБудь-якої залежності, забороненої правилами: CI ловить її й називає рядок.День на налаштування, далі безкоштовно
Схема на модульДжойнів і зовнішніх ключів між модулями — зміни, яку найважче скасувати потім.Частина запитів стає двома
Власність у код-рев’юНепомітних змін меж усередині непов’язаного pull request.Іноді ще один рев’юер

Найкращу віддачу дають архітектурні тести. Десяток рядків, які стверджують «ніщо поза модулем білінгу не має права імпортувати billing.internal», перетворюють правило, з яким усі згодні й якого ніхто не дотримує, на падіння збірки з конкретним іменем. Без них межі модулів живуть рівно доти, доки в команді лишається людина, якій вони не байдужі.

Де моноліт справді вичерпується

Реальні межі існують, і чесність щодо них відрізняє аргумент від уподобання. Жодна з них не стосується розміру коду.

  • Одна одиниця масштабування. Якщо обробці зображень треба вшістнадцятеро більше CPU, ніж решті, ви масштабуєте весь застосунок у шістнадцять разів заради неї.
  • Один рантайм і одна версія мови. Модуль, якому було б значно краще в іншому стеку, його не отримає.
  • Один ритм релізів. Дванадцять команд, що деплоять один артефакт, — це дванадцять команд, що координуються, і темп задає найповільніша.
  • Один радіус ураження. Витік пам’яті в рідко вживаному звіті кладе оформлення замовлення, бо в них спільний процес.
  • Час збірки й тестів росте з кодовою базою і зрештою робить цикл від злиття до продакшену настільки повільним, що змінюється поведінка людей.

Читайте цей список як діагностику, а не як вирок. У кожного пункту є дешевша відповідь до архітектурної: ненажерливий до CPU модуль можна виділити окремо, нічого більше не ділячи; час збірки реагує на паралелізацію тестів і кешування на рівні модулів; а конкуренція за релізи часто є проблемою гілкування й рев’ю, а не деплою. Виділіть один сервіс, що знімає реальне обмеження, — це не те саме рішення, що перехід на мікросервіси.

Моноліт проти мікросервісів

Модульний моноліт

  • Межі тримає інструментарій, який треба додати
  • Виклики є функціями — без часткових відмов
  • Одна транзакція охоплює модулі
  • Пересунути межу — це рефакторинг
  • Масштабується як одне ціле, в одному стеку

Мікросервіси

  • Межі тримає мережа, і оминути її не можна
  • Виклики падають на півдорозі — кожен потребує політики
  • Консистентність між сервісами — це сага
  • Пересунути межу — це міграція
  • Масштабується й розвивається посервісно

У кожному рядку той самий обмін: мікросервіси купують незалежність і платять за неї роботою з розподіленими системами. Ця робота не з’являється в жодному плані, ніколи не завершується і є причиною того, чому команда з шести осіб майже завжди швидша на моноліті. Купуйте незалежність тоді, коли є на що її витратити: кілька команд, які справді блокують одна одну, або один компонент із геть іншим профілем масштабування.

Як це виявляється в реальній поставці

Найпоширеніша дорога помилка в цій темі — не вибір моноліту чи мікросервісів. Це вибір мікросервісів, щоб полагодити проблему модульності. Команда повільна, бо все зв’язане, діагноз звучить як «треба це розділити», і поділ проходить по вже наявній зв’язності — даючи сервіси, які треба деплоїти разом, які мають спільну базу і які тепер ще й падають через мережу. У цього результату є назва: розподілений моноліт, і він строго гірший за те, що замінив.

Де він вироджується

  • Джойни через таблиці різних модулів зв’язують модулі через сховище, а не через інтерфейс.
  • Пакети за шарами — контролери, сервіси, репозиторії — групують за технічною роллю й дають кожній фічі всю кодову базу як поверхню.
  • Спільний модуль «common» чи «utils», від якого залежить усе і яким не володіє ніхто.
  • Правила модулів, узгоджені в документі, а не перевірені в CI, тримаються один квартал.
  • Тести, достатньо повільні, щоб їх перестали ганяти локально, — зникає той швидкий зворотний зв’язок, що робив моноліт дешевим.

Коли це застосовувати

Застосовуйте, коли

  • Продукт новий, і межі предметної області ще з’ясовуються.
  • Одна команда або кілька, що можуть узгодити реліз без болю.
  • Важлива транзакційна консистентність між фічами, а консистентність із часом була б проблемою для бізнесу.
  • Ви хочете витратити бюджет складності на предметну область, а не на інфраструктуру.
  • Ви готові забезпечувати межі модулів у збірці, а не в документі.

Уникайте, коли

  • Багато команд справді блокують одна одну на спільному релізі, і ціна координації виміряна.
  • Один компонент має геть інший профіль масштабування й мусить масштабуватися окремо.
  • Частини системи мають справді різні вимоги до доступності чи відповідності.
  • Компоненту було б значно краще в іншому стеку, і аргумент технічний, а не модний.

Було корисно?

Поділіться з тим, хто працює над тією ж задачею.