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 :
- Source versionnée. Une clé logique seule ne suffit pas ; la version qui a validé chaque payload doit être identifiable.
- Compilation stricte. Types, champs requis, bornes et politique des champs inconnus sont matérialisés dans un validateur déterministe.
- 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.
- 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é.
- Échec fermé. Un schéma absent, illisible ou incohérent refuse le payload ;
il ne bascule pas vers un
dict[str, Any]permissif. - 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.