Miksi sinun ei luultavasti pitäisi käyttää mikropalveluita (vielä) (Suomi (Finnish))

Miksi sinun ei luultavasti pitäisi käyttää mikropalveluita (vielä)

Monday, 29 December 2025

//

19 minute read

Mikropalveluista on tullut "vakavan järjestelmän" oletusarkkitehtuuri.

Jos haluat kuulostaa kypsältä, puhut palveluverkoista, tapahtumabusseista, jaetusta jäljityksestä ja "riippumattomuudesta". Jos haluat näyttää yrittäjävalmialta, piirrät laatikoita, kunnes kaavio näyttää jonkun heittämältä spagettikulholta.

Suurin osa junioreista ei kuitenkaan saa korjausta:

Mikropalvelut eivät ole sovellusarkkitehtuurin päivitys, vaan organisaatioskaalautumisstrategia.

Jos sinulla ei ole jo niiden ratkaisemaa organisaatiokipua, niiden varhainen hyväksyminen ei tee sinusta tulevaisuudenkestävää, vaan se tekee sinusta nykyhetken rikkinäisen.

Tämä ei ole teoreettinen argumentti, vaan insinöörien vaihtokauppa, ja kuten kaikki sovitukset, se käy järkeen vain silloin, kun ymmärtää maksamansa veron ja sen, mitä saa vastineeksi.

Olen ollut yhtä syyllinen kuin kuka tahansa "mikropalveluiden tekemiseen" ilman, että olen sisäistänyt täysin niiden aiheuttamia organisaatiokuluja. Sanastoon on helppo tarttua ja unohtaa, että todellinen haaste on monimutkaisuuden hallinta eri tiimeissä ja järjestelmissä.

Lopulta kyse on ohjelmistotekniikan vanhimmasta mallista: KISS - Pidä se yksinkertaisena, typerys.

Mikropalvelumyytti

Mikropalveluita myydään oletusarkkitehtuurina, koska kerronta on viettelevää:

  • Pienet palvelut ovat "puhdistavampia"
  • Hajautetut järjestelmät ovat "moderneja"
  • Autonominen on "vapaata"
  • Voit "skaalata myöhemmin", koska aloitit "oikealla"

Tämä kehystys on takaperin.

Mikropalveluissa ei ole kyse ensisijaisesti koodirakenteesta, vaan siitä, että kuka voi muuttaa mitäkin, puhumatta kenen kanssa, ja kuinka usein.

Jos sinulla ei ole:

  • Useita joukkueita purjehtii itsenäisesti
  • Ristiriitaiset prioriteetit
  • Koordinaation pullonkaulat
  • Käyttöönottoa koskeva väite
  • Todelliset omistusrajat

Sitten et ratkaise organisaatio-ongelmaa, vaan ostat sen.

Arkkitehtuurissa on viime kädessä kyse ihmisten mahdollistamisesta, ei laatikoiden liikuttamisesta.

Kaavio, jonka pitäisi olla varoitusmerkintä

Olet nähnyt tämän kaavion.

Varoittava mikropalveluiden arkkitehtuurikaavio

Tämä ei ole "esimerkkiarkkitehtuuri kopioitavaksi", vaan varoitusetiketti.

Kurssit opettavat usein mikropalveluita kaavioina, koska kaaviot ovat helpompia kuin opetetaan operatiivista vastuuta.

Tärkein korjaus on tämä: tuo kaavio ei ole kohdetila. kustannuspinta.

Jokainen laatikko on sitoumus:

  • Toiminta-aika
  • Käyttöönotto hallinnoitavaksi
  • Sopimus versioon
  • Nyt omistamasi vikatila
  • Päivystystarinaa ei voi sivuuttaa

Nuolet näyttävät vaikuttavilta ja kätkevät todellisuuden.

Koska jokainen nuoli on:

  • Latenssi
  • Osittainen epäonnistuminen
  • Retiitit
  • Aikalisät
  • Trace-kontekstin levinneisyys
  • "Työtä lavastuksessa" -valheet

Jos arkkitehtuuri vaatii tätä ensimmäisenä päivänä, et rakenna tuotetta vaan alustaa.

Mikropalveluiden piilohintamalli

Kuten kaikki arkkitehtoniset päätökset, mikropalveluissa on Monimutkaisuusvero (ks. Koodilla leikkiminen: monimutkaisuusveroErona on se, että veroyhdisteet ulottuvat jokaisen palvelurajan, jokaisen käyttöönoton ja jokaisen vikatilan yli.

Kustannukset ovat harvoin yksiselitteisiä, yleensä ne naamioidaan "parhaaksi käytännöksi".

Operatiivinen yleiskuva

Jokainen palvelu haluaa:

  • Sen oma rakennusputki
  • Sen oma käyttöönottokonfiguraatio
  • Sen oma seuranta ja hälyttäminen
  • Sen oma puunkorjuu, kojelaudat, käyttökirjat
  • Sen oma turva-asento (salaisuudet, autuus, sisäänkäynti)

Jos joukkue ei pysty tähän mukavasti, sillä ei ole mikropalveluita. jaettu monoliitti, jossa on ylimääräisiä askeleita.

Latenssi ja epäonnistuminen moninkertaistuvat

Monoliitissa puhelu on funktiopuhelu.

Mikropalveluissa "puhelu" on neuvoteltu aselepo seuraavien välillä:

  • Verkot
  • Kuormitusvaa'at
  • TLS
  • Auth-keskiohjelmisto
  • Järjestys
  • Aikalisät
  • Retiitit
  • Vastapaine

Virhetilat moninkertaistuvat:

  • Yksi alavirtaan on hidas → ylävirtaan kasautuvat langat
  • Retries vahvistaa kuormitusta → sinä DDoS itse kohteliaasti
  • Yksittäinen riippuvuusläpät → kolme palvelua heikentävät "satunnaisesti"

Vikoja ei vioiteta enää. järjestelmä ilmoittaa.

Sarjakuvaverosta kukaan ei puhu

Tässä on hinta, josta ei juuri koskaan puhuta mikropalveluiden ajamisessa: Serialisointi- ja deserialisaatio (SerDe) yläpuolella.

Jokainen palvelun raja tarkoittaa:

// 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

Kutakin puhelua kohden se on noin 150 μs puhdasta prosessoria ennen kuin verkko edes sekaantuu siihen.

Kerro se nyt puheluketjussa:

  • Tilauspalvelu deserialisoi pyynnön (150 μs)
  • Tilauspalvelu serialisoi Käyttäjän palvelupyyntöä (150 μs)
  • Käyttäjäpalvelu deserialisoi pyynnön (150 μs)
  • Käyttäjäpalvelu serialisoi vasteen (150 μs)
  • Tilauspalvelu vähentää käyttäjän vastetta (150 μs)
  • Tilauspalvelu serialisoi inventointipyynnön (150 μs)
  • ja niin edelleen

Viiden palvelun ketjussa kulutat ~1,5 milliä vain SerDeen Jos käytät XML:ää, Protocol Buffersia, jossa on heijastus tai tehottomia sarjakuvia, kerro se 2-10x:llä.

Kyllä, tätä voi vähentää tekniikoilla, kuten JSON-lähdesukupolvi verkossa:

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

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

Mutta teet yhä sarjatuotantoa, olet juuri tehnyt verosta hieman halvemman. Monoliitissa vero on nolla.

Todellinen esimerkki: Serde-katastrofi

Profiloin kerran osoitehakujärjestelmän tällä arkkitehtuurilla (kaikki Kubernetesin konteissa):

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

Go-palvelu käsitteli fanitusta useisiin tietolähteisiin ja aggregoituihin tuloksiin. Jokainen pyyntö tarkoitti useita sarjahumuja konttirajojen yli.

Tyypillisen osoitehaun erittely:

  • ASP.NET API: Sarjahakupyyntö → JSON (80 μs)
  • Verkko: ASP.NET → Go (varikset rypäskuormalla)
  • Go Service: Deserialize-pyyntö (40 μs)
  • Go Service: Faile out - serialisoi pyyntöjä usealle backendille (60 × N backends)
  • Verkko: Siirry → ASP.NET-haku (varikset)
  • Hakupalvelu: Deserialize requestion (90 μs)
  • Hakupalvelu: Build Elasticsearch query, serificaze (120 μs)
  • Verkko: Hae → Elastinenhaku (varikset)
  • Elastinen haku: Deserialize query, suorita, serialisoi tulokset (todelliset haut ~5ms, SerDe ~200μs)
  • Verkko: Elastinenhaku → Haku (varikset)
  • Hakupalvelu: Deserialize ES-vaste (300 μs), kartta verkkoaluemalliin, sarjakuva (180 μs)
  • Verkko: Hae → Go (Varies)
  • Go Service: Deserialize-vastaukset kaikista taustajoukoista (150 × N), aggregaatti, sarjanumero (80 μs)
  • Verkko: Go → ASP.NET (varikset)
  • ASP.NET API: Deserialisoi lopullinen vaste (120 μs)

Kolmelle takatolpalle:

  • Yhteensä SerDe yläpuolella: ~2 ms ennen kuin Elastisen etsintä edes alkoi
  • Verkko yläpuolella konttien välillä: ~2-3ms
  • Varsinainen haku + liiketoimintalogiikka: ~6ms

Käytimme lähes yhtä paljon aikaa SerDeen kuin varsinaiseen etsintään.

Kustannukset eivät olleet itse Go-palvelussa, vaan käyttökotelossa oli järkeä. palvelurajatJokainen konttiraja tarkoitti sarjanumeroa → verkkoa → deserialize.

Monoliitissa, joka tekee samaa fan-out-logiikkaa:

  • Samat Elastisen hakukyselyt
  • Sama aggregointilogiikka
  • Zero SerDe (vain metodipuhelut)
  • ~40 % vähemmän kokonaislatenssia

Kustannukset ovat seuraavat:

  • Skaalat, joissa on objektin monimutkaisuus (muunnellut esineet, kokoelmat, polymorfismi)
  • Soittosyvyydellä varustetut yhdisteet (palveluketjut)
  • Kirjoita prosessori jokaiseen pyyntöön (ei voi siirtää sitä pois)
  • Lisää GC-painetta (siirtymäjousi/tavusarjoista)
  • Lisää nopeasti, kun tekee satoja pyyntöjä sekunnissa

Monoliitissa tämä hinta on nolla. Objekti on jo muistissa.

Performance-myytti: läpäisykyky vastaan skaalautuvuus

Tässä on yleinen myyntipuhe: "Microservices parantaa suorituskykyä."

Tämä on takaperin.

Mikropalvelut ovat hitaammin Tarkennetaan, mitä tarkoitamme:

Läpäisykyky (pyynnöt sekunnissa CPU-ydintä kohti):

  • Monolith: Ylempi - nolla SerDe, nollaverkkohumala, suorat metodipuhelut
  • Mikropalvelut: Alempi - SerDe yläpuolella, verkon latenssi, konttien orkestrointi

Kallistuvuus (valmius käsitellä enemmän kokonaispyyntöjä lisäämällä resursseja):

  • Monolith: Rajoitettu pystysuuntainen skaalaus (isommat koneet)
  • Mikropalvelut: Horisontaaliset - lisää pullonkaulapalveluja

Mikropalvelut sallivat sinun mittakaava itsenäisesti. Jos varastopalvelusi tarvitsee 10x resursseja, mutta käyttäjäpalvelusi ei, voit skaalata ne erikseen. kun sitä tarvitsee.

Mutta se ei ole suoritusoptimointia, vaan skaalautumisstrategiaJa siihen liittyy vero.

Modernit monoliitit voivat skaalata myös

Väite "mikropalveluiden mittakaavan parantaminen" oli järkevämpi vuonna 2010, kun palvelimilla oli 4-8 ydintä.

Nykyaikaisissa palvelimissa on 32–128 ydintä. Yksi kone voi suorittaa tuhansia samanaikaisia toimintoja tehokkaasti.

Jos monoliittisi on rakennettu nykyaikaisten rinnakkaisten toteutusmallien avulla, se pystyy hoitamaan massiivisen läpimenon yhdellä käyttökerralla. Väliaikainen täytäntöönpanokoordinaattoreiden toiminta, voit:

// 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

Tästä saat:

  • Rinnakkaisuus: Työt kulkevat samanaikaisesti kaikkien ydinten välillä
  • Havainnointikelpoisuus: Rata vireillä, aktiivinen tai epäonnistunut
  • Vastapaine: Rajatut jonot estävät muistin sammumisen
  • Nolla ylimenoaEi sarjajakoa, ei verkkoa, ei hajautettua jäljitystä

Kun tee tarve skaalautua yhden koneen ulkopuolelle, voit:

  1. Suorita useita kertoja kuormantasaajan takana (täsmennettävä vaakataso)
  2. Käytä outbox-mallia async-työn jakeluun
  3. Jakautuminen avainten mukaan (asiakastunnus, alue jne.)

Olet käyttänyt monoliittia useita kertoja.

Asian ydin: Älä jaa "suorituskyvyn" mikropalveluihin. Jakaudu kun Tiettyjen aineosien riippumaton skaalautuminen perustelee toiminnan yleiskustannuksia.

Kognitiivinen kuorma

Mikropalvelut eivät vähennä monimutkaisuutta, vaan jakavat sen uudelleen.

"Yksinkertaisessa palvelussa" tarvitaan edelleen kontekstia:

  • Kuka sitä kutsuu?
  • Mitä "oikea" tarkoittaa?
  • Mitä tapahtuu, kun se on alhaalla?
  • Mikä on kääntösuunnitelma?
  • Mikä on riippuvuuskäyrä?

Ihmiset aliarvioivat tämän, koska kaaviot peittävät sen.

Mallintaminen ja sopimusajot

Jokaisesta rajasta tulee sopimus.
Jokaisesta sopimuksesta tulee:

  • Muuntamista koskevat säännöt
  • Yhteensopivuustakuut
  • Scheman evoluution rajoitteet
  • Koordinaation yläpuolella teeskentelit, ettei sinulla ollut

Voit vältellä tätä jonkin aikaa "käynnistellä kaikkea yhdessä".

Se on huipennus: jos kaikki otetaan käyttöön yhdessä, rakennetaan monoliitti, joka on vain huonompi.

Työkalut ennen arvoa

Aikainen käyttö on aina sama:

  • Palveluiden löytäminen
  • Salaisuuksien hallinta
  • Hajautettu jäljitys
  • Keskitetty puunkorjuu
  • Metriikka ja hälytys
  • CI/CD-mallit
  • Paikallinen dev-tarina, joka ei ole tuskallinen
  • Riippuvuusjohtaminen
  • Tapa pyörittää kaikkea itkemättä

Mikään näistä laivoista ei tuota arvoa.

Useimmat tiimit tekevät sen ennen kuin ovat todistaneet tuotteen ansaitsevan monimutkaisuuden.

Tämä on keskeinen virhe: maksat seremoniaveron ennen kuin olet ansainnut arvonKuten päivittäiset standupit, jotka tuhlaavat aikaa ilman yhdenmukaistamistaMikropalveluista voi tulla rituaaliarkkitehtuuria: vaikuttavaa katseltavaa, kallista ylläpidettävää ja irrallaan todellisesta ongelmasta, jota ratkot.

Yhdessä nämä kustannukset eivät katoa - ne kasautuvat, ja ne yhdistelevät sitä, tarvitsiko organisaatio ylipäätään mikropalveluita vai ei.

Mitä ihmiset unohtavat: ihmisiä ja joukkueita

Arkkitehtuuri auttaa ihmisryhmiä muuttamaan ohjelmistoja turvallisesti.

En halua tehdä vaikutusta muihin insinööreihin.
Ei tyydyttämään kaaviota.
Ei oikeuttamaan alustajoukkuetta, jota sinulla ei ole.

Joukkueen koolla ja käyttöönottokadenssilla on enemmän merkitystä kuin kuvioluettelolla.

  • Jos yksi joukkue omistaa koko etenemissuunnitelman, monoliitti on yleensä nopeampi ja turvallisempi.
  • Jos vielä ymmärrät järjestelmän laidasta laitaan, mikropalvelut vähentävät tätä selkeyttä.
  • Jos omistajuusrajat eivät ole vakaat, mikropalvelut pakottavat koordinointiin sovellusliittymien kautta, mikä on hitain käytettävissä oleva koordinaatiomekanismi.

Conway's Law ei ole valinnainen, vaan fysiikka.

Arkkitehtuurisi heijastaa viestintärakennettasi, pidit siitä tai et.

Mikropalvelut eivät luo autonomiaa, vaan vaativat sitä.

Modulaarisen monoliittin puolustaminen

Useimpien järjestelmien pitäisi alkaa monoliittina, mutta ei huolimattomana.

A modulaarinen monoliitti on

  • Yksi käyttöönottoyksikkö
  • Yksi juoksukerta
  • Yksi paikka vianetsintään
  • Yksi johdonmukainen näkemys tilasta
  • Mutta todellisilla sisärajoilla

Se tarkoittaa:

  • Verkkoaluemoduulit, joilla on selviä riippuvuuksia
  • Tyhjennä omistus koodilla
  • "Kaikki viittaa kaikkeen"
  • Koodipohjan sisällä täytäntöönpannut sopimukset

Kyse ei ole siitä, että pysymme yksinäisinä ikuisesti. ansaitse rajat ennen kuin teet niistä etäisiä.

Modulaarinen monoliitti pakottaa sinut opettelemaan taitoja, joita mikropalvelut esittävät ratkovansa:

  • Verkkoalueen mallintaminen (todelliset rajat, ei kansioiden rajat)
  • Huolenaiheiden erottaminen (ei "tehty palvelu")
  • Testattavuus
  • Vaihda kuria
  • Tietäen, mitä järjestelmäsi todella tekee

Jos et pysty rakentamaan puhdasta modulaarista monoliittia, mikropalvelut eivät pelasta sinua. Ne vain levittävät sotkua.

Hyvä Monolith-suunnittelu: kuviot, jotka skaalautuvat myöhemmin

Oikean monoliittisen muotoilun avulla lykkää mikropalvelupäätöstä Lukitsematta itseäsi sisään.

Outbox-kuvio: Asunc ilman jakelua

Yksi parhaista esimerkeistä on Outbox-kuvio - keino saavuttaa mahdollisimman johdonmukainen ja epäyhtenäinen käsittely Sisällä Monoliitti, jossa on puhdas tie jakeluun myöhemmin.

Sen sijaan, että:

// 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
}

Sinä kirjoitat:

// 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
}

Mitä tämä antaa sinulle:

  • Transaktioiden johdonmukaisuus: Tapahtumat ja data toimivat yhdessä tai eivät lainkaan
  • Kytkennät irrotetaan: Sähköposti/inventointilogiikka toimii async, ei estä tilausten luomista
  • Luotettavuus: Tapahtumat jatkuvat, vaikka loppupään järjestelmät olisivat alhaalla
  • Puhdista noutoreitti: Vaihda myöhemmin outbox-työntekijä Kafkalle/RabbitMQ:lle muuttamatta tilauslogiikkaa

Erytropoietiini SegmentCommercen otoshanke osoittaa tämän tuotantomallin:

// 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);
        }
    }
}

Tämä tapahtuu yhdessä sovelluksessa tänään. Kun sinun täytyy skaalata:

  1. Korvaa taustatyöntekijä viestibussikuluttajalla
  2. Julkaise outbox-tapahtumat Kafkalle/RabbitMQ:lle paikallisen käsittelyn sijaan
  3. Tilausnumero ei muutu

Olet rakentanut mikropalveluvalmiin infrastruktuurin maksamatta jakeluveroa.

Muita varhaisen tekemisen arvoisia malleja

  • CQRS (kevyt): Erilliset luku- ja kirjoitusmallit, vaikka ne jakaisivat DB:n
  • Tapahtumahankinta (valikoiva): Ainoastaan tilintarkastuskriittiset yhteisöt
  • Ominaisuusliput: Irrota käytöstä julkaisusta
  • Taustatyöpaikat: Älä estä pyyntöjä, käsittele async

Mikään näistä ei vaadi jakelua, vaan ne kaikki helpottavat jakelua myöhemmin.

Kun mikropalvelut todella aistivat

Mikropalveluissa on järkeä, kun rajoitukset ovat todellisia, eivät kunnianhimoisia.

Kriittinen kysymys ei ole: "Pitäisikö meidän käyttää mikropalveluita?" ovatko hyödyt toimintaveroa suuremmat?

Kuten valita väliltä pienet paikalliset mallit ja raja-alueet LLM:t, Tässä on kyse siitä, että ymmärretään, mihin monimutkaisuus kuuluu ja mihin epäonnistumistiloihin sinulla on varaa.

Hyviä liipaisimia

  • Joukkuekiista on mitattavissa: Joukkueet estävät toisiaan viikoittain
  • Riippumattomat käyttöönotot ovat välttämättömiä: Julkaisuväli vaihtelee verkkoalueittain
  • Skaala on epätasainen: Yksi osajärjestelmä tarvitsee 10x resursseja ja eristystä
  • Epäonnistuminen eristyneisyydellä on merkitystä: Yksi osa ei saa kaataa muita
  • Sääntelyn ja tietojen eristäminen on pakollistaRajat eivät ole neuvoteltavissa
  • Orgin rakenne on jo monijoukkueinen: Omistajuus on olemassa ja vakaa

Huonot aloitukset

  • "Voisimme skaalautua"
  • "Netflix tekee sen"
  • "Best practice"
  • "Se näyttää hyvältä"
  • Monoliittimme on sotkuinen, joten korjaamme sen jakamalla sen.

Jos syysi sisältää sanat May, lopulta, tai TulevaisuusvarmaSe ei luultavasti ole syy.

Tekninen todellisuus: Mitä mikropalvelut todellisuudessa maksavat

Tämä muuttuu, kun monoliitit jaetaan palveluihin.

Puheluketjut ja latenssi

Monolith:

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;
}

Kokonaislatenssi: ~50ms (käynnissä olevat puhelut + 2 DB-kyselyä)

Mikropalvelut:

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;
}

Kokonaislatenssi: ~400 ms (jopa optimistinen 50–80 ms per HTTP-puhelu todellisissa ympäristöissä + viestibussi julkaistaan ~20 ms)

Voittosi:

  • Jokainen palvelu voi toimia itsenäisesti
  • Inventointi ja hinnoittelu voivat skaalautua erillään Tilauksista
  • Epäonnistuminen sähköpostissa ei estä tilausten luomista (async-tapahtuma)

Se, mitä maksoit:

  • ~8x latenssin kasvu
  • 4 uutta vikapistettä (mikä tahansa palvelu voi olla alhaalla/hidas)
  • Verkko, DNS, TLS joka puhelussa
  • Serialisointi/deseriointi yleisellä tasolla (ks. aiempi kohta)
  • Loogisuuden, virrankatkaisijoiden ja aikakatkaisujen tarve
  • Hajautettu jäljitys vianetsintävirheisiin

Virhe räjähdysten käsittelyssä

Monolith-virheiden käsittely:

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);
}

Mikropalveluiden virheiden käsittely:

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

Jokaisessa verkkopuhelussa esitellään:

  • Aikalisäskenaariot
  • Verkkoviat
  • Palvelua ei ole saatavilla
  • Osittainen epäonnistuminen (pyyntö lähetetty, vastausta ei saatu)
  • Yritä uudelleenvahvistamista (kokeilusi voi laukaista heidän uudelleenyrittämisensä)
  • Jaetut tapahtumapainajaiset

Käyttöönoton koordinointi

Monolithin käyttöönotto:

# 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

Mikropalveluiden käyttöönotto:

Muutat järjestysrakennetta. Order sisältää UserTier suoraan (epänormaali suorituskyvylle).

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

Mikropalveluiden myötä jokainen muutos ylittää useita palveluita. Sopimuskehitys edellyttää:

  • Taaksepäin yhteensopivat ikkunat
  • Ominaisuusliput eri palveluissa
  • Yhteensovitetut ulostulot
  • Laajennetut testausmatriisit (Service A v1 + Service B v2, Service A v2 + Service B v1 jne.)

Mikään tästä ei ole mahdotonta, mutta se vaatii kuria, työkaluja ja kokemusta siitä, että suurin osa joukkueista ansaitsee vain vuosien tuskan jälkeen.

Vianetsintäkokemus

Monolithin vikailmoitus: "Järjestys epäonnistuu premium-käyttäjien kohdalla"

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

Mikropalveluiden vikailmoitus: "Järjestys epäonnistuu premium-käyttäjien kohdalla"

# 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

Tämä on todellisuutta: vikoja, jotka olivat 5 minuutin korjauksia, tulee usean joukkueen tutkimuksia.

Jos tämä kuulostaa äärimmäisyyksiltä, se on hyvä. Mikropalvelut ovat äärimmäistä tekniikkaa.

Miten kehittyä turvallisesti: Monolith → Mikropalvelut

Mikropalvelut ovat yksisuuntainen ovi.

Turvallinen reitti näyttää tylsältä:

1. Todista Rajat ensin

Jos et pysty vetämään rajaa monoliittiin (todennäköisillä riippuvuuksilla), et voi poistaa sitä turvallisesti.

Hanki saumat ensin paikalliseen kuntoon.

2. Pura lukemia ennen kirjoittamista

Lukeminen on helpompaa:

  • Vähemmän invariantteja
  • Johdonmukaisuutta koskevia vaatimuksia on vähemmän
  • Vähemmän rollback-painajaisia

Lue mallit ensin, jos tarvitset asumuseroa.

3. Suosi Asynciä ennen synkronointia

Synkroniset palvelupuhelut luovat riippuvuusketjuja.

Async-viestit luovat:

  • Buffering
  • Kestävyys
  • Erottaminen sinusta voi todella selvitä

Aloita erottelemalla tapahtumat ja faktat toisistaan, ei viipaloimalla päätetapahtumia.

4. Kurista, älä kirjoita uudelleen

Ei isoa pamausta.
Ei: "Kirjoitamme sen kunnolla."

Leikkaa raja pois, mittaa se, omista se ja jatka eteenpäin.

Jos et pysty selittämään, miksi palvelu on olemassa itsenäisesti, sitä ei pitäisi olla lainkaan.

Takeaway: Mikropalvelut maksavat vain, jos ymmärrät kauppatavaraa

Mikropalvelut eivät ole lähtökohta vaan lopputulos.

Monimutkaisuus on ansaittava. Hajautetut järjestelmät eivät ole virkamerkki vaan velkainstrumentti.

Vaihtokaupan yhtälö on yksinkertainen:

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

Useimmille joukkueille - erityisesti alkuvaiheen tai yhden joukkueen tuotteille - oikea puoli hallitsee. Maksat valtavat kustannukset hyödyistä, joita et vielä tarvitse.

Mutta kun olet:

  • 5+ joukkuetta astumassa toistensa varpaille
  • Käyttöönottojonot päivinä mitattuna
  • Osajärjestelmät, joilla on hurjan erilaiset skaalautumistarpeet
  • Tietojen eristämistä koskevat sääntelyvaatimukset

Sitten vasen puoli alkaa voittaa.

Päätöskehys

Kysy nämä kysymykset järjestyksessä:

  1. Voiko yksi joukkue yhä omistaa tämän?
    → Kyllä: pysy yksin. Ei: harkitse jakoa.

  2. Estävätkö joukkueet toistensa vapautumissyklit?
    → Kyllä: mikropalveluista voi olla apua. Ei: koordinaatio toimii.

  3. Onko meillä toiminnallista voimaa käyttää hajautettuja järjestelmiä?
    → Ei: rakenna se ensin. Kyllä: etene varovasti.

  4. Voimmeko jäljittää, debuggata ja lähettää useita palveluja ilman sankaritekoja?
    → Ei: et ole valmis. Kyllä: saatat olla.

  5. Olemmeko ensin todistaneet koodin rajat?
    → Ei: korjaa monoliitin rakenne. Kyllä: irrottaminen on turvallisempaa.

Jos et pääse kysymyksen 3 ohi luottavaisin mielin, et ole valmis mikropalveluihin - ja se on hyvä. Tylsä arkkitehtuuri on kilpailuetu.

Menestyksekkäimmät tuotteet rakennettiin ensin monoliitteiksi: GitHub, Shopify, Stack Overflow, Basecamp. Ne kehittyivät jaetuiksi järjestelmiksi vain, kun organisaation kipu sitä vaati.

Tehtäväsi ei ole rakentaa vaikuttavinta arkkitehtuuria, vaan tuottaa arvoa ja samalla minimoida sattumanvaraista monimutkaisuutta.

Eikä yksikään asiakas ole koskaan maksanut ylimääräistä, koska järjestelmäsi käytti Kafkaa.

Joskus se tarkoittaa mikropalveluita, yleensä hyvin jäsenneltyä monoliittia ja kuria pitää se sellaisena.

Finding related posts...
logo

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