Request
この章では、APIリクエストのデザインについて説明していきます。リクエストには、主に3種類、データの渡し方があります。
- URLのPathに含まれる、リソースを識別するものとしてのパラメータ
- Queryパラメータ(GETで良く使われる、?に続く文字列ですね)
- リクエストボディ
他にもHTTPリクエストという点では、HTTPヘッダも存在しますが、
HTTPヘッダはリクエストパラメータというより、
リクエストの形式や認証などに使われる、メタデータ的な意味合いがあるため
この章では扱いません。
それでは、それぞれの詳細を見ていきましょう。
Pathパラメータ
RESTFulなAPIを扱うとき、良く出てくる/user/:id -> /user/100
/product/:category/:product_id -> /product/book/300
のような、URLに含まれるパラメータですね。
正式名称は何というのでしょうか。
このパラメータは、Resourceを識別するために使われます。
Queryパラメータで識別しても良いのですが、Queryパラメータはどちらかというと、
APIの振る舞い・挙動を変える意味合いがあります。
この辺の考えは、RESTfulというアーキテクチャの話になってくるため、ここでは割愛します。
それでは実装してみましょう。
先ほどまでのソースコードを一旦、コミットします。
$ git add .
$ git commit -m "first commit"
そして、デザインファイルにResourceを追加します。
var _ = Resource("products", func() {
Action("show", func() {
Routing(GET("products/:category_id/:product_id"))
Params(func() {
Param("category_id", Integer, "カテゴリID")
Param("product_id", Integer, "プロダクトID")
})
Description("商品情報を取得します")
Response(OK, "text/plain")
})
})
※ product_id が一意ならcategory_idは不要にも思えるかも知れませんが、
そこは設計の話になり本章から逸脱するため、ご容赦ください。
コード生成、ビルド、起動します。
$ goagen bootstrap -d goasample/design
$ go build
$ ./goasample
そして、cliツールからアクセスしてみましょう。
$ go run tool/goasample-cli/main.go show products --category_id 5 --product_id 10
2017/05/30 08:37:59 [INFO] started id=BwvPY5MH GET=http://localhost:8080/products/5/10
2017/05/30 08:37:59 [INFO] completed id=BwvPY5MH status=404 time=16.410688ms
error: 404: {"id":"ybV8FQXs","code":"not_found","status":404,"detail":"/products/5/10"}
exit status 4
おや? HTTP status codeが404で返ってきてますね。
404とはResource Not Found
なぜでしょう?
実は、**リソースを追加した場合は
main.go
を手動で修正する必要があります。以下のコードを追加します。
// Mount "Products" controller
app.MountProductsController(service, NewProductsController(service))
差分を見ましょう。
c := NewHelloworldController(service)
app.MountHelloworldController(service, c)
+ // Mount "Products" controller
+ app.MountProductsController(service, NewProductsController(service))
+
// Start service
if err := service.ListenAndServe(":8080"); err != nil {
何をやっているかというと、アプリケーション(goasample)に、コントローラをMountしています。
初回のコード生成では特に意識しませんでしたが、2回目からはプログラマが行う必要があります。
それは
main.go
が編集可能なファイルであるため、自動生成してしまうとプログラマの修正が無かったことになってしまうからでしょう。そしてまた、goasampleを再起動し、cliツールでアクセスして見ましょう。
$ go run tool/goasample-cli/main.go show products --category_id 5 --product_id=10
status=200
で返ってきました。※cliツールでパラメータを指定するとき、
--category_id 5
--product_id=10
イコールで指定するか、スペースを空けるかの2パターンが使えます。ロジックの追加
自動生成されたapp配下のファイルを見ていると、Pathパラメータ(category_id、product_id)がコンテキストに追加され、コントローラのアクションに渡ってくるようになっています。products.goにロジックを追加してみましょう。
$ git add .
$ vim products.go
fmtパッケージをimportして、Pathパラメータを文字列にして返すようなロジックを実装します。
// Show runs the show action.
func (c *ProductsController) Show(ctx *app.ShowProductsContext) error {
// ProductsController_Show: start_implement
// Put your logic here
// ProductsController_Show: end_implement
return ctx.OK([]byte(fmt.Sprintf("カテゴリIDは%d 商品IDは %d です\n", ctx.CategoryID, ctx.ProductID)))
}
$ git diff products.go
diff --git a/products.go b/products.go
index 47f2d56..34d1610 100644
--- a/products.go
+++ b/products.go
@@ -1,6 +1,7 @@
package main
import (
+ "fmt"
"github.com/goadesign/goa"
"goasample/app"
)
@@ -22,5 +23,5 @@ func (c *ProductsController) Show(ctx *app.ShowProductsContext) error {
// Put your logic here
// ProductsController_Show: end_implement
- return nil
+ return ctx.OK([]byte(fmt.Sprintf("カテゴリIDは%d 商品IDは %d です\n", ctx.CategoryID, ctx.ProductID)))
}
また再起動し、cliでアクセスすると
$ go run tool/goasample-cli/main.go show products --category_id 5 --product_id=10
2017/05/30 08:57:36 [INFO] started id=4oA7CGOu GET=http://localhost:8080/products/5/10
2017/05/30 08:57:36 [INFO] completed id=4oA7CGOu status=200 time=5.013642ms
カテゴリIDは5 商品IDは 10 です
ちゃんと、レスポンスが表示されました!
Queryパラメータ
次は、URLのhttp://xxxx?hogehoge=123
の、 ?
以降のパラメータ( queryパラメータ )を定義していきましょう。
git add .
vim design/design.go
サンプルとして、
verbose
というパラメータを追加してみましょうか。ロジックとしては、商品の詳細情報も
queryパラメータは
Params()
というDSLを追加するだけです。design/design.go
Params(func() {
Param("category_id", Integer, "カテゴリID")
Param("product_id", Integer, "プロダクトID")
+ Param("verbose_mode", Boolean, "詳細情報も取得")
})
products.rb
$ git diff products.go
diff --git a/products.go b/products.go
index 34d1610..8cd8dfc 100644
--- a/products.go
+++ b/products.go
@@ -23,5 +23,9 @@ func (c *ProductsController) Show(ctx *app.ShowProductsContext) error {
// Put your logic here
// ProductsController_Show: end_implement
+ if ctx.VerboseMode != nil && *ctx.VerboseMode {
+ return ctx.OK([]byte(fmt.Sprintf("カテゴリIDは%d 商品IDは %d です。価格は1,000円です\n", ctx.CategoryID, ctx.ProductID)))
+ }
+
return ctx.OK([]byte(fmt.Sprintf("カテゴリIDは%d 商品IDは %d です\n", ctx.CategoryID, ctx.ProductID)))
}
APIをコールしてみましょう。
$ go run tool/goasample-cli/main.go show products --category_id 15 --product_id=101
2017/06/09 09:50:38 [INFO] started id=EEC+2YdD GET=http://localhost:8080/products/15/101
2017/06/09 09:50:38 [INFO] completed id=EEC+2YdD status=200 time=20.93987ms
カテゴリIDは15 商品IDは 101 です
verbose_modeをtrueにしてみます。
$ go run tool/goasample-cli/main.go show products --category_id 15 --product_id=101 --verbose_mode=true
2017/06/09 09:51:00 [INFO] started id=gUijOoNn GET=http://localhost:8080/products/15/101?verbose_mode=true
2017/06/09 09:51:00 [INFO] completed id=gUijOoNn status=200 time=6.468188ms
カテゴリIDは15 商品IDは 101 です。価格は1,000円です
ctx.verboseModeというパラメーがコンテキストに追加されて、アプリケーション上で使えるようになりました。
nilチェックをしているのは、verbose_modeが必須パラメータではないため、パラメータがポインタ型となっているためです。
パラメータが指定されていない場合はnilとなります。
パラメータを必須にしたい場合、 *Required*DSLを使用します。
$ git diff design/
diff --git a/design/design.go b/design/design.go
index 2d47446..4df4510 100644
--- a/design/design.go
+++ b/design/design.go
@@ -27,6 +27,7 @@ var _ = Resource("products", func() {
Param("category_id", Integer, "カテゴリID")
Param("product_id", Integer, "プロダクトID")
Param("verbose_mode", Boolean, "詳細情報も取得")
+ Required("verbose_mode")
})
Description("商品情報を取得します")
Response(OK, "text/plain")
これで、パラメータを指定しないでAPIをコールすると、
$ go run tool/goasample-cli/main.go show products --category_id 15 --product_id=101
2017/06/09 10:01:05 [EROR] required flag is missing flag=--verbose_mode
Error: required flag verbose_mode is missing
エラーとなります。
Payoad
最後はpayloadと呼ばれる、リクエストボディのパラメータ定義の説明をします。POST/PUT/PATCHメソッドなどで使うパラメータですね。
design/desgin.go
$ git diff design/design.go
diff --git a/design/design.go b/design/design.go
index 4df4510..3093937 100644
--- a/design/design.go
+++ b/design/design.go
@@ -32,4 +32,23 @@ var _ = Resource("products", func() {
Description("商品情報を取得します")
Response(OK, "text/plain")
})
+ Action("create", func() {
+ Routing(POST("products"))
+ Payload(CreateProductPayload)
+ Description("商品を作成します")
+ Response(OK, "text/plain")
+ })
+})
+
+var CreateProductPayload = Type("CreateProductPayload", func() {
+ Member("category_id", Integer, "カテゴリID", func() {
+ Example(3)
+ })
+ Member("product_id", Integer, "商品ID", func() {
+ Example(10)
+ })
+ Member("price", Integer, "価格", func() {
+ Example(1000)
+ })
+ Required("category_id", "product_id", "price")
})
Type
という、ユーザー定義DSLを使用してPayloadを定義しています。各パラメータは
Member
で定義されているのかわかりますね。そして、定義されたPayloadを、
show
というアクションと紐づけています。products.go
コントローラには、以下の処理を追加実装します。
func (c *ProductsController) Create(ctx *app.CreateProductsContext) error {
return ctx.OK([]byte(fmt.Sprintf("カテゴリID %d 商品ID %d 価格 %d で商品を作成しました\n", ctx.Payload.CategoryID, ctx.Payload.ProductID, ctx.Payload.Price)))
}
これで、cliツールからアクセスしてみると
$ go run tool/goasample-cli/main.go create products --payload '{"category_id": 5, "product_id": 10, "price": 1000}'
2017/06/09 10:22:09 [INFO] started id=sU8zFKMo POST=http://localhost:8080/products
2017/06/09 10:22:09 [INFO] completed id=sU8zFKMo status=200 time=7.963341ms
カテゴリID 5 商品ID 10 価格 1000 で商品を作成しました
--payload
パラメータで、JSONを指定しています。APIサーバといったら通常、JSONでのやりとりが普通になっていますね。
なので、レスポンスもJSON形式で返すのが理想です。
リクエストについては、基本的な設定を説明できたかと思います。
次章では、レスポンスの返し方について説明していきます。
コメント
コメントを投稿