Aller au contenu

Contrats gouvernés

L'anti-pattern « stringly-typed » ne signifie pas que tous les champs doivent être connus à la compilation. Il signifie qu'une donnée connaissable ne doit pas devenir une chaîne non validée par facilité.

Un produit à champs personnalisés ou métamodèle administrable rencontre un autre cas : le contrat est une donnée versionnée. MIC appelle ce modèle un contrat gouverné.

Deux familles, une même rigueur

Contrat Source de vérité Moment de construction Validation attendue
Statique Code versionné Import / compilation StrictSchema, champs inconnus refusés
Gouverné Registre ou base versionnée Exécution Schéma compilé, version identifiée, champs inconnus refusés

La règle commune est simple : aucune donnée ne traverse la frontière sans être validée contre le contrat applicable. Le moment où ce contrat est écrit ne change pas son niveau d'exigence.

Un dictionnaire opaque, stocké mais jamais interprété par le métier, reste un troisième cas légitime. Dès que le service lit un champ pour décider, filtrer ou autoriser, ce champ appartient à un contrat statique ou gouverné.

Propriétés obligatoires

Un registre de contrats gouvernés est fiable seulement s'il possède toutes les propriétés suivantes :

  1. Source versionnée. Une clé logique seule ne suffit pas ; la version qui a validé chaque payload doit être identifiable.
  2. Compilation stricte. Types, champs requis, bornes et politique des champs inconnus sont matérialisés dans un validateur déterministe.
  3. Cache borné. La clé de cache inclut la version du contrat. Un TTL n'est qu'un filet de sécurité, pas le mécanisme principal de cohérence.
  4. Invalidation explicite. Une modification publie un événement d'invalidation ou change une version monotone. Les instances ne devinent pas qu'un schéma a changé.
  5. Échec fermé. Un schéma absent, illisible ou incohérent refuse le payload ; il ne bascule pas vers un dict[str, Any] permissif.
  6. Observabilité. Chargements, hits de cache, invalidations, erreurs de compilation et version active sont mesurables sans exposer les données.

Évolution et compatibilité

Une nouvelle version ne remplace pas silencieusement l'ancienne pour les données historiques. Le service choisit explicitement une stratégie :

  • valider chaque écriture contre la version active et conserver cette version ;
  • relire avec la version d'origine ;
  • migrer vers une nouvelle version par un traitement explicite et auditable.

Les changements incompatibles doivent recevoir une nouvelle version. Une invalidation retire un validateur du cache ; elle ne réécrit pas les données déjà acceptées.

API MIC

SchemaRegistry construit un StrictSchema depuis une séquence de FieldDef chargée par l'application. Le modèle généré reste immuable et refuse les champs inconnus. La clé, par exemple resource:k8s:namespace@v3, doit porter la version que le produit veut conserver avec ses données.

from mic.contracts import FieldDef, SchemaRegistry


def load_fields(key: str) -> tuple[FieldDef, ...]:
    definition = definitions.get_required(key)
    return tuple(
        FieldDef(
            name=field.python_name,
            annotation=field.python_type,
            alias=field.external_name,
        )
        for field in definition.fields
    )


registry = SchemaRegistry(
    loader=load_fields,
    ttl=300,
    on_invalidate=metrics.schema_reloaded,
)

Payload = registry.get("resource:k8s:namespace@v3")
payload = Payload.model_validate(raw)

L'application reste propriétaire de la base ou du registre qui sert de source de vérité, ainsi que du transport d'invalidation. Lorsqu'un changement est publié, chaque instance appelle registry.invalidate(key) ; le TTL borne la divergence si cet événement est perdu. Le chargeur doit refuser une définition absente ; une définition incohérente lève SchemaDefinitionError au lieu de produire un modèle permissif.

SchemaRegistry met en cache les validateurs dans le processus. Il ne prétend pas être un registre distribué, réécrire les données historiques ou choisir la stratégie de migration du produit.

Voir aussi Contrats stricts et Stringly-typed APIs.