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

# 概要

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をここで加工するためのファイルのようです。

```groovy:index.gson
import restprofile.Emp

model {
	Iterable<Emp> empList
}

json tmpl.emp(empList ?: [])
```

```groovy:_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](http://docs.grails.org/3.2.7/ref/Controllers/respond.html)を使っていて、これが適切なタイプのレスポンスを返してくれます。

```groovy
// pick the best content type to respond with from the given formats
respond Book.get(1), formats: ['xml', 'json']
```

毎回`formats`を指定してもいいけど、同一コントローラ内でまとめて設定してしまいたい場合は[responseFormats](http://docs.grails.org/3.2.7/ref/Controllers/responseFormats.html)を使うようです。

```groovy: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リクエストのメソッドごとに呼び出すコントローラを変えるような設定がかかれてました。

```groovy: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はかなり簡単に出来るようにはなるので、あとはビジネスロジックの実装でどうやって肉付けしていくか、というところですね。実際には複数のドメインの情報取りまとめたりとか入ってくると思うので。