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) :

IntentionConfiguration
plusieurs B par Aaucune singularité sur BASE_POSITION
au plus un B par AenableSingularConstraint(BASE_POSITION)
au moins un B par AenableRequiredConstraint(BASE_POSITION)
exactement un B par Asingular + required sur BASE_POSITION
au plus un A par Bsingular sur TARGET_POSITION
1 ↔ 1singular 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.