10 - Features vs Domaine : la bonne decoupe
Ce que tu vas apprendre
- La relation entre une feature et le domaine
- Pourquoi une feature se mappe a des transitions, pas a des entités
- Pourquoi les features ne s'appellent pas entre elles
- Comment découper les features correctement
Prerequisites
Une feature n'est pas un domaine
Quand on debute, on confond souvent feature et domaine. "La feature d'ajout de Place" semble etre un domaine en soi. Mais non.
Une feature est un point d'entree utilisateur qui déclenché une ou plusieurs transitions dans le cycle de vie. Le domaine, lui, est le socle : le cycle de vie, les états, les invariants, les guards.
Analogie : le domaine est la voie ferree. Les features sont les gares. Les trains (les entités) roulent sur la voie ferree, et les gares permettent d'y acceder. Mais les gares ne definissent pas les rails.
Une feature se mappe a des transitions
Voici comment les features du domaine Place se mappent aux transitions du cycle de vie :
| Feature | Transitions gerees | Rôle |
|---|---|---|
add-place |
-> DRAFT -> ENRICHED -> READY_FOR_IMAGES |
Saisie + enrichissement |
image-processing |
READY_FOR_IMAGES -> IMAGES_PROCESSING -> IMAGES_PROCESSED |
Pipeline images |
image-validation |
IMAGES_PROCESSED -> READY_FOR_PUBLICATION |
Review humaine |
publication |
READY_FOR_PUBLICATION -> PUBLISHED |
Mise en ligne |
Chaque feature gere un sous-ensemble du cycle de vie. Aucune feature ne gere tout le cycle de vie. Et aucune feature ne "saute" des états.
Representation visuelle
Feature: add-place Feature: image-processing
| |
v v
+-------+ +----------+ +-----------------+ +-------------------+
| DRAFT |-->| ENRICHED |->| READY_FOR_IMAGES|-->| IMAGES_PROCESSING |
+-------+ +----------+ +-----------------+ +-------------------+
|
Feature: image-validation |
| v
+-----------+ +------------------------+ +------------------+
| PUBLISHED |<--| READY_FOR_PUBLICATION |<--| IMAGES_PROCESSED |
+-----------+ +------------------------+ +------------------+
^
|
Feature: publication
Les features ne s'appellent pas entre elles
C'est une regle fondamentale. La feature add-place ne doit jamais appeler directement la feature image-processing. Pourquoi ?
Si les features s'appellent
typescript// Mauvais : couplage direct entre features
async function addPlace(data: PlaceData) {
const place = await createPlace(data);
await enrichPlace(place.id);
// Appel direct a une autre feature
await imageProcessing.startProcessing(place.id); // couplage !
}
Problèmes :
add-placedoit connaîtreimage-processing- Si
image-processingchange son API,add-placecasse - On ne peut pas supprimer
image-processingsans modifieradd-place - Le cycle de vie est cache dans les appels entre features
Le bon modèle
Chaque feature avance l'entité dans le cycle de vie. Le prochain "consommateur" est un worker ou un cron qui reagit a l'état de l'entité :
typescript// Feature: add-place -- elle fait son travail et s'arrete
async function addPlace(data: PlaceData) {
const place = await createPlace(data); // status = DRAFT
const enriched = await transition(place, "ENRICH"); // status = ENRICHED
await transition(enriched, "REQUEST_IMAGES"); // status = READY_FOR_IMAGES
// Stop. L'entite est dans son etat final pour cette feature.
}
// Worker: image-processing -- il se declenche tout seul
// Ce worker tourne en permanence et traite les Places en READY_FOR_IMAGES
async function imageProcessingWorker() {
const places = await db.findByStatus("READY_FOR_IMAGES");
for (const place of places) {
await transition(place, "PROCESS_IMAGES");
}
}
Les deux features ne se connaissent pas. Elles sont reliees par le cycle de vie, pas par des appels directs. C'est exactement le type d'architecture qu'on met en place sur paltemps.fr pour decoupler les différentes parties du système.
Le pipeline de domaine est partage
Les transitions, les guards, les invariants -- tout ca est défini dans le domaine, pas dans les features. Les features sont des consommateurs du domaine.
Domaine (partage)
|-- transitions.ts // toutes les transitions
|-- guards.ts // tous les guards
|-- invariants.ts // tous les invariants
+-- types.ts // PlaceStatus, PlaceEntity
Features (points d'entree)
|-- add-place/ // utilise transitions DRAFT -> ENRICHED -> READY_FOR_IMAGES
|-- image-processing/ // utilise transitions READY_FOR_IMAGES -> IMAGES_PROCESSED
|-- image-validation/ // utilise transition IMAGES_PROCESSED -> READY_FOR_PUBLICATION
+-- publication/ // utilise transition READY_FOR_PUBLICATION -> PUBLISHED
Le test : supprimer une feature
Un domaine est bien decoupe quand supprimer une feature ne casse pas la définition du domaine.
Si tu supprimes la feature image-validation, le cycle de vie existe toujours. Les états IMAGES_PROCESSED et READY_FOR_PUBLICATION existent toujours. La transition entre eux existe toujours. Il n'y a juste plus personne pour la déclencher.
Si supprimer une feature casse le domaine, c'est que la logique métier est dans la feature au lieu d'etre dans le domaine. C'est un signal d'alarme.
Decouper par rôle, pas par ecran
On est tente de découper les features par ecran ou par page. "L'ecran de création" = une feature. "L'ecran de validation" = une autre. C'est un bon début, mais le vrai critère est le rôle :
- Qui utilise cette feature ? (un opérateur, un admin, un worker automatise ?)
- Quelles transitions elle déclenché ?
- Quel sous-ensemble du cycle de vie elle gere ?
Une feature = un rôle = un sous-ensemble de transitions.
Résumé
- Une feature est un point d'entree, pas un domaine. Elle se mappe a des transitions du cycle de vie.
- Les features ne s'appellent pas entre elles. Elles sont reliees par le cycle de vie.
- Le domaine (transitions, guards, invariants) est partage. Les features le consomment.
- Supprimer une feature ne doit pas casser la définition du domaine.
- Decouper par rôle et par transitions, pas par ecran.
Article précédent : 09 - La dette technique Article suivant : 11 - Glossaire complet