You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
RFC : retour d'expérimentation Effect sur server-nestjs
#2809
La PR #2759 a expérimenté Effect sur le module cluster de apps/server-nestjs : pipelines Effect.gen, erreurs métier typées, retries déclaratifs. Fermée sans fusion, elle sert de preuve de concept. Cette discussion ouvre la question : que retenir de l'expérience, et faut-il généraliser ?
Expérimentation
Le prototype a établi les faits suivants sur le module cluster :
Composition en pipelines Effect.gen + pipe, service retournant des effets, contrôleur décodant l'entrée et mappant les erreurs (ClusterNotFound, LabelTaken, EnvironmentsActive, HookFailed) vers 404/409/400/422.
Lectures rejouées avec backoff exponentiel jitteré via Effect.retry.
Build, lint et 12/12 tests cluster verts, pnpm test racine exit 0.
L'incrément ne portait que le câblage manifeste (effect@3.22.2 au catalogue runtime) ; le code de service annoncé n'a pas survécu à la PR. L'expérience reste donc partielle : la faisabilité mécanique est prouvée, le coût réel par module non mesuré.
Motivation
Les services de server-nestjs orchestrent Prisma, EventEmitter2 et les logs de façon impérative. Trois conséquences concrètes :
Le chemin d'erreur est invisible. Une méthode getCluster(id) ne dit rien de ClusterNotFound : l'exception part au runtime, remonte en 500 si le contrôleur l'a oubliée, et aucun type ne force le rappel.
Les rejeux sont dupliqués. Chaque lecture fragile réécrit son backoff à la main, avec des politiques divergentes d'un module à l'autre.
Les tests réclament le conteneur. Valider une branche d'erreur demande de monter le module Nest, ses providers et sa base.
Le contrat @ts-rest de packages/shared décrit déjà les réponses possibles par statut. Le service, lui, ne porte aucune de ces promesses dans son type.
Valeur observée et attendue
Le chemin d'erreur devient une donnée de signature. Une méthode retourne Effect<Cluster, ClusterServiceError> : le type E énumère les échecs métier. Le compilateur refuse un contrôleur qui ne les traite pas ; la classe des bugs « exception non mappée » disparaît par construction.
L'alignement avec le contrat-first devient mécanique. Chaque erreur typée se mappe vers le statut que le contrat @ts-rest déclare déjà. La chaîne contract → contrôleur → service reste typée de bout en bout.
Les politiques de résilience deviennent déclaratives.Effect.retry + Schedule.exponential avec jitter remplace le backoff manuel ; la politique se lit, se change en une ligne, se teste sans horloge artificielle.
La testabilité ne dépend plus du conteneur. Branches d'erreur et rejeux se valident par appel direct, sans monter le module Nest ni toucher la base.
La composition colle au système de hooks. Le cycle pre -> main -> post avec revert sur échec est un pipeline séquentiel à court-circuit : ce que la composition d'effets exprime nativement. Les ponts EventEmitter2 exigés par la parité s'enveloppent sans changer le bus.
L'expérience n'a rien remplacé : Prisma et EventEmitter2 restent, enveloppés (Effect.tryPromise) ; Nest garde l'injection de configuration et le routage.
Questions ouvertes
Généraliser en standard pour les nouveaux modules, ou réserver aux cas avec retries et erreurs typées ?
Séquencement : couche d'erreur d'abord, ou module par module au fil de la migration strangler ?
Si abandonné : effect sort du catalogue pnpm ?
Critère de décision : lisibilité contributeurs, coût par module mesuré sur le prochain module migré, couverture de tests ?
Risks and Mitigations
Deux styles d'écriture cohabitent pendant la migration strangler : convention documentée et revue par module limitent la dérive.
Courbe d'apprentissage (Effect.gen, Layer, Schedule) : commencer par le sous-ensemble erreurs + retry.
Les erreurs Effect ne traversent pas @ts-rest nativement : le mapping reste par module, coût assumé.
Alternatives
Statu quo : exceptions Nest + backoff manuel par service, aucun coût immédiat, chemin d'erreur non typé.
Adoption ciblée : Effect seulement dans les modules à forts rejeux, standard implicite à géométrie variable.
Autre bibliothèque d'effets : aucune candidate dans le monorepo, coût d'évaluation non porté.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Summary
La PR #2759 a expérimenté Effect sur le module
clusterdeapps/server-nestjs: pipelinesEffect.gen, erreurs métier typées, retries déclaratifs. Fermée sans fusion, elle sert de preuve de concept. Cette discussion ouvre la question : que retenir de l'expérience, et faut-il généraliser ?Expérimentation
Le prototype a établi les faits suivants sur le module
cluster:Effect.gen+pipe, service retournant des effets, contrôleur décodant l'entrée et mappant les erreurs (ClusterNotFound,LabelTaken,EnvironmentsActive,HookFailed) vers 404/409/400/422.Effect.retry.pnpm testracine exit 0.L'incrément ne portait que le câblage manifeste (
effect@3.22.2au catalogue runtime) ; le code de service annoncé n'a pas survécu à la PR. L'expérience reste donc partielle : la faisabilité mécanique est prouvée, le coût réel par module non mesuré.Motivation
Les services de
server-nestjsorchestrent Prisma,EventEmitter2et les logs de façon impérative. Trois conséquences concrètes :getCluster(id)ne dit rien deClusterNotFound: l'exception part au runtime, remonte en 500 si le contrôleur l'a oubliée, et aucun type ne force le rappel.Le contrat
@ts-restdepackages/shareddécrit déjà les réponses possibles par statut. Le service, lui, ne porte aucune de ces promesses dans son type.Valeur observée et attendue
Le chemin d'erreur devient une donnée de signature. Une méthode retourne
Effect<Cluster, ClusterServiceError>: le typeEénumère les échecs métier. Le compilateur refuse un contrôleur qui ne les traite pas ; la classe des bugs « exception non mappée » disparaît par construction.L'alignement avec le contrat-first devient mécanique. Chaque erreur typée se mappe vers le statut que le contrat
@ts-restdéclare déjà. La chaîne contract → contrôleur → service reste typée de bout en bout.Les politiques de résilience deviennent déclaratives.
Effect.retry+Schedule.exponentialavec jitter remplace le backoff manuel ; la politique se lit, se change en une ligne, se teste sans horloge artificielle.La testabilité ne dépend plus du conteneur. Branches d'erreur et rejeux se valident par appel direct, sans monter le module Nest ni toucher la base.
La composition colle au système de hooks. Le cycle
pre -> main -> postavecrevertsur échec est un pipeline séquentiel à court-circuit : ce que la composition d'effets exprime nativement. Les pontsEventEmitter2exigés par la parité s'enveloppent sans changer le bus.L'expérience n'a rien remplacé : Prisma et
EventEmitter2restent, enveloppés (Effect.tryPromise) ; Nest garde l'injection de configuration et le routage.Questions ouvertes
effectsort du cataloguepnpm?Risks and Mitigations
Effect.gen,Layer,Schedule) : commencer par le sous-ensemble erreurs + retry.@ts-restnativement : le mapping reste par module, coût assumé.Alternatives
Référence
#2759
All reactions