I. Intégrer de nouvelles préoccupations dans les applications objets 17
2.2. Notre approche pour un service de persistance
2.2.3. Illustration des mécanismes de requêtes
do session.start ; if p.age > 3 then p.set_age(p.age+1) ; end ; session.validate rescue session.abort end -- routine
Fig. 2.7.: Mise en œuvre explicite des mécanismes transactionnels.
matiquement6. Dans un univers où plusieurs applications se partagent les objets persistants, chaque application doit prendre le relais de l’exécutif pour déclencher, annuler ou valider une transaction (voir la figure 2.7) ; elles utiliseront respectivement les primitives de la classe
SESSION_MANAGER : start, abort et validate, disponibles via la primitive session de la
classe HERE.
On notera que dans le cas où l’annulation est le fait de l’application par le déclenchement d’une exception, c’est à l’application de demander l’annulation de la transaction (par exemple dans la clause rescue de la routine). Le mécanisme transactionnel a une influence sur la gestion des objets persistants ; en particulier, la synchronisation éventuelle (validation de la transac-tion) d’un objet persistant avec son image dans la mémoire volatile se fait à la terminaison de la transaction.
Lors de l’accès à un objet, le système doit en particulier pouvoir réagir aux situations suivantes : i) l’objet est occupé (utilisé) par une autre application, ii) l’objet est obsolète, c’est-à-dire qu’il a été marqué obsolète par une application disposant des droits suffisants sur l’objet7, iii) l’application n’a pas le droit d’accès à l’objet.
Dans le premier cas, l’exécutif réagira en fonction d’un indicateur géré par l’application et accessible à partir de la classe HERE. Si un objet est occupé, l’application peut demander à attendre qu’il ne soit plus utilisé ou abandonner sa requête. Dans le second cas, l’exécutif déclenchera une exception signifiant à l’application qu’elle ne peut utiliser un objet déclaré obsolète. Dans le dernier cas, l’exécutif déclenchera aussi une exception.
2.2.3. Illustration des mécanismes de requêtes
Nous proposons deux exemples afin de montrer i) l’expressivité du mécanisme, ii) l’intérêt de pouvoir voir une méthode comme un objet, c’est-à-dire considérer une méthode comme un objet de première classe et, iii) de présenter l’intégration par bibliothèque d’une partie des fonctionnalités liées à la gestion d’objets persistants.
Une requête est décrite dans une classe qui contient à la fois la fonction booléenne de requête (R-fonction) à appliquer à chaque objet de la collection persistante8 ainsi que des attributs éventuels correspondant aux paramètres de la R-fonction. Le code source de la figure 2.8 est un exemple de classe qui contient une fonction de requête (triviale) qui doit s’appliquer sur
6Une transaction est déclenchée dés qu’une routine est activée par un objet volatile sur un objet persistant, et elle se termine avec la fin de cette routine.
7Pour plus d’informations au sujet de la gestion des objets obsolètes, voir [183].
class BASIC_QUERY1
-- Query to execute on each person (R-function) feature
query_is_named(p : PERSON ) : BOOLEAN is do
Result := p.is_named(last_name,first_name) ; end -- query_is_named
-- Parameters of the R-function last_name : STRING is "Dupond" ; first_name : STRING is "Claire" end -- BASIC_QUERY1
Fig. 2.8.: Définition d’une requête.
... local context : BASIC_QUERY1 do context.create ; x := p.pcollection.select("query_is_named", one_argument(context)) ; ...
Fig. 2.9.: Utilisation d’une requête.
chaque objet de la pcollection des personnes. La mise en œuvre de la requête (figure 2.9) pourra être réalisée dans une autre classe comme par exemple la classe racine de l’application. Le premier argument de la primitive select est le nom de la R-fonction à appliquer à chaque objet de la collection persistante pour construire le résultat9. Comme ces traitements sont à la charge du serveur d’objets, celui-ci doit donc disposer à la fois de l’image O2 de la classe représentant la requête et d’un objet persistant de cette classe pour retrouver dynamiquement le nom de la R-fonction à exécuter ; cette information est appelée le contexte. Le deuxième argument est un tableau d’objets (ARRAY[ANY]) qui permet la transmission du contexte (toujours) et des paramètres de la R-fonction (éventuellement)10. Une variante permet une transmission plus directe de paramètres à la R-fonction (code source 2.10). Les primitives de sélection acceptent ces paramètres et les transmettent (code source 2.11). Tous les autres services de requête disponibles dans FLOO (cardinal, exists, all, do_all, do_if, count_if ) re-posent sur les mêmes principes d’utilisation.
Une requête emboîtée met en jeu plusieurs collections persistantes et permet ainsi de réaliser des traitements complexes. À titre d’exemple, nous proposons une requête (figure 2.12) qui permet de constituer la collection persistante des villes qui ont au moins 500000 habitants et qui ont au moins 200 habitants qui sont âgés de plus de 90 ans. Sa mise en œuvre pourra être décrite par le code source de la figure 2.13.
9La version 5 d’Eiffel permet sous certaines conditions de passer une méthode en paramètre et donc notre approche utilisant le nom de la routine pourrait être avantageusement remplacée par ce mécanisme.
10Les routines xxx _argument sont des routines utilitaires qui construisent un tableau avec xxx argument(s) puisque Eiffel2.3 (à la différence d’Eiffel3 et suivants) ne dispose pas de routines à liste d’arguments en nombre variable.
2.2. Notre approche pour un service de persistance class BASIC_QUERY2
feature
-- Query to execute on each person (R-function)
query_is_named(p : PERSON , last_name :STRING , first_name : STRING ) : BOOLEAN is do
Result := p.is_named(last_name, first_name) ; end ; -- query_is_named
end -- BASIC_QUERY2
Fig. 2.10.: Définition d’une requête paramétrée.
... local
context : BASIC_QUERY2 do
context.create ;
x := p.pcollection.select("query_is_named", three_arguments(context, "Dupond", Claire") ; ...
Fig. 2.11.: Utilisation d’une requête paramétrée.
class COMPLEX_QUERY feature
inhabitants_min : INTEGER is 200 ; town_size : INTEGER is 500000 age_max : INTEGER is 90
query_town(v : TOWN ) : BOOLEAN is do Result := type_pcollection("person") .select("person", two_arguments(current, v)) .cardinal >= inhabitants_min and v.nb_inhabitants >= town_size end ; -- query_town
query_person(p : PERSON , v : TOWN ) : BOOLEAN is do
Result := p.town.town_name.equal(v.nomville) and p.age > age_max end ; -- query_person
end -- COMPLEX_QUERY
Fig. 2.12.: Définition de requêtes emboîtées.
... local
context : COMPLEX_QUERY ; one_town : TOWN ;
towns : PCOLLECTION [TOWN ] do
towns ?= one_town.pcollection ; context.create ;
sel := towns.select("query_town", one_argument(context)) ; ...