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
Mikrotjänster har blivit standardarkitekturen för "allvarligt system".
Om du vill låta mogen, du talar om service mesh, händelsebussar, distribuerad spårning, och "oberoende utplaceringsbarhet". Om du vill se företag-klara, du rita lådor tills diagrammet ser ut som en skål spaghetti någon kastade på en whiteboard.
Men här är de korrigerande mest juniors inte ges:
Microservices är inte en uppgradering av applikationsarkitekturen, utan en organisatorisk skalningsstrategi.
Om du inte redan har den organisatoriska smärta de löser, att adoptera dem tidigt gör dig inte framtidssäker.
Det här är inget teoretiskt argument, det är en teknisk kompromiss, och som alla kompromisser, det är bara vettigt när du förstår skatten du betalar och vad du får i utbyte.
Jag har varit lika skyldig som någon annan att "göra mikrotjänster" utan att helt internalisera de organisatoriska omkostnader de medför. Det är lätt att fastna i ordförrådet och glömma att den verkliga utmaningen är att hantera komplexiteten mellan team och system.
I slutändan kommer det ner till det äldsta mönstret inom programvaruteknik: KISS - Håll det enkelt, din idiot.
Mikrotjänster säljs som standardarkitektur eftersom berättelsen är förförisk:
Den här inramningen är baklänges.
Mikrotjänster handlar inte i första hand om kodstruktur. vem kan ändra vad, utan att prata med vem, och hur ofta.
Om du inte har:
Då löser du inte ett organisatoriskt problem.
Arkitektur handlar i slutändan om att göra det möjligt för människor, inte att flytta runt lådor.
Du har sett det här diagrammet.

Detta är inte en "exempelarkitektur att kopiera". Det är en varningsetikett.
Kurser lär ofta ut mikrotjänster som diagram eftersom diagram är lättare än att undervisa om operativt ansvar.
Den viktiga reframing är detta: det diagrammet är inte ett mål tillstånd. Det är en Kostnadsyta.
Varje låda är ett åtagande:
Pilarna ser imponerande ut och döljer verkligheten.
Eftersom varje pil är:
Om en arkitektur kräver detta på dag ett, du bygger inte en produkt. Du bygger en plattform.
Som alla arkitektoniska beslut, microservices kommer med en Komplicerad skatt (se Att leka med koden: komplexitet skatt. Skillnaden är att denna skatt förenas över varje tjänstegräns, varje installation, och varje felläge.
Kostnaderna är sällan uttryckliga. De är vanligtvis förklädda till "bästa praxis".
Varje tjänst vill ha:
Om ett team inte kan göra detta bekvämt, har det inte microservices. en distribuerad monolit med extra steg.
I en monolit är ett samtal ett funktionssamtal.
Inom mikrotjänster är ett "samtal" en framförhandlad vapenvila mellan:
Misslyckandelägen multiplicerar:
Du felsökar inte längre. systemtillstånd.
Här är en kostnad som nästan aldrig diskuterats i microservices påverkan: serialisering och deserialisering (SerDe) overhead.
Varje tjänstegräns innebär:
// 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
Per samtal, det är ~150μs av ren CPU overhead innan nätverket ens blir inblandade.
Nu multiplicera det över en samtalskedja:
I en 5-tjänst kedja, spenderar du ~1,5ms bara på SerDe - och det är optimistisk JSON serialisering. Om du använder XML, protokoll Buffers med reflektion, eller ineffektiva serializers, multiplicera det med 2-10x.
Ja, du kan minska detta med tekniker som JSON källa generation i .NET:
[JsonSerializable(typeof(User))]
internal partial class UserJsonContext : JsonSerializerContext { }
// ~30-40% faster than reflection-based serialization
var json = JsonSerializer.Serialize(user, UserJsonContext.Default.User);
Du har gjort skatten lite billigare, men i en monolit är skatten noll.
Jag profilerade en gång ett adresssöksystem med denna arkitektur (alla i Kubernetes behållare):
ASP.NET API → Go Service (fan-out) → ASP.NET Search Service → Elasticsearch
Den Go tjänsten hanterade fläkt-ut till flera datakällor och aggregerade resultat. Varje begäran innebar flera serialisering humle över container gränser.
Uppdelningen för en typisk adresssökning:
För en 3-backend fläkt-out:
Vi spenderade nästan lika mycket tid på Serde som på själva sökandet.
Kostnaden var inte själva tjänsten Go - fan-out gjorde vettigt för användning fallet. Kostnaden var den Tjänstegränser. Varje container kant innebar serialisera → nätverk → deserialize.
I en monolit gör samma fan-out logik:
Denna kostnad:
I en monolit är kostnaden noll. Föremålet finns redan i minnet.
Här är en gemensam försäljning pitch: "Microservices förbättra prestanda."
Det här är baklänges.
Mikrotjänster är långsammare För samma beräkna resurser. Låt oss vara exakt om vad vi menar:
Genomgång (begäran per sekund per processorkärna):
Skalbarhet (möjlighet att hantera mer totala förfrågningar genom att lägga till resurser):
Mikrotjänster låter dig skala oberoende. Om din inventering tjänst behöver 10x resurser men din användartjänst inte, kan du skala dem separat. Det är värdefullt när du behöver det.
Men det är inte en prestandaoptimering. skalningsstrategiOch det kommer med en skatt.
Argumentet "mikrotjänster skala bättre" var mer vettigt under 2010 när servrar hade 4-8 kärnor.
Moderna servrar har 32-128 kärnor. En enda maskin kan köra tusentals samtidiga operationer effektivt.
Om din monolit är byggd med moderna samtidiga utförandemönster, kan den hantera massiv genomströmning på en enda distribution. Till exempel, med hjälp av mönster som Efemära samordnare av genomförandet, du kan :
// 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
Detta ger dig:
När du gör måste skala bortom en maskin, kan du:
Du kör fortfarande en monolit, du har använt den flera gånger.
I detta sammanhang skall följande punkt läggas till:: Dela inte upp i mikrotjänster för "prestanda". oberoende skalning av specifika komponenter motiverar de operativa omkostnaderna.
Mikrotjänster minskar inte komplexiteten.
Den "enkla tjänsten" behöver fortfarande följande sammanhang:
Folk underskattar detta eftersom diagram döljer det.
Varje gräns blir ett kontrakt.
Varje kontrakt blir:
Du kan undvika detta ett tag med "bara distribuera allt tillsammans".
Om du sätter ihop allt så bygger du en monolit.
De tidiga utgifterna är alltid samma:
Inget av detta fartyg produktvärde.
Och de flesta lag gör det innan de har bevisat att produkten förtjänar komplexiteten.
Detta är det centrala misstaget: du betalar ceremoniskatten innan du har förtjänat värdet- Som om jag inte hade nåt emot det. dagliga standups som slösar tid utan att leverera anpassning, microservices kan bli rituell arkitektur: imponerande att titta på, dyra att underhålla och kopplade från det verkliga problemet du löser.
Tillsammans försvinner inte dessa kostnader - de ökar, och de förstärker om organisationen behövde mikrotjänster eller inte.
Arkitektur finns för att hjälpa grupper av människor att ändra programvara på ett säkert sätt.
Inte för att imponera på andra ingenjörer.
Inte för att tillfredsställa ett diagram.
Inte för att rättfärdiga ett plattformsteam du inte har.
Gruppstorlek och distribution kadens betyder mer än mönsterkatalogen.
Conways lag är inte frivillig, det är fysik.
Din arkitektur kommer att spegla din kommunikationsstruktur oavsett om du vill det eller inte.
Mikrotjänster skapar inte autonomi, de kräver det.
De flesta system borde börja som en monolit, men inte slarvig.
En förteckning över de behöriga myndigheter som avses i punkt 1 i denna artikel ska upprättas i enlighet med det förfarande som avses i artikel 4 i förordning (EU) nr 952/2013. modulär monolit är
Det innebär följande:
Poängen är inte att förbli monolitisk för alltid. tjäna gränser innan du gör dem fjärr.
En modulär monolit tvingar dig att lära dig de färdigheter microservices låtsas lösa:
Om du inte kan bygga en ren modulär monolit, mikrotjänster kommer inte att rädda dig. De kommer bara att distribuera röran.
Rätt monolit design låter dig skjuta upp beslutet om mikrotjänster utan att låsa in dig själv.
Ett av de bästa exemplen är Utkorgsmönster - ett sätt att uppnå eventuell konsekvens och async-behandling inuti en monolit, med en ren väg till distribution senare.
I stället för:
// 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
}
Du skriver:
// 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
}
Vad detta ger dig:
och Projekt för urval av SegmentCommerce visar detta mönster i produktionen:
// 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);
}
}
}
Detta körs i en enda app idag. När du behöver skala:
Du har byggt mikrotjänster-klar infrastruktur utan att betala distributionsskatten.
Ingen av dessa kräver distribution. Alla av dem gör distributionen lättare senare.
Mikrotjänster är begripliga när begränsningarna är verkliga, inte aspirativa.
Den kritiska frågan är inte "ska vi använda mikrotjänster?" Det är: uppväger fördelarna driftsskatten?
Som att välja mellan små lokala modeller och gräns LLMs, Detta handlar om att förstå var komplexitet hör hemma och vilka fellägen du har råd med.
Om ditt skäl innehåller orden kan, så småningom, eller framtidssäkradDet är nog ingen anledning.
Här är vad som förändras när man delar upp en monolit i tjänster.
Engångs-:
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;
}
Total latens: ~50ms (i processen samtal + 2 DB frågor)
Mikrotjänster:
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;
}
Total latens: ~400ms (även optimistiska 50–80ms per HTTP samtal i verkliga miljöer + meddelande buss publicera ~20ms)
Vad du har vunnit:
Vad du betalade:
Hantering av monolitfel:
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);
}
Felhantering för mikrotjänster:
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...
Varje nätverkssamtal introducerar:
Utplacering av monolit:
# 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
Utbyggnad av mikrotjänster:
Du ändrar ordningsstrukturen. Order innehåller UserTier direkt (denormaliserad för prestanda).
# 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)
Med mikrotjänster korsar varje förändring flera tjänster.
Inget av detta är omöjligt. Men det kräver disciplin, verktyg, och erfarenhet som de flesta lag bara tjänar efter år av smärta.
Felrapport från monolit: "Order placering misslyckas för premium användare"
// Set breakpoint in PlaceOrder
// Step through each call
// User tier is null - ah, there's the bug
// Fix, test, deploy
Felrapport för mikrotjänster: "Order placering misslyckas för premium användare"
# 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
Detta är verkligheten: Fel som var 5-minutersfixar blir multi-team utredningar.
Om det låter extremt, bra. Mikrotjänster är extrem ingenjörskonst.
Mikrotjänster är en enkelriktad dörr.
Den säkra vägen ser tråkig ut:
Om du inte kan dra gränsen innanför monoliten (med verkställbara beroenden), kan du inte extrahera den säkert.
Få sömmarna rätt lokalt först.
Läsning är lättare:
Flytta läsa modeller ut först om du behöver separation.
Synkrona servicesamtal skapar beroendekedjor.
Async-meddelanden skapar:
Börja med att separera händelser och fakta, inte genom att skära slutpunkter.
Ingen stor smäll.
Inget "vi skriver om det ordentligt".
Skär av en gräns, mät den, äger den och fortsätt.
Om du inte kan förklara varför en tjänst existerar självständigt, borde den inte existera alls.
Mikrotjänster är ingen utgångspunkt, utan ett resultat.
Komplexitet bör förtjänas. Distribuerade system är inte ett märke - de är ett skuldinstrument.
Avräkningsekvationen är enkel:
Microservices Value = (Team Autonomy + Independent Scaling + Failure Isolation)
- (Latency Tax + SerDe Tax + Operational Overhead + Coordination Cost)
För de flesta lag - särskilt tidiga eller enkel-team produkter - den högra sidan dominerar. Du betalar enorma kostnader för förmåner du inte behöver ännu.
Men när du har:
...den vänstra sidan börjar vinna. Skatten blir berättigad.
Ställ följande frågor i ordning:
Kan ett lag fortfarande äga detta från början till slut?
→ Ja: stanna monolit. Nej: överväga att dela.
Är lag blockerade av varandras release cykler?
→ Ja: mikrotjänster kan hjälpa. Nej: samordning fungerar.
Har vi den operativa muskeln att köra distribuerade system?
→ Nej: bygga den först. Ja: fortsätt försiktigt.
Kan vi spåra, felsöka och distribuera flera tjänster utan hjältedåd?
→ Nej: du är inte redo. Ja: du kan vara.
Har vi bevisat gränserna i kod först?
→ Nej: fixa din monolit struktur. Ja: utvinning är säkrare.
Om du inte kan komma förbi fråga 3 med tillförsikt, är du inte redo för mikrotjänster - och det är bra. Tråkig arkitektur är en konkurrensfördel.
De mest framgångsrika produkterna byggdes som monoliter först: GitHub, Shopify, Stack Overflow, Basecamp. De utvecklades till distribuerade system först när den organisatoriska smärtan krävde det.
Ditt jobb är inte att bygga den mest imponerande arkitekturen. Det är att leverera värde samtidigt minimera oavsiktlig komplexitet.
Och ingen kund har någonsin betalat extra eftersom ditt system använde Kafka.
Ibland betyder det mikrotjänster. Vanligtvis betyder det en välstrukturerad monolit och disciplinen att hålla det på det sättet.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.