Pourquoi ne devrait-on probablement pas utiliser les microservices?
Les microservices sont devenus le système par défaut.
Si vous voulez sembler mature, vous parlez des filets de serviceM SK1 bus d’événements, traçage distribuéMNK3 et MRK4 déployabilité indépendanteMMK5 si vous voulez apparaître comme une entrepriseMEK6 prête MNK7 vous dessinez des boîtes jusqu’à ce que le diagramme ressemble à un bol de spaghetti que quelqu’un a jeté sur un panneau blancMBK8
Mais ici, la plupart des jeunes correctifs ne reçoivent pas de réponses correctives.
Les microservices ne sont pas une mise à niveau de l’architecture des applications. Ils constituent une stratégie organisationnelle d’agrandissement de la taille.
Si vous n’avez pas déjà la douleur organisationnelle qu’ils résouvent, adoptez-les tôt.
Ce n’est pas un argument théorique. Il s’agit d’un métier de génie.
J’ai été aussi coupable que n’importe qui d’entre eux de microservices sans internaliser pleinement les frais généraux organisationnels qu’ils entraînent.
En fin de compte, il s’agit du plus vieux modèle d’ingénierie logicielle. KISS (en anglais seulement) - Gardez-le tout simpleM SK1 stupide .
Le mythe des microservices
Les microservices sont vendus comme une architecture par défaut parce que le narratif est séduisant:
- Les petits services sont "cleaner"
- Les systèmes distribués sont "moderne"
- Autonomie est "free"
- Vous pouvez "scaler plus tardM SK1 parce que vous avez commencé "right"
Ce cadre est arrière.
Les microservices ne se rapportent pas principalement à la structure du code. qui peut changer ce que , sans parler à qui , et combien de fois.
Si vous n’avez pas de
- Plusieurs équipes expédiant indépendamment
- Priorités conflictuelles
- Obstacles de coordination
- Contention sur le déploiement
- Limites de propriété réelle
… alors vous ' ne résolvez pas un problème organisationnel . Vous M SK3 en achetez une
L’architecture, en définitive, consiste à permettre aux gens de ne pas se déplacer autour des boîtes.
Le diagramme qui devrait être un étiquette d'avertissement
Vous ' avez vu le diagramme sur LinkedIn de la publicité d'un cours Microservices avec tous ces 'v4' passerelle APIM SK3 un ensemble d'endos aléatoires etcMSC4
Note: J’ai eu l’image réelle de ce dernier, mais c’était mean pour moi d’utiliser celui-ci.
Ce n'est pas une architecture "exemplaire à copier". Il s'agit de l'étiquette d'avertissement
Les cours enseignent souvent les microservices en tant que diagrammes, car les diagrammes sont plus faciles qu’à enseigner la responsabilité opérationnelle.
Le remaniement important est que ce diagramme n’est pas un état cible. surface des coûts.
Chaque boîte est un engagement
- Un temps d'exécution pour fonctionner
- Un déploiement pour gérer
- Un contrat à une version
- Un mode de défaillance que vous possédez maintenant
- Une histoire d’appel sur - que vous pouvez ignorer
Les flèches sont impressionnantes. Elles cachent aussi la réalitéM SK1
Parce que chaque flèche est:
- Délai
- Défaut partiel
- Retraits
- Démarrages
- Tracer la propagation du contexte
- "Travaux en stagionnement" se trouve
Si une architecture l’exige dès le premier jour, vous ne construisez pas un produit. Vous construisons une plateformeM SK2
Le modèle de coûts cachés des microservices
Comme toutes les décisions architecturales, les microservices Impôt de complexité (voir Playing with Code: complexity tax. La différence réside dans le fait que cette taxe s’ajoute à toutes les limites de service , chaque déploiement
Les coûts sont rarement explicites.
Dépenses opérationnelles
Chaque service veut:
- Son propre pipeline de construction
- Configuration de son propre déploiement
- Son propre surveillance et alerte
- Son propre enregistrement, tableaux de bord, guides d'exécution
- Sa propre posture de sécurité (secrets, authM SK2 ingress )
Si une équipe peut't faire cela confortablement, il n’y a pas de microservices 'il y a un monolith réparti avec des étapes supplémentaires.
La durée et la multiplication des défaillances
Dans un monolith,, l'appel est un appel de fonction.
Dans le secteur des microservices, une "appel de " est un truc négocié entre
- Réseaux
- Équilibrateurs de charge
- TLS
- Auth middleware
- Serialisation
- Démarrages
- Retraits
- Pression arrière
Modes de défaillance multipliés:
- Un en aval est lente → les fils en amont s'accumulent
- Retours amplifier la charge → vous DDoS vous-même poliment
- Une seule dépendance s’écroule → trois services dégradés "randomly"
Vous n’êtes plus capable de débuger les bugs't états du système.
La taxe de serialisation dont personne ne parle
Il s’agit là d’un coût que l’on n’a presque jamais discuté dans le cadre de la défense des microservices. serialisation et déserialisation (SerDeM SK1 surcoût.
Chaque limite de service signifie:
// 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
Par appel, , signifie que ' ~150μs de CPU pure surcoût avant même que le réseau ne soit impliqué.
Maintenant, multipliez cela par une chaîne d’appels.
- Le service d'ordonnance décérialise la demande (150μsM SK1
- Service d'ordonnance serialise Demande de service (150μs)
- Le service d'utilisateur déserialise la demande (150μs)
- Le service d'utilisateur serialise la réponse (150μs)
- Le service d'ordonnance déserialise la réponse de l'utilisateur (150μsM SK1
- Service d'ordonnance de serialisation Demande d'inventaire (150μsM SK1
- …et ainsi de suite
Dans une chaîne de services 5-, vous , dépensez seulement sur SerDe - et que ' est une serialisation JSON optimiste. Si vous utilisez les tampons de protocole XMLM SK4 avec réflectationMNK5 ou des serialiseurs inefficacesMRK6, multipliez-le par :
Oui, vous pouvez le réduire avec des techniques comme Production de sources JSON dans .NET:
[JsonSerializable(typeof(User))]
internal partial class UserJsonContext : JsonSerializerContext { }
// ~30-40% faster than reflection-based serialization
var json = JsonSerializer.Serialize(user, UserJsonContext.Default.User);
Mais vous, ', êtes toujours en phase de serialisation.
Un exemple réel: La catastrophe du SerDe
J’ai une fois profilé un système de recherche d’adresses avec cette architecture (tous dans les conteneurs Kubernetes):
ASP.NET API → Go Service (fan-out) → ASP.NET Search Service → Elasticsearch
Le service Go a traité le fan-à plusieurs sources de données et à des résultats agrégés. Chaque demande signifiait que la serialisation multiple franchissait les frontières du conteneur .
Répartition d'une recherche type d'adresses:
- ASP.NET APIM SK1 Demande de recherche serialisée → JSON (80μsMSC4
- Réseau: ASPM SK1NET → Go (varies par charge de grappeMSC4
- Go Service: Déserialiser la demande (40μsM SK2
- Go Service: Ouvrir le ventilateur - serialiser les demandes à plusieurs backends (60μs
- Réseau: Sortir M SK1 ASP.Suite NET (variesMSC4
- Service de recherche: Déserialiser la demande M SK1μs)
- Service de recherche: Établissement d'une demande de recherche élastiqueM SK1 serialisation (120μs )
- Réseau: Recherche M SK1 Recherche élastique (varies)
- Recherche élastique: Déserialiser la rechercheMSC1 exécuter, serialiser les résultats M SK3 recherche réelle ~5msMST5 SerDe CSM6μsMSM7
- Réseau: Recherche élastique M SK1 Recherche (varies)
- Service de recherche: Déserialiser la réponse ES M SK1μs), cartographier à un modèle de domaineMSC3 serialiser (180μ sMST5
- Réseau: Recherche M SK1 Sortir (varies)
- Go Service: Déserialiser les réponses provenant de tous les backends M SK1μs | × N |), | Aggregé |
- Réseau: Go M SK1 ASP.NET (variesMSC4
- ASP.NET API: Deserialisez la réponse finale (120μsM SK3
Pour un ventilateur 3-backend-outM SK2
- Total des frais généraux de service: M SK1ms avant même que Elasticsearch n’ait commencé
- Dépenses de réseau entre les conteneurs: ~2-3ms
- Recherche réelle + logique d'affaires
Nous avons passé presque autant de temps sur SerDe que sur la recherche réelle.
Le coût était 't le service Go lui-même - ventilateur - les limites de service;. Chaque bordure de conteneur signifie serialiser → réseau
Dans un monolith faisant le même ventilateur-logice de sortieM SK1
- Les mêmes demandes Elasticsearch
- La même logique d'agrégation
- Zero inter-service SerDe M SK1just method calls)
- ~40% moins latence totale
Ce coût:
- Scales avec complexité d'objets (objets détenusM SK1 collections , polymorphisme)
- Composés avec une profondeur d'appel (chaînes de serviceM SK1
- Burns CPU on every request (can't cache it awayM SK2
- Accroît la pression du GC (ajustement de l'écoulement à partir des chaînesM SK1parités d'ordinateurs)
- Accroît rapidement lorsque vous faites des centaines de demandes par seconde.
Dans un monolith, ce coût est zéro. L'objet est déjà en mémoire
Le mythe de la performance: Capacité d’exécution vs. Échellement
Voici une formule de vente commune :
Il s’agit d’un retrait .
Les microservices sont plus lentement pour les mêmes ressources de calcul.
Répartition des sorties (requêtes par seconde par noyau de CPU):
- Monolith: Supérieur - Nulle SerDe, Nombre d'appels de méthode directe
- Microservices: Lower - SerDe overheadM SK2 latence du réseau, orchestration des conteneurs
Étalonnabilité ( Capacité de traiter plus de demandes totales en ajoutant des ressources ):
- Monolith: Limitée par escalade verticale (machines d'écrasement
- Microservices: Horizontal - ajouter plus d’instances de services à compression d’étranglement
Les microservices vous permettent mesurer indépendamment. Si votre service d’inventaire a besoin de ressources, mais que ce n’est pas le cas pour vos services d’utilisateurs, vous pouvez les élargir séparément. lorsque vous en avez besoin.
Mais ce n’est pas une optimisation de performance. Stratégie d'accroissement. Et il est accompagné d’une taxe
Les monoliths modernes peuvent aussi s'élargir
L’argument "méliorer l’échelle des microservices" était plus sensé dans 2010 lorsque les serveurs avaient des noyaux
Les serveurs modernes ont 32-128 des carottes. Une seule machine peut exécuter des milliers d’opérations concurrentes efficacementM SK2
Si votre monolith est construit avec des modèles modernes d'exécution simultanée,, il peut gérer une capacité de traitement massive sur un seul déploiement. Coordonnateurs d'exécution temporaire, vous pouvez
// 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
Cela vous donne:
- Parallèle: Les travaux se déroulent simultanément à travers tous les carottes
- Observabilité: Traitement en suspensM SK1Active/opérations insuffisantes
- Pression arrière: Les queues limitées évitent l'épuisement de la mémoire
- Nul frais généraux: Aucune serialisation, aucun réseauM SK2 aucune traçage distribué
Lorsque vous effectuer besoin d’élargir au-delà d’une seule machine, vous pouvez
- Exécuter plusieurs instances derrière un balayateur de charge (échelle horizontale sans état)
- Utiliser un modèle de boîte à l'extérieur pour la distribution de travail async
- Partition par clé (ID du client,régionM SK2 etcMSC3 à travers les instances
Vous utilisez encore un monolith, mais vous l’avez déjà déployé plusieurs fois.
Le point: DonM SK1t diviser en microservices pour "performance". Diviser lorsque Étalonnage indépendant de composants spécifiques justifie les frais généraux de fonctionnement.
Charge cognitive
Les microservices ne réduisent pas la complexité, mais les redistribuent.
Le service simple ""a toujours besoin du contexte :
- Qui l’appelle?
- Qu’est-ce que "correctM SK1 signifie?
- Que se passe-t-il lorsque le taux de natalité diminue?
- Qu’est-ce que le plan de ralentissement?
- Quel est le graphique de dépendance?
Les gens sous-estiment cela parce que les diagrammes le cachent.
Versionnement et démarrage des contrats
Chaque frontière devient un contrat.
Chaque contrat devient:
- Règles de versionnement
- Garanties de compatibilité
- Constraints de l'évolution du schéma
- Coordination au-dessus de la tête que vous prétendriez ne pas avoir
Vous pouvez éviter cela pendant un certain temps avec "just déployer tout ensemble".
C’est ce qui est le point saillant : si vous déployez tout ensemble, ,, vous avez construit un monolith.
Utilisation avant la valeur
Les dépenses précoces sont toujours les mêmes
- Détection des services
- Gestion des secrets
- Traçage distribué
- L'exploitation forestière centralisée
- Mesures et alertes
- Modèles de CD CI/
- L’histoire locale du dev qui n’est pas douloureuse
- Gestion de la dépendance
- Une façon de faire tout sans crier
Aucun de ces navires n’a la valeur marchande.
La plupart des équipes le font avant de prouver que le produit mérite la complexité.
C’est l’erreur centrale: Vous payez la taxe de cérémonie avant que vous ayez gagné la valeur. Comme des stand-ups quotidiens qui consacrent du temps à l’alignementLes microservices peuvent devenir une architecture rituelle.
Ensemble, ces coûts ne disparaissent pas.
Ce que les gensoublient: Les humains et les équipes
L’architecture existe pour aider les groupes humains à changer de logiciel en toute sécurité.
Ne pas impressionner les autres ingénieurs.
Ne pas satisfaire à un diagramme.
Ne pas justifier une équipe de plate-forme que vous n’avez pas
La taille de l’équipe et la cadence du déploiement sont plus importantes que le catalogue des modèles.
- Si une équipe possède l’ensemble de la feuille de route, un monolith est habituellement plus rapide et plus sûr.
- Si vous comprenez encore la fin du système, les microservices permettront de réduire cette clarté.
- Si vous n’avez pas de limites stables de propriété, les microservices imposent la coordination par l’entremise des API.
Conway' sa loi n’est pas optionnelle, ' non facultative, M SK2 elle est de la physique et des sciences.
Votre architecture reflètera votre structure de communication, qu’elle vous plaise ou non.
Les microservices ne créent pas l’autonomie; ils en ont besoin.
Défense du monolith modulaire
La plupart des systèmes devraient commencer comme un monolith. Mais pas comme un sloppy.
A monolith modulaire est:
- Une unité de déploiement
- Un temps d'exécution
- Un endroit pour débuger
- Une vision uniforme de l'État
- Mais avec des limites intérieures réelles
Cela signifie:
- Modules de domaine avec dépendance explicite
- Éclaircir la propriété dans le code
- Aucune " Tout renvoie à tout
- Contrats exécutés dans la base de code
Le point est de ne pas demeurer monolithique pour toujours. gagnez des limites avant de les rendre éloignées.
Un monolith modulaire vous oblige à apprendre les compétences que les microservices prétendent résoudre
- Modélisation des domaines ( limites réelles , limites non de dossiers)
- Separation des préoccupations (non "nous avons fait un service")
- Vérifiabilité
- Changer la discipline
- Connaissant ce que votre système fait en réalité
Si vous pouvez 't construire un monolith modulaire propre , les microservices gagneront
Good Monolith Design: Patterns that Scale Later
Le bon design du monolith vous permet de reporter la décision sur les microservices sans s’enfermer dans le système
Le modèle de la boîte d’envoi: Async sans distribution
L’un des meilleurs exemples est le Méthode de la boîte d'envoi - une façon d’atteindre la cohérence et le traitement asynchrone à l'intérieur un monolith, avec une voie propre vers la distribution ultérieure.
Au lieu de:
// 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
}
Vous écrivez:
// 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
}
Ce que cela vous donne:
- Cohérence des transactions: Les événements et les données s’engagent ensemble ou pas du tout
- Découplage: Courriel / Logique de l'inventaire fonctionne async, ne bloque pas la création d'ordonnances
- La fiabilité: Les événements persistent même si les systèmes en aval sont en baisse
- Nettoyage du chemin d'extraction: Plus tardM SK1 échanger le travailleur de la boîte à l'extérieur pour Kafka/RabbitMQ sans changer la logique d'ordre
L'article 5 de la Loi sur l'immigration et le statut des réfugiés Projet d'échantillonnage SegmentCommerce démontre ce modèle dans la production:
// 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);
}
}
}
Cela fonctionne dans une seule application aujourd’hui. Lorsque vous avez besoin d’élargir la taille de l’appareil:
- Remplacer le travailleur de fond par un consommateur d'un bus message
- Publier les événements de la boîte d'envoi à Kafka/RabbitMQ au lieu du traitement local
- Le code de placement ne change pas
Vous avez construit des microservices, une infrastructure prête à fonctionner sans payer la taxe de distribution.
Autres modèles à prendre en considération tôt
- CQRS (lightM SK1: Lire séparément/écrire les modèles même s'ils partagent une base de données
- Fourniture d'événements (selective): Pour la vérification-Entités critiques seulement
- Faits saillants: Déploiement du découplage à partir de la libération
- Fonctions de base: DonM SK1t demandes de blocage, processus async
Aucun de ces éléments n’exige une distribution.
Quand les microservices ont vraiment un sens
Les microservices ont un sens lorsque les contraintes sont réelles, non aspirativesM SK1
La question cruciale est de savoir si nous devrions utiliser des microservices. Les avantages sont-ils supérieurs à la taxe opérationnelle?
Par exemple, le choix entre petits modèles locaux et LLM frontaliers, il s’agit de comprendre où se situe la complexité et quels modes de défaillance vous pouvez vous permettre.
Les bons déclencheurs
- La contestation de l'équipe est mesurable: Les équipes se bloquent chaque semaine
- Des déploiements indépendants sont nécessaires: La cadence de diffusion diffère selon le domaine
- L'échelle est inégale: Un sous-système a besoin de ressources et d’isolement
- Questions d'isolement en cas de défaillance: Une composante ne doit pas détruire le reste
- Règlementation/l'isolement des données est obligatoire: Les limites ne sont pas négociables
- La structure Org est déjà multi-team: La propriété existe et est stable
Les mauvais déclencheurs
- "Nous pourrions l’élargir"
- "Netflix l’a fait"
- "Pratique exemplaireM SK1
- "C'est-ce qu'il faut, 'Il apparaîtra bien.
- " Notre monolith est chaotique, , donc nous le corrigerons en la divisant.
Si votre raison contient les mots suivants : le pouvoir, en fin de compte, ou future-proofIl n’y a probablement pas de raison pour cela.
La réalité technique: Le coût réel des microservices
Let's get concrete . HereM SK2s what changes when you split a monolith into services.
Chaînes d'appel et retard
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;
}
Total de latence: M SK1ms (in-appels de processus + |2 Demandes de fichiers de banque de données
Microservices:
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 de latence: M SK1ms (et même optimiste |50–80ms par appel HTTP dans des environnements réels |
Ce que vous avez gagné:
- Chaque service peut être déployé indépendamment
- L'inventaire et le prix peuvent être évalués séparément des commandes
- Failure dans le courriel ne bloque pas la création d’ordres't M SK1événement async)
Ce que vous avez payé:
- ~8x accroissement de la latence
- 4 nouveaux points de défaillance ( tout service peut être abaissé
- Réseau, DNS, surcoût de TLS pour chaque appel
- Serialisation/surcoût de serialisation (voir la section précédente )
- Nécessité de réessayer la logique, interrupteurs de circuits, arrêts de temps
- Traçage distribué aux défaillances de débug
Erreur de manipulation d'explosion
Traitement des erreurs de monolith:
try
{
var order = await PlaceOrder(request);
return Ok(order);
}
catch (InsufficientStockException)
{
return BadRequest(new { error = "Out of stock" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Order placement failed");
return StatusCode(500);
}
Traitement des erreurs de microservices:
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...
Chaque appel de réseau introduit:
- Systèmes d'interruption
- Défaillances du réseau
- Non-disponibilité du service
- Défaillances partielles (requête envoyéeM SK1 réponse non reçue )
- Retry amplification ( votre retry pourrait déclencher leur retry)
- Noyaux d'opérations distribuées
Coordination du déploiement
Déploiement de monolith:
# 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
Déploiement de microservices:
Vous'changez la structure de l’ordonnance. Maintenant Order inclure UserTier Directement (dénormalisé pour la performanceM SK1
# 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)
Avec les microservices, chaque changement traverse plusieurs servicesM SK1 L’évolution des contrats requiert :
- fenêtres de compatibilité arrière
- Fabrication de drapeaux à l'échelle des services
- Démarrages coordonnés
- Matrices d'essai étendues (Service A v1 + Service B v2, Service A v2 ≥+ Services B v 1, etcM SK7
Aucune de ces choses n’est impossible. Mais il faut une disciplineM SK1 des outils , et l’expérience que la plupart des équipes ne gagnent qu’après des années de douleur.
L'expérience de débugging
Rapport d'erreur de Monolith: "La mise en place de l'ordonnance a échoué pour les utilisateurs premiumsM SK1
// Set breakpoint in PlaceOrder
// Step through each call
// User tier is null - ah, there's the bug
// Fix, test, deploy
Rapport d'erreur de Microservices: "La mise en place de l'ordonnance a échoué pour les utilisateurs premiumsM SK1
# 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
C’est la réalité: Les bogues qui étaient 5-minute fixes deviennent des enquêtes multiM SK1team.
Si cela semble extrême, bonM SK1 Les microservices sont une technique extrêmement rigoureuse.
Comment évoluer en toute sécurité: Monolith → Microservices
Les microservices sont une porte d’accès unique.
Le chemin sécuritaire semble acharné:
1. Prouver les limites d’abord
Si vous pouvez 't dessiner la limite à l'intérieur du monolith (avec des dépendances exécutoires M SK2, vous pouvez't l'extraire en toute sécurité
Obtenir les taches correctes localement tout d’abord.
2. Extraire lectures avant écriture
Les lectures sont plus faciles:
- Moins invariantes
- Réduction des exigences en matière de cohérence
- Moins d'inconvénients
Si vous avez besoin d’une séparation, déplacez les modèles de lecture en premier lieu.
3. préférer l'async avant la synchronisation
Les appels de service synchrones créent des chaînes de dépendance.
Message async crée:
- Enfouissement
- Résilience
- Le découplage permet de survivre
Commencer par séparer les événements et les faits, non pas par trancher les points de finM SK1
4. StrangleM SK1 Don't Réécrire
Aucun grand choc.
Aucune "nous'je le réécrireai correctementM SK2
Étiez-le à partir d’un boundary, mesurez-le , vous en avez la propriétéM SK2 et continuez de le faire.
Si vous pouvez't expliquer pourquoi un service existe indépendammentM SK1 il ne devrait pas exister't n'existe pas du toutMSC3
Les microservices ne payent que si vous comprenez le commerce-Offs
Les microservices ne sont pas un point de départ.
La complexité doit être gagnée. Les systèmes distribués ne sont pas un badge.
L’équation de compensation est simple :
Microservices Value = (Team Autonomy + Independent Scaling + Failure Isolation)
- (Latency Tax + SerDe Tax + Operational Overhead + Coordination Cost)
Pour la plupart des équipes - particulièrement tôt - stade ou unité de production-produits d’équipe MSC3 le côté droit domineM SK4 Vous payez des coûts énormes pour les avantages que vous n’avez pas à obtenirMSC5 ne vous en avez pas encore besoinMST6
Mais quand vous avez
- 5+ les équipes s’entraînent
- Cotes de déploiement mesurées en jours
- Sous-systèmes ayant des besoins d'échelle très différents
- Exigences réglementaires relatives à l'isolement des données
… alors la gauche commence à gagnerM SK1 La taxe devient justifiée.
Cadre décisionnel
Posez ces questions dans l’ordre:
Une équipe peut-elle encore être propriétaire de cette fin?
→ Oui : rester monolith. NonM SK3 considérer la séparationMSC4Les équipes sont-elles bloquées entre elles?
→ Oui : Les microservices pourraient aider. NonM SK3 La coordination est en coursMSC4Nous avons-nous le muscle opérationnel pour exécuter les systèmes distribués?
→ NonM SK1 construisez-le d’abord . Oui: marchez soigneusementMSC4Pouvons-nous tracer, débug, et déployer des services multiples sans héroïquesM SK2
→ Non: vous'vous n'êtes pas prêtsMSC3 OuiM SK4 vous pourriez êtreMST5Nous avons-nous prouvé les limites dans le code d’abord?
→ Non : fixer votre structure monolithique. OuiM SK3 l'extraction est plus sûreMSC4
Si vous ne pouvez pas répondre à la question précédente, vous n’êtes pas prêts aux microservices. L’architecture boring est un avantage concurrentiel.
Les produits les plus couronnés de succès ont été construits comme des monoliths d’abord : GitHub, Shopify, Stack OverflowMSC3 BasecampM SK4 Ils n’ont évolué en systèmes distribués que lorsque la souffrance organisationnelle l’exigeait.
Votre tâche n’est pas de construire l’architecture la plus impressionnante, mais d’apporter une valeur tout en minimisant la complexité accidentelle.
Et aucun client n’a jamais payé d’excédent parce que votre système a utilisé Kafka.
Parfois, cela signifie des microservices.