GolangのgoaでAPIをデザインしよう(レスポンス編)
goaはいいぞ!
Golangのgoaの勉強に役立つ情報まとめ - ぺい
goaの情報をもっと見たい方は、上のリンクから確認してください
レスポンス
いきなりですが、以下のエンドポイントを見てください。RESTを知ってる人は、レスポンスが違うことが分かると思います。
GET /users/1 GET /users
開発するチームにもよって変わる可能生はあると思いますが、GET /users/1
は恐らくひとりのユーザー情報だけを返す。そして、GET /users
はひとり以上のユーザー情報を返す。
また、GET /users
はIDや名前だけで良かったり、GET /users/1
は詳細な情報が欲しかったり、同じリソースに対しての操作でも、レスポンスは変える必要があります。(と思っています)
具体的にレスポンスの例を示すと以下のような感じになります。
GET /users
[ { "id": 1, "name": "ユーザー1" }, { "ID": 2, "name": "ユーザー2" } ]
GET /users/1
{ "id": 1, "email": "satak47cpc@gmail.com", "name": "ユーザー1" }
また、場合によってはIDの一覧だけ欲しい時もあったり
{"america":3,"japan":2,"korea":4}
キーに意味を持たせた連想配列が欲しかったり
[1,2,3]
上記のような需要全てに対応することが出来ます。実際に動かしてみましょう。
goaはコード生成する時に少し長めのコマンドが必要になるので、Makefileを作っておくと良いと思います。
ちなみに、コマンドやフォルダ構成は毎回同じものを使うので、テンプレートを作成しました。よろしければ利用してください。
もし、もっと便利なフォーマットがあればPRをください。
$ go get github.com/tikasan/goa-stater または $ ghq get github.com/tikasan/goa-stater
APIデザインをする
package design import ( . "github.com/goadesign/goa/design" . "github.com/goadesign/goa/design/apidsl" ) var _ = Resource("response", func() { BasePath("/response") Action("list", func() { Description("ユーザー(複数)") Routing( GET("/users"), ) // 複数返す Response(OK, CollectionOf(UserMedia)) Response(BadRequest, ErrorMedia) }) Action("show", func() { Description("ユーザー(単数)") Routing( GET("/users/:id"), ) // 単一 Response(OK, UserMedia) Response(BadRequest, ErrorMedia) }) Action("hash", func() { Description("ユーザー(ハッシュ)") Routing( GET("/users/hash"), ) // 連想配列 Response(OK, HashOf(String, Integer)) Response(BadRequest, ErrorMedia) }) Action("array", func() { Description("ユーザー(配列)") Routing( GET("/users/array"), ) // 配列 Response(OK, ArrayOf(Integer)) Response(BadRequest, ErrorMedia) }) })
MediaTypeを定義する
package design import ( . "github.com/goadesign/goa/design" . "github.com/goadesign/goa/design/apidsl" ) var UserMedia = MediaType("application/vnd.user+json", func() { Description("example") Attributes(func() { Attribute("id", Integer, "id", func() { Example(1) }) Attribute("name", String, "名前", func() { Example("hoge") }) Attribute("email", String, "メールアドレス", func() { Example("satak47cpc@gmail.com") }) Required("id", "name", "email") }) // 特別な指定がない場合はdefaultのMediaType View("default", func() { Attribute("id") Attribute("name") Attribute("email") }) // tinyという名前の場合は、簡潔なレスポンスフォーマットにすることが出来る View("tiny", func() { Attribute("id") Attribute("name") }) })
Controllerを定義する
goagen bootstrap -d github.com/tikasan/goa-stater/design
特にビジネスロジックなどはなしで、値を返すだけのコードを実装します。
package controller import ( "github.com/goadesign/goa" "github.com/tikasan/goa-stater/app" ) // ResponseController implements the response resource. type ResponseController struct { *goa.Controller } // NewResponseController creates a response controller. func NewResponseController(service *goa.Service) *ResponseController { return &ResponseController{Controller: service.NewController("ResponseController")} } // Array runs the array action. func (c *ResponseController) Array(ctx *app.ArrayResponseContext) error { // ResponseController_Array: start_implement // Put your logic here // ResponseController_Array: end_implement res := make([]int, 3) res[0] = 1 res[1] = 2 res[2] = 3 return ctx.OK(res) } // Hash runs the hash action. func (c *ResponseController) Hash(ctx *app.HashResponseContext) error { // ResponseController_Hash: start_implement // Put your logic here // ResponseController_Hash: end_implement res := make(map[string]int, 3) res["japan"] = 2 res["america"] = 3 res["korea"] = 4 return ctx.OK(res) } // List runs the list action. func (c *ResponseController) List(ctx *app.ListResponseContext) error { // ResponseController_List: start_implement // Put your logic here // ResponseController_List: end_implement res := make(app.UserTinyCollection, 2) u1 := &app.UserTiny{ ID: 1, Name: "ユーザー1", } u2 := &app.UserTiny{ ID: 2, Name: "ユーザー2", } res[0] = u1 res[1] = u2 return ctx.OKTiny(res) } // Show runs the show action. func (c *ResponseController) Show(ctx *app.ShowResponseContext) error { // ResponseController_Show: start_implement // Put your logic here // ResponseController_Show: end_implement res := &app.User{} res.ID = 1 res.Name = "ユーザー1" res.Email = "satak47cpc@gmail.com" return ctx.OK(res) }
準備が整ったので、実行してリクエストを投げてみましょう!
$ go run main.go
$ curl -v 'http://localhost:8080/api/v1/response/users/1' * Trying ::1... * Connected to localhost (::1) port 8080 (#0) > GET /api/v1/response/users/1 HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.43.0 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: application/vnd.user+json < Date: Sat, 06 May 2017 04:33:08 GMT < Content-Length: 63 < {"ID":1,"email":"satak47cpc@gmail.com","name":"ユーザー1"} * Connection #0 to host localhost left intact
$ curl -v 'http://localhost:8080/api/v1/response/users' * Trying ::1... * Connected to localhost (::1) port 8080 (#0) > GET /api/v1/response/users HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.43.0 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: application/vnd.user+json; type=collection < Date: Sat, 06 May 2017 04:35:02 GMT < Content-Length: 66 < [{"ID":1,"name":"ユーザー1"},{"ID":2,"name":"ユーザー2"}] * Connection #0 to host localhost left intact
いい感じのデータが返ってきました。goaはいいぞ!