はじめに
goa の API デザインについて,デザインを定義する4つの要素について概要を説明します.
- API … API サーバの定義
- MediaType … レスポンスデータの定義
- Resource … APIが管理するデータへのアクセス方法 / エンドポイントなどを定義
- Payload … API に送信するデータの定義
とりあえずこれらを押さえておけば一通りのAPIは書けるはず!(たぶん)
今回は4つのうちの API と MediaType を説明します.
準備:API サンプル
おなじみの最小構成サンプル.
デザインのパッケージ名は design
.あと,goa のライブラリを dot インポートしてますが,これはそういう流儀なので呪文だと思って許して下さい.以下に出てくる API
とか Resource
とか MediaType
といった関数は,これらのライブラリの中で定義されている関数です.
これらの関数の説明は goa :: Design-first API Generation に説明がありますので,こちらを参照しながら読んでいただけるといいかと思います.
このような関数を組み上げて API のデザインを書いていくスタイルが goa のスタイルになります.
package design // The convention consists of naming the design // package "design" import ( . "github.com/goadesign/goa/design" // Use . imports to enable the DSL . "github.com/goadesign/goa/design/apidsl" ) var _ = API("cellar", func() { // API defines the microservice endpoint and Title("The virtual wine cellar") // other global properties. There should be one Description("A simple goa service") // and exactly one API definition appearing in Scheme("http") // the design. Host("localhost:8080") }) var _ = Resource("bottle", func() { // Resources group related API endpoints BasePath("/bottles") // together. They map to REST resources for REST DefaultMedia(BottleMedia) // services. Action("show", func() { // Actions define a single API endpoint together Description("Get bottle by id") // with its path, parameters (both path Routing(GET("/:bottleID")) // parameters and querystring values) and payload Params(func() { // (shape of the request body). Param("bottleID", Integer, "Bottle ID") }) Response(OK) // Responses define the shape and status code Response(NotFound) // of HTTP responses. }) }) // BottleMedia defines the media type used to render bottles. var BottleMedia = MediaType("application/vnd.goa.example.bottle+json", func() { Description("A bottle of wine") Attributes(func() { // Attributes define the media type shape. Attribute("id", Integer, "Unique bottle ID") Attribute("href", String, "API href for making requests on the bottle") Attribute("name", String, "Name of wine") Required("id", "href", "name") }) View("default", func() { // View defines a rendering of the media type. Attribute("id") // Media types may have multiple views and must Attribute("href") // have a "default" view. Attribute("name") }) })
では本題.
API定義
var _ = API("cellar", func() { // API defines the microservice endpoint and Title("The virtual wine cellar") // other global properties. There should be one Description("A simple goa service") // and exactly one API definition appearing in Scheme("http") // the design. Host("localhost:8080") })
API全体の定義です.これは見てもらえば何となく分かると思います.関数API
のようにデザインのトップレベルに書くDSL関数のことを,goa ではトップレベル API DSL と呼んでいます.トップレベル API は,下記の4つがあります.これらはこれから順に説明していきますが,関数Type
は Payload の説明の時に解説します.
関数 API
の返値は _
で捨てられています.慣れるまで気持ち悪いですけど,こういうものだと思って下さい.
API で定義されている要素の説明
要素 | 説明 |
---|---|
Title | APIのタイトル.ドキュメントなどで表示される |
Description | このAPIの詳しい説明.ドキュメントなどで表示される |
Scheme | "http"や"https"などの URL scheme をセットできる |
Host | サービスするホスト名とポート |
この他にも,関数 Version
や Licence
,Docs
なんてのも指定可能です.また,セキュリティの設定が必要な場合はここにセキュリティ設定用の要素が入ることもあります.
詳細はドキュメントを参照して下さい.細かいところはまた別の機会に説明したいと思います.
MediaType定義
// BottleMedia defines the media type used to render bottles. var BottleMedia = MediaType("application/vnd.goa.example.bottle+json", func() { Description("A bottle of wine") Attributes(func() { Attribute("id", Integer, "Unique bottle ID") // id は整数型 Attribute("href", String, "API href for making requests on the bottle") // href は文字列 Attribute("name", String, "Name of wine") // name は文字列 Required("id", "href", "name") // 上記のうちで必須なものをここに指定する }) View("default", func() { // default View は必須 Attribute("id") Attribute("href") Attribute("name") }) })
関数API
と違って,関数MediaType
の返値は変数に保存されています.これは,この変数を使って,別の場所(定義)でこのメディアタイプを利用できるようにするためです.
メディアタイプは,レスポンスデータの形式を定義します.メディアタイプに名前をつけて(ここでは "application/vnd.goa.example.bottle+json".これは自分で適当に定義します),関数Attributes
でレスポンスに含まれるデータをすべて列挙します.この例では,id
/ href
/ name
が指定されています.また,それぞれに型が定義できます.id
は Integer
,href
と name
は String
で定義されています.
型として利用できる基本的なものは,Integer
/ Number
/ String
/ Boolean
などがあります.Number
は浮動小数点になります.JSON データと対応してもらうと理解しやすいと思います.また,配列を表すための関数 ArrayOf
や,Hash を表すための HashOf
があります.例えば,ArrayOf(Integer)
とすれば JSON の [1,2,3,4]
のような配列,HashOf(String, Integer)
とすれば JSON の {"orange":1, "apple":3}
のようなデータ形式が表現できます.
goaの基本型 | golangでの表現 | JSONでの表現 |
---|---|---|
Integer | int | number |
Number | float | number |
String | string | string |
Boolean | bool | boolean |
DateTime | time.Time | RFC3339な文字列 |
UUID | uuid.UUID | RFC4122な文字列 |
Any | interface{} | --- |
ここで注意したいのは,関数Attributes
で定義したのは,レスポンスデータに現れうるデータ要素であって,実際のレスポンス形式ではないということです.
実際のレスポンス形式は 関数View
に Attributes
で定義したデータ要素を組み合わせて作ります.
たとえば,上の例ではレスポンスは "default" View で定義されていて,
{id: 1, href: "/bottles/1", "name" : "Bottle #1"}
と返ります.関数Required
はデータ要素の値がゼロ値でも,要素として必須かどうかを示しています.たとえば,name
を必須要素から外した場合,返却される name
の値が空文字列の場合には
{id: 1, href: "/bottles/1"}
のように name
が省略されたレスポンスが返されます.ですが,まぁ,省略されちゃうと分かりにくいので,メディアタイプの定義で,Attribute を必須要素から外すことはあんまりないかなという気がします.
やるとしたら,実は同じメディアタイプに対して異なる View をいくつか定義することも可能なので,詳細なレスポンスを返したいときと,省略したレスポンスを返したいときで View をそれぞれ用意しておくという風にする方がいいかもしれません.
説明したように,View はいくつか定義可能ですが,"default"
View は必須なので必ず定義して下さい.
余談:メディアタイプはどうやって決めるか?
慣習的に "application/vnd.<リソースの名前>+<形式>" で決めるようです.vnd
というのはベンダー定義のメディアタイプであることを示しています.goa は特に何も指定しなければレスポンスは JSON 形式になるので,形式の部分は "+json" が指定されています.この辺の説明は Web API: The Good Parts の 4.4節に分かりやすくまとまっているのを参考にさせてもらいました.Web API: The Good Parts いい本なので是非.
- 作者: 水野貴明
- 出版社/メーカー: オライリージャパン
- 発売日: 2014/11/21
- メディア: 大型本
- この商品を含むブログ (7件) を見る