Documentation · Modéliser
La structure et la cardinalité sont deux choses séparées.
Déclarer un attribut ne dit rien de sa cardinalité. C'est une règle explicite qui impose « au plus une valeur », « au moins une », « unique » — et cette règle, elle aussi, fait partie du graphe.
Le point de départ
Un attribut n'est pas un champ.
Écrire vehicle.setAttribute("Power") signifie seulement que Power est applicable à Vehicle. Rien n'impose, par défaut, qu'un véhicule possède exactement une puissance.
La règle explicite
On exprime l'intention métier.
Si l'intention est « un Vehicle possède au plus une Power », on l'écrit :
Generic power = vehicle.setAttribute("Power");
power.enablePropertyConstraint();Le vocabulaire des règles
Des contraintes posées sur le graphe.
Les méthodes les plus courantes sont exposées directement sur Generic. Certaines portent sur le nœud entier, d'autres sur un axe de ses components.
enablePropertyConstraint(); // au plus un holder pour une même composition
enableSingularConstraint(position); // au plus un link par Generic sur cet axe
enableRequiredConstraint(position); // au moins un, vérifié au flush()
enableUniqueValueConstraint(); // value unique entre instances du meta
setInstanceValueClassConstraint(c); // type Java de la value
enableReferentialIntegrity(position);
disableReferentialIntegrity(position);
enableCascadeRemove(position);
Les positions usuelles sont ApiStatics.NO_POSITION (-1), BASE_POSITION (0), TARGET_POSITION (1), TERNARY_POSITION (2). Pour une relation binaire Relation(base, target), l'axe 0 est la base et l'axe 1 la cible.
Cardinalités de relation
Quelques recettes.
Pour une relation relation = a.setRelation("R", b) :
| Intention | Configuration |
|---|---|
plusieurs B par A | aucune singularité sur BASE_POSITION |
au plus un B par A | enableSingularConstraint(BASE_POSITION) |
au moins un B par A | enableRequiredConstraint(BASE_POSITION) |
exactement un B par A | singular + required sur BASE_POSITION |
au plus un A par B | singular sur TARGET_POSITION |
| 1 ↔ 1 | singular sur base + target ; required selon le métier |
La règle est générale : un axe contraint décrit le Generic qui occupe cette position dans les components du holder ou du link.
Une propriété complète
Combiner les règles.
« Puissance entière, obligatoire, unique par véhicule » s'écrit :
Generic power = vehicle.setAttribute("Power");
power.enablePropertyConstraint();
power.enableRequiredConstraint(ApiStatics.BASE_POSITION);
power.setInstanceValueClassConstraint(Integer.class);Vérification au commit
flush() est une frontière.
Les contraintes structurelles sont contrôlées pendant la construction des mutations. RequiredConstraint, elle, est vérifiée au flush() : le cache peut contenir temporairement un objet incomplet, et c'est la validation transactionnelle qui l'exige complet.
Suppression
L'intégrité référentielle est un choix de modèle.
Les composants créent de vraies dépendances. L'intégrité référentielle est active par défaut : une suppression normale respecte les nœuds encore utilisés comme meta, super ou component. Les options par axe (disableReferentialIntegrity, enableCascadeRemove) sont des choix explicites, pas un moyen de faire disparaître une erreur.
Avant de désactiver l'intégrité, la bonne question est : « si ce composant disparaît, que doit devenir le Generic qui le référence ? ». Une violation métier invalide le différentiel courant : le moteur fait clear() puis lève une RollbackException dont la cause décrit l'erreur réelle.
Un exemple cohérent
Customer et Contract.
Un petit domaine où le moteur valide structure, cardinalités, required, classes de valeurs et transaction dans le même flush().
Generic customer = engine.setInstance("Customer");
Generic contract = engine.setInstance("Contract");
Generic customerCode = customer.setAttribute("Code");
customerCode.enablePropertyConstraint();
customerCode.enableRequiredConstraint(ApiStatics.BASE_POSITION);
customerCode.setInstanceValueClassConstraint(String.class);
Generic owner = contract.setRelation("Owner", customer);
owner.enableSingularConstraint(ApiStatics.BASE_POSITION);
owner.enableRequiredConstraint(ApiStatics.BASE_POSITION);
// Unité de travail.
Generic acme = customer.setInstance("Acme");
acme.setHolder(customerCode, "C-001");
Generic c42 = contract.setInstance("contract-42");
c42.setLink(owner, null, acme);
engine.getCurrentCache().flush();
Le moteur valide à la fois la structure du graphe, les cardinalités, le required, les classes de valeurs, la cohérence des composants et la transaction MVCC. L'application ne réimplémente pas ces invariants dans chaque interface.
Continuer
Sous le cache, il y a un moteur.
Comment le flush() devient une transaction, comment la concurrence est gérée, et ce que la persistance garantit vraiment : la page suivante.
