Rest with grails 3

46
@jennstrater #Devoxx #restwithgrails3 Creating RESTful Web Services with Grails 3 Jennifer Strater Object Partners, Inc.

Transcript of Rest with grails 3

@jennstrater#Devoxx #restwithgrails3

Creating RESTful Web Services with Grails 3

Jennifer Strater Object Partners, Inc.

@jennstrater#Devoxx #restwithgrails3

About Me

• Senior Consultant at Object Partners, Inc.

• Co-Founder of Gr8Ladies

@jennstrater#Devoxx #restwithgrails3

Agenda

• Getting Started

• Resource Annotation

• RESTful Controllers

• Key Features

• Supporting Multiple Media Types

• Custom Response Formats

• Versioning

@jennstrater#Devoxx #restwithgrails3

New in Grails 3

• Based on Spring Boot

• Switched to Gradle for build system

• Major structural changes

• configuration

• tests

• scripts directory

@jennstrater#Devoxx #restwithgrails3

Web API profile

• Introduced in Grails 3.0.5

• Removes Unnecessary Features

• no GSPs

• New Features

• Grails Command line support

• domain classes with resource annotation

• RESTful controllers

• json/gson views* (Grails 3.1+)

@jennstrater#Devoxx #restwithgrails3

Getting Started

• gvm use grails 3.0.9

• grails create-app gr8data --profile=web-api

@jennstrater#Devoxx #restwithgrails3

@jennstrater#Devoxx #restwithgrails3

• grails>run-app

@jennstrater#Devoxx #restwithgrails3

@jennstrater#Devoxx #restwithgrails3

The Task - Gr8Data

• Capture and display gender ratios at companies using Groovy and related technology around the world

• Currently available at: http://jlstrater.github.io/gr8ladies-d3/

• Data stored in a json file with contributions via github pull requests

• Demo for this talk: http://github.com/jlstrater/gr8data

@jennstrater#Devoxx #restwithgrails3

Data Model

• Company

• name

• Country

• name

• abbreviation

• continent

• various stats

@jennstrater#Devoxx #restwithgrails3

First Endpointgrails> create-domain-resource gr8data.Country | Created grails-app/domain/gr8data/Country.groovy | Created src/test/groovy/gr8data/CountrySpec.groovy

@jennstrater#Devoxx #restwithgrails3

package gr8data import grails.rest.* @Resource(readOnly = false, formats = ["json", "xml"])class Country {}

Country.groovy

@jennstrater#Devoxx #restwithgrails3

package gr8data import grails.rest.* @Resource(+ uri="/countries", readOnly = false, formats = ["json", "xml"])class Country {+ String name + String abbreviation + String continent}

@jennstrater#Devoxx #restwithgrails3

GET

• curl http://localhost:8080/countries

• []

@jennstrater#Devoxx #restwithgrails3

POST (with errors)curl -H "Content-Type: application/json" -X POST \—data "{"name": "Belgium"}" http://localhost:8080/countries

Status: 422 Unprocessable Entity

@jennstrater#Devoxx #restwithgrails3

POST (successful)curl -H "Content-Type: application/json" -X POST -d "{"name": "Belgium", "abbreviation": "BE", "continent": "Europe"}" http://localhost:8080/countries

Status: 201 Created

@jennstrater#Devoxx #restwithgrails3

PUTcurl -H "Content-Type: application/json" -X PUT -d "{"name": "United States of America", "abbreviation": "USA", "continent": "North America"}"

http://localhost:8080/countries

@jennstrater#Devoxx #restwithgrails3

PUTcurl -H "Content-Type: application/json" -X PUT -d "{"name": "United States of America", "abbreviation": "USA", "continent": "North America"}"

http://localhost:8080/countries/2

status: 200 OK

@jennstrater#Devoxx #restwithgrails3

PATCHcurl -H "Content-Type: application/json" -X PATCH -d "{"abbreviation": "US"}"

http://localhost:8080/countries/2

status: 200 OK

@jennstrater#Devoxx #restwithgrails3

GETcurl http://localhost:8080/countries

@jennstrater#Devoxx #restwithgrails3

Resource Annotation Recap

• Automatic Mappings

• No controllers created

• URLMappings.groovy not updated

• HTTP Status Codes

• HTTP Methods

@jennstrater#Devoxx #restwithgrails3

RESTful ControllersNew Domain Class

grails> create-domain-class gr8data.Company | Created grails-app/domain/gr8data/Company.groovy | Created src/test/groovy/gr8data/CompanySpec.groovy

@jennstrater#Devoxx #restwithgrails3

package gr8data class Company { static constraints = { }}

RESTful Controllers

@jennstrater#Devoxx #restwithgrails3

package gr8data class Company {+ String name+ Country country static constraints = { }}

RESTful Controllers

@jennstrater#Devoxx #restwithgrails3

RESTful Controllersgrails> create-restful-controller gr8data.Company | Created grails-app/controllers/gr8data/CompanyController.groovy

*REMEMBER* to update UrlMappings.groovy+ "/companies"(controller: "company", action: "index", method: "GET")+ "/companies"(controller: "company", action: "save", method: "POST")+ "/companies/$id"(controller: "company", action: "update", method: "PUT")+ "/companies/$id"(controller: "company", action: "patch", method: "PATCH")+ "/companies/$id"(controller: "company", action: "delete", method: "DELETE")…

@jennstrater#Devoxx #restwithgrails3

package gr8data import grails.rest.* import grails.converters.* class CompanyController extends RestfulController { static responseFormats = ["json", "xml"] CompanyController() { super(Company) }}

@jennstrater#Devoxx #restwithgrails3

URI Customization

• Resource Annotation

• parameters

• i.e. @Resouce(uri="/countries", readOnly=true)

• UrlMappings.groovy

• resources

• uri

• methods

• response formats

• etc

@jennstrater#Devoxx #restwithgrails3

"/$controller/$action?/$id?(.$format)?"{ constraints {

// apply constraints here }

}

Default Mapping

@jennstrater#Devoxx #restwithgrails3

URL Mappings Reportgrails>url-mappings-report

@jennstrater#Devoxx #restwithgrails3

HTTP Methods

• via mappings

• "/custom"(controller="custom", action=“index”, method=“GET")

• via controllers

• static allowedMethods = [save: "POST", update: "PUT"]

@jennstrater#Devoxx #restwithgrails3

Default Media Types• xml if not specified

• on mappings and controllers

• formats = ["json","xml"]

• extension

• /countries.xml

• /countries.json

• Requesting a format not included returns 406 Not Acceptable

@jennstrater#Devoxx #restwithgrails3

Custom Media Types

• Add to Mime Types

@jennstrater#Devoxx #restwithgrails3

Custom Response Formats

• Marshallers/Renderers

• registration

• bootstrap.groovy

• beans

• JSON views*

@jennstrater#Devoxx #restwithgrails3

import grails.converters.JSON

class Bootstrap { def init = { servletContext -> JSON.registerObjectMarshaller(Country) {

return [id: it.id,name: it.name, abbrev: it.abbreviation,continent: it.continent

] } }}

grails-app/init/Bootstrap.groovy

@jennstrater#Devoxx #restwithgrails3

import grails.converters.JSONimport gr8data.CompanyJsonRenderer

beans = { companyJsonRenderer CompanyJsonRenderer}

conf/spring/resources.groovy

@jennstrater#Devoxx #restwithgrails3

class CompanyJsonRenderer extends AbstractRenderer<Company> { CompanyJsonRenderer() { super(Company, [MimeType.JSON, MimeType.TEXT_JSON] as MimeType[]) } void render(Company company, RenderContext context) { context.contentType = MimeType.JSON.name JsonBuilder builder = new JsonBuilder(id: company.id, name: company.name, country: company.country.name) builder.writeTo(context.writer) }}

Json Renderer

@jennstrater#Devoxx #restwithgrails3

class CompanyCollectionJsonRenderer implements ContainerRenderer<List, Company> { Class<List> getTargetType() { List } Class<Company> getComponentType() { Company } MimeType[] getMimeTypes() { [MimeType.JSON] as MimeType[] } void render(List companies, RenderContext context) { … }}

Json Collection Renderer

@jennstrater#Devoxx #restwithgrails3

JSON view*

• JSON views replace GSPs in web-api profile

• Convention Based

• index.gsp -> index.gson

*Grails 3.1+

@jennstrater#Devoxx #restwithgrails3

json {message "Not Found"error 404

}

JSON views*

@jennstrater#Devoxx #restwithgrails3

Versioning

• URI

• http://api.example.com/v1/resource/{id}

• http://example.com/api/v1/resource/{id}

• Custom Header

• Content Type

@jennstrater#Devoxx #restwithgrails3

Versioning in Grails

• URI

• "/hello/v1"(controller="hello", namespace: "v1")

• "/hello/v2"(controller="hello", namespace: "v2")

• Accept Header

• "/hello"(version:"1.0", controller="hello", namespace: "v1")

• "/hello"(version:"2.0",controller="hello", namespace: "v2")

@jennstrater#Devoxx #restwithgrails3

Version By Namespacepackage gr8data.v1

class HelloController { static namespace = "v1"

def index() {render "Hello, World (v1)"

}}

package gr8data.v2

class HelloController { static namespace = "v2"

def index() {render "Hello, World (v2)"

}}

@jennstrater#Devoxx #restwithgrails3

Security

• Spring Security Rest Plugin by Álvaro Sánchez Mariscal

• Token based authentication

• Greach 2014 Video

• Grails 2 only

• Spring Security Plugin for Grails 3

• OAuth for Spring Boot

• Screencast by Bobby Warner of Agile Orbit

@jennstrater#Devoxx #restwithgrails3

Conclusion

• Grails web-api profile streamlines API creation.

• Grails defaults support HTTP status codes, HTTP methods, custom media types, custom response formats, and versioning fairly easily.

• More exciting changes coming to the web-api profile with Grails 3.1!

@jennstrater#Devoxx #restwithgrails3

To Learn More

• Restful Web Services in Grails 3 at Gr8Conf US

• YouTube / Slides

• Restful Grails 3 at SpringOne2GX by Jeff Brown

• YouTube / Slides

• Grails 3.X Update at SpringOne2GX by Graeme Rocher

• YouTube / Slides

• Grails Documentation

• REST