Aller au contenu

ADR-0032 — Le noyau est une bibliothèque, pas un service : langage par couche, déclencheurs d'une forme-service

Statut 🟢 Accepté — 2026-06-24 ; complète l'ADR-0031 (topologie β). Non implémenté (oriente le plan strangler T0→T5).
Portée Forme de consommation du noyau quantis-actuariat (bibliothèque in-process vs service réseau) et choix de langage par couche. Ne touche aucun chiffre (oracles K1/K-OHADA inviolés).
Décision Le noyau est une bibliothèque consommée en-process, pas un service réseau ; Python pour le noyau, Rust en accélérateur chirurgical (PyO3), Go ou Python pour les services qui l'entourent ; les besoins futurs (compute lourd/GPU, consommateur non-Python) s'adressent en ajoutant une forme-service par-dessus la lib, jamais en réimplémentant les maths.
Réf. ADR-0031 — Pivot framework, topologie β ; ADR-0002 — Backend consomme actuariat_lib (package épinglé) ; ADR-0001 — Tables réglementaires embarquées

🎯 À retenir

Le noyau est du calcul pur, sans état, déterministe — une fonction, pas une ressource. On le consomme donc comme bibliothèque installée dans chaque surface (en-process), et non comme microservice réseau : un service ajouterait un saut réseau pour appeler une fonction, un point de défaillance unique (le moteur tombe, tout calcul de toute surface tombe), et reperdrait l'indépendance de version que β devait garantir, le tout au détriment du déterminisme audité (oracle au franc). Côté langage : Python pour la maths (écosystème actuariel, déjà validé, auditable, vectorisé), Rust seulement en accélérateur ciblé d'un point chaud prouvé (appelé depuis Python, gardé par les oracles), Go réservé aux services autour, jamais à la maths. Les deux besoins futurs cités (compute lourd/GPU ; consommateur Go/Rust) s'obtiennent en greffant une forme-service sur la lib — coût faible et additif, car un service n'est qu'un wrapper sur un noyau déjà prouvé. Une langue par étage, pas une langue pour tout.

Contexte

L'ADR-0031 extrait le noyau et le fait consommer par N surfaces. Restait une question d'architecture distincte, soulevée par le PO : le noyau ne pourrait-il pas être un service déployé (un type de service Dokploy), que les surfaces appelleraient par le réseau ? Et corollaire : quel langage pour la maths — Python, Rust ou Go ? Deux besoins futurs ont été versés au débat : (1) l'intégration prévue de compute lourd / GPU (Monte-Carlo, stochastique) pour certains modules ; (2) l'exploration possible de Go ou Rust pour de futurs modules consommateurs.

Le noyau, par nature, est du calcul pur : (population, hypothèses) -> (DBO, décomposition), sans base, sans session, sans effet de bord, et reproductible au franc (oracle K1 = 2 751 205,018849). Sa partie lourde (DBO de portefeuille) est déjà vectorisée (numpy → vitesse C sur les tableaux).

Décision

  • D-NOYAU-1 — Bibliothèque in-process, pas service réseau. Le noyau est installé dans l'image de chaque surface et s'exécute en-process (from actuariat_lib import …, appel direct). On n'en fait pas un microservice que les surfaces appellent par HTTP/gRPC. Motifs : (a) un saut réseau + sérialisation (population de centaines/milliers de têtes) pour appeler une fonction, à chaque calcul — et la sensibilité / le labo rejouent en boucle ; (b) point de défaillance unique : un service moteur indisponible casse tous les calculs de toutes les surfaces, là où une lib pinnée laisse chaque surface autonome ; (c) perte de l'indépendance de version — raison même de β : en lib, IFC pin v1.4, Assurances v1.5, releases indépendantes ; un service unique impose une version vivante partagée ; (d) déterminisme et audit : une lib figée dans l'image est auditable, un service redéployé peut changer le calcul silencieusement sous les surfaces — inacceptable pour un moteur réglementé. Le « contrat d'API stable » (D-EYWA-2/4) est déjà porté par les signatures versionnées des fonctions : pas besoin de réseau pour avoir un contrat.

  • D-NOYAU-2 — Langage : Python pour la maths, Rust en accélérateur ciblé, Go autour. Le noyau reste Python : l'écosystème actuariel/scientifique y vit (numpy, scipy, lifelib, QuantLib-Python ; pour la suite GPU/stochastique : JAX, cupy, Numba), il est déjà validé (oracles verts), auditable (le code épouse la formalisation), et sa partie lourde est vectorisée. Rust n'entre qu'en accélérateur chirurgical : si le profilage désigne un point chaud, on réécrit cette fonction en Rust (PyO3) appelée depuis Python, elle aussi gardée par les oracles — jamais un rewrite du noyau (un port cross-langage doit reproduire l'oracle au franc : sémantique float64, interpolation de tables, arrondis → risque réel sur le déterminisme validé). Go est le moins adapté à la maths (écosystème numérique pauvre) et réservé aux services / workers qui entourent le noyau (API, orchestration de jobs), lesquels le consomment sans le contenir. Une langue par étage.

  • D-NOYAU-3 — Les besoins futurs s'ajoutent par-dessus la lib, sans migration. Ni le compute lourd ni le polyglotte n'imposent de migrer le noyau vers un service ; les deux s'obtiennent en greffant une forme-service sur la bibliothèque, quand le besoin est concret : compute lourd / GPU → un worker asynchrone (job-submit/poll, scalable, GPU) qui importe la lib (le compute lourd réclame un worker de toute façon, lib ou service) ; consommateur non-Python → une façade-service (FastAPI/gRPC mince) qui enveloppe la lib et l'expose sur le réseau (Go/Rust appellent l'API). Dans les deux cas la lib reste l'unique source de vérité. Le coût de migration est faible et additif précisément parce qu'un service n'est qu'un wrapper sur un noyau déjà prouvé (le cher — maths + validation — est payé une fois ; le service hérite gratuitement de la garantie des oracles). On a même déjà le patron : le backend ActuaryLab actuel est un FastAPI qui enveloppe la lib. Cette propriété dépend d'une API noyau propre (D-EYWA-2) : ces besoins futurs sont donc un argument pour T1/T2, et le contrat d'engagement sera conçu sérialisable (mappable JSON/protobuf — evaluer(…) l'est déjà) pour qu'une façade future soit quasi gratuite.

Conséquences

  • Positif. Simplicité et vitesse (numpy en-process, zéro réseau sur le chemin de calcul) ; pas de SPOF ; indépendance de version par surface préservée ; déterminisme et auditabilité maximisés ; effet de levier de l'écosystème Python ; les deux portes futures restent ouvertes à coût faible.
  • Coûts / limites assumés. Un consommateur non-Python exige une façade (ajoutée au besoin, pas gratuite mais bornée) ; un noyau pur Python ne donne pas le polyglotte-in-process gratuit (besoin rare) ; une perf extrême demanderait un vrai projet d'extension Rust (borné mais non trivial). Aucun de ces coûts n'est payé tant que le besoin n'est pas là.
  • Invariants. Les oracles K1/K-OHADA gardent tout accélérateur ou port (rien n'entre en production sans les reproduire au franc) ; jamais deux implémentations des maths en parallèle (pas de réécriture Go/Rust dupliquant la source de vérité).

Déclencheur de réexamen (pré-enregistré)

  • Un consommateur non-Python (module Go/Rust/Node) a besoin des maths → ajouter une façade-service (HTTP/gRPC enveloppant la lib), pas réimplémenter.
  • Un besoin de compute lourd async / GPU se concrétise → ajouter un worker asynchrone important la lib ; au besoin un sous-module stochastique/GPU (JAX/cupy) dans le noyau.
  • Le profilage prouve un point chaud → réécrire cette fonction en Rust (PyO3), gardée par les oracles ; pas de rewrite.
  • Le polyglotte-in-process devient une exigence dure (rare) → instruire séparément un cœur Rust à FFI (PyO3 + C ABI), décision lourde (rewrite + re-validation au franc) à peser à ce moment-là.