Document mis à jour le 13 mai 2001. Je suis ouvert à n'importe quelle suggestion qui pourrait aider à améliorer ces matériaux. N'hésitez pas d'écrire à l'auteur, Jonathan Revusky.
This document in EnglishRendu ici, on suppose que vous avez déjà fait fonctionner l'exemple, comme on explique ici. Une fois que vous avez franchi ces démarches mécaniques, vous voudrez peut-être un peu d'aide afin de mieux comprendre les éléments qui composent cet exemple.
Si cet exemple représente un grand saut relatif à l'exemple antérieur, c'est parce que ce servlet garde et récupère les données de l'application d'un répositoire persistant. Tout de même vous resterez peut-être étonné en voyant que cet exemple ne requiert pas beaucoup plus de code que l'exemple antérieur. Cela est dû à l'usage des API's que Niggle fournit pour la persistance de données. Cela nous permet de garder les "détails grossiers" bien encapsulés dans des fichiers externes en XML.
Les API's de Niggle de données persistantes qu'on introduit
se trouvent dans le package com.revusky.niggle.data
. Il y a
3 abstractions importantes dont on se sert ici:
com.revusky.niggle.data.MutableDataSource
, com.revusky.niggle.data.Record
,
et com.revusky.niggle.data.DataRegistry
.
Bien qu'il existe de diverses terminologies enracinées
dans l'informatique et la théorie des bases de données,
on espère que la plupart des programmeurs se sentent confortables
avec la terminologie qu'on a adoptée ici: un mutable data source, c'est
un objet qui nous permet de garder, recupérer, et modifier des records.
Un record, c'est la même chose conceptuellement qu'une ligne
dans une table d'une base de données relationelle. Le troisième
objet dont je viens de faire mention, c'est le data registry, qui
encapsule les métadonnées du système. Il nous permet
de chercher les sources de données disponibles par nom. D'ailleurs,
il nous permet d'obtenir une nouvelle instance d'un type de record, pour pouvoir
le remplir ces champs avec des données. Remarquez que ces metadonnées sont
définies dans les fichiers XML, recorddefs.xml et datasources.xml. Donc,
on peut dire que l'objet com.revusky.niggle.data.DataRegistry
représente le pont entre la partie qui est code java et la spécification
externe en XML.
Essayons donc de nous orienter en regardant le code java pour voir où on utilise ces nouvelles API's que je viens de mentionner.
Dans cet exemple, le code qui récupère les données
se trouve dans quatre méthodes, getData()
,
getAllEntries()
, getEntry()
et newEntry()
.
Jettez un coup d'oeil au méthode getData
.
Il contient seulement deux lignes. La première ligne obtient
une instance (il n'y en a qu'une d'ailleurs, c'est un singleton)
de notre objet du type DataRegistry
.
Cela nous permet de chercher dedans pour trouver notre
DataSource par son nom registré.
Remarquez que le nom de "rolodex_data" est assez arbitraire.
Bon, à part le fait que c'est le nom de la source de données qu'on
a spécifié dans le fichier externe, datasources.xml. Donc,
getData()
nous pêchent la référence à
la DataSource dont on se sert dans ce servlet.
Le méthode getAllEntries()
nous rend tous les records
dans le DataSource dans un conteneur du type java.util.List
.
Ça se fait en invoquant le méthode select()
de notre DataSource avec un argument nul. Je devrais mentionner
en passant qu'en général, ce méthode reçoit un argument
du type com.revusky.niggle.data.RecordFilter
qui indique un sous-ensemble de records à récupérer. Mais
si on y passe un null comme paramètre, comme on fait ici, la liste
qu'on reçoit contient tous les records de la source de données.
Le méthode getEntry()
est quelque peu plus compliqué.
Le méthode suppose que la clé primaire (primary key) du record
avec lequel on travaille est incrustée dans la pétition HTTP par moyen de
unique_id=
. S'il n'y a aucune entrée disponible
de cette façon, le méthode rend une nouvelle "entrée vierge"
par moyen du méthode newEntry()
. Ce que fait ce dernier méthode,
c'est qu'il obtient une nouvelle entrée du répositoire de données --
c'est à dire, notre instance de com.revusky.niggle.data.DataRegistry
--
en indiquant le type de record qui nous intéresse, en ce cas-ci, il s'agit
de "rolodex_entry". Remarquez que ceci fonctionne
parce que les metadonnées de "rolodex_entry" sont
spécifiées dans le fichier recorddefs.xml.
Ça peut être très instructif de comparer et contraster ce servlet
avec celui de l'exemple antérieur, le livret d'invités. Par exemple,
les deux exemples contiennent un méthode qui s'appelle
execEntries() qui, en fait, sont presque identiques.
La différence clé, c'est que cet exemple exploite la couche
de persistance que Niggle fournit pour obtenir la liste
d'entrées à exposer au mécanisme de pages modèles.
Dans l'exemple antérieur, tous les éléments de la liste
n'étaient que des instances de java.util.Hashtable
.
Mais ici, ce sont des instances de com.revusky.niggle.data.Record
.
C'est important de bien remarquer que les records de Niggle
peuvent être exposées au mécanisme de pages modèles de la même façon
qu'un Hashtable (ou n'importe quel objet qui implémente java.util.Map.)
Ce code profite de cette prestation aussi dans le méthode execEdit()
.
Il y a un autre aspect qui rend cet exemple plus complet que l'antérieur.
Tandis que le livret d'invités ne permettait que l'insertion,
cet exemple offre la possibilité de modifier et d'effacer les entrées.
Donc, le méthode execProcess()
est un peu plus complèxe et il existe aussi un méthode execDelete()
pour effacer.
Regardons d'abord le méthode execDelete()
vu
que c'est plus simple. Dans la première ligne, le code obtient
le paramètre unique_id=
de la pétition CGI.
Donc, il s'arrange pour obtenir le record correspondant. La ligne
suivante invoque le méthode delete()
de la DataSource,
qui prend comme paramètre la clé primaire
du record. Ceci fait, on définit la variable page, qui représente
la page modèle qu'on va montrer à l'usager. Cela correspond à
"ack.nhtml" qu'on utilise pour renseigner à l'usager
que l'opération a eu lieu. On définit la variable booléenne deleted
pour indiquer que l'opération a été celle d'effacer une entrée.
Le méthode execProcess()
qui traite
la page d'édition, c'est un peu plus complèxe. C'est dû au fait
qu'il doit distinguer entre l'insertion d'une entrée tout neuve et la
modification d'une entrée qui existait déjà. Comme vous pouvez voir,
on récupère l'entrée avec laquelle opérer par moyen du méthode getEntry()
.
Maintenant, getEntry()
rend une des deux possibilités suivantes:
une "entrée vierge" nouvelle (s'il n'y avait pas de paramètre
genre unique_id= avec lequel travailler) ou le record existant qu'il
faut modifier. On se sert du méthode isImmutable()
pour
distinguer entre ces deux cas. Ce qu'il faut comprendre, c'est qu'un record neuf
dans Niggle est créé en un état mutable.
Lorsqu'on l'introduit dans un conteneur genre DataSource, il devient
immutable. Afin de modifier ce record, ce qu'on fait
à vrai dire, c'est qu'on crée un clone mutable, on y fait les modifications,
et ensuite, on le remplace. Certains lecteurs se rendront compte que
cette disposition qui peut paraître à première vue quelque peu baroque existe
pour de bonnes raisons. Il s'agit de garantir le "thread safety",
c'est à dire l'intégrité des données dans un environnement
multitâche. Bon, on va exposer ces idées plus à fond ailleurs. En ce moment,
ce qu'il faut savoir, c'est que l'acte de modifier un record existant, c'est
fondamentalement différent de l'insertion d'un record tout neuf.
C'est pour ça que, lorsqu'il s'agit un record existant, il nous faut
invoquer getMutableCopy
avant de pouvoir remplir les champs à partir des paramètres
de la pétition CGI.
Le méthode DataUtil.fillRecordFromServletRequest()
est
une façon commode pour remplir les champs d'un record à partir des paramètres d'une
pétition CGI. Il se sert des metadonnées associées au record pour itérer à travers
ses champs est les remplir, en se basant sur les paramètres de la pétition HTTP.
Après avoir invoqué ce méthode, on se sert du méthode insert() ou bien update()
de la DataSource. Ça dépend de s'il s'agit d'une entrée neuve
ou la modification d'une entrée existante. Finalement, après avoir
executé les modifications des données, on obtient la page modèle
qu'il faut utiliser pour renseigner à l'usager que l'opération a eu lieu.
On expose l'entrée à la page qui est interpretée
comme une variable du genre correspondance.
La dernière nouveauté de cet exemple se trouve chez le méthode
recover()
. Il s'agit d'un méthode qu'on peut
redéfinir et qui nous fournit un endroit pour récupérer d'erreurs.
Par exemple, si, dans notre configuration de métadonnées,
on définit un champ ("last name" par exemple)
comme étant obligatoire, et l'usager ne le remplit pas,
la moteur de persistance sous-jacente lancera une exception
du type com.revusky.niggle.data.MissingDataException
.
Le méthode recover() nous fournit une opportunité de traiter
ces conditions "doucement" disons.
Maintenant, vous devriez jeter un coup d'oeil aux fichiers de configuration en XML. Commençons avec datasources.xml. Ceci ne devrait pas être trop difficile à lire, même si vous n'avez jamais travaillé avec XML. En ce cas-ci, on se sert du XML comme format de configuration externe legible pour les humains. Ce faisant, on maintient les détails de la couche de persistance totalement séparés de la logique du code de l'application.
Il s'agit ici de l'usage le plus minimaliste d'un tel fichier vu qu'on n'y définit qu'une DataSource, qui est assignée le nom "rolodex_data".
L'attribut CLASS indique l'implémentation concrète qu'on
utilise. Il s'agit de com.revusky.niggle.data.inmemory.InMemoryRecordSet
.
Cette classe est une implémentation de l'interface
com.revusky.niggle.data.MutableDataSource
qui maintient les données en mémoire et garde les garde dans un
fichier de texte. C'est une implémentation très utile surtout dans l'étape
de developpement. D'ailleurs, on peut s'en servir pour des projets en production
jusqu'à une échelle moyenne. Au moment donné, on pourra toujours
convertir l'application à utiliser une DATASOURCE CLASS qui est
une façade devant une base de données externe telles que l'Oracle
ou quoi que ce soit. Et cela se fait essentiellement en changeant
quelques lignes d'XML. Vu que le code java ne dépend pas de l'implémentation
derrière, il continue de fonctionner sans changements.
En tout cas, comme notre terminologie suggère,
les records d'un InMemoryRecordSet
se mantiennent en mémoire.
Tout de même, on les écrit à un fichier de texte ASCII. Les deux proprietés qu'on définit ici,
STORE et PERSIST_FREQUENCY, configurent l'endroit où se trouve
ce fichier et la fréquence avec laquelle, on l'écrit tout à nouveau.
Si vous aviez configuré une DataSource genre JDBC ici (regardez datasources2.xml)
il vous faudrait sans doute fixer quelques paramètres encore,
mais on peut laisser ces détails pour plus tard. Ce qui est important
de comprendre, c'est que les détails du mécanisme de persistance de
données sont complètement transparents tant du point de vue du code java
de l'application que la couche de présentation, c'est-à-dire les pages modèles.
Maintenant, il faut regarder l'autre partie du puzzle, les métadonnées des records. Si vous regardez le fichier recorddefs.xml, vous verrez qu'il définit exactament un type de record, qui s'appelle "rolodex_entry".
Le premier champ de notre définition de ce record, c'est le champ "unique_id", qui sert aussi de clé primaire ou primary key. Notre DataSource assignera une clé unique chaque fois qu'on inserte un record neuf dans le système.
Les autres champs qu'on définit ici sont tous des chaînes. Certains sont obligatoire, c'est-à-dire "required" et d'autres ne le sont pas. Certains des champs contiennent des normalisations qui fournissent de l'information additionnelle au système sur comment "nettoyer" les données quand on les lit de l'interface de l'usager. En tout cas, vous pourrez vous rendre compte assez facilement de ce que ça veut dire "required" ou bien "capitalized" en jouant avec le servlet.
Cet exemple présente déjà presque tous les éléments conceptuels d'une application plus complèxe basée sur Niggle. Si vous atteignez un certain niveau de confort avec tous ces éléments, vous aurez supéré les principaux obstacles dans l'apprentissage de Niggle. Bien sûr qu'il y a pas mal d'autres prestations, et d'autres apparaîtront avec le temps, vu que Niggle, c'est un projet vivant qui se développe encore. Tout de même, les choses que vous apprendrez à partir de ce moment sont de nature plutôt incrémentale.
Remarquez que le fichier datasources2.xml contient une configuration alternative qui utilise une BD externe pour la persistance. Pour faire fonctionner cela, vous serez obligé sans doute de changer certains des paramètres.
En ce moment, j'ai seulement vérifié que cet exemple fonctionne avec MySQL. Je ne vois aucun raison pour que ça ne marche pas avec une BD quelleconque, tout en supposant qu'elle dispose d'un driver JDBC. Néanmoins, ça m'intereserrait beaucoup obtenir quelque confirmation de ça. Bien evidemment! Si vous avez fait fonctionner cet exemple avec une autre BD, ou bien si vous l'avez essayé mais il y a eu des problèmes, s'il vous plaît, écrivez-moi pour conter votre expérience!
En tout cas, une bonne approche pour le moment sera de faire que votre logique d'application fonctionne avec les fichiers plats, vu que cela n'exige pas la configuration d'aucun outil externe. Ensuite, vous pourrez changer è une BD externe au moment où il sera nécessaire.