Voici la suite promise de ce brainstorming épistolaire sur la
faisabilité d'un SGBDR où les données, une fois écrites, ne
changeraient plus d'état. Maintenant nous allons explorer l'API.
Partons de la notion de propriété, non pas au sens marxiste mais au
sens JavaBean. Déjà il faut revoir cette dernière puisque nous voulons
des objets immuables, et utilisons au passage les Generics qui
n'existaient pas quand les JavaBeans ont été créés.
<<<
public class Property< T, U > {
public T set( U value ) { ... }
}
>>>
Le premier argument T correspond au type de l'objet à laquelle la
propriété est rattaché. Le second argument U correspond au type de la
valeur manipulée. Comme les détenteurs des propriétés sont immuables,
l'appel à ``#set(...)`` se contente de renvoyer une copie modifiée.
A quoi ressemble l'utilisation d'une telle propriété ? Il suffit de
renvoyer une instance attachée à l'objet propriétaire. Comme tout cela
est compliqué, avec plein des paramètres dans le constructeur et tout,
notre objet utilisant une propriété sera une interface.
<<<
public interface Stuff {
Property< Stuff, String > nickname() ;
Property< Stuff, Stuff > other() ;
}
>>>
Après on peut écrire du code comme suit, en se basant sur une
``Factory`` imaginaire qui renvoit toujours les bons types grâce à
l'inférence de type de Java 5.
<<<
Stuff stuff = Factory.create() ;
stuff = stuff.nickname().set( "foo" ) ;
>>>
A elle toute seule, l'interface Stuff définit un schéma de base de
données, avec une relation "many-to-one" pour rester dans la
terminologie Hibernate.
Mais on a peut-être envie d'ajouter des contraintes. Comme le système
d'annotations d'Hibernate validator manque de typage statique, voici
une autre approche complètement typée :
<<<
public interface Stuff {
Property< Stuff, String > nickname() ;
Property< Stuff, Stuff > other() ;
Stuff PROTOTYPE = PrototypeTools.create( Stuff.class ) ;
PropertyConstraints CONSTRAINTS = PropertyConstraints.create(
Constraints.on( PROTOTYPE.other() ).notNull(),
Constraints.on( PROTOTYPE.nickname() ).regex( "\\w+" ).notNull()
) ;
}
>>>
Java ne permet pas directement de fournir des morceaux de code
impératif dans une interface mais il y a moyen de se débrouiller avec
des constantes. L'avantage est que le ``public static final`` est
implicite. Mais comme chaque instance d'objet Property sera rattachée
à une instance de Stuff, comment faire en sorte d'accéder d'une façon
typée statiquement à ces membres d'instance à partir d'un contexte
statique ? En utilisant une instance "spéciale" dont le seul rôle est
de se "souvenir" de ses méthodes qui ont été appelées. C'est bien sûr
ce que fait l'instance ``PROTOTYPE`` qui va se baser sur un dynamic
proxy.
Une fois muni de notre ``PROTOTYPE`` nous pouvons ajouter des
contraintes à la définition de la classe, ces contraintes étant
conservées dans une autre constante ``CONSTRAINTS``. La classe
utilitaire Constraints génère des objets Constraint fortement typés
autour de la propriété qu'ils manipulent, par exemple il n'est pas
possible d'utiliser ``regex()`` sur une propriété qui manipule un
Boolean.
Je n'insiste pas sur les collections qui nécessitent un vrai travail
de fond. Il faudra de toutes façons jeter les contrats muables autour
de ``java.util.Collection`` et on imagine déjà des choses genre
``Sequence`` et ``Bag``. Le système de contraintes permettrait au
passage d'exprimer des relations inverses mises à jour
automatiquement, style :
<<<
public interface Thing {
SequenceProperty< Thing, Stuff > otherStuff ;
Property< Thing, Stuff > special() ;
Thing PROTOTYPE = PrototypeTools.create( Thing.class ) ;
PropertyConstraints CONSTRAINTS = PropertyConstraints.create(
Constraints.on( PROTOTYPE.otherStuff(), PROTOTYPE.special() ).
linked().notNull()
)
) ;
}
>>>
Evidemment avec les First Class Methods on pourrait se passer de ces
acrobaties. Notons qu'exposer les propriétés d'une interface en
conservant le typage statique peut fournir un coup de jeune aux
mécanismes de binding avec les composants d'une interface graphique,
par exemple.
Maintenant remplaçons notre ``Factory`` par une base de donnée
conservant ces objets immuables et leurs révisions successives. Il
faut à l'évidence faire figurer cette notion de révision dans l'API,
ce qui donne :
<<<
final Revision last = database.getLastRevision() ;
final Stuff stuff1 = last.query(
Stuff.PROTOTYPE.nickname(), "foo" ).first() ;
>>>
On effectue une modification :
<<<
final Stuff stuff2 = stuff1.nickname().set( "bar" ) ;
final Revision updated = Database.upgrade( stuff2 ) ;
>>>
Comme chaque objet connaît sa propre révision d'origine et les
changements apportés, on peut savoir à tout moment où il en est. A
l'intérieur du système de persistance local (équivalent à la session
Hibernate) on conserverait une information sur la requête d'origine
qui permettrait de mettre à jour notre ``stuff`` après écriture dans
la base. Par exemple l'instruction ci-dessous débouche sur un null ou
une exception (devinette : pourquoi ?) :
<<<
final Stuff stuff3 = updated.requery( stuff1 ) ; // Plus rien !
>>>
Ensuite il faut envisager les cas de conflits. Heureusement on peut
s'aider de la notion de branche acceptant des données qui dérogent aux
contraintes :
<<<
final Stuff stuff4 = stuff1.nickname().set( null ) ; // Interdit !
final Revision sick = Database.upgradeOrKeepConflicts( stuff4 ) ;
if( sick.isConflictBranch() {
...
}
>>>
C'est là une façon d'exposer toutes les transactions en cours, les
tripes à l'air, et de les faire durer indéfiniment sans verrouiller
toute la base !
Tout cela diverge complètement des standards Java, mais une fois le
principe de base intégré la syntaxe me semble assez naturelle. On peut
reprocher l'adhérence à Java, mais on doit pouvoir obtenir des
équivalents dans d'autres langages. Au moins là on a zéro "impedance
mismatch" entre le langage et le système de persistance.
Si je suis courageux je réfléchirai à comment intégrer les versions
successives de schémas.
Enjoy !
c.
=== Références
Une alternative aux propriétés JavaBeans dans un contexte JDK 1.4 :
http://joda.sourceforge.net/beans.html
Une déclinaison en Scala :
http://www.scala-lang.org/docu/examples/files/properties.html
Un vieux projet qui repose sur de la génération de bytecode pour
"attraper" la référence à la méthode appelée :
http://tweed.sourceforge.net/tutorial/bindings.html
La dernière spec des First Class Methods :