chainedFunction
chainedFunction répond à un problème de coordination en Clean Architecture : dans un use case, on doit parfois associer des opérations qui mettent à jour des entités différentes. La chained function représente alors l'agrégat qui rend ces opérations solidaires dans une même frontière de cohérence métier.
Elle permet au domaine de déclarer explicitement que plusieurs actions métier pures sont liées. Les fonctions passées à chainedFunction ne font pas d'injection de dépendance et n'appellent pas de port : elles contrôlent uniquement le cycle de vie des entités. Le use case orchestre ensuite l'agrégat, les ports et les effets techniques.
Exemple interactif
Pourquoi l'utiliser ?
Utilisez chainedFunction quand un agrégat métier n'est cohérent que si plusieurs opérations nommées se produisent ensemble.
Par exemple, publier un commentaire peut demander :
- de créer l'entité commentaire ;
- de produire une entité commentaire valide ;
- de produire une entité article mise à jour.
La persistance reste dans le use case via le système de port de la librairie. La chained function modélise seulement le contrat d'agrégat : "créer le commentaire" et "incrémenter le nombre de commentaires de l'article" sont des actions métier liées.
INFO
Dans ce contexte, repository reste un alias sémantique de port via createRepository.
Garanties
chainedFunction apporte ces garanties au niveau du typage :
- les liens sont exposés un par un, dans l'ordre de déclaration ;
- l'implémentation ne peut pas accéder à un lien suivant avant d'avoir appelé le lien courant ;
- le chemin de succès doit terminer avec
chainEnd(...); - une fonction chaînée peut arrêter le flux en retournant un
Either.Left; - l'implémentation peut aussi retourner directement un
Either.Left. - les fonctions chaînées restent des fonctions de domaine pures.
Syntaxe
Signature
function chainedFunction(
function1: [name: string, fn: Function, requirements?: C.RequirementsChainedFunction],
function2: [name: string, fn: Function, requirements?: C.RequirementsChainedFunction],
...functions: [name: string, fn: Function, requirements?: C.RequirementsChainedFunction][]
): ChainedFunctionImplémentation
const aggregate = chainedFunction(...functions);
const result = aggregate(function *(firstLink, { breakIfLeft, unwrapResult }) {
const [value, nextLink] = yield *firstLink(({ functionName }) => functionName(...args));
return chainEnd(value);
});Paramètres
function1: première fonction métier pure de la chaîne.function2: deuxième fonction métier pure de la chaîne.functions: fonctions métier pures supplémentaires, exécutées dans l'ordre de déclaration.requirements(élément de tuple optionnel sur chaque fonction) : valeurs requises typées qui doivent être fournies lors de l'appel du link généré pour cette fonction.firstLink: premier link généré et passé au callback d'implémentation.breakIfLeft: helper synchrone injecté dans le callback. Il accepte une valeur potentiellementEither.Left, stoppe la chaîne si c'est unLeft, sinon retourne la valeur discriminée sans leLeft.unwrapResult: helper synchrone injecté dans le callback. Il accepte le tuple retourné par un link ([value, nextLink | chainEnd]), dépaquettevalueviaunwrap(...)et renvoie le tuple avec le même second élément.
Valeur de retour
chainedFunction(...) retourne une fonction d'agrégat chaîné. Son appel retourne :
- la valeur brute passée à
chainEnd(value)sur le chemin de succès ; - le
Either.Leftretourné par une fonction chaînée ; - le
Either.Leftretourné directement par l'implémentation.
Flux d'erreur
Quand une fonction chaînée retourne un Either.Left, le générateur le yield et chainedFunction arrête l'implémentation avant d'exécuter les links suivants. Les erreurs métier doivent être représentées avec Either.Left ; les exceptions lancées et les promesses rejetées ne sont pas interceptées.
breakIfLeft suit la même règle, mais côté callback : il sert à court-circuiter explicitement le flux à partir d'une valeur intermédiaire synchrone (value | Left) avant d'appeler le link suivant.
unwrapResult permet de simplifier le traitement des valeurs wrapées retournées par un link : il conserve le flux de chaînage (nextLink / chainEnd) tout en donnant directement la valeur dépaquetée pour l'étape suivante.
Requirements et invariants de cycle de vie
Les requirements sont un outil de typage pour contrôler le cycle de vie métier. Ils obligent l'appelant à fournir certaines valeurs typées avant d'exécuter un link.
Ces valeurs ne sont pas nécessairement utiles comme arguments runtime de la fonction suivante. Leur rôle principal est de prouver, à la compilation, que des informations préalables ont bien été obtenues dans le flux (validation effectuée, contexte d'autorisation chargé, étape précédente terminée, etc.).
Exemple requirements
Voir aussi
useCase- Appelle une logique applicative avec dépendances.port- Déclare un contrat de port.repository- Alias sémantique decreatePort.Either- Représente des valeurs explicites de succès et d'erreur.
