Grails

Grails 3.Xのcreate-appでrest-apiプロファイルのアプリを作成する(1)

More than 1 year has passed since last update.


概要

Grails 3.2.7 でRESTのアプリのプロファイルを使ってお手軽にドメインを叩けるようにするまでのお話。

スキャフォルディングで作れるレベルまでなら割とすぐ出来ました。

(Grails 2.X系とプロキシの設定方法変わっててそこでだいぶ引っかかりましたが・・・)


使用環境

Windows 64bit + posh-gvm + Grails 3.2.7 + Groovy 2.4.9


手順


create-app

まずはcreate-appコマンドのオプションに --profile=rest-api をつけてアプリケーションを作成。

PS C:\Users\KKZ\Git> grails create-app restprofile --profile=rest-api

| Application created at C:\Users\KKZ\Git\restprofile

ざっと見たディレクトリ見た限りでは、grails-app\assets 配下のimagesとかjavascriptsみたいな静的コンテンツ用のフォルダや素材が配置されなくなるっぽい。


create-domain-class

ここからは通常のGrailsアプリと同じで、使用するドメインを create-domain-class で作成。

PS C:\Users\KKZ\Git> cd restprofile

PS C:\Users\KKZ\Git\restprofile> grails create-domain-class emp
| Resolving Dependencies. Please wait...
CONFIGURE SUCCESSFUL
| Created grails-app/domain/restprofile/Emp.groovy
| Created src/test/groovy/restprofile/EmpSpec.groovy
PS C:\Users\KKZ\Git\restprofile> grails create-domain-class dept
| Resolving Dependencies. Please wait...
CONFIGURE SUCCESSFUL
| Created grails-app/domain/restprofile/Dept.groovy
| Created src/test/groovy/restprofile/DeptSpec.groovy

ドメインに書く内容は従来と変わらないので、省略。


generate-all

PS C:\Users\KKZ\Git\restprofile> grails generate-all Emp

| Rendered template Controller.groovy to destination grails-app\controllers\restprofile\EmpController.groovy
| Scaffolding completed for grails-app\domain\restprofile\Emp.groovy
| Rendered template index.gson to destination grails-app\views\emp\index.gson
| Rendered template show.gson to destination grails-app\views\emp\show.gson
| Rendered template _domain.gson to destination grails-app\views\emp\_emp.gson
| Scaffolding completed for grails-app\domain\restprofile\Emp.groovy
| Rendered template Spec.groovy to destination src\test\groovy\restprofile\EmpControllerSpec.groovy
| Scaffolding completed for grails-app\domain\restprofile\Emp.groovy
| Rendered template FunctionalSpec.groovy to destination src\integration-test\groovy\restprofile\EmpFunctionalSpec.groovy
| Scaffolding completed for grails-app\domain\restprofile\Emp.groovy
PS C:\Users\KKZ\Git\restprofile> grails generate-all Dept

通常のスキャフォルディングとは異なり、views/ドメイン名の配下には *.gson ファイルが配置されていく。

中身を見てみるとどうやら返却するJSONをここで加工するためのファイルのようです。


index.gson

import restprofile.Emp

model {
Iterable<Emp> empList
}

json tmpl.emp(empList ?: [])



_emp.gson

import restprofile.Emp

model {
Emp emp
}

json g.render(emp)


1個1個のドメインの中身をJSON側にどう見せるか、を指定するのが _ドメイン名.gson で、どのメソッドを呼ばれたらモデルはどう見せるか、がメソッド名.gsonで、どうやって見せるためのデータをかき集めるかはコントローラー側で記載していくようだ。

スキャフォルディングの素のままだとshow, save, index, update, deleteが作られていました。

JSONのレスポンスを返すタイミングでは、returnとかrenderじゃなくてrespondを使っていて、これが適切なタイプのレスポンスを返してくれます。

// pick the best content type to respond with from the given formats

respond Book.get(1), formats: ['xml', 'json']

毎回formatsを指定してもいいけど、同一コントローラ内でまとめて設定してしまいたい場合はresponseFormatsを使うようです。


DeptController.groovy

package restprofile

import static org.springframework.http.HttpStatus.*
import grails.transaction.Transactional

@Transactional(readOnly = true)
class DeptController {

static responseFormats = ['json', 'xml']
static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]

def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
respond Dept.list(params), model:[deptCount: Dept.count()]
}

def show(Dept dept) {
respond dept
}

@Transactional
def save(Dept dept) {
if (dept == null) {
transactionStatus.setRollbackOnly()
render status: NOT_FOUND
return
}

if (dept.hasErrors()) {
transactionStatus.setRollbackOnly()
respond dept.errors, view:'create'
return
}

dept.save flush:true

respond dept, [status: CREATED, view:"show"]
}

@Transactional
def update(Dept dept) {
if (dept == null) {
transactionStatus.setRollbackOnly()
render status: NOT_FOUND
return
}

if (dept.hasErrors()) {
transactionStatus.setRollbackOnly()
respond dept.errors, view:'edit'
return
}

dept.save flush:true

respond dept, [status: OK, view:"show"]
}

@Transactional
def delete(Dept dept) {

if (dept == null) {
transactionStatus.setRollbackOnly()
render status: NOT_FOUND
return
}

dept.delete flush:true

render status: NO_CONTENT
}
}


単に使ってなかったから存在を知らなかったけど、respondはけっこう昔のGrailsのバージョンからあった模様。

URLへのアクセスとコントローラー呼出の紐付をする UrlMappings.groovy ではHTTPリクエストのメソッドごとに呼び出すコントローラを変えるような設定がかかれてました。


UrlMappings.groovy

package restprofile

class UrlMappings {

static mappings = {
delete "/$controller/$id(.$format)?"(action:"delete")
get "/$controller(.$format)?"(action:"index")
get "/$controller/$id(.$format)?"(action:"show")
post "/$controller(.$format)?"(action:"save")
put "/$controller/$id(.$format)?"(action:"update")
patch "/$controller/$id(.$format)?"(action:"patch")

"/"(controller: 'application', action:'index')
"500"(view: '/error')
"404"(view: '/notFound')
}
}



実行結果

run-appした上で、cURLやPostmanからPOSTパラメータを指定してcreateしてから、GETでindex呼んでみるとサーバー上でドメインのデータが作られていることが確認できました、とさ。

C:\> curl localhost:8080/emp

[{"empName":"KKZ","empNo":1234}]

デフォルトのスキャフォルディングでドメインのCRUDはかなり簡単に出来るようにはなるので、あとはビジネスロジックの実装でどうやって肉付けしていくか、というところですね。実際には複数のドメインの情報取りまとめたりとか入ってくると思うので。