Slide 1

Slide 1 text

Grails 3でWeb APIを簡単に作ろう! 2015/4/17 #jggug

Slide 2

Slide 2 text

山本 和樹 yamkazu

Slide 3

Slide 3 text

祝 Grails 3 リリース

Slide 4

Slide 4 text

Grails 3 ✓ Gradleでビルドシステムを一新 ✓ Spring Boot上でアプリケーショ ンを再構築

Slide 5

Slide 5 text

今日はWeb API

Slide 6

Slide 6 text

GrailsにおけるWeb API ✓ Grails 2.3からREST関係のサポートが強化 ✓ @Resouce ✓ RestfulController ✓ Renderers

Slide 7

Slide 7 text

@Resource

Slide 8

Slide 8 text

@Resource ✓ Domainクラスにアノテーションを指定 ✓ REST対応コントローラの生成 ✓ URLマッピングへの設定の追加

Slide 9

Slide 9 text

import grails.rest.Resource
 
 @Resource(uri = '/books')
 class Book {
 String title
 }

Slide 10

Slide 10 text

Endpoints HTTP Method URI Controller Action GET /books index POST /books save GET /books/${id} show PUT /books/${id} update DELETE /books/${id} delete

Slide 11

Slide 11 text

データフォーマットの指定 ✓ クエリパラメータを使う ✓ 拡張子を使う ✓ メディアタイプを使う

Slide 12

Slide 12 text

クエリパラメータを使う http://api.example.com/books?format=json

Slide 13

Slide 13 text

拡張子を使う http://api.example.com/books.json

Slide 14

Slide 14 text

メディアタイプを使う GET /books Host: api.example.com Accept: application/json

Slide 15

Slide 15 text

DEMO: 1

Slide 16

Slide 16 text

各社サポートのデータフォーマット Twitter JSON Github JSON Flickr JSON, XML Amazon XML 出典: Web API: The Good Parts

Slide 17

Slide 17 text

XMLとJSONのトレンド http://www.google.com/trends/explore?q=xml+api#q=xml%20api%2C%20json%20api&cmpt=q XML JSON

Slide 18

Slide 18 text

LSUDsとSSKDs ✓ LSUDs(large set of unknown developers) ✓ 未知のたくさんの開発者 ✓ FacebookやTwitterをはじめ、パブリックにAPIをドキュメントともに 公開し、誰でもが登録して使えるようにしたもの ✓ SSKDs(small set of known developers) ✓ 既知の小数の開発者 ✓ 一方でSSKDsをターゲットとしたAPIとは、たとえば自社サービスのス マートフォンクライアント向けのAPIなど、APIを利用する開発者が限 られている http://thenextweb.com/dd/2013/12/17/future-api-design-orchestration-layer/ http://techblog.netflix.com/2014/03/the-netflix-dynamic-scripting-platform.html http://techblog.netflix.com/2012/07/embracing-differences-inside-netflix.html

Slide 19

Slide 19 text

RestfulController

Slide 20

Slide 20 text

RestfulController ✓ Domainクラスを指定するだけで 簡単にRESTfulなコントローラ を作成できる

Slide 21

Slide 21 text

import grails.rest.RestfulController
 
 class BookController extends RestfulController {
 
 static responseFormats = ['json', 'xml']
 
 BookController() {
 super(Book)
 }
 }

Slide 22

Slide 22 text

class UrlMappings {
 
 static mappings = { …
 '/books'(resources: 'book')
 }
 }

Slide 23

Slide 23 text

DEMO: 2

Slide 24

Slide 24 text

$ grails url-mapping-report

Slide 25

Slide 25 text

レンダリングの カスタマイズ

Slide 26

Slide 26 text

レンダリングの流れ Controller Renderer Converter Marshaller render as JSON/XML marshalObject render respond

Slide 27

Slide 27 text

レンダリングするプロ パティを指定する

Slide 28

Slide 28 text

import grails.rest.render.json.JsonRenderer
 
 beans = {
 bookRenderer(JsonRenderer, Book) {
 includes = ['title']
 }
 }

Slide 29

Slide 29 text

DEMO: 3

Slide 30

Slide 30 text

レスポンスレンダリン グを細かく制御する

Slide 31

Slide 31 text

import grails.converters.JSON
 
 class BootStrap {
 
 def init = { servletContext ->
 JSON.registerObjectMarshaller Book, { Book book, JSON converter ->
 converter.build {
 id book.id
 title book.title
 }
 // MapでもOK
 //[id: book.id, title: book.title]
 }
 } …
 }

Slide 32

Slide 32 text

DEMO: 4

Slide 33

Slide 33 text

ユーザがプロパティを指定可能にする http://api.example.com/books?fields=title

Slide 34

Slide 34 text

class BookController extends RestfulController {
 … @Override
 def show() {
 respond queryForResource(params.id), includes: includeFields
 }
 
 private List getIncludeFields() {
 params.fields?.split(',') as List ?: Collections.emptyList()
 }
 }

Slide 35

Slide 35 text

注意 CustomMarshallerを登録している場合は 自分でincludesを制御しなければならない

Slide 36

Slide 36 text

DEMO: 5

Slide 37

Slide 37 text

レスポンスグループを定義する http://api.example.com/books?detail=short http://api.example.com/books?detail=full { "id": 1, "title": "My Book" } { "id": 1, "title": "My Book", "isbn": "…", …}

Slide 38

Slide 38 text

class BootStrap {
 
 def init = { servletContext ->
 JSON.createNamedConfig('short') {
 it.registerObjectMarshaller Book, { Book book, JSON converter ->
 [id: book.id, title: book.title]
 }
 }
 JSON.createNamedConfig('full') {
 it.registerObjectMarshaller Book, { Book book, JSON converter ->
 [id: book.id, title: book.title, isbn: book.isbn]
 }
 }
 …
 } …
 }

Slide 39

Slide 39 text

import grails.rest.render.RenderContext
 import org.grails.plugins.web.rest.render.json.DefaultJsonRenderer
 
 class MyJsonRenderer extends DefaultJsonRenderer {
 
 MyJsonRenderer(Class targetType) {
 super(targetType)
 }
 
 @Override
 protected void renderJson(T object, RenderContext context) {
 def namedConfiguration = context.arguments.detail ?: 'short'
 
 JSON.use(namedConfiguration) {
 renderJson(object as JSON, context)
 }
 }
 }

Slide 40

Slide 40 text

DEMO: 6

Slide 41

Slide 41 text

エンベロープを使いたい { books: [ { "id": 1, "title": "Groovy"}, { "id": 2, "title": "Grails"} ] }

Slide 42

Slide 42 text

import grails.converters.JSON
 class MyJSON extends JSON {
 
 String key
 
 @Override
 void render(Writer out) throws ConverterException {
 …
 if (key) {
 writer.object()
 writer.key(key)
 value(target)
 writer.endObject()
 } else {
 value(target)
 }
 …
 } …
 }

Slide 43

Slide 43 text

class MyJsonRenderer extends DefaultJsonRenderer {
 
 String key
 
 @Override
 protected void renderJson(T object, RenderContext context) {
 …
 JSON.use(namedConfiguration) {
 def converter = object as MyJSON
 converter.key = key
 converter.prettyPrint = context.arguments.pretty
 renderJson(converter, context)
 }
 } …
 }

Slide 44

Slide 44 text

DEMO: 7

Slide 45

Slide 45 text

ページネーションしたい { "books": [ { "id": 1, "title": "Groovy"}, { "id": 2, "title": "Grails"} ], "paging": { "total-count": 2, "current-max": 10, "current-offset": 0 } } http://api.example.com/books? max=10&offset=0&order=desc&sort=title

Slide 46

Slide 46 text

class MyJSON extends JSON {
 
 Map paging
 
 @Override
 void render(Writer out) throws ConverterException { …
 if (paging) {
 writer.key('paging')
 value(paging)
 } …
 } …
 }

Slide 47

Slide 47 text

DEMO: 8

Slide 48

Slide 48 text

Versioning

Slide 49

Slide 49 text

バージョンの指定 ✓ URLに埋め込む ✓ Accept-Versionヘッダを使う ✓ メディアタイプを使う

Slide 50

Slide 50 text

URLに埋め込む http://api.example.com/v1/books

Slide 51

Slide 51 text

Accept-Versionヘッダを使う GET /books Host: api.example.com Accept-Version: v1

Slide 52

Slide 52 text

メディアタイプを使う GET /books Host: api.example.com Accept: application/vnd.example.v1+json

Slide 53

Slide 53 text

// sample/v1/BookController.groovy package sample.v1
 
 class BookController extends RestfulController {
 
 static String namespace = 'v1'
 …
 }
 // sample/v2/BookController.groovy package sample.v2
 
 class BookController extends RestfulController {
 
 static String namespace = 'v2'
 …
 }

Slide 54

Slide 54 text

class UrlMappings {
 
 static mappings = {
 '/v1/books'(resources: 'book', namespace: 'v1')
 '/v2/books'(resources: 'book', namespace: 'v2') …
 }
 }

Slide 55

Slide 55 text

DEMO: 9

Slide 56

Slide 56 text

Testing!

Slide 57

Slide 57 text

テスト ✓ Integrationテストを使うとサーバ立ち上 がってテストできる ✓ 適当なHTTPのクライアント使ってテスト ✓ TestRestTemplateがオススメ

Slide 58

Slide 58 text

def "GET /books/{id}"() {
 when:
 def entry = template.getForEntity("${baseUrl}/v2/books/${book.id}", Map)
 
 then:
 entry.statusCode == HttpStatus.OK
 entry.body.title == 'test book'
 entry.body.isbn == '1234'
 }

Slide 59

Slide 59 text

DEMO: 10

Slide 60

Slide 60 text

まとめ ✓ @Resouce,RestfulController,Render erのカスタマイズで簡単に柔軟に Web APIが作れる ✓ フルスタックなのでDBの連携も簡単

Slide 61

Slide 61 text

参考書籍 http://www.amazon.co.jp/dp/4873116864 http://www.amazon.co.jp/dp/4774166278

Slide 62

Slide 62 text

参考URL ✓ Best Practices for Designing a Pragmatic RESTful API ✓ http://www.vinaysahni.com/best-practices- for-a-pragmatic-restful-api ✓ GrailsGoodness ✓ http://mrhaki.blogspot.com/search/label/ GrailsGoodness%3AREST

Slide 63

Slide 63 text

おまけ

Slide 64

Slide 64 text

web-micro

Slide 65

Slide 65 text

Enjoy Grails 3!