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
Monday, 29 December 2025
Мікрослужби стали типовою "здоровою системою" архітектурою.
Якщо ви хочете виглядати зрілими, ви говорите про сервіси, автобуси для подій, розподілені слідкування і " незалежні впровадження ." Якщо ви хочете виглядати підготованими, ви малюєте коробки, аж доки діаграма не виглядатиме як миска спагеті, яку хтось кинув на білу дошку.
Але більшість молодших не отримують корекції:
Мікрослужби не є оновленням архітектури програми. Вони є стратегією широкомасштабного масштабування.
Якщо у вас вже немає такого болю, який вони вирішують, прийняття їх на ранній стадії не зробить вас стійкими до майбутнього, це зробить вас зараз непрацездатними.
Це не теоретичний аргумент, це інженерний торговий бізнес, і, як і всі торгові відмовки, це має сенс тільки, коли ви розумієте податок, який ви платите, і те, що ви отримуєте взамін.
Я був таким же винним, як і будь-хто, хто робив мікрослужби, не повністю втілюючи в життя організацію, яку вони втілюють, і яку легко втягнути у словниковий запас, і забути, що справжнім викликом є управління складністю між командами і системами.
Врешті-решт, ми перейдемо до найстарішого зразка в програмному проектуванні: KISS - Поводь себе просто, дурню.
Мікрослужби продаються як типова архітектура, тому що історія спокуслива:
Це обрамлення задом наперед.
Мікрослужби в першу чергу стосуються не структури коду, а структури коду. хто може змінити що, не розмовляючи з ким, і як часто.
Якщо у вас немає:
Ви не вирішуєте організаційну проблему, ви купуєте її.
Архітектура, в кінцевому рахунку, дозволяє людям, а не переміщує коробки.
Ви бачили цю діаграму.

Це не " приклад архітектури для копіювання ." Це мітка попередження.
На курсах, зазвичай, навчання мікрослужбовців у якості діаграм, оскільки діаграми простіші, ніж навчання відповідальності за функціонування.
Важливим рималізмом є те, що діаграма - це не цільовий стан. поверхня вартості.
Кожна коробка - це зобов'язання.
Вони також приховують реальність.
Тому що кожна стрілка така:
Якщо архітектура вимагає цього у перший день, ви не створюєте продукту. Ви будуєте платформу.
Як і всі архітектурні рішення, мікрослужби приходять з a Складний податок (Дивіться Гра з кодом: заплутаний податокРізниця в тому, що ці податкові субстанції на всіх кордонах сервісів, кожній реабілітації та кожному режимі провалу.
Зазвичай їх замасковують як "найкращу практику."
Кожна служба хоче:
Якщо команда не може зробити це комфортно, вона не має мікрослужби. розподілений моноліт з додатковими кроками.
У моноліті виклик - це виклик функції.
У мікрослужбах "дзвінок" - це перемир'я між:
Кількість режимів невдач:
Ви більше не зневаджуєте вад. Ви зневаджуєте системні стани.
Ось витрати, які майже ніколи не обговорювалися в мікрослужбах, а це є закликом: Серіалізація і знецінення (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 мс чистого процесора перш ніж мережа буде включена.
Помножте це на ланцюг викликів:
В цепке с 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
Служба " го " працювала з декількома джерелами даних і сукупними результатами. Кожен з запитів означав декілька послідовних перестрибків за межами контейнера.
Збій типового пошуку адрес:
Для трьохстороннього вентилятора:
Ми проводили майже стільки ж часу на Серде, як на реальних пошуках.
Ціна була не сама послуга го, не має сенсу для справи використання. Межі службКожен кордон з контейнерами означав послідовність → мережу → дезінфекцію.
У моноліті відбувається така ж логіка:
Ця вартість:
У моноліті ця вартість дорівнює нулю. Об'єкт вже у пам'яті.
Ось загальна ідея продажів: "Служба Майкро покращує продуктивність."
Це задом наперед.
Мікрослужбами є повільніше Давайте будемо точно говорити про те, що ми маємо на увазі:
Пробірка (запит на секунду на ядро процесора):
Масштабування (спроможність виконувати загальніші запити додаванням ресурсів):
Мікрослужби дозволяють вам незалежно від масштабу. Якщо ваша служба інвентаризації потребує 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 Потрібно збільшити масштаб за межами однієї машини, ви можете:
Ти все ще керуєш монолітом.
Точка: Не розділяйтесь на мікрослужби для " виконання ." Розділіть, коли незалежне масштабування окремих компонентів Оправляет операционную над головою.
Мікрослужби не зменшують складності.
"Просте служіння" все ще потребує контексту:
Люди недооцінюють це, тому що схеми ховають його.
Кожна межа стає контрактом.
Кожен контракт стає:
Ты можешь отказаться от этого некоторое время, если "просто раскроешь все вместе."
Це ударна лінія: якщо ви розгорнете все разом, ви сконструювали моноліт.
Рання витрата завжди однакова:
Жодне з цих кораблів не дає продукту.
І більшість команд роблять це до того, як доведуть, що продукт гідний складності.
Це основна помилка. Ви платите податок перед тим, як заслужите ціну.. Наприклад щоденні відстоювання, які марнують час, не вирівнюючи його., мікрослужби можуть перетворитися на ритуальну архітектуру: вражаюче, оглядове, дороге для підтримання, і від'єднане від реальної проблеми, яку ви вирішуєте.
Об'єднані разом, ці витрати не зникають - вони комбінуються, і в першу чергу, чи потрібна організація мікрослужбовці.
Архітектура існує, щоб допомогти групам людей безпечно змінювати програмне забезпечення.
Не для того, щоб справити враження на інших інженерів.
Не для задоволення діаграми.
Не для того, щоб виправдати команду платформи, якої ти не маєш.
Розмір команди і пропускна здатність мають більше значення, ніж каталог шаблонів.
Закон Конвея не є обов'язковим, це фізика.
Архітектура відображує вашу комунікацію незалежно від того, подобається це вам чи ні.
Мікрослужби не створюють автономності.
Більшість систем мають починатися як моноліт, але не недбалі.
А 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
}
Що це дає вам:
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);
}
}
}
Цю програму запущено у окремому додатку сьогодні. Якщо вам потрібно змінити масштаб:
Ви побудували мікрослужбовчу інфраструктуру без сплати податку на розподіл.
Жоден з них не потребує розподілу.
Мікрослужби мають сенс, коли ці обмеження реальні, а не жадані.
Критичне питання не в тому, чи слід нам використовувати мікрослужби? чи пільги перевищують оподаткування?
Наприклад, вибір між малі локальні моделі і криві LLM, йдеться про розуміння того, де є складність і які режими невдачі ви можете собі дозволити.
Якщо ваша причина містить слова можлива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 мс)
Те, що ви отримали:
Те, що ви заплатили:
Обробка помилок 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)
З мікрослужбами кожна зміна перетинає декілька служб. Контрактна еволюція вимагає:
Але для цього не потрібно дисципліни, інструменту та досвіду, які працюють у більшості команд лише після багатьох років болю.
Звіт про помилку 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-хвилинними виправленнями, перетворюються на багатотехнічні дослідження..
Мікрослужби - це надзвичайна інженерія.
Мікрослужби - це односторонні двері.
Безпечний шлях виглядає нудним:
Якщо ви не можете намалювати межі всередині моноліту (з умовними залежностями), ви не можете витягти їх безпечно.
Спочатку зніми швів.
Просте читання:
Посади спершу моделі читання, якщо тобі потрібно роз'єднатися.
Синхронна служба викликає ланцюги залежностей.
Створення асинхронного обміну повідомленнями:
Почніть з розділення подій та фактів, а не з кінцевих точок.
Не великий вибух.
Ні "ми перепишемо це правильно."
Згинайте границі, виміряйте її, купіть і продовжуйте.
Якщо ви не можете пояснити, чому служба існує незалежно, вона не повинна існувати взагалі.
Мікрослужби - це не початок, а результат.
Складність має бути зароблена. Розподілені системи не є значком - вони є борговим інструментом.
Рівняння компромісу просте:
Microservices Value = (Team Autonomy + Independent Scaling + Failure Isolation)
- (Latency Tax + SerDe Tax + Operational Overhead + Coordination Cost)
Для більшості команд - особливо для ранніх стадій чи однотемнарів - домінує права сторона - ви платите величезні витрати на пільги, які вам ще не потрібні.
Але коли маєте:
Податок стає виправданим.
Ставте ці запитання в порядку:
Чи може одна команда володіти цим кінцевим результатом?
→ Так: залишайся монолітом.
Команда заблокована циклами звільнення один одного?
→ Так: мікрослужби можуть допомогти.
У нас є діючі м'язи, щоб керувати розподіленими системами?
→ Ні: Спочатку збудуйте його.
Чи можемо ми відстежити, знеохочувати та розкривати багато служб без героїзму?
→ Ні: ви не готові.
Мы сначала доказали границы кода?
→ Ні: полагодьте монолітну структуру.
Якщо ви не можете з упевненістю відповісти на питання 3, ви не готові до мікрослужби - і це нормально. Знущатися з архітектурою - це конкурентна перевага.
Найбільш успішними продуктами спочатку були моноліти: GitHub, Choyify, Stack Overflow, Basecamp.
Ваше завдання - не побудувати найвражаючішу архітектуру, а передати цінність в той час, як мінімізувати випадкову складність.
І жоден клієнт ніколи не платив більше, тому що ваша система використовувала Кафку.
Зазвичай, це означає добре структурований моноліт і дисципліну, щоб зберегти його таким чином.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.