Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Grails 3でWeb APIを簡単に作ろう!

Grails 3でWeb APIを簡単に作ろう!

G* Workshop G*なWeb API での資料です
https://jggug.doorkeeper.jp/events/22473

Kazuki YAMAMOTO

April 17, 2015
Tweet

More Decks by Kazuki YAMAMOTO

Other Decks in Programming

Transcript

  1. Endpoints HTTP Method URI Controller Action GET /books index POST

    /books save GET /books/${id} show PUT /books/${id} update DELETE /books/${id} delete
  2. 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
  3. import grails.rest.RestfulController
 
 class BookController extends RestfulController<Book> {
 
 static

    responseFormats = ['json', 'xml']
 
 BookController() {
 super(Book)
 }
 }
  4. 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]
 }
 } …
 }
  5. class BookController extends RestfulController<Book> {
 … @Override
 def show() {


    respond queryForResource(params.id), includes: includeFields
 }
 
 private List<String> getIncludeFields() {
 params.fields?.split(',') as List<String> ?: Collections.emptyList()
 }
 }
  6. 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]
 }
 }
 …
 } …
 }
  7. import grails.rest.render.RenderContext
 import org.grails.plugins.web.rest.render.json.DefaultJsonRenderer
 
 class MyJsonRenderer<T> extends DefaultJsonRenderer<T> {


    
 MyJsonRenderer(Class<T> 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)
 }
 }
 }
  8. 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)
 }
 …
 } …
 }
  9. class MyJsonRenderer<T> extends DefaultJsonRenderer<T> {
 
 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)
 }
 } …
 }
  10. ページネーションしたい { "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
  11. class MyJSON extends JSON {
 
 Map paging
 
 @Override


    void render(Writer out) throws ConverterException { …
 if (paging) {
 writer.key('paging')
 value(paging)
 } …
 } …
 }
  12. // sample/v1/BookController.groovy package sample.v1
 
 class BookController extends RestfulController<Book> {


    
 static String namespace = 'v1'
 …
 }
 // sample/v2/BookController.groovy package sample.v2
 
 class BookController extends RestfulController<Book> {
 
 static String namespace = 'v2'
 …
 }
  13. class UrlMappings {
 
 static mappings = {
 '/v1/books'(resources: 'book',

    namespace: 'v1')
 '/v2/books'(resources: 'book', namespace: 'v2') …
 }
 }
  14. 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'
 }
  15. 参考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