Oyun Kitabı
Comment fonctionne une tranche verticale de l’intérieur ? (Comment Fonctionne Une Tranche Verticale DE Linterieur)
Comment une tranche verticale circule-t-elle en interne, de la demande à la validation, du gestionnaire à l'agrégation, de l'événement de boîte d'envoi à la lecture du modèle jusqu'au coût du cloud ? CQRS…
Tranche verticale – Ingénierie orientée fonctionnalités
Partie 2 de 4
Résumé en 30 secondes
Lorsqu’une requête HTTP parvient au système, elle n’est pas dirigée vers une seule méthode. Tout d'abord, il est importé de la frontière extérieure, vérifié, passé par le contrôle d'autorisation, si nécessaire, une transaction est ouverte, la règle de domaine est appliquée, les données sont enregistrées, l'événement est sécurisé et le côté lecture est mis à jour.
Vertical Slice organise ce parcours autour d'une seule intention d'utilisateur.```text HTTP Request | v Endpoint / Controller | v Mediator.Send(command) | v Pipeline Behaviors | +--> Validation +--> Authorization +--> Logging / Metrics +--> Idempotency +--> Transaction v Command Handler | v Aggregate | v Repository | v Outbox | v Database Commit
## Problème : pourquoi un petit bon de livraison devient-il cher ?
Imaginons que nous ajoutions un bon de livraison à la commande dans une application Marketplace. À première vue, il ne s'agit que d'un champ de chaîne. Cependant, en production, le même champ apparaît dans le récapitulatif du paiement, le panneau du vendeur, l'intégration de l'expédition, l'e-mail du client et le dossier d'audit.```text
AddDeliveryNote
-> sipariş kuralı
-> validasyon
-> audit
-> notification
-> seller projection
-> müşteri görünümü
```Dans la structure en couches, ce changement est réparti entre les rôles techniques. Dans Vertical Slice, la question change : à quelle intention utilisateur appartient ce comportement et à quelle limite de cohérence doit-il être réalisé ?
## Concepts à la première mention```text
Command
Sistemin durumunu değiştirmek isteyen niyettir. Örneğin AddDeliveryNote bir command'dir; başarılı olabilir, reddedilebilir veya hata alabilir.
Query
Sistemin durumunu değiştirmeden veri okuma isteğidir. Örneğin GetOrderSummary bir query'dir.
Handler
Bir command veya query'nin uygulama seviyesindeki akışını yöneten sınıftır. Handler orkestrasyon yapar; iş kuralının kalıcı sahibi olmamalıdır.
Pipeline Behavior
Handler'a ulaşmadan önce veya sonra çalışan ortak teknik adımdır. Validation, authorization, logging, idempotency ve transaction burada konumlanabilir.
Aggregate
Sistemde asla bozulmaması gereken iş kurallarını, yani invariant'ları, koruyan domain nesnesidir. Örneğin iptal edilmiş siparişe teslimat notu eklenememesi aggregate kuralıdır.
Read Model
Domain entity'nin kopyası değildir; bir ekranın veya API cevabının ihtiyaç duyduğu okuma şeklidir.
Projection
Event veya write model değişikliklerinden read model üreten süreçtir. Örneğin DeliveryNoteUpdated event'inden SellerOrderSummary read model'ini günceller.
Outbox
Veritabanı değişikliği ile event yayınını aynı local transaction içinde güvenceye alan kayıt desenidir.
CDC
Change Data Capture, veritabanı loglarından değişiklikleri okuyup dış sistemlere taşıma yaklaşımıdır. Domain event tasarımı yerine DB değişimini kaynak alır.
```Ces pièces ne sont pas interchangeables. Le gestionnaire n'est pas une règle de domaine ; Appelle le comportement du domaine. Le pipeline n’est pas une politique commerciale ; L'application courante est la ligne de sécurité. Outbox n'est pas un courtier ; C'est le dossier transactionnel de l'événement qui ne doit pas être perdu.
## Histoire vraie : tranche de note de commande
Concevons un cas d'utilisation `AddDeliveryNote`. L'utilisateur souhaite ajouter un bon de livraison à la commande. Le système vérifie d'abord le droit de l'utilisateur à modifier l'ordre, vérifie la longueur de la note, exécute le comportement sur l'agrégat, enregistre la modification et génère des événements pour mettre à jour d'autres écrans.```text
Features/Orders/AddDeliveryNote
AddDeliveryNoteEndpoint.cs
AddDeliveryNoteCommand.cs
AddDeliveryNoteValidator.cs
AddDeliveryNoteHandler.cs
AddDeliveryNoteResponse.cs
AddDeliveryNoteTests.cs
```Ce répertoire ne doit pas être un cimetière de « mini-couches ». Chaque fichier a une seule responsabilité : le point de terminaison porte le contrat HTTP, la commande exprime l'intention, le validateur maintient la limite d'entrée, le gestionnaire gère le flux, l'agrégat implémente la règle métier, les tests prouvent le comportement.
## Conception d'algorithmes
Au niveau du pseudocode, slice fonctionne comme ceci :```text
algorithm AddDeliveryNote(command):
input: orderId, customerId, note, requestId
output: AddDeliveryNoteResult
validate command
reject if requestId was processed before
order = orderRepository.load(orderId)
reject if order does not exist
reject if order.customerId != customerId
order.addDeliveryNote(note)
outbox.add(DeliveryNoteUpdated(orderId, note, occurredAt))
transaction.commit()
return success(orderId, note)
```La complexité temporelle est O(1) en flux normal ; comme il est chargé via une seule identité globale, un nombre fixe de règles sont exécutées et un nombre fixe d'événements sont écrits. Si le contrôle d'idempotence est effectué avec un index unique, le coût de recherche est pratiquement O (log n), tandis qu'avec un cache basé sur le hachage ou un magasin clé-valeur, le coût moyen est O (1). La complexité de la mémoire est O(1) ; Le gestionnaire ne transporte que l'état global et les données de commande nécessaires, et non l'intégralité de l'historique des commandes.
## Pourquoi était-ce un problème ?
Le problème n'est généralement pas la longueur du code. Le problème est que le même comportement est représenté avec des demi-informations sur plusieurs couches techniques.```text
Controller doğrular gibi yapar
Service iş kuralı gibi yapar
Repository veri kuralı gibi yapar
Frontend tekrar kontrol eder
Test mock sırasını korur
```Dans ce modèle, la responsabilité unique est visible mais pas au niveau comportemental. Techniquement, chaque classe peut être petite ; mais la décision commerciale est fragmentée. Vertical Slice déplace SOLID des noms de classe vers la propriété du comportement. La ségrégation d'interface signifie ici l'utilisation de ports étroits comme `IAddDeliveryNoteAuthorization`, `IOrderRepository` et `IRequestDeduplicationStore` au lieu d'un contrat volumineux comme `IOrderService`.
## Pipeline CQRS et Mediator
La distinction entre commande et requête rend claire l’intention de la tranche.```csharp
public sealed record AddDeliveryNoteCommand(
Guid OrderId,
Guid CustomerId,
string Note,
string RequestId) : ICommand<AddDeliveryNoteResult>;
public sealed class AddDeliveryNoteHandler(
IOrderRepository orders,
IRequestDeduplicationStore deduplication,
IOutboxWriter outbox,
IUnitOfWork unitOfWork)
: ICommandHandler<AddDeliveryNoteCommand, AddDeliveryNoteResult>
{
public async Task<AddDeliveryNoteResult> Handle(
AddDeliveryNoteCommand command,
CancellationToken cancellationToken)
{
if (await deduplication.ExistsAsync(command.RequestId, cancellationToken))
{
return AddDeliveryNoteResult.AlreadyProcessed(command.OrderId);
}
var order = await orders.GetByIdAsync(command.OrderId, cancellationToken)
?? throw new OrderNotFoundException(command.OrderId);
order.EnsureOwnedBy(command.CustomerId);
order.AddDeliveryNote(command.Note);
await outbox.AddAsync(
DeliveryNoteUpdated.From(order),
cancellationToken);
await unitOfWork.CommitAsync(cancellationToken);
return AddDeliveryNoteResult.Updated(order.Id, order.DeliveryNote);
}
}
```Utiliser Mediator est un outil, pas un objectif ici. La vraie valeur est que le pipeline déplace les flux répétitifs tels que la vérification, l’autorisation, la transaction, la journalisation et l’idempotence hors du gestionnaire. Cependant, la sélection des bibliothèques doit être évaluée en fonction de la licence, de la conformité AOT, du coût de réflexion et du processus d'approbation institutionnelle ainsi que des performances techniques. Avant de rendre une décision-cadre permanente, le texte de la licence et la politique organisationnelle doivent être vérifiés.
## Comment se déroule le côté requête ?
Le côté commandement est axé sur les règles métier et les transactions. Le côté requête se concentre sur l’efficacité de la lecture et l’expérience utilisateur. La plupart du temps, nous ne souhaitons pas charger l'agrégat pour afficher une liste de commandes ; Un modèle à lecture étroite adapté à l'écran suffit.```text
GET /orders
|
v
Authorization
|
v
Cache
|
v
Read Database / Search Index
|
v
DTO Mapping
|
v
Response
```Le cache n'est pas seulement un outil de performance dans cette chaîne ; C'est aussi une décision de coût. Les données lues fréquemment, modifiées rarement et tolérant un délai de quelques secondes conviennent à la mise en cache. Pour les données qui nécessitent une forte cohérence, comme le statut des paiements, la politique de cache doit être conçue avec beaucoup plus de soin.
## Alternatives
| Alternatives | Force | Prix accepté |
| --- | --- | --- |
| Service CRUD simple | Coût de démarrage le plus bas | À mesure que les comportements se multiplient, le service commun grossit |
| Tranche verticale, pas de CQRS | La fonctionnalité garantit la localité | L’intention de lecture/écriture peut être moins visible |
| Tranche verticale + CQRS | La distinction entre intention, test et pipeline devient claire | Il faut davantage de discipline en classe et dans les contrats |
| Modèle de lecture/écriture physiquement séparé | Performances de lecture et indépendance d'échelle | Ajoute une cohérence événementielle, une boîte d'envoi, une projection et des coûts d'exploitation |
| Microservices | Déploiement et mise à l'échelle indépendants | Le réseau, la cohérence des données, l'observabilité et les coûts d'équipe augmentent |
Sur une petite surface CRUD, la meilleure décision est souvent Vertical Slice + simple gestionnaire. Le CQRS physique ou le microservice ne doit être choisi que si la surcharge de lecture/écriture, la propriété de l'équipe et l'isolation des pannes justifient le coût.
## Décision : d'abord une frontière logique, puis une séparation physique
Ma décision par défaut est la suivante : séparez d’abord logiquement la tranche au sein de l’application ; Ensuite, si la mesure impose une séparation physique, ajoutez un modèle de lecture, un courtier, un cache ou un service distinct.```text
Aşama 1: Modular monolith içinde slice
Aşama 2: Command/query ayrımı
Aşama 3: Outbox ve projection
Aşama 4: Ayrı read store veya cache
Aşama 5: Gerekirse ayrı deploy birimi
```Cette séquence reporte le coût mais pas la conception. Si la limite du code est tracée correctement, la séparation physique n'est pas une réécriture mais une soustraction contrôlée.
## Séparation des données : le logique n'est pas la même chose que le physique
Le réplica en lecture n’est pas CQRS. Le réplica lit le même schéma à partir d'un nœud différent ; CQRS, quant à lui, remodèle le modèle de lecture en fonction du modèle d'utilisation.```text
Replica
→ aynı tablo
→ aynı join ihtiyacı
→ daha fazla okuma kapasitesi
Projection
→ ekran için hazırlanmış veri
→ daha az join
→ daha düşük latency ve I/O
```Par exemple, au lieu de rejoindre `CityId` à chaque fois dans la liste de commandes, écrire `CityName` dans la projection est une décision de dénormalisation. Cette décision crée un coût de synchronisation supplémentaire côté écriture ; Côté lecture, il peut alimenter l’écran avec une seule lecture.
## Double écriture, boîte d'envoi et CDC
Le flux le plus dangereux est :```text
1. Siparişi veritabanına yaz
2. Broker'a event gönder
3. İkinci adımda sistem çöksün
```Dans ce cas, il y a une commande mais aucun événement. L'expédition, la notification ou la projection sont laissées pour compte. Transactional Outbox comble cette lacune : les données métiers et l’événement à publier sont écrits dans la même transaction locale. Un éditeur ou un processus basé sur CDC déplace ensuite l'enregistrement de la boîte d'envoi vers le courtier.
| Critère | Boîte d'envoi | CDC |
| --- | --- | --- |
| Contrôle des événements de domaine | L'application détermine | DB détermine le changement |
| Intention de l'événement | Ouvrir | Indirect |
| Dépendance du schéma de base de données | inférieur | Élevé |
| Contenu de l'événement | Conçu avec un langage commercial | Affecté par la structure de la table/du journal |
| Conformité des microservices | Très élevé | Cela dépend |
| Coût d'installation | Plus de codes d'application | Plus d'informations sur les infrastructures |
CDC est un outil puissant ; C’est particulièrement utile pour insuffler des changements à partir des systèmes existants. Toutefois, si l'application détermine la signification de l'événement de domaine, Outbox produit un contrat plus clair.
Une livraison au moins une fois peut entraîner une livraison répétée. Le consommateur doit donc être idempotent.```text
DeliveryNoteUpdated event'i iki kez geldi
→ projection aynı version'ı gördü
→ ikinci işlem no-op oldu
```L’idempotence n’est pas ici un ornement ; C'est l'assurance pratique de la cohérence des données dans un système distribué.
## Coût du Cloud : Pourquoi le slice affecte-t-il la facture ?
Vertical Slice n'est pas seulement une organisation de code ; Cela rend également visible l’unité d’échelle. Si le côté écriture de l'extraction est gourmand en CPU et le côté lecture du catalogue est gourmand en mémoire et en cache, forcer les deux à utiliser le même profil de ressources produit du gaspillage.```text
Checkout command side
→ düşük concurrency
→ yüksek tutarlılık
→ transaction ve fraud kontrolü
Catalog query side
→ yüksek concurrency
→ cache ve projection
→ düşük latency beklentisi
```Les charges asymétriques nécessitent des ressources asymétriques. Cependant, chaque cache, NAT, équilibreur de charge, file d'attente, magasin de lecture et unité de déploiement distincte ajoute un nouvel élément à la facture. Par conséquent, la décision architecturale doit être prise avec des mesures réelles : latence p95, taux de lecture/écriture, saturation de la connexion, sortie, volume de nouvelles tentatives et rayon de souffle incident.
## Analyse des performances et de la mémoire
Le chemin actif d’une tranche ne doit pas produire d’allocation inutile. Au lieu de traduire directement le modèle de requête en entité de domaine, le gestionnaire déplace uniquement les champs dont il a besoin. La lecture de la projection au lieu de charger l'agrégat côté requête réduit les coûts de CPU, de mémoire et d'E/S.```text
Command path
→ tek aggregate
→ kısa transaction
→ sabit event sayısı
Query path
→ ihtiyaca göre projection
→ minimum column set
→ pagination veya cursor
```La complexité de la requête de liste est O(p) si la taille de la page est `p`. Avec la pagination du curseur, la mémoire reste O(p); Le stockage de la liste complète consomme de la mémoire O(n) et crée des risques inutiles en production.
## Stratégie de test
Les tests Vertical Slice doivent optimiser la confiance comportementale, et non le nombre de simulations.
1. Les tests du validateur prouvent les valeurs limites : note vierge, longueur maximale, caractères interdits.
2. Les tests du gestionnaire vérifient les flux réussis et les flux d'erreurs : pas de commande, erreur de propriété, demande en double.
3. Les tests de domaine préservent les invariants globaux : aucune note ne peut être ajoutée aux commandes annulées.
4. Les tests d'intégration prouvent que la transaction et l'enregistrement de la boîte d'envoi se produisent ensemble.
5. Les tests d'architecture vérifient qu'une tranche ne dépend pas des classes internes d'une autre tranche.
6. Les tests de projection montrent que le résultat reste idempotent lorsque l'événement se reproduit.
Le but de la pyramide des tests n’est pas d’isoler chaque classe ; est de trouver rapidement le point où la décision est annulée.
## Compromis
Vertical Slice produit des fichiers plus petits. C'est un prix conscient. À leur tour, la raison du changement, la couverture des tests et la surface de restauration deviennent plus visibles.
En cas de mauvaise application, deux risques surviennent. La première est que chaque tranche produit son propre mini-framework. La seconde est que le dossier `Shared` s'agrandit à l'infini et devient le nouveau nom de l'ancien monolithe en couches. La solution réside dans des interfaces étroites, des ports ouverts, des tests architecturaux et un enregistrement des décisions avec ADR.
## Fréquemment confus```text
❌ Vertical Slice = controller'dan repository'ye kadar her şeyi aynı klasöre koymak
✓ Kullanıcı niyetinin davranış, veri ve test sınırını birlikte tasarlamaktır.
❌ CQRS = mutlaka ayrı veritabanı
✓ CQRS önce niyet ayrımıdır; fiziksel ayrım ayrı bir maliyet kararıdır.
❌ Mediator = mimari
✓ Mediator yalnızca dispatch ve pipeline aracıdır; mimari sınırı feature sahipliği belirler.
❌ Outbox = exactly-once garantisi
✓ Outbox event kaybını önler; consumer yine idempotent olmalıdır.
❌ Read replica = projection
✓ Replica aynı modeli çoğaltır; projection okuma ihtiyacına göre yeni model üretir.
Liste de contrôle de décision
- Slice représente-t-il une intention d'utilisateur unique ?
- Le gestionnaire effectue-t-il uniquement l'orchestration des applications ou stocke-t-il les règles de domaine ?
- La commande et la requête doivent-elles partager le même modèle de réponse, ou est-il plus clair qu'elles sont séparées ?
- La frontière de transaction reste-t-elle à l’intérieur d’un seul agrégat ou d’une frontière de cohérence consciente ?
- La diffusion de l'événement est-elle liée à la même transaction locale que l'enregistrement de données via la boîte d'envoi ?
- Le consommateur reste-t-il idempotent lorsqu'il reçoit un événement en double ?
- La projection réduit-elle réellement le besoin d'écrans ou ajoute-t-elle des coûts opérationnels inutiles ?
- Existe-t-il des preuves de la latence, du coût ou du rayon d'explosion du p95 pour la décision de mettre en cache, de négocier, de lire, de stocker et de déployer séparément ?
- Les interfaces sont-elles étroites ou se sont-elles transformées en un sac contenant tous les cas d'utilisation, comme
IOrderService? - Les tests architecturaux préservent-ils les limites des fonctionnalités au stade de la compilation ou du CI ?
Si la réponse à ces questions n’est pas claire, il est souvent moins coûteux de simplifier les limites des tranches plutôt que d’ajouter davantage d’infrastructures.
Ce qu'il faut retenir de cet article
- Une tranche verticale est la limite de comportement d'une seule intention d'utilisateur, de la requête au test.
- CQRS, Mediator, Outbox et Projection ne sont pas la même décision ; Chacun résout différents problèmes de coûts et de sécurité.
- La vérification préalable des limites logiques des fonctionnalités permet de contrôler le coût de la séparation physique.
- Réduit la perte d'événements de boîte d'envoi dans le système distribué ; L'idempotence empêche la relivraison de causer des dommages.
- Le coût du cloud n’est pas externe à l’architecture ; Chaque limite devient un élément de calcul, d'E/S, de réseau et d'opération.
Flux de bout en bout```text
POST /orders | v Endpoint / Controller | v Mediator.Send() | v Validation | v Authorization | v Transaction | v Command Handler | v Aggregate | v Repository | v Outbox | v Commit | v Broker / Kafka | v Projection | v Read Database | v GET /orders | v Query Handler | v Frontend
Dans la section suivante, nous examinerons comment maintenir ensemble les limites des tranches verticales, des agrégats DDD et des monolithes modulaires en production.
FAQ
Frequently asked questions
Est-il correct de « Vertical Slice = mettre tout, du contrôleur au référentiel, dans le même dossier » ?
Il s'agit de concevoir ensemble le comportement, les données et les limites de test de l'intention de l'utilisateur.
"CQRS = base de données nécessairement séparée" est-il correct ?
CQRS est une distinction axée sur l'intention d'abord ; la séparation physique est une décision de coût distincte.
« Comment fonctionne une tranche verticale de l'intérieur ? » Qu'est-ce que ça dit ?
Comment une tranche verticale circule-t-elle en interne, de la demande à la validation, du gestionnaire à l'agrégation, de l'événement de boîte d'envoi à la lecture du modèle jusqu'au coût du cloud ? CQRS…
Principes d'ingénierie appris
- La véritable limite d'une tranche n'est pas le dossier, mais l'intention de l'utilisateur et la décision de cohérence, qui varient pour la même raison.
- CQRS et la boîte d'envoi ne sont pas une exigence de séparation physique, mais des outils qui rendent explicites l'intention et le coût de la fiabilité.
- L'architecture de production doit être mesurée par l'organisation du code ainsi que par les coûts de latence p95, d'E/S, de sortie, de nouvelle tentative et de restauration.
Continuer la lecture
Continuer la lecture
Suivant en série
Des calques aux fonctionnalités : pourquoi la tranche verticale est-elle née ?
Pourquoi l’architecture en couches ralentit-elle le changement à mesure qu’elle se développe ? Pas la disposition des dossiers de Vertical Slice ;…
Articles connexes
De CRUD (Créer, Lire, Mettre à jour, Supprimer) à CQRS (Command Query Responsibility Segregation) : le problème est le modèle, pas le code
Qu'est-ce que CQRS, quelle est la différence entre CRUD et CQRS et quand faut-il utiliser CQRS ? Un guide expliquant pourquoi un seul modèle ne suffit pas…
Articles connexes
Comment fonctionne le pipeline CQRS (Command Query Responsibility Segregation) ? Anatomie du flux de commande et de requête
Qu’est-ce que le pipeline de requêtes CQRS ? Comment une requête HTTP se déroule-t-elle via Controller, MediatR, le comportement du pipeline, le…