概要
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をここで加工するためのファイルのようです。
import restprofile.Emp
model {
Iterable<Emp> empList
}
json tmpl.emp(empList ?: [])
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を使うようです。
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リクエストのメソッドごとに呼び出すコントローラを変えるような設定がかかれてました。
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はかなり簡単に出来るようにはなるので、あとはビジネスロジックの実装でどうやって肉付けしていくか、というところですね。実際には複数のドメインの情報取りまとめたりとか入ってくると思うので。