> Orchestration de workflows transactionnels avec gestion de compensation (pattern Saga)
npm install yoonite-saga> Orchestration de workflows transactionnels avec gestion de compensation (pattern Saga)
Yoonite Saga est une librairie TypeScript/Node.js permettant de définir et d'exécuter des workflows transactionnels complexes, inspirée du pattern Saga. Elle facilite la gestion d'enchaînements d'étapes, la validation, les conditions d'exécution, la compensation (rollback) en cas d'erreur, et l'injection de services. Idéale pour les architectures distribuées ou les processus métier nécessitant robustesse et traçabilité.
---
``bash`
npm i yoonite-saga
---
- Step : Une étape du workflow, pouvant contenir plusieurs actions (invoke), une validation, une condition d'exécution, et une compensation.
- Invoke : Une action à exécuter dans une étape. Peut être asynchrone et enrichir le contexte d'exécution.
- Compensation : Fonction de rollback exécutée si une erreur survient dans le workflow.
- Condition : Fonction permettant de conditionner l'exécution d'une étape ou d'une action.
- Context : Objet partagé et enrichi tout au long du workflow.
- Services : Objets injectés pour être utilisés dans les actions du workflow.
---
`typescript
import { SagaBuilder, SagaProcessor } from "yoonite-saga";
const builder = new SagaBuilder({ debug: true });
const workflow = builder
.step("Initialisation")
.invoke(() => ({ userId: "abc" }))
.step("Traitement")
.invoke(({ userId }) => {
console.log(Traitement pour l'utilisateur ${userId});
})
.build();
const processor = new SagaProcessor();
processor.add(workflow);
const response = await processor.start();
console.log(response);
`
---
Chaque invoke peut retourner un objet qui enrichit le contexte pour les étapes suivantes :
`typescript
.step("Créer le compte")
.invoke(() => ({ accountId: "123" }))
.step("Afficher le compte")
.invoke(({ accountId }) => {
console.log(Compte créé : ${accountId});`
})
`typescript/accounts/${accountId}
.step("Récupérer les infos")
.invoke(async ({ accountId }) => {
const infos = await api.get();`
return { infos };
})
`typescript/cats/${accountId}
.step("Récupérer les animaux")
.invoke("Chats", async ({ accountId }) => {
const cats = await api.get();/dogs/${accountId}
return { cats };
})
.invoke("Chiens", async ({ accountId }) => {
const dogs = await api.get();`
return { dogs };
});
`typescript/vaccines/${dogId}
.step("Vacciner le chien")
.condition(({ dogs }) => dogs && dogs.length > 0)
.invoke("Vaccins", async ({ dogs }) => {
const { dogId } = dogs[0];
const vaccines = await api.get();`
return { vaccines };
})
`typescript/cats/${accountId}
.step("Récupérer les animaux")
.invoke("Chats", {
condition: ({ hasCats }) => hasCats,
action: async ({ accountId }) => {
const cats = await api.get();/dogs/${accountId}
return { cats };
}
})
.invoke("Chiens", {
condition: ({ hasDogs }) => hasDogs,
action: async ({ accountId }) => {
const dogs = await api.get();`
return { dogs };
}
})
Si une erreur est levée, la saga s'arrête et exécute les compensations définies sur les étapes déjà passées :
`typescript/orders
.step("Créer la commande")
.invoke(async () => {
const orderId = await api.post(, { amount: 100 });/orders/${orderId}
return { orderId };
})
.withCompensation(async ({ orderId }) => {
await api.delete();
})
.step("Expédition")
.invoke(() => {
throw new Error("Erreur d'expédition");
})
`
On peut aussi définir une compensation sur un invoke :
`typescript/orders
.step("Créer la commande")
.invoke({
action: async () => {
const orderId = await api.post(, { amount: 100 });/orders/${orderId}
return { orderId };
},
withCompensation: async ({ orderId }) => {
await api.delete();`
}
})
Vous pouvez valider le contexte à chaque étape avec un DTO compatible class-validator :
`typescript
import { IsString } from "class-validator";
class MyDto {
@IsString()
accountId: string;
}
.step("Créer le compte")
.invoke(() => ({ accountId: "123" }))
.validate(MyDto)
`
---
Le résultat retourné par le processor contient :
`typescript`
{
state: "success" | "failed",
context: { ... }, // Contexte final
history: any[], // Historique détaillé de l'exécution
errors: Error[], // Liste des erreurs rencontrées
}
---
Vous pouvez injecter des services (ex: logger, clients API, etc.) à utiliser dans vos actions :
`typescript
const processor = new SagaProcessor({
myLogger: customLogger,
api: myApiClient,
});
.step("Log")
.invoke((ctx, { myLogger }) => {
myLogger.log("Hello saga!");
})
``
---
- Orchestration simple et lisible de workflows transactionnels
- Gestion automatique des erreurs et rollback (compensation)
- Validation, conditions, et enrichissement du contexte
- Historique d'exécution pour audit/debug
- Injection de services pour actions personnalisées
---
MIT