Implémenter automatiquement un service de données avec GORM
Avec sa nouvelle version 6.1, la librairie d'accès aux données GORM pour Hibernate, utilisée par le framework web Grails (mais pouvant également s'en affranchir), propose l'implémentation automatique de services de données (data services).
En quoi cela diffère t-il des opérations CRUD qui sont injectés dans les objets métiers pris en charge par GORM, et des différentes manières d'effectuer des requêtes à partir des types de ces objets ?
Les services de données GORM vont plus loin : ils ont la capacité d'implémenter automatiquement pour vous des interfaces et des classes abstraites, pour faciliter l'implémentation de la logique de persistance et de requêtage des objets métiers.
Les aspects intéressants de cette approche sont :
- le typage fort ;
- la performance, car les services générés sont produits au moment de la compilation (transformations AST) ;
- la gestion transactionnelle automatique.
Examinons ensemble un exemple d'application Grails 3.3 classique, odelia-gina-dataservices, partagé sur Bitbucket, et définissant un service de données appelé NoteService
portant sur l'objet métier Note
, destiné à conserver des notes.
On se limitera dans cet article à la description de quelques méthodes de service.
La classe métier Note
La classe métier Note
y est déclarée ainsi :
1 2 3 4 5 6 7 8 |
|
La classe Note
comprend les propriétés dateCreated
et text
, dateCreated
faisant l'objet d'une prise en charge particulière par GORM (qui renseignera automatiquement sa valeur avec la date d'enregistrement de l'objet en base de données).
Dans l'application d'exemple, on crée d'ailleurs plusieurs objets Note
en base de données lors du démarrage, dans la closure init
de la classe BootStrap
:
1 2 3 4 5 |
|
Définition du service de données NoteService
Apparaissant dans la couche services de l'application Grails, le service de données est simplement une interface, NoteService
, annotée avec @grails.gorm.services.Service
permettant de définir sur quelle entité métier le service de données porte :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Grâce à l'annotation @Service
, et à un ensemble de convention sur les noms des méthodes de l'interface, GORM pourvoit automatiquement à leurs implémentations lors de la compilation.
Comme l'on peut s'en douter, compte-tenu de son nom et de sa signature, la méthode de service listNotes
permet de renvoyer une liste d'entités Note
enregistrées en base de données. Le paramètre args
de type Map
permettra de passer sous la forme d'une Map Java, un ensemble de paramètres auxquels les développeurs Grails sont habitués pour effectuer de la pagination, ou demander un tri particulier par exemple.
Voyons maintenant comment invoquer le service de données depuis le contrôleur NoteController
défini dans l'application d'exemple.
Invoquer le service de données NoteService
Une référence au service de données s'obtient facilement par le mécanisme d'injection de Spring, avec l'annotation @Autowired
.
C'est ce que l'on retrouve dans le contrôleur NoteController
du projet d'exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Vous pouvez constater que l'action du contrôleur list()
appelle la méthode listNotes()
du service de données, dont on obtient une référence dans la propriété noteService
du contrôleur par injection.
La méthode NoteService.listNotes
retourne une liste d'entités Note
qui est rendue par l'action sous la forme d'une liste d'objets JSON.
Obtenir la liste des notes
A l'exécution, en mode développement, en naviguant vers l'URL http://localhost:8080/note/list, vous obtiendriez quelque chose comme :
[{"id":1,"dateCreated":"2017-08-23T11:31:56Z","text":"Hello!"},{"id":2,"dateCreated":"2017-08-23T11:31:56Z","text":"Salut !"},{"id":3,"dateCreated":"2017-08-23T11:31:56Z","text":"Hola !"},{"id":4,"dateCreated":"2017-08-23T11:31:56Z","text":"Ciao !"}]
Remarquez que dans l'appel à la méthode listNotes()
du service NoteService
, on transmet une Map contenant une entrée avec la clé max
fixant le nombre maximal d'éléments à retourner.
Note : la valeur associée à cette clé peut être influencée par la présence d'un paramètre d'URL max
déclenchant l'action du contrôleur (par exemple, si l'on souhaite limiter le nombre d'éléments à afficher à 2, on utilisera l'URL http://localhost:8080/note/list?max=2).
Notez également la présence de l'annotation @GrailsCompileStatic
sur la classe du contrôleur : celle-ci n'est bien sûr pas obligatoire, mais permettra de détecter dès la compilation des erreurs de saisie dans les noms des méthodes du service de données.
Récupérer une note
En ayant défini la méthode Note getNote(Serializable id)
dans le service NoteService
, GORM génère également le code nécessaire à la récupération d'une note particulière à partir de son identifiant.
Voici un exemple trivial d'action de notre contrôleur permettant de renvoyer une note particulière au format JSON, au moyen d'un appel à la méthode getNote()
du service de données :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Ainsi, utilisez par exemple l'URL http://localhost:8080/note/get/1 pour obtenir l'entité Note
d'id 1
au format JSON. L'id est automatiquement extrait de l'URL et se retrouve dans le paramètre id
de l'action get()
; il est ensuite utilisé comme paramètre dans l'appel de la méthode de service getNote()
, qui retourne alors une entité Note
à partir des données en base.
Utiliser un finder dynamique
Toujours en se basant sur un nom de méthode de service suivant une certaine convention, GORM permet l'implémentation automatique de requêtes appelées dynamic finders. Cela est en lien avec les mêmes types de méthode que l'on peut invoquer en tant que méthodes static
sur les types des objets métiers.
Voyons commet obtenir la liste des notes contenant un certain modèle de phrase, tout en permettant de la pagination, et plus. Cela correspond à la méthode de service suivante :
1
|
|
La nom de la méthode commençant par findAllBy
, GORM sait qu'il a affaire à un finder dynamique qui portera sur la propriété text
(car Text
suit findAllBy
) de la classe Note
, et sur laquelle il devra appliquer l'opérateur Like
avec le premier paramètre passé à la méthode (le second paramètre étant réservé à des paramètres supplémentaires pour le support de la pagination par exemple).
Pour tester cette méthode, comme dans l'application d'exemple, utilisez une action de contrôleur ressemblant à ceci :
1 2 3 4 |
|
Ainsi, soumette une requête HTTP à l'application, avec cette URL http://localhost:8080/note/search?pattern=H%25, permettra d'obtenir la liste des notes commençant par H
, au format JSON.
Utiliser une requête Where
GORM permet déjà d'exprimer des requêtes plus complexes grâce à la méthode static
where
injectée sur les types des objets métiers. On retrouve la même approche avec les services de données, avec l'application de l'annotation @grails.gorm.services.Where
sur une méthode de service.
1 2 |
|
Ici, on souhaite obtenir la liste des notes contenant un certain modèle de phrase et qui ont été créées après une certaine date.
Dans l'annotation, la requête est exprimée dans une closure et en utilisant des opérateurs du langage Groovy ; on y retrouve les propriétés text
et dateCreated
de la classe métier Note
, ainsi que les paramètres pattern
et fromDate
passés à la méthode du service.
Dans le projet d'exemple, on a défini une action, searchFrom
, permettant d'invoquer la méthode searchNotes()
du service :
1 2 3 4 5 6 |
|
Plus loin
Au travers de ces quelques exemples, nous avons à peine gratté la surface du sujet des services de données, et il heureux de constater les améliorations constantes apportées à Grails et à GORM !
N'hésitez pas à vous reporter à la documentation, et à utiliser le projet odelia-gina-dataservices pour réaliser vos propres expérimentations.