goa のインストールと実行

概要

goa は DSL で書かれた API デザインを goa のツールで変換してコードを生成します. そのコード生成ツールは goagen です.まずはこれをインストールして,実際に動かしてみるところまで説明します.

生成されるファイルとか,利用する API デザインについてはここでは触れませんが,おいおい説明していきたいと思います.

今回は,goagen のインストールと使い方の概要までです.

インストール

$ go install github.com/goadesign/goa/goagen

でインストールできます.vendoring する場合は vendor フォルダに github.com/goadesign/goa を配置してください. vendor 以下の goagen フォルダまで降りていってビルドしておきます.

$ cd ./vendor/github.com/goadesign/goa/goagen
$ go build
$ cd ../../../../../

実行

基本的な使い方

$ goagen <サブコマンド> -d <デザインの配置してあるパッケージパス>

vendoring してるなら

$ ./vendor/github.com/goadesign/goa/goagen <サブコマンド> -d <デザインの配置してあるパッケージパス>

で実行できます.goagenリポジトリgoagen 以下の gen_* というフォルダがサブコマンドに対応していて,実行するたびにプラグイン的にここの下をビルドして利用しているので,goagen をインストールしたからといってリポジトリを消してしまうとうまく動かなくなってしまうので注意です.

注意

デザインの配置してあるパッケージパスは,$GOPATH/src からの相対パスを指定する必要があります. $GOPATH/src/cellar/design なら,cellar/design を指定します.

(参考) goa/goagen 以下の構造 :

github.com/goadesign/goa/
├── goagen/
│   ├── codegen/
│   ├── gen_app/                ← gen_app は app というサブコマンドに対応している
│   ├── gen_client/
│   ├── gen_js/
│   ├── gen_main/
│   ├── gen_schema/
│   ├── gen_swagger/
│   ├── meta/
│   └── utils/
(snip...)

とりあえず動かしてみよう!

色々説明するより動かしてみるのが手っ取り早いので動かしてみましょう.

準備

自分の $GOPATH/src の下に cellar というフォルダを作ってください. 以下ではこのフォルダで作業することを前提とします.

$ mkdir $GOPATH/src/cellar
$ cd $GOPATH/src/cellar

サンプルのAPIデザインを配置します.天下り的に以下の内容を design/desing.go に保存してください. これは https://goa.design/learn/guide/ でサンプルとして紹介されているワインセラーを題材にしたAPIサーバの最小のサンプルです. /bottles/:bottleIDというエンドポイントに管理しているワインのIDをセットすると,ワインの情報が得られるというようなものです.

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")
        })
})

コード生成

$ goagen bootstrap -d cellar/design
app
app/contexts.go
app/controllers.go
app/hrefs.go
app/media_types.go
app/user_types.go
app/test
app/test/bottle_testing.go
main.go
bottle.go
tool/cellar-cli
tool/cellar-cli/main.go
tool/cli
tool/cli/commands.go
client
client/client.go
client/bottle.go
client/user_types.go
client/media_types.go
swagger
swagger/swagger.json
swagger/swagger.yaml

これで APIサーバのモック,クライアント,swagger ドキュメントが生成されました.

.
├── app                            
│   ├── contexts.go
│   ├── controllers.go
│   ├── hrefs.go
│   ├── media_types.go
│   ├── test
│   │   └── bottle_testing.go
│   └── user_types.go
├── bottle.go
├── client
│   ├── bottle.go
│   ├── client.go
│   ├── media_types.go
│   └── user_types.go
├── design
│   └── design.go
├── main.go
├── swagger
│   ├── swagger.json
│   └── swagger.yaml
└── tool
    ├── cellar-cli
    │   └── main.go
    └── cli
        └── commands.go

サーバを動かしてみる

$ go run *.go

でサーバが立ち上がります.立ち上がったサーバは何も意味ある動作しませんが,/bottles/:bottleID というエンドポイントが出来ているのでここに curl とかでアクセスするとレスポンスがあります.

$ curl -XGET localhost:8080/bottles/1
HTTP/1.1 200 OK
Content-Type: application/vnd.goa.example.bottle+json
Date: Sun, 11 Sep 2016 04:31:20 GMT
Content-Length: 29

{"href":"","id":0,"name":""}

また,クライアントも同時に出来ているので,クライアントをたたけばレスポンスを得られます.

$ go run tool/cellar-cli/main.go show bottle bottles/1
2016/09/11 13:33:50 [INFO] started id=BMdh+bm3 GET=http://localhost:8080/bottles/1
2016/09/11 13:33:50 [INFO] completed id=BMdh+bm3 status=200 time=3.924862ms
{"href":"","id":0,"name":""}

goagen サブコマンド

指定できるサブコマンドは以下です.bootstrap は初回にだけ実行するサブコマンドで,main,app,client,swaggerエイリアスになってます.この辺のコマンドは Makefile 作っておいて実行するのがいいと思います.ここでは触れませんが,main コマンドで生成されるファイル以外は編集不要なコードです.実際ファイルを見てみると,先頭部分に The content of this file is auto-generated, DO NOT MODIFY と書かれているが分かると思います.

サブコマンド 説明
bootstrap 初回に実施するコマンド (main/app/client/swaggerを実行する)
main APIサーバの main とコントローラーを作業ディレクトリに生成.このコマンドで生成されるファイルは上書きされない (--forceオプションで上書きを強制可能)
app アプリケーションのテンプレートを app ディレクトリ以下に生成
client APIサーバに対応するクライアントのコードを client / tool ディレクトリ以下に生成
swagger APIサーバの使用を swagger 形式で swagger ディレクトリ以下に出力
js JavaScriptAPIサーバクライアントを js ディレクトリ以下に生成
schema APIJSON スキーマschema ディレクトリに生成
gen サードパーティの generator を利用するときに指定する.gorma とか使いたいときに利用

参考