Back to "Чому вам, мабуть, не слід використовувати мікрослужби?"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Architecture Microservices Opinion

Чому вам, мабуть, не слід використовувати мікрослужби?

Monday, 29 December 2025

Мікрослужби стали типовою "здоровою системою" архітектурою.

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

Але більшість молодших не отримують корекції:

Мікрослужби не є оновленням архітектури програми. Вони є стратегією широкомасштабного масштабування.

Якщо у вас вже немає такого болю, який вони вирішують, прийняття їх на ранній стадії не зробить вас стійкими до майбутнього, це зробить вас зараз непрацездатними.

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

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

Врешті-решт, ми перейдемо до найстарішого зразка в програмному проектуванні: KISS - Поводь себе просто, дурню.

Легенда про мікрослужби

Мікрослужби продаються як типова архітектура, тому що історія спокуслива:

  • Маленькі послуги "чистіші"
  • Розподілені системи - "сучасні"
  • Autonomy "безкоштовно"
  • Ви можете "масштаб пізніше," тому що ви почали "праворуч"

Це обрамлення задом наперед.

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

Якщо у вас немає:

  • Невідповідне пересилання декількох команд
  • Переривання пріоритетів
  • Координаційні утруднення
  • Суперечки в розв'язанні
  • Межі справжнього власника

Ви не вирішуєте організаційну проблему, ви купуєте її.

Архітектура, в кінцевому рахунку, дозволяє людям, а не переміщує коробки.

Діаграма, яка має бути попереджальною міткою

Ви бачили цю діаграму.

Обережна діаграма архітектури мікрослужби

Це не " приклад архітектури для копіювання ." Це мітка попередження.

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

Важливим рималізмом є те, що діаграма - це не цільовий стан. поверхня вартості.

Кожна коробка - це зобов'язання.

  • Робочий час
  • Як давати собі раду
  • Умовний контракт з версією
  • Режим невдачі, який тепер є вашим власником
  • Історія на виклик, яку не можна ігнорувати

Вони також приховують реальність.

Тому що кожна стрілка така:

  • Затримка
  • Часткова помилка
  • Повторні спроби
  • Тайм- аут
  • Розподіл контексту трасування
  • "Праця в застій" брехнею.

Якщо архітектура вимагає цього у перший день, ви не створюєте продукту. Ви будуєте платформу.

Прихована модель мікрослужб

Як і всі архітектурні рішення, мікрослужби приходять з a Складний податок (Дивіться Гра з кодом: заплутаний податокРізниця в тому, що ці податкові субстанції на всіх кордонах сервісів, кожній реабілітації та кожному режимі провалу.

Зазвичай їх замасковують як "найкращу практику."

Надлишкова заголовка

Кожна служба хоче:

  • Його власний трубопровод.
  • Його власні налаштування впровадження
  • Його власний моніторинг і сигналізація
  • Його власна лісозаготівля, панелі приладів, програмні книги
  • Її власна поза безпеки (секретна, автентифікація, інгресія)

Якщо команда не може зробити це комфортно, вона не має мікрослужби. розподілений моноліт з додатковими кроками.

Множення за частотою і невдачами

У моноліті виклик - це виклик функції.

У мікрослужбах "дзвінок" - це перемир'я між:

  • Мережі
  • Баланси завантаження
  • TLS
  • Auth middleware
  • Серіалізація
  • Тайм- аут
  • Повторні спроби
  • Пригнічення

Кількість режимів невдач:

  • Один вниз по стрічці є повільним → upstream ниток нагромадження
  • Підсилювачі завантажуються → Ви, DDOS сам ввічливо
  • Одна залежність блимає → три служби, що деградують "випадково"

Ви більше не зневаджуєте вад. Ви зневаджуєте системні стани.

Податковий податок, про який ніхто не говорить

Ось витрати, які майже ніколи не обговорювалися в мікрослужбах, а це є закликом: Серіалізація і знецінення (SerDe) над головою.

Кожна межа служби означає:

// Monolith: direct object reference
var user = _userService.GetUser(userId);  // < 1μs
var tier = user.Tier;                      // Memory access

// Microservices: serialize → network → deserialize
var json = JsonSerializer.Serialize(user);        // ~50μs for a complex object
var bytes = Encoding.UTF8.GetBytes(json);         // ~10μs
// ... send over network ...
var responseJson = await response.Content.ReadAsStringAsync();  // ~20μs
var user = JsonSerializer.Deserialize<User>(responseJson);      // ~70μs
var tier = user.Tier;                                           // Finally

На виклик, це ~150 мс чистого процесора перш ніж мережа буде включена.

Помножте це на ланцюг викликів:

  • Служба порядку зменшує запит (150 мс)
  • Служба order перелічує запити служби користувача у послідовному режимі (150 мс)
  • Служба користувача зменшує запит (150 мс)
  • Служба користувача перелічує відповідь у послідовному режимі (150 мс)
  • Служба порядку зменшує відповідь користувача (150 мс)
  • Служба порядку послідовно виконує запит Inventory (150μs)
  • і так далі

В цепке с 5-ми послугами ты тратишь всего лишь на сирDe Якщо ви використовуєте XML, буфери протоколів з відбиттям, або неефективні серіалізатори, перемножте це на 2-10x.

Так, ви можете зменшити це за допомогою таких технічних прийомів, як Покоління джерела JSON в NET:

[JsonSerializable(typeof(User))]
internal partial class UserJsonContext : JsonSerializerContext { }

// ~30-40% faster than reflection-based serialization
var json = JsonSerializer.Serialize(user, UserJsonContext.Default.User);

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

Справжній приклад.

Я колись проігнорував систему пошуку адрес цією архітектурою (всі у контейнерах Kubernetes):

ASP.NET API → Go Service (fan-out) → ASP.NET Search Service → Elasticsearch

Служба " го " працювала з декількома джерелами даних і сукупними результатами. Кожен з запитів означав декілька послідовних перестрибків за межами контейнера.

Збій типового пошуку адрес:

  • API ASP.NET: запит на пошук у послідовному режимі → JSON (80 мс)
  • Мережа: ASP.NET → Перехід (версія завантаження групування)
  • Go Service: Прибрати запит (40 мс)
  • Go Service: Розгортання - послідовні запити до декількох серверів (60μs × N серверів)
  • Мережа: Go → ASP. NET Пошук (vries)
  • Служба пошуку: Прибрати запит (90μs)
  • Служба пошуку: збирання запиту Elatisearch, послідовність (120 мс)
  • Мережа: Пошук → Elatisearch (vries)
  • Еластичний пошук: Delicize query, exec, serialize результатів (поточний пошук ~5 мс, SerDe ~200 мс)
  • Мережа: Elatisearch → Пошук (vries)
  • Служба пошуку: Прибрати відповідь ES (300 мс), карту доменної моделі, послідовність (180 мк)
  • Мережа: Пошук → Перехід (версії)
  • Служба го: Прибрати відповіді від всіх серверів (150 мс × N), сукупність, послідовність (80 мс)
  • Мережа: Go → ASP.NET (vries)
  • API ASP.NET: Прибрати останню відповідь (120 мс)

Для трьохстороннього вентилятора:

  • Всього сірDe над головою: ~2 мм перед тим, як почали олімпійське дослідження
  • Мережа над головою між контейнерами: ~2- 3 мс
  • Поточний пошук і бізнес- логіка: ~6 мс

Ми проводили майже стільки ж часу на Серде, як на реальних пошуках.

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

У моноліті відбувається така ж логіка:

  • Подібні запити до Elatischesearch
  • Така ж логіка агрегації
  • Нульова комунікаційна служба SerDe (просто виклики методів)
  • ~40% менше загальної затримки

Ця вартість:

  • Масштаби з складністю об' єктів (відсутні об' єкти, колекції, поліморфізм)
  • Комбіновані з глибиною викликів (сервісні ланцюжки)
  • Записати ЦП за кожен запит (не вдалося кешувати його)
  • Підвищує тиск на GC (накопичувальний відтік з масивів рядків/ байтів)
  • Додає швидко, коли ви виконуєте сотні запитів за секунду

У моноліті ця вартість дорівнює нулю. Об'єкт вже у пам'яті.

Міф швидкодії: Втрачена швидкість

Ось загальна ідея продажів: "Служба Майкро покращує продуктивність."

Це задом наперед.

Мікрослужбами є повільніше Давайте будемо точно говорити про те, що ми маємо на увазі:

Пробірка (запит на секунду на ядро процесора):

  • Монолітrussia_ subjects. kgm: Вища - нуль SerDe, нульова мережа стрибків, прямі методи викликів
  • Мікрослужби: Нижня над головою Серде, спізнення в мережі, оркестрування контейнерів

Масштабування (спроможність виконувати загальніші запити додаванням ресурсів):

  • Монолітrussia_ subjects. kgm: Межа вертикальної зміни масштабу (більше машин)
  • Мікрослужби: Горизонтально - додайте більше екземплярів вузьких служб

Мікрослужби дозволяють вам незалежно від масштабу. Якщо ваша служба інвентаризації потребує 10x ресурсів, але ваша послуга користувача цього не робить, ви можете обчислювати їх окремо. коли вона вам потрібна..

Але це не оптимізація. Стратегія масштабуванняУ них є податок.

Сучасні моноліти теж можуть збільшуватися

Аргумент "Змінювання послуг краще" з'явився у 2010 році, коли сервери мали 4-8 ядер.

Сучасні сервери мають 32- 128 ядер. Один комп' ютер може ефективно виконувати тисячі послідовних операцій.

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

// Process thousands of concurrent operations in a monolith
services.AddEphemeralWorkCoordinator<TranslationRequest>(
    async (request, ct) => await TranslateAsync(request, ct),
    new EphemeralOptions { MaxConcurrency = Environment.ProcessorCount * 4 });

// Bounded, observable, efficient concurrent processing
// No network overhead, no SerDe, no container orchestration
// Can handle 10k+ req/sec on a single machine

Це дає вам:

  • Паралельне ставлення: Робота одночасно проходить через усі ядра
  • Спостереження: Доріжка з черги/ активності/ помилки операцій
  • Пригнічення: Обмежені черги запобігають виснаженню пам'яті.
  • Нуль накладних: Без послідовності, без мережі, без розподіленого сліду

Коли ти do Потрібно збільшити масштаб за межами однієї машини, ви можете:

  1. Запустити декілька екземплярів за балансатором завантаження (нерухомий горизонтальний масштаб)
  2. Використовувати шаблон " вихідні " для асинхронного розподілу завдань
  3. Розділ за ключем (ідентифікатор ідентифікатора, регіону тощо) у всіх випадках@ info/ plain

Ти все ще керуєш монолітом.

Точка: Не розділяйтесь на мікрослужби для " виконання ." Розділіть, коли незалежне масштабування окремих компонентів Оправляет операционную над головою.

Розподіл когнітивного навантаження

Мікрослужби не зменшують складності.

"Просте служіння" все ще потребує контексту:

  • Хто це називає?
  • Що означає "правильне"?
  • Що буде, коли він впаде?
  • Який план зворотного повернення?
  • Який графік залежності?

Люди недооцінюють це, тому що схеми ховають його.

Видозміна і субортний дрейф

Кожна межа стає контрактом.
Кожен контракт стає:

  • Версія правил
  • Сумісність гарантує
  • Обмеження схеми еволюції
  • Координація над вами удає, що у вас немає

Ты можешь отказаться от этого некоторое время, если "просто раскроешь все вместе."

Це ударна лінія: якщо ви розгорнете все разом, ви сконструювали моноліт.

Перед значенням інструмента

Рання витрата завжди однакова:

  • Знаходження служб
  • Керування таємницями
  • Розподілити слідкування
  • Центральна лісозаготівля
  • Метрики і сигналізація
  • Шаблони CI/CD
  • Локальна історія dev, яка не є болісною
  • Керування залежностями
  • Шлях до бігу за всім без плачу.

Жодне з цих кораблів не дає продукту.

І більшість команд роблять це до того, як доведуть, що продукт гідний складності.

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

Об'єднані разом, ці витрати не зникають - вони комбінуються, і в першу чергу, чи потрібна організація мікрослужбовці.

Що люди забувають.

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

Не для того, щоб справити враження на інших інженерів.
Не для задоволення діаграми.
Не для того, щоб виправдати команду платформи, якої ти не маєш.

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

  • Якщо одна команда володіє цілою картою доріг, моноліт зазвичай швидший і безпечніший.
  • Якщо ви все ще розумієте, що таке "кінець," мікрослужби зменшать цю ясність.
  • Якщо у вас немає стабільних кордонів, мікрослужбовці змусять координацію через API - найповільніший механізм координації.

Закон Конвея не є обов'язковим, це фізика.

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

Мікрослужби не створюють автономності.

Захист моденного моноліту

Більшість систем мають починатися як моноліт, але не недбалі.

А modular Monolith є:

  • Один модуль експозиції
  • Один запуск
  • Одне місце для зневаджування
  • Один послідовний погляд на стан
  • Але зі справжніми внутрішніми бар'єрами

Це означає:

  • Модулі домену з явними залежностями
  • Зняти власника у коді
  • Ні "всі посилання на все"
  • Контракти, застосовані всередині кодової бази.

Суть не в тому, щоб залишатися монолітом назавжди. отримувати межі, перш ніж робити їх віддаленими.

Модульний моноліт змушує вас вивчати навички мікрослужби, що видають себе за розв' язання:

  • Моделювання доменів (дійсне обмеження, а не межі тек)
  • Відокремлення тривоги (не "ми зробили службу")
  • Випробовування
  • Змінити покарання
  • Знаючи, що насправді робить ваша система.

Якщо ви не можете побудувати чистий модульний моноліт, мікрослужби вас не врятують.

Добрий монолітний дизайн: візерунки, які масштабуватимуть пізніше

Правильний монолітний дизайн дозволяє Відкласти рішення про мікрослужби не зачиняючись.

Шаблон теки " Вихідні ": синхронізація без розповсюдження

Одним з найкращих прикладів є Шаблон outbox - спосіб досягнення остаточної послідовності та синхронної обробки всередині моноліт, з чистим шляхом до розподілу пізніше.

Замість:

// Tightly coupled synchronous code
public async Task PlaceOrder(Order order)
{
    await _orderRepo.Save(order);
    await _emailService.SendConfirmation(order);  // Blocks on external service
    await _inventoryService.Reserve(order.Items); // Blocks on another service
}

Ви пишете:

// Outbox pattern: write events to a table
public async Task PlaceOrder(Order order)
{
    await using var transaction = await _db.Database.BeginTransactionAsync();
    
    // Save order
    await _orderRepo.Save(order);
    
    // Write events to outbox table (same transaction)
    _db.OutboxMessages.Add(new OutboxMessage
    {
        EventType = "OrderPlaced",
        Payload = JsonSerializer.Serialize(order),
        CreatedAt = DateTime.UtcNow
    });
    
    await _db.SaveChangesAsync();
    await transaction.CommitAsync();
    
    // Background worker picks up outbox events and publishes them
}

Що це дає вам:

  • Операційна послідовність: Події і дані, пов'язані між собою або зовсім не пов'язані
  • ДекоулінгCity in New Jersey USA: логіка ел. пошти/ inventory запускає асинхронізацію, не блокує створення порядку
  • Респективність: Події продовжуються, навіть якщо західні системи зруйновані.
  • Очистити шлях видобування: Пізніше, міняйте робочу теку на Kafka/RabbitMQ без зміни порядку логіки

The Зразок проекту з відрізкомCommerce демонструє цей зразок у виробництві:

// Mostlylucid.SegmentCommerce/Services/Queue/PostgresOutbox.cs
public class PostgresOutbox
{
    public async Task PublishAsync<T>(string eventType, T payload)
    {
        var message = new OutboxMessage
        {
            EventType = eventType,
            Payload = JsonSerializer.Serialize(payload),
            CreatedAt = DateTime.UtcNow
        };
        
        _db.OutboxMessages.Add(message);
        // Caller commits transaction
    }
}

// Background service
public class OutboxProcessor : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var pending = await _db.OutboxMessages
                .Where(m => !m.Processed)
                .OrderBy(m => m.CreatedAt)
                .Take(100)
                .ToListAsync();
                
            foreach (var message in pending)
            {
                await ProcessMessage(message);
                message.Processed = true;
            }
            
            await _db.SaveChangesAsync();
            await Task.Delay(1000, stoppingToken);
        }
    }
}

Цю програму запущено у окремому додатку сьогодні. Якщо вам потрібно змінити масштаб:

  1. Замінити фонового працівника на споживача повідомлень
  2. Оприлюднити події вихідні для Kafka/RabbitMQ замість локальної обробки
  3. Код розташування порядку не змінюється

Ви побудували мікрослужбовчу інфраструктуру без сплати податку на розподіл.

Інші приклади варті того, щоб робити це раніше

  • CQRS (світло): Відокремлювати моделі читання/ запису, навіть якщо вони мають спільний DB
  • Розподіл події (вибір): Лише для елементів- аудиторів
  • Прапорці можливостей: Decuple expelling from page
  • Тло завдань: Не блокуйте запитів, обробіть асинхронізацію

Жоден з них не потребує розподілу.

Коли мікрослужби насправді приймаються

Мікрослужби мають сенс, коли ці обмеження реальні, а не жадані.

Критичне питання не в тому, чи слід нам використовувати мікрослужби? чи пільги перевищують оподаткування?

Наприклад, вибір між малі локальні моделі і криві LLM, йдеться про розуміння того, де є складність і які режими невдачі ви можете собі дозволити.

Добрі результати

  • Суперечки у команді вимірюються: Команди блокують один одного щотижня
  • Потрібні незалежні розгортання: Відмова від випуску відрізняється на одну область
  • Масштаб нерівний: Одна підсистема потребує 10x ресурсів і ізольованості
  • Справа у ізоляції невдач: Один компонент не повинен забирати решту
  • Впорядкування/ Дані є обов' язковим: Обов' язки не об' єднані
  • Структура Org вже складається з декількох комп' ютерів: Власники існують і є стабільними

Погані наслідки

  • "Ми можемо масштабувати."
  • "Netflix робить це"
  • "Бестова практика"
  • Все буде добре.
  • "Наш моноліт заплутаний, тому ми його полагодимо поділом."

Якщо ваша причина містить слова можливаThe role of the transaction, in past tense, врешті-решт, або special- problemЦе, мабуть, не причина.

Технічна реальність: чого насправді коштують мікрослужби

Ось що зміниться, якщо розділити моноліт на послуги.

Ланцюжок викликів і легкість

Моноліт:

public async Task<Order> PlaceOrder(OrderRequest request)
{
    var user = await _userService.GetUser(request.UserId);
    var inventory = await _inventoryService.CheckStock(request.Items);
    var price = _pricingService.Calculate(request.Items, user.Tier);
    
    var order = new Order { /* ... */ };
    await _orderRepository.Save(order);
    await _emailService.SendConfirmation(order);
    
    return order;
}

Загальна кількість скасування: ~50 мс (викликів процесу + 2 запити до DB)

Мікрослужби:

public async Task<Order> PlaceOrder(OrderRequest request)
{
    // HTTP call to User Service (network + TLS + serialization)
    var user = await _httpClient.GetAsync<User>($"http://user-service/users/{request.UserId}");
    
    // HTTP call to Inventory Service
    var inventory = await _httpClient.PostAsync<StockResult>(
        "http://inventory-service/check", request.Items);
    
    // HTTP call to Pricing Service
    var price = await _httpClient.PostAsync<PriceResult>(
        "http://pricing-service/calculate", 
        new { items = request.Items, tier = user.Tier });
    
    // HTTP call to Order Service
    var order = await _httpClient.PostAsync<Order>(
        "http://order-service/orders", request);
    
    // Event published to message bus
    await _messageBus.Publish(new OrderPlaced { OrderId = order.Id });
    
    return order;
}

Загальна запізнення: ~400ms (навіть оптимістично 50}80 мс на виклик HTTP в реальному середовищі + повідомлення автобуса повідомлення опублікувати ~20 мс)

Те, що ви отримали:

  • Кожна служба може виконуватися незалежно
  • Inventory і Priorence можуть масштабувати окремо від порядку
  • Невдача у повідомленні електронної пошти не блокує створення порядку (або синхронізацію)

Те, що ви заплатили:

  • ~8x збільшення затримки
  • 4 нових точки помилок (будь- яка служба може бути нижчою/ нижчою)
  • Мережа, DNS, TLS над головою під час кожного дзвінка
  • Поверхня послідовної/ розсіювання (Дивіться розділ на початку статті)
  • Потрібен для повторних спроб логіки, переривів, таймерів
  • Розподілити слідкування за помилками зневаджування

Вибух під час обробки помилок

Обробка помилок Monolith:

try
{
    var order = await PlaceOrder(request);
    return Ok(order);
}
catch (InsufficientStockException)
{
    return BadRequest(new { error = "Out of stock" });
}
catch (Exception ex)
{
    _logger.LogError(ex, "Order placement failed");
    return StatusCode(500);
}

Обробка помилок мікрослужб:

try
{
    // Call User Service
    var user = await _httpClient.GetAsync<User>($"http://user-service/users/{request.UserId}");
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
    return NotFound(new { error = "User not found" });
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.ServiceUnavailable)
{
    // Retry with exponential backoff?
    // Circuit breaker opened?
    // Fail fast or degrade gracefully?
    _logger.LogWarning("User service unavailable, retrying...");
    await Task.Delay(TimeSpan.FromMilliseconds(100));
    // ... retry logic ...
}
catch (TaskCanceledException ex)
{
    // Timeout - was the request processed? Do we retry?
    _logger.LogError("User service timeout");
    return StatusCode(503, new { error = "Service temporarily unavailable" });
}

try
{
    var inventory = await _httpClient.PostAsync<StockResult>(
        "http://inventory-service/check", request.Items);
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.BadRequest)
{
    // Business error from remote service
    var errorDetails = await ex.Content.ReadAsAsync<ErrorResponse>();
    return BadRequest(new { error = errorDetails.Message });
}
catch (HttpRequestException ex)
{
    // Which service failed? Network issue? Service down?
    // Do we have a fallback? Cached data? Fail fast?
    _logger.LogError(ex, "Inventory service failed");
    // Maybe try a backup instance?
    // ... more retry logic ...
}

// And repeat for every service call...

Кожен сигнал по мережі:

  • Тайм- аут
  • Невдача мережі
  • Службу неавторизованою
  • Часті невдачі (відіслано запит, відповідь не отримано)
  • Повторна амортизація (можна викликати повторну спробу)
  • Повсюдні кошмари з операціями

Координація впровадження

Розвиток моноліту:

# Build
dotnet publish -c Release

# Run migrations
dotnet ef database update

# Deploy
docker push myapp:v1.2.3
kubectl set image deployment/myapp myapp=myapp:v1.2.3

# Rollback if needed
kubectl rollout undo deployment/myapp

Розвиток мікрослужб:

Ви змінюєте структуру порядку. Order включення UserTier напряму (ненормально для швидкодії).

# 1. Deploy Pricing Service v2 (understands both old and new Order format)
kubectl set image deployment/pricing-service pricing-service=pricing:v2.0.0

# 2. Wait for rollout and monitor
kubectl rollout status deployment/pricing-service
# Check metrics - is v2 working with old order format?

# 3. Deploy Order Service v2 (starts sending new format)
kubectl set image deployment/order-service order-service=order:v2.0.0

# 4. Monitor for errors
# If Pricing v2 has a bug, you can't just rollback Order service
# You have to rollback both in reverse order

# 5. Deploy User Service v2 (stops including tier in response)
# But wait - old Order Service instances might still be running!
# Need zero-downtime rollout or maintain backward compat

# 6. Eventually clean up old code paths in Pricing Service v3
# (6 months later because you're scared to remove backward compat)

З мікрослужбами кожна зміна перетинає декілька служб. Контрактна еволюція вимагає:

  • Вікна зворотної сумісності
  • Прапорці можливостей через служби
  • Координатовані потоки
  • Розширені математичні перевірки (Service A v1 + Service B v2 Service A v2 + Service B v1 тощо)

Але для цього не потрібно дисципліни, інструменту та досвіду, які працюють у більшості команд лише після багатьох років болю.

Досвід, який знеохочує

Звіт про помилку Monolith: @ info/ rich " Розташування пари не працює для внесених користувачів "

// Set breakpoint in PlaceOrder
// Step through each call
// User tier is null - ah, there's the bug
// Fix, test, deploy

Звіт про ваду мікрослужби: " Розташування пари не працює для внесених користувачів "

# 1. Check logs - which service failed?
kubectl logs -l app=order-service --tail=100

# 2. Oh, it's calling Pricing service. Check those logs
kubectl logs -l app=pricing-service --tail=100

# 3. Grep for correlation ID across all services
stern -l app=order-service,app=pricing-service,app=user-service \
  | grep "correlation-id-xyz"

# 4. Reconstruct the call chain from distributed traces
# Open Jaeger/Zipkin, find the trace
# User service returned 200 OK
# Pricing service returned 400 Bad Request
# Error: "user.tier is required"

# 5. Check User service - did it send tier?
# Look at schema version - ah, User v1.2 stopped sending tier
# When did that deploy? Was Pricing service updated?

# 6. Check API contracts
# Pricing expects tier, User stopped sending it
# Who approved this change?

# 7. Fix requires coordinating two teams
# Can't just deploy a fix - need contract negotiation

Це реальність. Помилки, які були 5-хвилинними виправленнями, перетворюються на багатотехнічні дослідження..

Мікрослужби - це надзвичайна інженерія.

Як безпечно еволюціонувати: Monolith → Microservices

Мікрослужби - це односторонні двері.

Безпечний шлях виглядає нудним:

Перша - обмеження

Якщо ви не можете намалювати межі всередині моноліту (з умовними залежностями), ви не можете витягти їх безпечно.

Спочатку зніми швів.

2. Розпакувати читання перед записами

Просте читання:

  • Менше івнітаріїв
  • Вимоги до послідовності у меншій кількості@ info: whatsthis
  • Меньше кошмаров, возвращающихся назад.

Посади спершу моделі читання, якщо тобі потрібно роз'єднатися.

3. Надавайте перевагу синхронізації перед синхронізацією

Синхронна служба викликає ланцюги залежностей.

Створення асинхронного обміну повідомленнями:

  • Буферизація
  • Стійкість
  • Декорація Вас дійсно може вижити

Почніть з розділення подій та фактів, а не з кінцевих точок.

4. Стискання, не переписуйтесь

Не великий вибух.
Ні "ми перепишемо це правильно."

Згинайте границі, виміряйте її, купіть і продовжуйте.

Якщо ви не можете пояснити, чому служба існує незалежно, вона не повинна існувати взагалі.

Винахід: мікрослужби платять, лише якщо розумієте торговельні поступки

Мікрослужби - це не початок, а результат.

Складність має бути зароблена. Розподілені системи не є значком - вони є борговим інструментом.

Рівняння компромісу просте:

Microservices Value = (Team Autonomy + Independent Scaling + Failure Isolation)
                     - (Latency Tax + SerDe Tax + Operational Overhead + Coordination Cost)

Для більшості команд - особливо для ранніх стадій чи однотемнарів - домінує права сторона - ви платите величезні витрати на пільги, які вам ще не потрібні.

Але коли маєте:

  • 5+ команди наступають один на одного на пальці
  • Черги впровадження, що вимірюються у днях
  • Підсистеми з дуже різними потребами масштабування
  • Вимоги реєстрації для відокремлення даних

Податок стає виправданим.

Вирішальний процес

Ставте ці запитання в порядку:

  1. Чи може одна команда володіти цим кінцевим результатом?
    → Так: залишайся монолітом.

  2. Команда заблокована циклами звільнення один одного?
    → Так: мікрослужби можуть допомогти.

  3. У нас є діючі м'язи, щоб керувати розподіленими системами?
    → Ні: Спочатку збудуйте його.

  4. Чи можемо ми відстежити, знеохочувати та розкривати багато служб без героїзму?
    → Ні: ви не готові.

  5. Мы сначала доказали границы кода?
    → Ні: полагодьте монолітну структуру.

Якщо ви не можете з упевненістю відповісти на питання 3, ви не готові до мікрослужби - і це нормально. Знущатися з архітектурою - це конкурентна перевага.

Найбільш успішними продуктами спочатку були моноліти: GitHub, Choyify, Stack Overflow, Basecamp.

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

І жоден клієнт ніколи не платив більше, тому що ваша система використовувала Кафку.

Зазвичай, це означає добре структурований моноліт і дисципліну, щоб зберегти його таким чином.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.