J'ai longtemps considéré l'orchestration de flux comme un cas d'école de sur-ingénierie. Mon raisonnement était simple : un code linéaire et explicite est plus facile à maintenir qu'un assemblage de composants dispersés. Orchestre-t-on vraiment un système en dispersant ses méthodes dans des classes d'xetension pour les recomposer ensuite ? Le bénéfice n'était pas évident.
Mais l'expérience en équipe "middle-office" m'a fait changer de perspective. L'orchestration de flux n'est plus un luxe, c'est une nécessité pour survivre dans un environnement multi-partenaires.
Le problème des branchements conditionnels
Un middle-office métier doit intégrer de nombreux acteurs, chacun avec ses propres spécificités. Sans mécanisme d'isolation, le code se transforme en un enchevêtrement de conditions :
if (partner.equals(PartnerA) || partner.equals(PartnerB)) {
// logique commune
if (partner.equals(PartnerA)) {
// spécificités A
}
if (partner.equals(PartnerB)) {
// spécificités B
}
}
Avec une dizaine de partenaires, ce schéma devient ingérable. Chaque intervenant ne maîtrise qu'une portion du métier, mais le code est imbriqué au point que toute modification risque d'impacter des flux critiques. La dette technique s'accumule, et la moindre régression peut provoquer un incident de production.
Les leviers de résolution
Deux problématiques émergent : l'isolation du code et les points d'extension métier. Les solutions reposent sur :
- Un moteur de flux configurant des chaînes d'exécution différenciées par métier
- Un moteur d'extensions permettant à chaque partenaire de surcharger les portions spéciifques
Configuration des chaînes d'exécution
Chaque produit pouvant avoir un parcours transactionnel distinct, il convient de définir des chaînes spécifiques. La classe DemoMemberPurchaseExtension illustre la configuration de plusieurs parcours via un système d'extension.
Anatomie d'un nœud de flux
Chaque nœud expose quatre méthodes : execute, onSuccess, onFailure et postProcess.
Exécution du flux
L'appel à FlowChainService.run déclenche l'enchaînement séquentiel des nœuds, sur le modèle de la chaîne de responsabilité. Chaque execute est invoqué à tour de rôle. En cas d'exception, le mécanisme de compensation (onFailure) est déclenché en ordre inverse. Si tous les traitements réussissent, les hooks onSuccess sont exécutés en sens inverse également.
Mécanisme interne du moteur
Voici une reconstruction de la logique d'exécution :
public <C> void run(ExecutionChain<C> chain, C ctx) {
Throwable error = null;
int cursor = -1;
for (StepNode<C> step : chain.steps()) {
try {
step.execute(ctx);
cursor++;
} catch (FlowSkipException skip) {
logger.warn("Étape {} a demandé l'arrêt du flux", step.name());
break;
} catch (Throwable t) {
error = t;
break;
}
}
if (error != null) {
for (int i = cursor; i >= 0; i--) {
StepNode<C> step = chain.steps().get(i);
try {
step.onFailure(ctx, error);
} catch (Throwable t) {
logger.error("Échec rollback sur {}", step.name(), t);
}
}
} else {
for (int i = cursor; i >= 0; i--) {
StepNode<C> step = chain.steps().get(i);
try {
step.onSuccess(ctx);
} catch (Throwable t) {
logger.error("Échec success sur {}", step.name(), t);
}
}
}
for (int i = cursor; i >= 0; i--) {
StepNode<C> step = chain.steps().get(i);
try {
step.postProcess(ctx, error);
} catch (Throwable t) {
logger.error("Échec callback sur {}", step.name(), t);
}
}
if (error != null) {
throw new FlowExecutionException(error);
}
}
L'implémentation de référence est disponible dans le projet MemberClub, qui propose une solution open-source de transactions d'adhésion payante :
https://gitee.com/juejinwuyang/memberclub
Le projet intègre notamment : Mybatis-Plus, ShardingSphere, Redisson, Apollo, Spring Cloud, RabbitMQ, H2, Swagger, ainsi que MapStruct. On y trouve également des composants maison : moteur de flux, moteur d'extensions, retry distribué, gestion d'inventaire, verrous distribués via Redis Lua.