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

Partie 2 - Réalisation du projet

Rémi Masson

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 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 cette seconde partie, nous allons réaliser une application (IHM, droits utilisateurs, gestion de plugins, etc.) autour de ce modèle de données et aller jusqu'au déploiement.

Commentez

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

- Auteur : Rémi Masson

- Parution : 23 février 2015

- Public visé :

- Licence : 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 ® 2015 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.

Sommaire

- I. Présentation de Grails

- II. Premier écran

- III. Et les taglib…

- IV. Mise en place d'un système d'authentification

- V. Suite des écrans

- VI. Un peu de clarté dans les URI

- VII. Génération d'une interface d'administration avec le scaffolding

- VIII. Et les plugins dans tout cela ?

- IX. Déploiement final

- X. Conclusion

- XI. Remerciements

I - Présentation de Grails

Logo Grails

Projet initié en 2005 par Graeme Rocher, il avait pour but d'apporter un équivalent de Ruby on Rails dans le monde Java. Le nom Grails est d'ailleurs une contraction de Groovy, langage sur lequel il est basé et Rails, l'ensemble devant former 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, Gails, c'est quoi exactement ?

Il s'agit d'un framework MVC Open Source qui fonctionne sur une JVM (Java 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 ;

- DRY : éviter la redondance de code ;

- MDA : 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 (voir le chapitre VII), il est très rapide de générer un premier prototype (même incomplet) de l'application.

II - Premier écran

Pour rappel, la définition du projet ainsi que tout le modèle de données ont été décrits dans l'article précédent. N'hésitez pas à vous y reporter avant de continuer celui-ci.

Le premier écran à générer est l'écran principal de l'application listant les événements en cours. Les écrans sont gérés à partir des contrôleurs, que Grails peut générer très rapidement avec la commande create-controler.

Création du contrôleur Event

create-controller it.resto.Event

Le fichier EventController.groovy est créé dans grails-app/controllers/it/resto (et toujours son pendant EventControllerSpec.groovy pour les tests dans test/unit/it/resto).

EventController.groovy

package it.resto

class EventController {

def index() { }
}

Le squelette de code est vide, mais il est là et fonctionnel !

En lançant (run-app) et testant (http://localhost:8080/it-resto) l'application, on constate qu'un nouveau contrôleur est accessible dans la liste proposée. Pour l'instant, il ne fait pas grand-chose.

Dans un contrôleur, chaque méthode correspond à une URI de l'application. C'est-à-dire que l'URL http://localhost:8080/it-resto/event/ va correspondre à la méthode index (créée par défaut) de l'EventController. Si une nouvelle méthode, listEvent par exemple, est créée dans ce contrôle, on y accèdera par http://localhost:8080/it-resto/event/listEvent (souvenez-vous : Convention Over Configuration).

Revenons à l'application, la première page doit renvoyer la liste des événements (normalement, deux chargés au démarrage par l'initialisation du bootstrap). Cette méthode est listEvent et définie de la façon suivante :

Méthode listEvent

def listEvent() {
render Event.listOrderByName()
}

Event.listOrderByName renvoie la liste des Event présents dans la base, grâce au GORM, qui va interpréter la méthode comme devant renvoyer la liste des Event (list) ordonnée par (OrderBy) leurs noms (Name). Cette construction est automatique (voir article précédent).

La méthode renvoie donc tous les événements disponibles ordonnés par nom (Name). Un premier test rapide (en plus du test unitaire) est de vérifier que la liste est bien renvoyée dans un navigateur :

http://localhost:8080/it-resto/event/listEvent.

Bien… mais, pas très présentable. C'est là qu'intervient le GSP (Groovy Server Page), qui est la partie view de Grails, assez proche du JSP ou de l'ASP.

Comme décrit en introduction, le framework Grails préfère la convention à la configuration, donc en créant dans grails-app/views/event/ la gsp listEvent.gsp, le lien entre vue et contrôleur est automatique. On n'utilise pas la commande generate-view, car elle doit être associée à une classe du domain.

Enfin, pour que le GSP soit totalement pris en compte, modifiez la méthode listEvent de la façon suivante :

Méthode listEvent

def listEvent() {
[events: Event.listOrderByName()]
}

Le contrôleur ne fait plus le rendu directement, mais passe par le paramètre events à la vue.

Au niveau de la vue, la liste se génère de la façon suivante :

GSP de liste des Events

<h2>Liste des événements en cours</h2>
<ul style="font-size: 100%">
<g:each var="event" in="${events}">
<li><a href="/it-resto/event/voteEvent?eventId=${event.id}">${event.name}</a>, proposé par ${event.owner.name}</li>
</g:each>
</ul>

En vérifiant, nous constatons que la page est correctement rendue dans le navigateur.

Liste d'événements

III - Et les taglib…

Maintenant que nous avons la liste des événements, il faudrait aussi pouvoir afficher la date. Mais, plutôt que de rechercher des méthodes complexes de formatage de la date, Grails propose un système de taglib, qui permet de réaliser des « views helper » directement dans une GSP.

Le fonctionnement s'avère assez proche d'un contrôleur. La taglib est créée par une simple commande :

Création d'une taglib

create-tag-lib it.resto.tag.Date

Comme précédemment, deux fichiers sont créés :

- le premier pour la taglib : grails-app/taglib/it/resto/tag/ DateTagLib.groovy ;

- le second pour les tests unitaires : test/unit/it/resto/tag/DateTagLibSpec.groovy.

Tout comme les contrôleurs, c'est le nom de la méthode qui détermine le nom du tag (encore une fois, Convention Over Configuration).

Si nous souhaitons un tag de ce style, où format indique le gabarit de la date de sortie souhaitée :

Taglib DateFormat

<g:dateFormat format="hh:mm" date="${event.eventDate}"/>

La tag-lib devra être de cette forme :

def dateFormat = { attrs, body ->
if (attrs.date != null) {
out << new java.text.SimpleDateFormat(attrs.format).format(attrs.date)
}
else {
out << ""
}
}

Le body correspond au contenu qui sera renvoyé dans la GSP.

Si l'attribut date est vide, une chaine vide est renvoyée, sinon c'est une date au format souhaité.

La prise en compte des modifications étant faite à chaud, la vue peut être modifiée et le rendu vérifié instantanément. D'ailleurs, il est possible de le tester tout de suite en changeant listEvent.gsp pour prendre en compte le nouveau tag créé :

Utilisation de la Taglib dateFormat

<li><a href="/it-resto/event/voteEvent?eventId=${event.id}">${event.name}</a>, proposé par ${event.owner.name} (le <g:dateFormat format="dd/MM/yyyy" date="${event.eventDate}"/> à <g:dateFormat format="hh:mm" date="${event.eventDate}"/>)</li>

Liste des événements avec prise en compte d'une taglib

IV - Mise en place d'un système d'authentification

La première page mise en place, il faut penser à la protéger (ainsi que les prochaines pages) en identifiant les utilisateurs à leur arrivée sur le site. Comment ? En utilisant un filtre sur les requêtes qui teste sur chacune s'il y a bien eu authentification.

La création d'un filtre se fait avec la commande create-filters. Dans notre cas, pour le filtre d'authentification, on aura :

Création d'un filtre

grails create-filters it.resto.Authenticate

Le filtre est grails-app/conf/it/resto sous le nom AuthenticateFilters.groovy et les tests unitaires sont disponibles dans test/unit/it/resto/AuthenticateFiltersSpec.groovy.

Tous les filtres doivent être définis dans un bloc filters (def filters = {…}) avec un nom et une portée. Le nom du filtre est le nom de la méthode et le scope correspondant au paramètre de la méthode.

Par exemple, dans notre cas d'authentification, nous souhaitons que tous les accès aux différents contrôleurs soient vérifiés. Le bloc filters est défini de cette façon :

Définition d'un filtre d'authentification

def filters = {
loginCheck(controller: '*', action: '*') {
before = {
if (!session.user && !actionName.equals('login')) {
redirect(controller: "Event", action: "login")
return false
}
}
}
}

Le nom du filtre est loginCheck et il s'applique à tous les contrôleurs et toutes leurs actions.

Dans cet exemple, l'interceptor est avant l'action. Le fait de retourner false bloque tous les autres filtres. La condition teste qu'il n'y a pas de champ user en session (pas encore identifié) et si l'action n'est pas login (il faut éviter de bloquer la page de login également).

Si personne n'est authentifié, le filtre redirige vers l'action login du controller Event (seul contrôleur actuellement existant).

Ecran de login

Derrière cette page de login, on ne retrouve aucune spécificité particulière. Simplement un formulaire, qui renvoie avec une action d'authentification.

Fenêtre de login

<div class="container-fluid">
<div class="row-fluid">
<div class="span12">
<div class="page-header">
<h1>
IT-RESTO <small>organisez vos repas</small>
</h1>
</div>
<h2>Autentification</h2>

<form method='post' action='login'>
<input type='text' name='login'/><br/>
<input type='password' name='password'/><br/>
<input type='submit' name='Creation'/>
</form>
</div>
</div>
</div>

Cette action d'authentification associée à la page de login vérifie la corrélation entre le login et le password (dans it.resto.EventController).

Gestion du login

def login() {
if (params.login != null) {
def user = User.findByLogin(params.login)

if (user != null) {
if (user.password == params.password) {
session.user = user.login
session.userAdmin = user.admin
session.userLastName = user.lastName
session.userName = user.name
redirect(controller: "Event", action: "listEvent")
}
}
}
[login: false, message: 'Login ou mot de passe incorrect']
}

User.fingByLogin renvoie un User en fonction de son login. Toujours et encore GORM qui fait son travail… automatiquement (voir article précédent).

Si le résultat est bon, l'identification a bien lieu et on est renvoyé vers la liste des événements.

Dans l'exemple de filtre étudié ci-dessus, on a utilisé la possibilité du filtre de lancer son code avant l'action. Il est à noter qu'il existe trois moments où il peut s'exécuter :

- before  : s'exécute avant l'action. S'il retourne false, cela indique que les filtres suivants et l'action ne seront pas traités ;

- after : s'exécute après l'action. Il a la possibilité de modifier le rendu de la vue ;

- afterView : s'exécute après le rendu de la vue.

Pour quitter l'application, ou simplement offrir la possibilité de changer d'utilisateur, une action de logout peut être implémentée en invalidant la session, puis en redirigeant vers la page de login (ou toutes autres pages, mais le filtre fera revenir à la page de login)

Logout

def logout() {
session.invalidate()
redirect(controller: "Event", action: "login")
}

V - Suite des écrans

Il faut maintenant créer encore deux pages. La première pour la création d'un nouvel événement.

Création d'un événement

Ici, aucune difficulté, une vue est un simple formulaire proposant deux champs, d'un côté et de l'autre, une méthode associée dans le contrôleur it.resto.EventController qui se nommera createEvent.

Création d'un Event

def createEvent() {
if (params.name != null) {
try {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm", Locale.FRENCH)
def event = new Event(name: params.name, eventDate:df.parse(params.dateEvent), owner:User.findByLogin(session.user)).save()
redirect(controller: "Event", action: "listEvent")
} catch (java.text.ParseException pexp) {
// si la date est mal saisie
[create: false]
}
}
else {
[create: false]
}
}

La vérification de l'existence du paramètre name passé en request, permet de tester que la formule a été envoyée. Puis, l'événement est créé (avec nom, date et utilisateur en session) en une fois : new et save.

Création d'un événement en base

new Event(name: params.name, eventDate:df.parse(params.dateEvent), owner:User.findByLogin(session.user)).save()

Pour finir, le contrôleur se redirige vers la méthode listEvent du contrôleur EventContoller avec la commande redirect(controller: "Event", action: "listEvent")

Dans ce cas, à la fin de l'exécution de l'action createEvent, on passera tout de suite dans l'action listEvent de l'EventController pour une exécution « classique » et c'est la vue de cette action qui sera appelée. On arrivera donc directement sur la liste des événements, avec le nouveau créé dans la liste.

La seconde page présente en détail l'événement sélectionné et permet de voter au sein de celui-ci.

Pour cette page, il y a un peu plus de besoins fonctionnels :

- afficher les votes existants ;

- donner la possibilité de voter si ce n'est pas déjà fait par l'utilisateur.

Tous ces traitements (création des votes et renvoi de la liste des votes pour un événement spécifique) sont réalisés par un service it.resto.EventService.

On crée le service pour deux raisons :

- éviter de mettre trop de code « métier » dans les controllers qui ne doivent gérer que les entrées et sorties vers les vues ;

- intérêt pédagogique : même si le service n'est pas très évolué, il permet de voir comment en créer un et l'utiliser.

Pour créer un service, on reste toujours dans la même philosophie pour Grails :

Création d'un service

grails create-service it.resto.Event

Cette classe va contenir deux services. Un premier pour créer des votes, prenant en compte la liste des restaurants sélectionnés.

Création d'un nouveau Vote

def createNewVote(Event event, List restaurants, User user) {
def vote = new Vote(user:user, event:event, restaurants:restaurants)
event.addToVotes(vote)
vote.save(failOnError: true)
event.save(failOnError: true)
}

Un vote est constitué d'un utilisateur, d'un événement et d'une liste de restaurants sélectionnés.

Dès la création du vote, il est possible de passer en paramètre l'ensemble des éléments qui le composent (User, Event et liste de restaurants).

Pour terminer, le vote doit être ajouté à l'événement Event avant que tout soit sauvegardé.

Le second service est l'équivalent en lecture. Il retourne une description des votes d'un événement, sous forme d'une liste de map, contenant les utilisateurs et les résultats de leurs votes.

Renvoie la liste des Votes pour un Event

List getVotesDesc(Event event) {
def restaurants = Restaurant.listOrderByName()
def votes = event.getVotes()

def resultMap = []
for (vote in votes) {
def restoList = []
for (restaurant in restaurants) {
restoList.add(vote.getRestaurants().contains(restaurant))
}
def tmp = [
user:vote.getUser(),
listVotesByRestaurants:restoList
]
resultMap.add(tmp)
}
return resultMap
}

La méthode renvoie une liste de Map contenant l'utilisateur ayant réalisé le vote, ainsi qu'une liste de boolean indiquant si les restaurants ont été sélectionnés ou non.

En groovy, une liste se déclare comme ceci : def restoList = []. Une map, quant à elle, peut être définie de cette façon :

Définition d'une Map

def myMap = [
user:vote.getUser(),
listVotesByRestaurants:restoList
]

L'injection du service dans le contrôleur se fait par une simple déclaration : def eventService (encore une fois, convention plutôt que configuration !).

La structure est renvoyée dans la GSP via le contrôleur. Dans la GSP, cette structure est traitée de la façon suivante :

Affichage des votes dans la GSP

<g:each var="vote" in="${votes}">
<tr><td>${vote.user.lastName} ${vote.user.name}</td>
<g:each var="restaurantSelected" in="${vote.listVotesByRestaurants}">
<td><img src="/it-resto/images/${restaurantSelected}.png" width="20"></td>
</g:each>
</tr>
</g:each>

Et voilà, j'ai le détail de mon événement avec la possibilité de voter.

Liste des votes

VI - Un peu de clarté dans les URI

Les URI du site sont dépendantes du nom des contrôleurs. L'action listEvent du contrôleur EventController est accessible par http://localhost:8080/it-resto/event/listEvent.

Simple pour le développeur, mais un peu moins lisible pour les utilisateurs. Le fichier grails-app/conf/ UrlMappings.groovy permet de gérer facilement la cartographie des URI.

Ici, nous ne souhaitons rien de trop compliqué. Il faut juste changer la page d'accueil de l'application en modifiant la règle du / par celle-ci :

Règle de rewrite d'URI

"/"(controller: "Event", action: "listEvent")

En arrivant dans l'application, on voit directement le rendu de la méthode listEvent d'EventController.groovy, et non plus la liste des contrôleurs disponibles.

VII - Génération d'une interface d'administration avec le scaffolding

Toute l'application est maintenant prête à être utilisée, mais il n'existe, pour l'instant aucun moyen simple de l'administrer (créer/supprimer un utilisateur, ajouter des restaurants ou toutes autres tâches qui seront de la responsabilité de l'administrateur de l'application).

Le scaffolding est une technique supportée par certains langages et frameworks MVC. À partir du modèle de données défini pour une application, il est possible de générer le code et les écrans CRUD associés.

Afin de faire cela rapidement, Grails propose un mécanisme de scaffolding, qui génère les interfaces nécessaires pour intervenir sur les différents objets du domaine.

Il faut créer un contrôleur pour chaque domaine.

Génération des contrôleurs manquants

grails create-controller it.resto.Vote
grails create-controller it.resto.User
grails create-controller it.resto.Restaurant

Les trois nouveaux contrôleurs sont créés ainsi que les classes de tests unitaires associés. Dans chacun des quatre Controller existants, il faut ajouter la ligne suivante :

Mofidication de configuration

static scaffold = true

Voilà, l'interface d'administration est prête !

Si vous trouvez que les libellés ne sont pas très lisibles. N'hésitez pas à ajouter une méthode toString dans chaque définition des objets du domaine.

Exemple pour it.resto.User

Méthode toString

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

Petit inconvénient de la méthode, l'interface administrateur est accessible à tous les utilisateurs du service pour l'instant.

Définissons les utilisateurs ayant les capacités d'un administrateur, en ajoutant une propriété admin dans l'objet domain it.resto.User

boolean admin= false

Pour éviter tout problème, la valeur est initialisée à false (utilisateur sans droit administrateur).

Seconde étape, générez les GSP associées aux interfaces du scaffolding afin de les spécialiser en limitant leurs accès (de la même façon, sur d'autres projets, il est possible de modifier l'aspect graphique).

Génération des vues

create-view it.resto.Vote
create-view it.resto.Event
create-view it.resto.User
create-view it.resto.Restaurant

Pour un Event, on retrouve les fichiers générés dans grails-app/views/event. On y trouve :

- _form.gsp : la forme qui sera utilisée pour la création et la modification d'un élément ;

- create.gsp : page de génération d'un nouvel élément ;

- edit.gsp : modification d'un élément ;

- index.gsp : liste des éléments présents en base ;

- show.gsp : détails d'un élément ;

Il faut donc mettre en place une nouvelle taglib : it.resto.tag. AdminTagLib qui va se charger de filtrer en fonction des droits utilisateurs (s'agit-il d'un administrateur ou non ?).

Encore une fois, il faut appeler les commandes grails de création d'une taglib.

Filtre d'administration

static defaultEncodeAs = 'raw'

def isAdmin = { attrs, body ->
if (session.userAdmin) {
out << body()
}
else {
if (attrs.errorMsg != null) {
out << attrs.errorMsg
}
}
}

Ici, si l'utilisateur identifié dans l'application est administrateur, alors le contenu du tag est affiché, sinon, c'est le message d'erreur présent dans la taglib qui est renvoyé.

La valeur de userAdmin en session est à ajouter dans l'action d'authentification.

L'encodage (defaultEncodeAs) est défini à raw, afin d'éviter que les messages renvoyés soient transformés en HTML.

Si defaultEncodeAs est défini à raw, la valeur renvoyée est utilsée comme telle. Si on met html (valeur par défaut), la valeur est encodée en HTML.

La taglib est ajoutée dans chaque page générée (sauf _form.gsp), de la façon suivante :

Filtre d'autorisation d'accès à l'espace adminstation

<body>
<g:isAdmin errorMsg="Non autorisé">
(...)
</g:idAdmin>
</body>

Cette fois, nous y sommes ! Il ne manque qu'une page listant les quatre pages d'index de chaque domaine. Une page admin.gsp avec les autres vues fait l'affaire. Pendant que nous y sommes, ajoutons également un lien dans les différentes pages de l'application, toujours en utilisant le même tag-lib, mais cette fois, sans message d'erreur.

Vérification dans les pages existantes du mode d'utilisation (adminClaude Leloup 2015-01-09T20:07:37.85admib ? ou non)

<g:isAdmin>
<a href="admin" class="btn btn-info btn-xs" type="button">>> Administration</a>
</g:isAdmin>

Pour tester, relancer l'application : run-app

VIII - Et les plugins dans tout cela ?

Nous avons vu toutes les possibilités de Grails en termes de génération de classe, de test, de génération de méthode au runtime (via GORM). Mais, une des grandes forces du framework est le nombre de plugins disponibles (http://grails.org/plugins/) pour réaliser ses applications (actuellement, plus d'un millier) mais aussi la facilité d'utilisation dans un projet.

Pour illustrer cela, nous allons ajouter la dernière fonctionnalité manquante dans l'application : permettre de supprimer les it.resto.Event déjà réalisés, c'est-à-dire ceux dont la date (eventDate) est dépassée dans le temps.

Pour cela, on utilise le plugin Quartz 2.X Scheduler (http://grails.org/plugin/quartz2).

L'ajout de plugin se fait simplement en ajoutant sa référence dans le fichier BuildConfig.groovy (grails-app/conf/) dans la propriété grails.project.dependency.resolution.plugins.

Extrait du BuildConfig.groovy

plugins {
// plugins for the build system only
build ":tomcat:7.0.42"

// plugins for the compile step
compile ":scaffolding:2.0.1"
compile ':cache:1.1.1'
compile ":quartz2:2.1.6.2"

// plugins needed at runtime but not for compilation
runtime ":hibernate:3.6.10.2" // or ":hibernate4:4.1.11.2"
runtime ":database-migration:1.3.5"
runtime ":jquery:1.10.2"
runtime ":resources:1.2.1"
// Uncomment these (or add new ones) to enable additional resources capabilities
//runtime ":zipped-resources:1.0.1"
//runtime ":cached-resources:1.1"
//runtime ":yui-minify-resources:0.1.5"
}

Avant que le plugin soit pris en compte, il faut recompiler le projet : grails compile.

Le plugin est automatiquement récupéré et le framework Grails le prend en compte.

Son implémentation sera la suivante :

Job de purge des événements passés

class CleanJob {
static triggers = {
cron name:'cronTrigger', startDelay:10000, cronExpression: '0 0 6 * * ?' // exécution, tous les jours à 6 h
}

def execute() {
def listPastEvent = Event.findAllByEventDateLessThan(new Date())
for (event in listPastEvent) {
event.delete()
}

}
}

Le triggers permet de définir la période et la récurrence de l'exécution du batch. Celui-ci permet de rechercher les événements antérieurs à la date présente et de les supprimer. Les méthodes findAllByEventDateLessThan et delete sont encore une fois automatiquement générées.

IX - Déploiement final

Le lancement de l'application a toujours été effectué avec la commande run-app, mais au moment de passer en production, il n'est pas recommandé d'utiliser cette méthode (uniquement pour le développement !).

En production, la documentation Grails insiste fortement pour la génération d'un war et le déploiement dans un conteneur de servlet compatible Servlet 2.5 (tel que Eclipse Virgo, GlassFish, IBM WebSphere, Jboss, Jetty, Oracle Weblogic, Resin, SpringSource tc Server ou Tomcat). La liste est assez importante pour répondre à beaucoup de cas de configuration. Une documentation d'installation est également disponible sur le wiki Grails.

La génération peut se faire en fonction de l'environnement d'exécution (pour rappel, voir la configuration des Data Sources qui inclut déjà les environnements de recettes et de production).

Par exemple, pour la production, cela donnera :

Génération du livrable de production

grails prod war

X - Conclusion

Grails est un framework souple et facile d'utilisation dans un cadre d'application Web sur une JVM. Tout au long de cet article, différents aspects du framework ont été abordés. De l'installation, à la création des différents éléments d'un projet pour finir par une mise en production potentielle. Mais, il ne s'agit que d'un aperçu et beaucoup de détails n'ont pas pu être couverts.

Pour aller plus loin, la documentation Grails est très complète également (http://grails.org/doc/2.4,4/guide/index.html).

Les sources relatives à cet article sont sur GitHub : https://github.com/masson-r/it-resto

XI - Remerciements

Je remercie l'équipe de Développez, mais surtout :

- Mickael Baron, qui m'a aidé tout au long de mon apprentissage des procédures du site Developpez.com ;

- Robin56.

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