Tutorial Grails : Réaliser une application avec un framework de haute productivité

Partie 1 - Initialisation du projet

Framework Open Source dit de haute productivité dans l'écosystème Java, Grails a derrière lui une solide communauté et fait partie des projets SpringSource. Mais, que vaut-il par rapport à Groovy ? Est-ce simplement un autre framework Java de plus ?

Au travers de la réalisation d'une application simple, it-resto, nous pourrons entrevoir les avantages et inconvénients de cet environnement.

Cet article, en deux parties, présente dans la première la mise en place du modèle et les tests associés.

Dans la seconde partie, nous verrons comment réaliser une application autour de ce modèle de données.

Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Présentation de Grails

Logo Grails
Logo Grails

Projet initié en 2005 par Graeme Rocher, il avait pour but d'apporter un équivalent de Ruby on Rails au monde Java. Le nom Grails est d'ailleurs une contraction de Groovy, langage sur lequel il est basé et de Rails, l'ensemble formant Grails (Les Graals anglais, ce qui explique le logo).

Aujourd'hui, nous en sommes à la version 2.4.4 (sortie en octobre 2014).

Mais, du coup, Grails, c'est quoi exactement ?

Il s'agit d'un framework Open Source se basant sur une architecture MVCModel-View-Controller qui fonctionne sur une JVMJava Virtual Machine. Sa philosophie tourne autour des points suivants :

  • java-like dynamic language : les scripts Groovy sont compilés en byte-code, permettant de profiter de la puissance de Java. Il s'appuie sur son écosystème en utilisant Spring, Hibernate, etc. ;
  • convention over configuration : système de configuration tendant à simplifier la vie du développeur (et limiter le code à écrire/maintenir). Par exemple, les objets du modèle auront le même nom que les tables auxquelles ils sont associés. Le mapping devient automatique ;
  • DRYDon't Repeat Yourself : éviter la redondance de code ;
  • MDAModel Driven Architecture : une portion du code est créée à partir du modèle, on obtient ainsi une partie du squelette de l'application. Il est donc conseillé de débuter par la génération du modèle ;
  • prototypage : grâce au scaffolding, il est très facile de générer un premier prototype (même incomplet) de l'application. Il devient aisé de générer des interfaces fonctionnelles directement issues du modèle de données.

II. Définition du projet

Pour les besoins pédagogiques de l'article, l'application nommée it-resto, va être développée.

Mais, dans tout projet, il faut un cahier des charges. Alors, décrivons rapidement ce que nous souhaitons.

It-resto permet aux utilisateurs de participer à un événement. Pour chaque événement, il est possible de voter pour un ou plusieurs restaurants parmi une liste précise. L'ensemble des votes permet de définir le restaurant qui accueillera l'événement.

Un cas d'utilisation simple serait, au sein d'un groupe de collègues, de permettre de sélectionner le lieu où ils choisissent de manger durant leurs pauses de midi.

L'utilisateur pourra :

  • s'authentifier ;
  • créer un événement ;
  • participer à un événement (sans limitation de droits), en votant (une seule fois) pour un ou plusieurs restaurants dans un événement.

L'administrateur pourra gérer (Create, Update, Read et Delete) les utilisateurs, les restaurants, les événements et les votes.

Les événements passés et leurs votes associés seront automatiquement supprimés toutes les nuits.

III. Premier pas avec Grails

Avant d'aller plus loin, il est conseillé d'avoir installé et configuré l'environnement Grails.

Le projet est défini… nous savons où aller. Alors, allons-y !

Pour créer le projet it-retso, il faut lancer la commande depuis un shell (ou sous Windows, une invite de commande DOS, Babun, ConEmu, etc.) :

Création du projet it-resto
Sélectionnez
1.
grails create-app it-resto

Un squelette d'arborescence du projet est créé dans le répertoire it-resto.

Arborescence de projet Grails
Arborescence de projet Grails

Le répertoire grails-app est le plus important. C'est ici que vont se trouver les principales ressources du projet :

  • conf : ensemble des fichiers de configuration pour Spring, Hibernate et le projet en cours ;
  • controllers : pas de surprise, ce sont les classes contôleurs ;
  • views : il s'agit des vues du projet, au format gsp ;
  • le répertoire lib stockera les différentes bibliothèques du projet, qu'elles soient ajoutées via maven, ivy ou manuellement ;
  • src : les sources java et/ou groovy ;
  • pour finir, test… comme son nom l'indique, on y retrouve les tests unitaires, mais aussi les tests d'intégration.

Pour tester que tout s'est bien déroulé, il est possible de lancer le projet (grails run-app) et d'accéder au projet (http://localhost:8080/it-resto/).

Pour information, Grails est nativement propulsé par Tomcat.

IV. Mise en place du modèle

L'un des principes de base de Grails est d'être orienté modèle. La première étape est donc de générer le modèle de données (pour aller plus loin : MDA).

Notre modèle possède quatre éléments distincts :

  • Restaurant : modélisation des restaurants à sélectionner par les utilisateurs dans un événement ;
  • User : utilisateur enregistré ;
  • Event : événement de base, qui englobera les votes des utilisateurs ;
  • Vote : sur chaque événement, un utilisateur pourra sélectionner un ou plusieurs restaurants s'il souhaite y participer.

Afin de simplifier la compréhension de tous, un petit schéma :

Modèle de données
Modèle de données

Pour aller plus vite, passons en mode interactif. Ce mode permet de travailler directement dans la console Grails, sans avoir à relancer le framework à chaque commande. De plus, les changements sont pris en compte dynamiquement.

Lancez Grails, avec la commande suivante :

Commande de lancement de Grails
Sélectionnez
1.
grails

Vous êtes à présent dans la console Grails. Il faut générer les quatre éléments du modèle de données (le domain).

Commande de génération du modèle
Sélectionnez
1.
2.
3.
4.
create-domain-class it.resto.restaurant
create-domain-class it.resto.user
create-domain-class it.resto.event
create-domain-class it.resto.vote

À chaque fois, deux fichiers sont créés :

  • le premier, dans grails-app/domain/it/resto/*.groovy, pour la définition du domain ;
  • le second, dans test/unit/it/resto/*Spec.groovy pour les tests unitaires ;

En fonction du besoin du projet, les différents domain doivent être complétés. Pour le domain Restaurant, on aura :

Déclaration de la classe Restaurant : Restaurant.groovy
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
class Restaurant {
    String name

    static constraints = {
        name blank: false, unique:true
    }
}

Le code Groovy a une syntaxe assez proche de Java, mais un peu plus épurée. Pour en savoir un peu plus, vous pouvez consulter cette introduction à Groovy.

L'objet it.resto.Restaurant n'est défini que par son nom (name). Ensuite, dans le bloc constraints, on peut définir toutes les contraintes sur les différents champs.

Dans notre cas, le nom du restaurant sera non null (par défaut dans les domain Grails), non vide (blank:false) et unique (unique:true).

Bien que plus complète, la déclaration d'un User est assez similaire.

Déclaration de la classe User : User.groovy
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
class User {

    String name
    String lastName
    String login
    String password
    String email
    boolean admin= false

    String toString() {
        return this.lastName + ' ' + this.name
    }

    static constraints = {
        name blank: false
        lastName blank: false
        login size: 5..15, blank: false, unique: true
        password size: 5..15, blank: false
        email email: true
    }
}

Cette fois, les contraintes sont plus importantes. On notera surtout que la taille du login et du password doivent être comprises entre 5 et 15 caractères (size: 5..15). Le champ email, quant à lui, doit répondre aux contraintes d'un email (email: true).

Maintenant, passons à la classe it.resto.Event, qui devra être liée avec plusieurs votes.

Déclaration de la classe Event : Event.groovy
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
class Event {

    String name
    Date eventDate
    User owner
    static hasMany = [votes: Vote]

    String toString() {
        return this.name;
    }

    static constraints = {
        name blank: false
        eventDate nullable: true
    }
}

En regardant l'ensemble des contraintes, on se rend compte que tous les champs sont obligatoires.

La relation de type 1 ↔ N entre l'Event et les Vote est défini avec le mot clef hasMany, qui déclare une liste de type it.resto.Vote, accessible par le champ votes.

Pour chaque Event, on garde également une trace de l'utilisateur qui crée l'Event, dans le champ owner, qui permet de faire une relation de type 1 ↔1 entre un Event et un User.

Terminons avec le Vote qui va relier un Event, un User et la liste des Restaurant que le participant aura sélectionnés.

Déclaration de la classe Vote : Vote.groovy
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
class Vote {

    User user

    static hasMany = [restaurants: Restaurant]

    static belongsTo = [event:Event]

    String toString() {
        def listRestautant
        for (restaurant in restaurants) {
            listRestautant += restaurant.name + ' - '
        }
        return this.user.login + ' : ' + listRestautant
    }

    static constraints = {
    }
}

La classe it.resto.User définie dans le Vote représente la relation lien direct de type 1↔1, ce qui signifie qu'un vote est attaché à un seul et unique User.

Les références vers les restaurants sélectionnés par l'utilisateur sont déclarées avec le mot clef hasMany. Un Vote est relié à un ou plusieurs Restaurant.

L'Event est déclaré avec belongsTo, indiquant que le Vote lui appartient. On retrouve son équivalent en hasMany dans la classe it.resto.Event.

En supprimant un Event, les Vote associés seront également supprimés.

Dans tous les cas, l'identifiant technique id est implicite.

Voilà, nous sommes prêts, GORMGrails Object Relationnal Mapping va faire le reste. Il s'agit d'une surcouche au framework hibernate qui génère automatiquement toute une série de méthodes telles que load, save, exist… mais également une partie plus « magique » ; des méthodes de recherches comme findBy* qui sont définies en fonction de la classe. Celles-ci sont utilisables directement dans la suite du projet (implémentation implicite), ce qui réduit le code de l'application et les tests à réaliser.

Par exemple, pour la classe it.resto.Restaurant, on retrouvera la findByName ou findByNameLike.

Dans le tableau ci-dessous, quelques exemples de suffixe pour le findBy.

findBy…

InList

Dans une liste de valeurs données

LessThan

Inférieur à une valeur donnée

LessThanEquals

Inférieur ou égal à une valeur donnée

GreaterThan

Supérieur à une valeur donnée

GreaterThanEquals

Supérieur ou égal à une valeur donnée

Like

Recherche d'un pattern dans une chaîne

Ilike

Similaire au Like, mais insensible à la casse

NotEqual

Différent de la valeur donnée

InRange

Se situant entre deux valeurs ordonnées

Rlike

Similaire au Like, mais avec la possibilité de réaliser des expressions régulières

Between

Se situant entre deux valeurs

IsNotNull

Contenu n'est pas null

IsNull

Contenu est null

V. Mise en place des premiers tests

En parallèle de la création des différentes classes de domain (quatre fichiers dans it-resto/grails-app/domain/it/resto), Grails a également généré des classes de test (dans it-resto/grails-app/test/unit/it/resto). Elles se présentent toutes de la même manière avec une méthode setup, qui sera exécutée avant chaque méthode de test, ainsi qu'une méthode cleanup exécutée après le test.

Il ne reste plus qu'à écrire les méthodes de test pour valider le bon fonctionnement des classes du domain.

Ces méthodes doivent être écrites en plusieurs sections, connues sous le nom de given-when-then :

  • la section given décrit l'état du système avant de débuter le scénario. Ce sont les conditions préalables ;
  • la section when est le déroulement du scénario à spécifier ;
  • la section then va décrire et tester les changements attendus suite au scénario réalisé dans la section when.

Un test sur la création et la validation du domain Restaurant ressemblera à ce qui suit. Dans ce premier test, nous allons vérifier que les contraintes sur le nom du restaurant sont bien respectées (ni null, ni blank).

Test de la classe Restaurant
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
void 'test constraint Restaurant'() {
        given:
            mockForConstraintsTests Restaurant

        when: 'the restaurant name is null'
        def restaurant = new Restaurant()
        restaurant.name = null

        then: 'validation should fail'
        !restaurant.validate()
        restaurant.hasErrors()
        print restaurant.errors['name']

        when: 'the restaurant name is blank'
        restaurant.name = ''

        then: 'validation should fail'
        !restaurant.validate()
        restaurant.hasErrors()
        print restaurant.errors['name']

    }

Dans la section given, la seule chose définie est le mockForConstraintsTests qui « mocke » la classe it.resto.Restaurant (toute la machine Grails n'est pas lancée durant les tests). Ce « mock » ajoute la méthode valide() à la classe, mais permet aussi de détecter plus simplement des erreurs avec la propriété errors. De plus, les messages sont « allégés »  et plus simples à analyser.

Par contre, il y a une limitation. Il ne faut pas oublier que le contexte d'exécution est un test unitaire et non pas d'intégration. Il n'y a pas d'ajout de méthodes au runtime par GORM par exemple (findBy*, etc).

Dans la section when, c'est la zone d'exécution du scénario de test. Ici, c'est un objet it.resto.Restaurant qui est créé, mais le nom (name) est null (explicitement déclaré).

Dans la section then, on valide l'état de sortie du scénario. La méthode valide() va déterminer si toutes les contraintes sont respectées.

Toutes les instructions de la section then doivent être vraies, le contraire indiquant une erreur du test. Pour simplifier un peu plus la détection des erreurs, la méthode hasErrors() indique si des erreurs ont été rencontrées, puis le message est disponible en fonction du champ en erreur. Dans cet exemple, ce sera restaurant.errors['name'].

Pour démarrer le test, lancez la commande test-app (ou grails test-app si vous avez quitté le mode interactif). À la fin, un rapport est disponible dans \target\test-reports\.

Il est recommandé de réaliser cette étape au fur et à mesure de la création des classes du domain.

VI. Peuplement de la base

Le squelette de l'application a été réalisé et le modèle de données est prêt (définition des objets domain ainsi que les tests unitaires sur les contraintes). Avant de réaliser les premiers écrans et pour avoir des éléments à y afficher, il faut peupler la base de données.

Cela se fait simplement, depuis le fichier grails-app/conf/BootStrap.groovy qui contient la méthode init exécutée au lancement de l'application et la méthode destroy quand l'application est arrêtée.

Il suffit de mettre en place le code suivant dans la méthode init :

Peuplement de la base
Sélectionnez
1.
2.
3.
4.
5.
if (!Restaurant.count()) {
    print 'Create'
    new Restaurant(name: 'King Croissant').save(failOnError: true)
    new Restaurant(name: 'Les trois mi-temps').save(failOnError: true)
}

Restaurant.count() dénombre les restaurants en base. Cette condition évite de créer systématiquement les mêmes éléments à chaque lancement de l'application (ce qui arrive régulièrement en phase de développement).

Pour créer un nouveau Restaurant et le stocker en base, on le fera de cette façon :

Enregistrement en base
Sélectionnez
1.
new Restaurant(name: 'King Croissant').save(failOnError: true)

La même chose doit être faite pour les autres éléments du modèle : User, Event et Vote.

Pour la base de données sous-jacente, Grails propose une connexion à la base H2 en natif, en mode stockage en mémoire et une console d'administration accessible via http://localhost:8080/it-reso/console. L'identifiant par défaut est admin et il n'y a pas de mot de passe.

Attention ! Si vous utilisez cette base pour votre application de production, n'oubliez pas de changer le mot de passe admin et de bloquer l'accès à la console, sinon cela revient à exposer tout le contenu de la base sur Internet.

La configuration de la base se fait dans le fichier grails-app/conf/DataSource.groovy, au niveau de la propriété environments.development.datasource :

Configuration de la Datasource
Sélectionnez
1.
2.
3.
4.
dataSource {
    dbCreate = "create-drop"
    url = "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
}

Initialement, la propriété dbCreate est à create-drop, mais elle peut prendre d'autres valeurs, selon le besoin :

  • create : supprime les tables, les index, etc. avant de recréer le schéma au démarrage ;
  • create-drop : semblable à create, mais en supprimant les tables à l'arrêt de l'application ;
  • update : met à jour la table en créant les tables et les index manquants, sans perdre les données existantes ;
  • validate : ne fait aucun changement dans la base de données, mais compare le schéma existant avec le schéma configuré dans l'application (objet domain) et génère un rapport.

Attention : create-drop et create sont à manier avec précaution en dehors d'un environnement de développement.

On retrouve des configurations équivalentes pour les environnements de test et de production.

VII. Conclusion

Voilà, nous venons de voir comment :

  • créer une application Grails à partir de zéro ;
  • mettre en place le modèle de données et poser des contraintes sur celui-ci ;
  • réaliser des tests de validation du modèle ;
  • peupler la base en vue des premiers développements.

Dans la suite de cet article, nous réaliserons les différents écrans de l'application et irons jusqu'au déploiement complet de l'application it-resto.

Pour aller plus loin, toutes les sources de l'article sont sur GitHub : https://github.com/masson-r/it-resto

La documentation de Grails, très complète, est disponible ici : http://grails.org/doc/2.4.4/guide/index.html

Enfin, pour ceux qui sont allergiques aux lignes de commandes, il existe le GTSGrails Tool Suite qui permet de simplifier l'approche de l'environnement Grails.

VIII. Remerciements

Je remercie l'équipe de Developpez, mais surtout :

  • Mickael Baron, qui m'a aidé tout au long de mon apprentissage des procédures du site Developpez.com ;
  • Robin56 ;
  • Franouch et f-leb, pour leurs relectures.

Je remercie plus particulièrement Daniel pour sa relecture technique attentive et Nicolas pour ses tests sur it-resto.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Rémi Masson. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.