yoonite-saga
v1.2.25
Published
> Orchestration de workflows transactionnels avec gestion de compensation (pattern Saga)
Maintainers
Readme
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é.
Installation
npm i yoonite-sagaConcepts clés
- 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.
Exemple simple
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);Utilisation avancée
1. Enrichir le contexte
Chaque invoke peut retourner un objet qui enrichit le contexte pour les étapes suivantes :
.step("Créer le compte")
.invoke(() => ({ accountId: "123" }))
.step("Afficher le compte")
.invoke(({ accountId }) => {
console.log(`Compte créé : ${accountId}`);
})2. Actions asynchrones
.step("Récupérer les infos")
.invoke(async ({ accountId }) => {
const infos = await api.get(`/accounts/${accountId}`);
return { infos };
})3. Plusieurs invokes par étape
.step("Récupérer les animaux")
.invoke("Chats", async ({ accountId }) => {
const cats = await api.get(`/cats/${accountId}`);
return { cats };
})
.invoke("Chiens", async ({ accountId }) => {
const dogs = await api.get(`/dogs/${accountId}`);
return { dogs };
});4. Conditions sur les étapes
.step("Vacciner le chien")
.condition(({ dogs }) => dogs && dogs.length > 0)
.invoke("Vaccins", async ({ dogs }) => {
const { dogId } = dogs[0];
const vaccines = await api.get(`/vaccines/${dogId}`);
return { vaccines };
})5. Conditions sur les invokes
.step("Récupérer les animaux")
.invoke("Chats", {
condition: ({ hasCats }) => hasCats,
action: async ({ accountId }) => {
const cats = await api.get(`/cats/${accountId}`);
return { cats };
}
})
.invoke("Chiens", {
condition: ({ hasDogs }) => hasDogs,
action: async ({ accountId }) => {
const dogs = await api.get(`/dogs/${accountId}`);
return { dogs };
}
})6. Gestion des erreurs et compensation
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 :
.step("Créer la commande")
.invoke(async () => {
const orderId = await api.post(`/orders`, { amount: 100 });
return { orderId };
})
.withCompensation(async ({ orderId }) => {
await api.delete(`/orders/${orderId}`);
})
.step("Expédition")
.invoke(() => {
throw new Error("Erreur d'expédition");
})On peut aussi définir une compensation sur un invoke :
.step("Créer la commande")
.invoke({
action: async () => {
const orderId = await api.post(`/orders`, { amount: 100 });
return { orderId };
},
withCompensation: async ({ orderId }) => {
await api.delete(`/orders/${orderId}`);
}
})7. Validation de données
Vous pouvez valider le contexte à chaque étape avec un DTO compatible class-validator :
import { IsString } from "class-validator";
class MyDto {
@IsString()
accountId: string;
}
.step("Créer le compte")
.invoke(() => ({ accountId: "123" }))
.validate(MyDto)Résultat de l'exécution
Le résultat retourné par le processor contient :
{
state: "success" | "failed",
context: { ... }, // Contexte final
history: any[], // Historique détaillé de l'exécution
errors: Error[], // Liste des erreurs rencontrées
}Injection de services
Vous pouvez injecter des services (ex: logger, clients API, etc.) à utiliser dans vos actions :
const processor = new SagaProcessor({
myLogger: customLogger,
api: myApiClient,
});
.step("Log")
.invoke((ctx, { myLogger }) => {
myLogger.log("Hello saga!");
})Pourquoi utiliser Yoonite 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
Licence
MIT
