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
I microservizi sono diventati l'architettura predefinita del "sistema serio."
Se si vuole sembrare maturo, si parla di mesh di servizio, bus eventi, tracciamento distribuito, e "dispiegabilità indipendente." Se si vuole guardare enterprise-ready, si disegna caselle fino a quando il diagramma sembra una ciotola di spaghetti qualcuno ha lanciato a una lavagna bianca.
Ma qui c'è il correttivo che la maggior parte dei junior non vengono dati:
I microservizi non sono un aggiornamento dell'architettura delle applicazioni, ma una strategia organizzativa di scalatura.
Se non hai già il dolore organizzativo che risolvono, adottarli in anticipo non ti rende a prova di futuro.
Questo non è un argomento teorico, è un trade-off ingegneristico, e come tutti i compromessi, ha senso solo quando capisci la tassa che stai pagando e quello che ricevi in cambio.
Sono stato colpevole come chiunque di "fare microservizi" senza internalizzare completamente l'overhead organizzativo che comportano. E 'facile farsi coinvolgere nel vocabolario e dimenticare che la vera sfida è la gestione della complessità tra team e sistemi.
Alla fine si tratta del modello più antico in ingegneria software: KISS - Sii semplice, stupido.
I microservizi sono venduti come architettura predefinita perché la narrazione è seducente:
Quest'inquadratura e' all'indietro.
I microservizi non riguardano principalmente la struttura del codice. chi può cambiare cosa, senza parlare con chi, e quanto spesso.
Se non hai:
Non stai risolvendo un problema organizzativo, ne stai comprando uno.
L'architettura, in ultima analisi, consiste nell'abilitare le persone, non spostare scatole.
Hai visto questo diagramma.

Questa non è una "architettura di esempio da copiare." È un'etichetta di avviso.
I corsi insegnano spesso i microservizi come diagrammi perché i diagrammi sono più facili dell'insegnamento della responsabilità operativa.
L'importante reframing è questo: quel diagramma non è uno stato bersaglio. superficie di costo.
Ogni scatola è un impegno:
Le frecce sembrano impressionanti, nascondono anche la realtà.
Perché ogni freccia è:
Se un'architettura richiede questo il primo giorno, non si sta costruendo un prodotto. Si sta costruendo una piattaforma.
Come tutte le decisioni architettoniche, i microservizi sono dotati di Tassa di complessità (vedere Giocare con il codice: la tassa di complessitàLa differenza è che questa tassa composti attraverso ogni confine di servizio, ogni spiegamento, e ogni modalità di guasto.
I costi sono raramente espliciti, di solito sono travestiti da "migliori pratiche."
Ogni servizio vuole:
Se una squadra non può farlo comodamente, non ha microservizi. un monolito distribuito con gradini supplementari.
In un monolito, una chiamata è una chiamata di funzione.
Nei microservizi, una "chiamata" è una tregua negoziata tra:
Le modalità di guasto si moltiplicano:
Non debug bug più. Debug Stati di sistema.
Ecco un costo che non viene quasi mai discusso nella difesa dei microservizi: serializzazione e deserializzazione (SerDe).
Per "confine di servizio" si intende:
// 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 chiamata, sono ~150μs di pura CPU sopra prima che la rete venga coinvolta.
Ora moltiplicatelo attraverso una catena di chiamate:
In una catena di 5 servizi, si sta spendendo ~ 1.5m solo su SerDe - e questa è serializzazione JSON ottimista. Se stai usando XML, Buffer di protocollo con riflessione, o serializzatori inefficienti, moltiplicalo per 2-10x.
Sì, si può ridurre questo con tecniche come Generazione sorgente JSON in .NET:
[JsonSerializable(typeof(User))]
internal partial class UserJsonContext : JsonSerializerContext { }
// ~30-40% faster than reflection-based serialization
var json = JsonSerializer.Serialize(user, UserJsonContext.Default.User);
Ma stai ancora facendo serializzazione, hai appena fatto la tassa un po' piu' economica, in un monolito, la tassa e' zero.
Una volta ho profilato un sistema di ricerca degli indirizzi con questa architettura (tutti in contenitori Kubernetes):
ASP.NET API → Go Service (fan-out) → ASP.NET Search Service → Elasticsearch
Il servizio Go ha gestito il fan-out a più fonti di dati e risultati aggregati. Ogni richiesta significava serializzazione multipla attraverso i confini dei container.
La ripartizione per una ricerca tipica indirizzo:
Per un fan-out a 3 backend:
Stavamo passando quasi tanto tempo su SerDe quanto su una vera e propria ricerca.
Il costo non era il servizio Go in sé - fan-out aveva senso per il caso di utilizzo. Il costo era il limiti di servizio. Ogni bordo contenitore significa serializzare → rete → deserializzare.
In un monolito che fa la stessa logica fan-out:
Questo costo:
In un monolito, questo costo è zero. L'oggetto è già in memoria.
Ecco un passo di vendita comune: "Microservices migliorare le prestazioni."
Questo e' al contrario.
I microservizi sono: più lento per le stesse risorse di calcolo. Cerchiamo di essere precisi su ciò che intendiamo:
Flusso di cassa (richieste al secondo per nucleo della CPU):
Scalabilità (capacità di gestire più richieste totali aggiungendo risorse):
I microservizi ti permettono scala indipendentemente. Se il vostro servizio di inventario ha bisogno di risorse 10x, ma il vostro servizio utente non, è possibile scalare separatamente. quando ne hai bisogno.
Ma non e' un'ottimizzazione delle prestazioni. Strategia di scalatura. E viene con una tassa.
L'argomento "Microservices scale better" ha avuto più senso nel 2010 quando i server avevano 4-8 core.
I server moderni hanno 32-128 core. Una singola macchina può eseguire migliaia di operazioni contemporaneamente in modo efficiente.
Se il vostro monolito è costruito con modelli di esecuzione contemporanei moderni, può gestire il flusso massiccio su una singola distribuzione. Per esempio, utilizzando modelli come Coordinatori di esecuzione effimeri, puoi:
// 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
Questo ti dà:
Quando tu fare necessità di scalare oltre una macchina, è possibile:
Stai ancora usando un monolito, l'hai appena dispiegato piu' volte.
Il punto: Non dividere in microservizi per "prestazioni." Dividere quando scalamento indipendente di componenti specifici giustifica la copertura operativa.
I microservizi non riducono la complessità, li ridistribuiscono.
Il "servizio semplice" ha ancora bisogno di contesto:
La gente sottovaluta questo perché i diagrammi lo nascondono.
Ogni confine diventa un contratto.
Ogni contratto diventa:
Puoi evitarlo per un po' con "dispiegare tutto insieme."
Questa è la battuta: se dispiegate tutto insieme, avete costruito un monolito. Solo uno peggiore.
La spesa iniziale è sempre la stessa:
Nessuna di queste navi valore del prodotto.
E la maggior parte dei team lo fanno prima di aver dimostrato che il prodotto merita la complessità.
Questo è l'errore centrale: paghi la tassa di cerimonia prima di aver guadagnato il valore. Come standup giornalieri che perdono tempo senza fornire allineamento, i microservizi possono diventare architettura rituale: impressionante da guardare, costoso da mantenere, e disconnesso dal problema che stai risolvendo.
Prendendo insieme, questi costi non scompaiono - si mescolano. E si mescolano se l'organizzazione ha bisogno di microservizi in primo luogo.
L'architettura esiste per aiutare i gruppi di esseri umani a cambiare software in modo sicuro.
Non per impressionare gli altri ingegneri.
Non per soddisfare un diagramma.
Non per giustificare una squadra di piattaforme che non hai.
Le dimensioni del team e la cadenza di distribuzione contano più del catalogo dei pattern.
La legge di Conway non e' facoltativa, e' fisica.
La vostra architettura rifletterà la vostra struttura di comunicazione che vi piaccia o meno.
I microservizi non creano autonomia, ne hanno bisogno.
La maggior parte dei sistemi dovrebbe iniziare come un monolito, ma non come uno sciattino.
A monolito modulare è:
Significa:
Il punto non è rimanere monolitici per sempre. Il punto è quello di guadagnare i confini prima di renderli remoti.
Un monolito modulare ti costringe a imparare le abilità che i microservizi fingono di risolvere:
Se non riesci a costruire un monolito modulare pulito, i microservizi non ti salveranno.
Il disegno del monolite destro ti permette rinviare la decisione relativa ai microservizi senza chiuderti dentro.
Uno dei migliori esempi è il Schema outbox - un modo per raggiungere l'eventuale coerenza e l'elaborazione asincrona dentro un monolito, con un percorso pulito verso la distribuzione in seguito.
Invece di:
// 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
}
Scrivi:
// 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
}
Ciò che questo ti dà:
La Progetto campione SegmentCommerce dimostra questo modello di produzione:
// 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);
}
}
}
Questo viene eseguito in una singola app oggi. Quando si ha bisogno di scalare:
Hai costruito un'infrastruttura pronta per i microservizi senza pagare la tassa di distribuzione.
Nessuno di questi richiede la distribuzione. Tutti rendono la distribuzione più facile in seguito.
I microservizi hanno senso quando i vincoli sono reali, non aspirazionali.
La domanda critica non è "dovremmo usare i microservizi?" E': i benefici superano l'imposta operativa?
Come scegliere tra piccoli modelli locali e LLM di frontiera, si tratta di capire dove appartiene la complessità e quali modalità di guasto si può permettersi.
Se la tua ragione contiene le parole potrebbe, alla fine, oppure a prova di futuroProbabilmente non e' un motivo.
Ecco cosa cambia quando dividi un monolito in servizi.
Monolito:
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;
}
Latenza totale: ~50m (chiamate in corso + 2 domande DB)
Microservizi:
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;
}
Latenza totale: ~400ms (anche ottimista 50 0,0680ms per chiamata HTTP in ambienti reali + bus di messaggio pubblica ~20ms)
Cio' che hai guadagnato
Quello che hai pagato:
Gestione degli errori monoliti:
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);
}
Gestione degli errori dei microservizi:
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...
Ogni chiamata di rete introduce:
Distribuzione di monoliti:
# 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
Dispiegamento di microservizi:
Stai cambiando la struttura dell'ordine. Order include UserTier direttamente (denormalizzato per le prestazioni).
# 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)
Con i microservizi, ogni cambiamento attraversa molteplici servizi. L'evoluzione del contratto richiede:
Niente di tutto questo è impossibile, ma richiede disciplina, strumenti ed esperienza che la maggior parte delle squadre guadagnano solo dopo anni di dolore.
Segnalazione bug monolith: "Il posizionamento dell'ordine fallisce per gli utenti premium"
// Set breakpoint in PlaceOrder
// Step through each call
// User tier is null - ah, there's the bug
// Fix, test, deploy
Segnalazione bug Microservices: "Il posizionamento dell'ordine fallisce per gli utenti premium"
# 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
Questa è la realtà: bug che erano correzioni di 5 minuti diventano indagini multi-squadra.
Se questo suona estremo, bene. I microservizi sono ingegneria estrema.
I microservizi sono una porta a senso unico.
Il sentiero sicuro sembra noioso:
Se non è possibile tracciare il confine all'interno del monolito (con dipendenze esecutive), non è possibile estrarlo in modo sicuro.
Ottenere le cuciture a destra localmente prima.
Le letture sono più facili:
Sposta prima i modelli di lettura se hai bisogno di separazione.
Le chiamate di servizio sincronizzate creano catene di dipendenza.
La messaggistica async crea:
Inizia separando eventi e fatti, non tagliando gli endpoint.
Niente gran botto.
Niente "noi lo riscriviamo correttamente."
Tagliare il contorno, misurarlo, possederlo, e continuare.
Se non puoi spiegare perché un servizio esiste indipendentemente, non dovrebbe esistere affatto.
I microservizi non sono un punto di partenza, ma un risultato.
La complessità dovrebbe essere guadagnata. I sistemi distribuiti non sono un distintivo - sono uno strumento di debito.
L'equazione trade-off è semplice:
Microservices Value = (Team Autonomy + Independent Scaling + Failure Isolation)
- (Latency Tax + SerDe Tax + Operational Overhead + Coordination Cost)
Per la maggior parte dei team - in particolare i prodotti per la prima fase o per la singola squadra - domina il lato giusto. Si pagano costi enormi per i benefici che non hai ancora bisogno.
Ma quando l'hai fatto
...allora il lato sinistro comincia a vincere. L'imposta diventa giustificata.
Fate queste domande in ordine:
Una squadra puo' ancora possedere questo end-to-end?
→ Sì: rimanere monolito. No: considerare la divisione.
Le squadre sono bloccate dai cicli di rilascio dell'altro?
→ Sì: i microservizi potrebbero aiutare. No: il coordinamento sta funzionando.
Abbiamo il muscolo operativo per far funzionare i sistemi distribuiti?
→ No: costruirlo prima. Sì: procedere con attenzione.
Possiamo rintracciare, debug e distribuire più servizi senza eroismi?
→ No: non sei pronto. Sì: potresti esserlo.
Abbiamo prima provato i limiti del codice?
→ No: riparate la struttura del monolito. Sì: l'estrazione è più sicura.
Se non riesci a superare la domanda 3 con fiducia, non sei pronto per i microservizi - e va bene. L'architettura noiosa è un vantaggio competitivo.
La maggior parte dei prodotti di successo sono stati costruiti come monoliti prima: GitHub, Shopify, Stack Overflow, Basecamp. Si sono evoluti in sistemi distribuiti solo quando il dolore organizzativo richiesto.
Il vostro compito non è quello di costruire l'architettura più impressionante. È quello di fornire valore minimizzando la complessità accidentale.
E nessun cliente ha mai pagato extra perché il vostro sistema ha usato Kafka.
A volte questo significa microservizi. Di solito significa un monolito ben strutturato e la disciplina per mantenerlo in questo modo.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.