Grails erlaubt seit der Version 1 die Erstellung REST basierter Services mit Boardmitteln.
Folgender Artikel zeigt anhand eines simplen Beispiels, wie zügig die Implementierung eines Service vorgenommen werden kann und gibt dem noch nicht mit Grails vertrauten Entwickler einen kurzen Einstieg in die grundsätzliche Vorgehensweise zur Erstellung einer Applikation mit Grails.
Die Aufgabe
Es soll eine einfache Personenverwaltung realisiert werden. Ein Objekt 'Person' wird über folgende Attribute definiert:
- Vorname
- Nachname
- Geschlecht
- Homepage
- E-Mail Adresse
- Geburtstag
- Geburtsort
Die Daten aller Personen, das Anlegen, Ändern und Löschen soll zusätzlich zur Website auch über einen REST Service möglich sein.
Das Grundgerüst
Die Applikation wird mit grails create-app GrailsRestTest angelegt. Nach der erfolgreichen Erstellung des Projekts, wird im Projektordner 'GrailsRestTest' die Domain-Klasse 'Person' mit grails create-domain-class Person angelegt.
Der nackten Klasse werden die in der Aufgabe definierten Attribute hinzugefügt:
class Person {
static constraints = {
firstName(blank:false, maxSize:200)
lastName(blank:false, maxSize:200)
gender(inList:["m", "f"], maxSize:1)
homepage(url:true)
email(email:true)
}
String firstName
String lastName
Date birthday
String gender
String placeOfBirth
String homepage
String email
}
Durch den Aufruf von grails generate-all Person werden nun die Views 'list', 'edit', 'show', 'create' angelegt sowie der 'PersonController' mit den entsprechenden Actions.
Das Grundgerüst ist fertiggestellt. Nach einem grails run-app sollte die Applikation unter http://localhost:8080/GrailsRestTest/ erreichbar sein.
Wer noch nicht mit Grails vertraut ist, mag sich vielleicht über die Geschwindigkeit wundern, mit der dieses Grundgerüst fertigestellt wurde. Falls Interesse geweckt wurde und auch für das Verständnis des weiteren Artikel empfehle ich den Besuch von Grails.org und Grails (framework) - Wikipedia, the free encyclopedia.
Implementierung des REST Service
Vorbereitung
Vor der Implementierung der Services wird eine kleine Anpassung in der URLMappings.groovy im config Ordner vorgenommen:
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
// Die folgenden zwei Zeilen müssen hinzugefügt werden
"/rest/person/$id?"(controller:"person",action:"rest")
"500"(view:'/error')
}
}
Der Service soll nach dem Schema rest/controller/id angesprochen werden können. Die vollständige Adresse würde also http://localhost:8080/GrailsRestTest/rest/person/2 lauten. Üblich in Grails ist allerdings das Schema controller/action/id. Die Änderung bewirkt genau das entsprechende Verhalten für den 'PersonController'. Anfragen nach dem gewollten Schema werden an die Action 'rest' im 'PersonController' weitergeleitet. Die Unterscheidung welche Aktion vorgenommen werden soll (Ändern, Löschen, Ausgeben...) wird anhand der HTTP Operationen vorgenommen:
- GET - Ausgeben aller Personen (bei Übermittlung einer ID Ausgabe einer spezifischen Person)
- POST - Anlegen einer Person
- PUT - Ändern einer Person
- DELETE - Löschen einer Person
Der grundsätzliche Aufbau der rest Action im 'PersonController' sieht wie folgt aus:
def rest = {
if (request.method == "GET") {
// ...
} else if (request.method == "POST") {
// ...
} else if (request.method == "PUT") {
// ...
} else if (request.method == "DELETE") {
// ...
} else {
response.status = 406
}
}
Weiterhin wird dem 'PersonController' ein Import import grails.converters.* hinzugefügt. Durch den Import verfügt man über die JSON und XML Converter die die Rückgabe der Daten als JSON oder XML ermöglichen.
Abfrage von Personen

Der erste zu implentierende Service ist auch von der Realisierung der einfachste: Es sollen die Daten aller Personen abgefragt werden oder aber bei Übermittlung einer Id die spezifische Person. Über die Weboberfläche sollte vorher ein ordentlicher Bestand an Personen angelegt werden.
Im 'PersonController' wird nun die entsprechende Logik in der Bedingung request.method == "GET" implementiert:
def rest = {
if (request.method == "GET") {
if (params.id != null)
render Person.get(params.id) as JSON
else
render Person.list() as JSON
} else if (request.method == "POST") {
// ...
} else if (request.method == "PUT") {
// ...
} else if (request.method == "DELETE") {
// ...
} else {
response.status = 406
}
}
Durch die Anweisung render Person.method() as JSON kommt der erwähnte Converter ins Spiel. Unser Ergebnis wird ohne weiteres Zutun als JSON gerendert / ausgeliefert.
Test
Zum Test im Firefox wurde für diesen und alle weiteren Tests das Plugin Poster verwendet. Mit Poster hat man die Möglichkeit, seinen HTTP-Request genau nach seinen Anforderungen "zusammenzustellen". Die Abfrage von http://localhost:8080/GrailsRestTest/rest/person/ würde z.B. folgendes zurückliefern:

Bei Abfrage von http://localhost:8080/GrailsRestTest/person/2 ein Ergebnis das wie folgt aussehen könnte:

Anlegen einer Person
Das Anlegen einer Person über einen REST Service wird in der Bedingung `request.method == "POST"' realisiert:
def rest = {
if (request.method == "GET") {
// ...
} else if (request.method == "POST") {
def json = request.JSON
def person = new Person()
person.properties = json
/*
person.firstName = json.firstName
person.lastName = json.lastName
person.birthday = json.birthday
person.email = json.email
person.gender = json.gender
person.homepage = json.homepage
person.placeOfBirth = json.placeOfBirth
*/
if (person.save()) {
response.status = 201
render person as JSON
} else {
response.status = 500
render "Unable to create a person due to errors: \n ${person.errors}"
}
} else if (request.method == "PUT") {
// ...
} else if (request.method == "DELETE") {
// ...
} else {
response.status = 406
}
}
Die Logik ist so ausgelegt, dass sie nur JSON Objekte verarbeiten kann, die eine Person repräsentieren. request.JSON ist eine Instanz von Grails JSONObject, das transparent das Parsen der Anfrage in ein JSON Objekt übernimmt.
Entspricht die Benamung der jeweiligen Attribute im JSON Objekt denen der Domain Klasse, können die Attribute der Domain Klasse mit person.properties = json in einem Rutsch gesetzt werden. Die alternative Methode, jedes JSON-Attribut der Entsprechung der Domain Klasse zuzuordnen, ist im auskommentierten Teil dargestellt.
Soll nun eine Person angelegt werden, wird ein entsprechendes JSON Objekt per POST an den Service übermittelt. Im Erfolgsfall erhält man die neu generierte Person als JSON Repräsentation zurück.
Ändern einer Person
Das Ändern einer Person wird in der Bedingung request.method == "PUT" realisiert:
def rest = {
if (request.method == "GET") {
// ...
} else if (request.method == "POST") {
// ...
} else if (request.method == "PUT") {
def json = request.JSON
def person = Person.get(json.id)
person.properties = json
if (person.save()) {
response.status = 200
render person as JSON
} else {
response.status = 500
render "Unable to update a person due to errors: \n ${person.errors}"
}
} else if (request.method == "DELETE") {
// ...
} else {
response.status = 406
}
}
Auch hier gilt das gleiche Prinzip wie beim Anlegen einer Person. Repräsentiert das JSON Objekt mit seiner Benamung der Attribute denen der Domain Klasse, so können mit person.properties = json die Änderungen an der Person in einem Schritt durchgeführt werden.

Löschen einer Person
Das Löschen einer Person erfolgt mit der HTTP Operation "DELETE". Zum Löschen ist nur der entsprechende Aufruf von http://localhost:8080/GrailsRestTest/rest/person/[id] nötig. Im Body des Requests werden keine weiteren Informationen übermittelt.

def rest = {
if (request.method == "GET") {
// ...
} else if (request.method == "POST") {
// ...
} else if (request.method == "PUT") {
// ...
} else if (request.method == "DELETE") {
def personId = params.id
def person = Person.get( personId )
if(person){
person.delete()
render "Successfully deleted person with id ${personId}"
} else{
response.status = 404 //Not Found
render "${personId} not found."
}
} else {
response.status = 406
}
}
Vielen Dank für dieses Turorial - hat mir bei einem Test Projekt sehr weiter geholfen.
Gruß HS
Ich have hier ziemliche Probleme, per "PUT" die "params" zu bekommen. URLMapping ist identisch. Params und request.JSON sind NULL.
Irgendwelche Tips oder Updates?
Danke!
Hallo PUTe,
du sagst, request.JSON ist null. D.h. du kannst bestätigen, dass serverseitig eine Reaktion stattgefunden hat? Wie sieht dein Client aus? Klappt ein "DELETE"?
Gruß,
Oliver