Créer une application Web MVC avec Grails

1. Vue d'ensemble

Dans ce didacticiel, nous allons apprendre à créer une application Web simple à l'aide de Grails.

Grails (plus précisément la dernière version majeure) est un framework construit sur le projet Spring Boot et utilise le langage Apache Groovy pour développer des applications Web.

Il est inspiré du framework Rails pour Ruby et est construit autour de la philosophie de la convention sur la configuration qui permet de réduire le code standard .

2. Configuration

Tout d'abord, dirigons-nous vers la page officielle pour préparer l'environnement. Au moment de ce tutoriel, la dernière version est la 3.3.3.

En termes simples, il existe deux façons d'installer Grails: via SDKMAN ou en téléchargeant la distribution et en ajoutant des binaires à la variable d'environnement PATH.

Nous ne couvrirons pas la configuration étape par étape car elle est bien documentée dans la documentation Grails.

3. Anatomie d'une application Grails

Dans cette section, nous aurons une meilleure compréhension de la structure de l'application Grails. Comme nous l'avons mentionné précédemment, Grails préfère la convention à la configuration, c'est pourquoi l'emplacement des fichiers définit leur objectif. Voyons ce que nous avons dans le répertoire grails-app :

  • assets - un endroit où nous stockons des fichiers d'actifs statiques tels que des styles, des fichiers javascript ou des images
  • conf - contient les fichiers de configuration du projet:
    • application.yml contient des paramètres d'application Web standard tels que la source de données, les types mime et d'autres paramètres liés à Grails ou Spring
    • resources.groovy contient des définitions de haricot printanier
    • logback.groovy contient la configuration de la journalisation
  • contrôleurs - chargés de traiter les demandes et de générer des réponses ou de les déléguer aux vues. Par convention, lorsqu'un nom de fichier se termine par * Controller , le framework crée un mappage d'URL par défaut pour chaque action définie dans la classe de contrôleur
  • domaine - contient le modèle commercial de l'application Grails. Chaque classe vivant ici sera mappée aux tables de la base de données par GORM
  • i18n - utilisé pour le support de l'internationalisation
  • init - un point d'entrée de l'application
  • services - la logique métier de l'application vivra ici. Par convention, Grails créera un bean singleton Spring pour chaque service
  • taglib - l'emplacement des bibliothèques de balises personnalisées
  • vues - contient des vues et des modèles

4. Une application Web simple

Dans ce chapitre, nous allons créer une application Web simple pour gérer les étudiants. Commençons par appeler la commande CLI pour créer un squelette d'application:

grails create-app

Lorsque la structure de base du projet a été générée, passons à l'implémentation de composants d'application Web réels.

4.1. Couche de domaine

Alors que nous implémentons une application Web pour gérer les étudiants, commençons par générer une classe de domaine appelée Student :

grails create-domain-class com.baeldung.grails.Student

Et enfin, ajoutons-y les propriétés firstName et lastName :

class Student { String firstName String lastName }

Grails applique ses conventions et mettra en place un mappage objet-relationnel pour toutes les classes situées dans le répertoire grails-app / domain .

De plus, grâce au trait GormEntity, toutes les classes de domaine auront accès à toutes les opérations CRUD , que nous utiliserons dans la section suivante pour implémenter les services.

4.2. Couche de service

Notre application gérera les cas d'utilisation suivants:

  • Affichage d'une liste d'étudiants
  • Créer de nouveaux étudiants
  • Suppression d'étudiants existants

Implémentons ces cas d'utilisation. Nous allons commencer par générer une classe de service:

grails create-service com.baeldung.grails.Student

Dirigeons-nous vers le répertoire grails-app / services , trouvons notre service nouvellement créé dans le package approprié et ajoutons toutes les méthodes nécessaires:

@Transactional class StudentService { def get(id){ Student.get(id) } def list() { Student.list() } def save(student){ student.save() } def delete(id){ Student.get(id).delete() } }

Notez que les services ne prennent pas en charge les transactions par défaut . Nous pouvons activer cette fonctionnalité en ajoutant l' annotation @Transactional à la classe.

4.3. Couche de contrôleur

Afin de rendre la logique métier disponible pour l'interface utilisateur, créons un StudentController en appelant la commande suivante:

grails create-controller com.baeldung.grails.Student

Par défaut, Grails injecte les beans par noms . Cela signifie que nous pouvons facilement injecter l' instance singleton StudentService dans notre contrôleur en déclarant une variable d'instance appelée StudentsService .

Nous pouvons désormais définir des actions de lecture, de création et de suppression d'élèves.

class StudentController { def studentService def index() { respond studentService.list() } def show(Long id) { respond studentService.get(id) } def create() { respond new Student(params) } def save(Student student) { studentService.save(student) redirect action:"index", method:"GET" } def delete(Long id) { studentService.delete(id) redirect action:"index", method:"GET" } }

Par convention, l' action index () de ce contrôleur sera mappée à l'URI / student / index , l' action show () à / student / show et ainsi de suite.

4.4. Afficher le calque

Après avoir configuré nos actions de contrôleur, nous pouvons maintenant procéder à la création des vues de l'interface utilisateur. Nous allons créer trois pages Groovy Server pour répertorier, créer et supprimer des étudiants.

Par convention, Grails rendra une vue basée sur le nom et l'action du contrôleur. Par exemple, l' action index () de StudentController se résoudra en /grails-app/views/student/index.gsp

Let's start with implementing the view /grails-app/views/student/index.gsp, which will display a list of students. We'll use the tag to create an HTML table displaying all students returned from the index() action in our controller.

By convention, when we respond with a list of objects, Grails will add the “List” suffix to the model name so that we can access the list of student objects with the variable studentList:


    
  • Create

We'll now proceed to the view /grails-app/views/student/create.gsp, which allows the user to create new Students. We'll use the built-in tag, which displays a form for all properties of a given bean:

Finally, let's create the view /grails-app/views/student/show.gsp for viewing and eventually deleting students.

Among other tags, we'll take advantage of , which takes a bean as an argument and displays all its fields:


    
  • Students list

4.5. Unit Tests

Grails mainly takes advantage of Spock for testing purposes. If you are not familiar with Spock, we highly recommend reading this tutorial first.

Let's start with unit testing the index() action of our StudentController.

We'll mock the list() method from StudentService and test if index() returns the expected model:

void "Test the index action returns the correct model"() { given: controller.studentService = Mock(StudentService) { list() >> [new Student(firstName: 'John',lastName: 'Doe')] } when:"The index action is executed" controller.index() then:"The model is correct" model.studentList.size() == 1 model.studentList[0].firstName == 'John' model.studentList[0].lastName == 'Doe' }

Now, let's test the delete() action. We'll verify if delete() was invoked from StudentService and verify redirection to the index page:

void "Test the delete action with an instance"() { given: controller.studentService = Mock(StudentService) { 1 * delete(2) } when:"The domain instance is passed to the delete action" request.contentType = FORM_CONTENT_TYPE request.method = 'DELETE' controller.delete(2) then:"The user is redirected to index" response.redirectedUrl == '/student/index' }

4.6. Integration Tests

Next, let's have a look at how to create integration tests for the service layer. Mainly we'll test integration with a database configured in grails-app/conf/application.yml.

By default, Grails uses the in-memory H2 database for this purpose.

First of all, let's start with defining a helper method for creating data to populate the database:

private Long setupData() { new Student(firstName: 'John',lastName: 'Doe') .save(flush: true, failOnError: true) new Student(firstName: 'Max',lastName: 'Foo') .save(flush: true, failOnError: true) Student student = new Student(firstName: 'Alex',lastName: 'Bar') .save(flush: true, failOnError: true) student.id }

Thanks to the @Rollback annotation on our integration test class, each method will run in a separate transaction, which will be rolled back at the end of the test.

Take a look at how we implemented the integration test for our list() method:

void "test list"() { setupData() when: List studentList = studentService.list() then: studentList.size() == 3 studentList[0].lastName == 'Doe' studentList[1].lastName == 'Foo' studentList[2].lastName == 'Bar' }

Also, let's test the delete() method and validate if the total count of students is decremented by one:

void "test delete"() { Long id = setupData() expect: studentService.list().size() == 3 when: studentService.delete(id) sessionFactory.currentSession.flush() then: studentService.list().size() == 2 }

5. Running and Deploying

Running and deploying apps can be done by invoking single command via Grails CLI.

For running the app use:

grails run-app

By default, Grails will setup Tomcat on port 8080.

Let's navigate to //localhost:8080/student/index to see what our web application looks like:

If you want to deploy your application to a servlet container, use:

grails war

to create a ready-to-deploy war artifact.

6. Conclusion

Dans cet article, nous nous sommes concentrés sur la façon de créer une application Web Grails en utilisant la philosophie de la convention sur la configuration. Nous avons également vu comment effectuer des tests unitaires et d'intégration avec le framework Spock.

Comme toujours, tout le code utilisé ici peut être trouvé sur GitHub.