Erstellen Sie eine MVC-Webanwendung mit Grails

1. Übersicht

In diesem Tutorial erfahren Sie, wie Sie mit Grails eine einfache Webanwendung erstellen.

Grails (genauer gesagt die neueste Hauptversion) ist ein Framework, das auf dem Spring Boot-Projekt aufbaut und die Apache Groovy-Sprache zum Entwickeln von Web-Apps verwendet.

Es ist vom Rails Framework für Ruby inspiriert und basiert auf der Philosophie der Konvention über Konfiguration, die es ermöglicht, den Code für Boilerplates zu reduzieren .

2. Setup

Gehen wir zunächst zur offiziellen Seite, um die Umgebung vorzubereiten. Zum Zeitpunkt dieses Tutorials ist die neueste Version 3.3.3.

Einfach ausgedrückt gibt es zwei Möglichkeiten, Grails zu installieren: über SDKMAN oder durch Herunterladen der Distribution und Hinzufügen von Binärdateien zur Umgebungsvariablen PATH.

Wir werden das Setup nicht Schritt für Schritt behandeln, da es in den Grails-Dokumenten gut dokumentiert ist.

3. Anatomie einer Grails App

In diesem Abschnitt erhalten wir ein besseres Verständnis der Anwendungsstruktur von Grails. Wie bereits erwähnt, bevorzugt Grails die Konvention gegenüber der Konfiguration, daher definiert der Speicherort der Dateien ihren Zweck. Mal sehen, was wir im grails-app- Verzeichnis haben:

  • Assets - ein Ort, an dem statische Assets-Dateien wie Stile, Javascript-Dateien oder Bilder gespeichert werden
  • conf - enthält Projektkonfigurationsdateien:
    • application.yml enthält Standardeinstellungen für Webanwendungen wie Datenquelle, MIME-Typen und andere Einstellungen für Grails oder Spring
    • resources.groovy enthält Spring Bean-Definitionen
    • logback.groovy enthält die Protokollierungskonfiguration
  • Controller - verantwortlich für die Bearbeitung von Anforderungen und das Generieren von Antworten oder das Delegieren dieser an die Ansichten. Wenn ein Dateiname mit * Controller endet , erstellt das Framework standardmäßig eine Standard-URL-Zuordnung für jede in der Controller-Klasse definierte Aktion
  • domain - enthält das Geschäftsmodell der Grails-Anwendung. Jede hier lebende Klasse wird von GORM auf Datenbanktabellen abgebildet
  • i18n - wird zur Unterstützung der Internationalisierung verwendet
  • init - ein Einstiegspunkt der Anwendung
  • Services - hier wird die Geschäftslogik der Anwendung leben. Gemäß der Konvention erstellt Grails für jeden Dienst eine Spring-Singleton-Bean
  • taglib - der Ort für benutzerdefinierte Tag-Bibliotheken
  • Ansichten - enthält Ansichten und Vorlagen

4. Eine einfache Webanwendung

In diesem Kapitel erstellen wir eine einfache Web-App zum Verwalten von Schülern. Beginnen wir mit dem Aufruf des CLI-Befehls zum Erstellen eines Anwendungsskeletts:

grails create-app

Wenn die Grundstruktur des Projekts erstellt wurde, können Sie mit der Implementierung der eigentlichen Web-App-Komponenten fortfahren.

4.1. Domänenschicht

Während wir eine Webanwendung für den Umgang mit Studenten implementieren, beginnen wir mit der Generierung einer Domänenklasse namens Student :

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

Zum Schluss fügen wir die Eigenschaften firstName und lastName hinzu :

class Student { String firstName String lastName }

Grails wendet seine Konventionen an und richtet eine objektrelationale Zuordnung für alle Klassen im Verzeichnis grails-app / domain ein .

Darüber hinaus haben dank des GormEntity-Merkmals alle Domänenklassen Zugriff auf alle CRUD-Operationen , die wir im nächsten Abschnitt zum Implementieren von Diensten verwenden werden.

4.2. Serviceschicht

Unsere Anwendung behandelt die folgenden Anwendungsfälle:

  • Anzeigen einer Liste von Schülern
  • Neue Schüler erstellen
  • Bestehende Schüler entfernen

Lassen Sie uns diese Anwendungsfälle implementieren. Wir beginnen mit der Generierung einer Serviceklasse:

grails create-service com.baeldung.grails.Student

Gehen wir zum Verzeichnis grails-app / services , suchen unseren neu erstellten Service im entsprechenden Paket und fügen alle erforderlichen Methoden hinzu:

@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() } }

Beachten Sie, dass Services standardmäßig keine Transaktionen unterstützen . Wir können diese Funktion aktivieren, indem wir der Klasse die Annotation @Transactional hinzufügen .

4.3. Controller-Schicht

Um die Geschäftslogik für die Benutzeroberfläche verfügbar zu machen, erstellen wir einen StudentController, indem Sie den folgenden Befehl aufrufen:

grails create-controller com.baeldung.grails.Student

Standardmäßig injiziert Grails Bohnen nach Namen . Dies bedeutet, dass wir die StudentService- Singleton-Instanz einfach in unseren Controller einfügen können, indem wir eine Instanzvariable mit dem Namen " StudentsService" deklarieren .

Wir können jetzt Aktionen zum Lesen, Erstellen und Löschen von Schülern definieren.

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" } }

By convention, the index() action from this controller will be mapped to the URI /student/index, the show() action to /student/show and so on.

4.4. View Layer

Having set up our controller actions, we can now proceed to create the UI views. We will create three Groovy Server Pages for listing, creating and removing Students.

By convention, Grails will render a view based on controller name and action. For example,the index() action from StudentController will resolve to /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

In diesem Artikel haben wir uns darauf konzentriert, wie eine Grails-Webanwendung unter Verwendung der Philosophie der Konvention über Konfiguration erstellt wird. Wir haben auch gesehen, wie Unit- und Integrationstests mit dem Spock-Framework durchgeführt werden.

Wie immer ist der gesamte hier verwendete Code auf GitHub zu finden.