Back to "Varför du förmodligen inte bör använda Microservices (ännu)"

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

Varför du förmodligen inte bör använda Microservices (ännu)

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.

Myten om mikrotjänster

Mikrotjänster säljs som standardarkitektur eftersom berättelsen är förförisk:

  • Små tjänster är "renare"
  • Distribuerade system är "moderna"
  • Autonomin är "fri"
  • Du kan "skala senare" eftersom du började "rätt"

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:

  • Flera lag fraktar självständigt
  • Motstridande prioriteringar
  • Samordningsflaskhalsar
  • Omständigheter i fråga om utplaceringen
  • Reala ägargränser

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.

Diagrammet som bör vara en varningsetikett

Du har sett det här diagrammet.

Ett varnande arkitekturdiagram för mikrotjänster

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:

  • En körtid för att arbeta
  • Utplacering för att hantera
  • Avtal till version
  • Ett felläge som du nu äger
  • En jourhistoria du inte kan ignorera

Pilarna ser imponerande ut och döljer verkligheten.

Eftersom varje pil är:

  • Tröskelvärde
  • Partiellt fel
  • Restriktioner
  • Tidsgränser
  • Spårets sammanhangsförökning
  • "Arbetar i iscensättning" ligger

Om en arkitektur kräver detta på dag ett, du bygger inte en produkt. Du bygger en plattform.

Den dolda kostnadsmodellen för mikrotjänster

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".

Operativt överläge

Varje tjänst vill ha:

  • Dess egen byggledning
  • Dess egen installationskonfiguration
  • Dess egen övervakning och varning
  • Dess egen loggning, instrumentpaneler, runböcker
  • Dess egen säkerhetsställning (hemligheter, sanning, intrång)

Om ett team inte kan göra detta bekvämt, har det inte microservices. en distribuerad monolit med extra steg.

Latency och misslyckande multiplikation

I en monolit är ett samtal ett funktionssamtal.

Inom mikrotjänster är ett "samtal" en framförhandlad vapenvila mellan:

  • Nätverk
  • Belastningsbalanser
  • TLS
  • Auth middleware för användning i mitten
  • Seriealisering
  • Tidsgränser
  • Restriktioner
  • Mottryck

Misslyckandelägen multiplicerar:

  • En nedströms är långsam → uppströms trådar stapla upp
  • Retries förstärker belastning → du DDoS dig själv artigt
  • En enda beroendeklaff → tre tjänster försämrar "slumpmässigt"

Du felsökar inte längre. systemtillstånd.

Seriealiseringsskatten Ingen talar om

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:

  • Orderservice deserializes begäran (150μs)
  • Orderservice serialiserar begäran om användartjänst (150μs)
  • Användartjänst deerializes begäran (150μs)
  • Användartjänsten serialiserar svaret (150μs)
  • Order service deserializes Användarsvar (150μs)
  • Orderservice serialiserar lagerförfrågan (150μs)
  • och så vidare

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.

Ett verkligt exempel: Serdekatastrofen

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:

  • ASP.NET API: Serialisera sökningsförfrågan → JSON (80μs)
  • Nätverk: ASP.NET → Gå (varianter av klusterbelastning)
  • Gå tjänst: Deserialisera begäran (40μs)
  • Gå tjänst: Fläkt ut - serialisera förfrågningar till flera gränssnitt (60μs × N-backends)
  • Nätverk: Gå → ASP.NET Sök (varianter)
  • Söktjänst: Deserialisera begäran (90μs)
  • Söktjänst: Bygg Elasticsearch fråga, serialisera (120μs)
  • Nätverk: Sök → Elasticsearch (varianter)
  • Elasticsearch: Deserialize fråga, köra, serialisera resultat (faktisk sökning ~5ms, SerDe ~200μs)
  • Nätverk: Elastisk sökning → Sök (varianter)
  • Söktjänst: Deserialize ES response (300μs), karta till domänmodell, serialize (180μs)
  • Nätverk: Sök → Gå (varianter)
  • Go Service: Deserialisera svar från alla bakgrundsprogram (150μs × N), aggregat, serialisera (80μs)
  • Nätverk: Gå → ASP.NET (varianter)
  • ASP.NET API: Deserialisera det slutliga svaret (120μs)

För en 3-backend fläkt-out:

  • Totalt SerDe overhead: ~2ms innan Elasticsearch även börjat
  • Nätet mellan containrar: ~2-3ms
  • Faktisk sökning + affärslogik: ~6ms

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:

  • Samma Elastiska sökfrågor
  • Samma aggregeringslogik
  • Zero inter-service SerDe (just method calls)
  • 40% mindre total latens

Denna kostnad:

  • Skalor med objektets komplexitet (nested objekt, samlingar, polymorphism)
  • Föreningar med anropsdjup (servicekedjor)
  • Bränner CPU på varje begäran (kan inte cache den bort)
  • Ökar GC-trycket (allokering churn från sträng / byte matriser)
  • Lägger upp snabbt när du gör hundratals förfrågningar per sekund

I en monolit är kostnaden noll. Föremålet finns redan i minnet.

Prestandamyt: Genomströmning vs. skalbarhet

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):

  • Monolit och salter av monolit: Högre - noll SerDe, noll nätverk humle, direkt metod samtal
  • Mikrotjänster: Lower - Serde overhead, nätverkslatency, container orkestrering

Skalbarhet (möjlighet att hantera mer totala förfrågningar genom att lägga till resurser):

  • Monolit och salter av monolit: Begränsat till vertikal skalning (större maskiner)
  • Mikrotjänster: Horisontellt - lägga till fler fall av flaskhals tjänster

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.

Moderna monoliter kan också skala

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:

  • Parallellism: Arbete körs samtidigt över alla kärnor
  • Observationsförmåga: Spårad/aktiv/misslyckad verksamhet
  • Mottryck: Gränsade köer förhindrar minnesuttömmande
  • Noll omkostnader: Ingen seriellisering, inget nätverk, ingen distribuerad spårning

När du gör måste skala bortom en maskin, kan du:

  1. Kör flera instanser bakom en belastningsbalanser (stateless horisontell skalning)
  2. Använd ett utkorgsmönster för async arbetsfördelning
  3. Uppdelning efter nyckel (kund-ID, region, etc.) mellan instanser

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.

Kognitiv belastning

Mikrotjänster minskar inte komplexiteten.

Den "enkla tjänsten" behöver fortfarande följande sammanhang:

  • Vem kallar det för det?
  • Vad betyder "korrekt"?
  • Vad händer när den är nere?
  • Vad är planen för upprullning?
  • Vad är beroendekurvan?

Folk underskattar detta eftersom diagram döljer det.

Versions- och kontraktsdrift

Varje gräns blir ett kontrakt.
Varje kontrakt blir:

  • Versionsregler
  • Förenlighetsgarantier
  • Begränsningar av schemats utveckling
  • Coordination overhead du låtsades att du inte hade

Du kan undvika detta ett tag med "bara distribuera allt tillsammans".

Om du sätter ihop allt så bygger du en monolit.

Verktyg före värde

De tidiga utgifterna är alltid samma:

  • Upptäckt av tjänsten
  • Hantering av hemligheter
  • Distribuerad spårning
  • Centraliserad loggning
  • Metrisk och varning
  • CI/CD-mallar
  • Lokal historia som inte är smärtsam
  • Hantering av beroendeförhållanden
  • Ett sätt att sköta allt utan att gråta

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.

Vad folk glömmer: Människor och lag

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.

  • Om ett lag äger hela färdplanen är en monolit oftast snabbare och säkrare.
  • Om du fortfarande förstår systemet end-to-end, mikrotjänster kommer att minska denna klarhet.
  • Om du inte har stabila ägargränser, kommer microservices att tvinga samordning via API:er - vilket är den långsammaste samordningsmekanismen som finns tillgänglig.

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.

Till försvar för den modulära monoliten

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

  • En utplaceringsenhet
  • En gång
  • Ett ställe att felsöka
  • En konsekvent syn på tillståndet
  • Men med verkliga inre gränser

Det innebär följande:

  • Domänmoduler med tydliga beroenden
  • Rensa ägarskap i kod
  • Inget "allt refererar till allt"
  • Avtal som verkställts inom kodbasen

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:

  • Domänmodellering (verkliga gränser, inte kataloggränser)
  • Separation av problem (inte "vi gjorde en tjänst")
  • Testbarhet
  • Ändra disciplin
  • Att veta vad ditt system faktiskt gör

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.

Bra monolit design: Mönster som skalar senare

Rätt monolit design låter dig skjuta upp beslutet om mikrotjänster utan att låsa in dig själv.

Utkorgsmönstret: Asynkronisera utan fördelning

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:

  • Transaktionskonsistens: Händelser och data förbinder tillsammans eller inte alls
  • Frikoppling: E-post/inventeringslogik kör async, blockerar inte ordningsskapande
  • Tillförlitlighet: Händelser kvarstår även om nedströmssystem är nere
  • Ren extraktionsväg: Senare byta ut utkorgsarbetaren mot Kafka/RabbitMQ utan att ändra ordningslogiken

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:

  1. Ersätt bakgrundsarbetaren med en meddelandebusskonsument
  2. Publicera utkorgshändelser till Kafka/RabbitMQ istället för lokal bearbetning
  3. Orderplaceringskoden ändras inte

Du har byggt mikrotjänster-klar infrastruktur utan att betala distributionsskatten.

Andra mönster som är värda att göra tidigt

  • CQRS (ljus): Separat läsa / skriva modeller även om de delar en DB
  • Händelseanskaffning (selektiv): Endast för revisionskritiska enheter
  • Flaggor för funktioner: Frikoppla utplacering från frisläppning
  • BakgrundsjobbBlockera inte förfrågningar, processa async

Ingen av dessa kräver distribution. Alla av dem gör distributionen lättare senare.

När mikrotjänster faktiskt gör känsla

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.

Bra triggers

  • Lagstriden är mätbar: Lag blockerar varandra varje vecka
  • Oberoende utplaceringar är nödvändiga: Release kadensen varierar per domän
  • Skala är ojämn: Ett delsystem behöver 10x resurser och isolering
  • Oavsiktlig isolering: En komponent får inte ta ner resten
  • Isolering av regler och uppgifter är obligatorisk: Gränser är inte förhandlingsbara
  • Org struktur är redan multi-team: Äganderätten finns och är stabil

Dåliga triggers

  • "Vi kan skala"
  • "Netflix gör det"
  • "Bästa praxis"
  • "Det kommer att se bra ut"
  • "Vår monolit är stökig, så vi fixar den genom att dela den."

Om ditt skäl innehåller orden kan, så småningom, eller framtidssäkradDet är nog ingen anledning.

Den tekniska verkligheten: Vad Microservices faktiskt kostar

Här är vad som förändras när man delar upp en monolit i tjänster.

Anropskedjor och latens

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:

  • Varje tjänst kan använda sig av oberoende
  • Inventering och prissättning kan skalas separat från beställningar
  • Fel i e- post blockerar inte skapandet av order (async händelse)

Vad du betalade:

  • 8x ökning av latens
  • 4 nya felpoäng (alla tjänster kan vara nere/låga)
  • Nätverk, DNS, TLS overhead på varje samtal
  • Seriealisering/deserialisering omkostnader (se tidigare avsnitt)
  • Behov av regressionslogik, strömbrytare, timeouts
  • Distribuerad spårning till felsökning

Fel vid hantering av explosion

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:

  • Tidsbegränsade scenarier
  • Nätverksfel
  • Otillgängliga tjänster
  • Partiella fel (önskad begäran, ej inlämnad respons)
  • Försök med förstärkning (ditt försök kan utlösa deras försök)
  • Utdelade transaktionsmardrömmar

Samordning av genomförandet

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.

  • Fönster för bakåtkompatibilitet
  • Flaggor för alla tjänster
  • Samordnade utrullningar
  • Utökade testmatriser (Service A v1 + Service B v2, Service A v2 + Service B v1 etc.)

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.

Erfarenheten av felsökning

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.

Hur man utvecklar säkert: Monolit → Microservices

Mikrotjänster är en enkelriktad dörr.

Den säkra vägen ser tråkig ut:

1. Bevisa gränser först

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.

2. Extrahera läser innan skriver

Läsning är lättare:

  • Färre variationer
  • Färre krav på överensstämmelse
  • Färre bakslagsmardrömmar

Flytta läsa modeller ut först om du behöver separation.

3. Föredra Async före synkronisering

Synkrona servicesamtal skapar beroendekedjor.

Async-meddelanden skapar:

  • Buffring
  • Motståndskraft
  • Frikoppling kan du faktiskt överleva

Börja med att separera händelser och fakta, inte genom att skära slutpunkter.

4. Strange, skriv inte om

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.

Takeaway: Microservices betalar bara om du förstår Trade-Offs

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:

  • 5+ lag kliver på varandras tår
  • Utplaceringsköer mätt i dagar
  • Delsystem med helt olika skalbehov
  • Regleringskrav för isolering av uppgifter

...den vänstra sidan börjar vinna. Skatten blir berättigad.

En ram för beslut

Ställ följande frågor i ordning:

  1. Kan ett lag fortfarande äga detta från början till slut?
    → Ja: stanna monolit. Nej: överväga att dela.

  2. Är lag blockerade av varandras release cykler?
    → Ja: mikrotjänster kan hjälpa. Nej: samordning fungerar.

  3. Har vi den operativa muskeln att köra distribuerade system?
    → Nej: bygga den först. Ja: fortsätt försiktigt.

  4. Kan vi spåra, felsöka och distribuera flera tjänster utan hjältedåd?
    → Nej: du är inte redo. Ja: du kan vara.

  5. 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.

logo

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