Goa v3 Getting Started をやってみる

概要

Goa v3 のちょっとした説明からサンプルデザインでのサーバ起動、アクセス、swagger-ui の利用までを簡単にまとめました。

Goa v3 について

Goa はひとつのデザインを書くだけで、サーバ・クライアント、swagger ドキュメントを生成できる API フレームワークです。

最近、プロジェクトが進み、Goa に v2, v3 という2つのバージョンが加わりました。 といっても、v2, v3 の違いは go.mod サポートなし/ありの違いだけです。 私見としては、v2 は過渡期に用意されたという位置づけで、v3 に移行していくのかなと思います。 また、go 1.13 から GO111MODULE=auto の振る舞いが変更されて、go.mod 対応必須の環境が整いつつあることを考えると、 これから Goa の新しいバージョンを試すなら v3 をやっていくのがいいのではないかと思います。

Goa v1 と v3 の違い

v3 では v1 での知見が生かされて、機能がかなり整理され、個々の DSL の位置づけなどが明確になりました。 また、v3 では HTTP だけでなく、gRPC をサポートすることになり、HTTP と gRPC の2つのトランスポートをほとんど意識することなく、 ビジネスロジックを作成することが出来ます。

もちろん swagger ドキュメントの生成もサポートしています。

機能が整理されたので、v3 は v1 とでは DSL の書き方がかなり変わった部分もありますが、DSL が整理されたので、 慣れれば v1 よりもデザインの見通しがよさそうです。

ドキュメント

Goa v3 のドキュメントは以下です。

goa.design

また、日本語バージョンもあります。

goa.design

準備

  • Goa v3 を go get します
    • インストール先は $GOPATH/goa.desing/goa であることに注意してください
      • go get -u goa.design/goa/v3
      • go get -u goa.design/goa/v3/...
  • 以下の2つを用意します。これは、goa.desing/goa に入っている Makefile を利用して、make depend して用意することも出来ます。

参考:make depend

depend:
        @go get -v $(DEPEND)
        @go install github.com/hashicorp/go-getter/cmd/go-getter && \
                go-getter https://github.com/google/protobuf/releases/download/v$(PROTOC_VERSION)/$(PROTOC).zip $(PROTOC) && \
                cp $(PROTOC_EXEC) $(GOBIN) && \
                rm -r $(PROTOC) && \
                echo "`protoc --version`"
        @go install github.com/golang/protobuf/protoc-gen-go
        @go get -t -v ./...
  • デザインを作成するモジュール用ディレクトリを用意します(プロジェクトのディレクトリになります)
    • どこかに calc というフォルダを用意します
    • メモ:GoLandを利用している場合は go mod を有効にしておきましょう

Getting Started

goa.desing の Getting Started に沿って説明していきます。

デザインを用意してひな形コードを生成

  • 作業フォルダを用意します
    • (go 1.13 より前のバージョンでは環境変数 GO111MODULE=on で作業します)
  • goa.desing から calc のデザインをコピーして、desing/calc.go として保存します
    • swagger の位置は ../../gen/http/openapi.json になっていますが、実行位置からの相対位置なので、作業ディレクトリで起動するなら ./gen/http/openapi.json にしておきましょう
package design

import (
    . "goa.design/goa/v3/dsl"
)

var _ = API("calc", func() {
    Title("Calculator Service")
    Description("Service for adding numbers, a Goa teaser")
    Server("calc", func() {
        Host("localhost", func() {
            URI("http://localhost:8000")
            URI("grpc://localhost:8080")
        })
    })
})

var _ = Service("calc", func() {
    Description("The calc service performs operations on numbers.")

    Method("add", func() {
        Payload(func() {
            Field(1, "a", Int, "Left operand")
            Field(2, "b", Int, "Right operand")
            Required("a", "b")
        })

        Result(Int)

        HTTP(func() {
            GET("/add/{a}/{b}")
        })

        GRPC(func() {
        })
    })

    Files("/openapi.json", "./gen/http/openapi.json")
})

goa gen

  • go mod init calc します(パッケージ名 calc を指定します)
  • goa gen calc/design
    • ここで指定している calc は mod に指定したパッケージ名

goa gen を実行すると、gen ディレクトリ以下に生成されたコードが配置されます。 gen 以下のコードは goa gen するたびに上書きされます。ユーザーが gen ディレクトリ以下のファイルを編集することは ありません

goa example

example で作成されるファイルはユーザーが調整するためのサンプル実装という位置づけです。 なので、ファイルがすでに存在する場合は 上書きされません。 上書きしたい場合はすでにあるファイルを消したり、待避したりしてから goa example します。

ビジネスロジックを実装

サービスメソッド(v1 ではコントローラと呼んでいたもの)を実装します。

calc.go というファイルがプロジェクト直下に生成されるので、このサービスメソッドを実装します。

    // Add implements add.
    func (s *calcsrvc) Add(ctx context.Context, p *calc.AddPayload) (res int, err error) {
       s.logger.Print("calc.add")
       return p.A + p.B, nil     // ← a と b を足し算するビジネスロジックを足しました
    }

起動

サーバをビルド&起動します。

ビルド

$   go build ./cmd/calc

起動

$  ./calc

サンプルでは、HTTP と gRPC の2つのトランスポートでサーバが起動します。

  • HTTP port:8000
  • gRPC port :8080

試しに HTTP にアクセスしてみます。a=1 b=2 でアクセスして 3 が返ってくる例です。

$ curl -iii -XGET localhost:8000/add/1/2
    HTTP/1.1 200 OK
    Content-Type: application/json
    Date: Mon, 10 Jun 2019 08:45:02 GMT
    Content-Length: 2
    
    3

このときのサーバログは以下のようになります。

$ ./calc
[calcapi] 16:08:56 HTTP "Add" mounted on GET /add/{a}/{b}
[calcapi] 16:08:56 HTTP "./gen/http/openapi.json" mounted on GET /openapi.json
[calcapi] 16:08:56 serving gRPC method calc.Calc/Add
[calcapi] 16:08:56 HTTP server listening on "localhost:8000"
[calcapi] 16:08:56 gRPC server listening on "localhost:8080"
[calcapi] 16:09:00  id=YIHAytTy req=GET /add/1/2 from=127.0.0.1
[calcapi] 16:09:00 calc.add
[calcapi] 16:09:00  id=YIHAytTy status=200 bytes=2 time=235.817µs
[calcapi] 16:09:04  id=9at8zmRX method=/calc.Calc/Add bytes=4
[calcapi] 16:09:04 calc.add
[calcapi] 16:09:04  id=9at8zmRX status=OK bytes=2 time=201.201µs

クライアントをビルド

HTTP は curl で確認できますが、gRPC はそうもいかないのでクライアントを用意します。 クライアントもすでに goa example で生成されているので、ビルドするだけです。

$ go build ./cmd/calc-cli

メモ: calc-cli$GOBIN/calc-cliに配置されます

実際クライアントを使ってみます。まずは gRPC にアクセスしてみます。

    $ calc-cli --url="grpc://localhost:8080" calc add --message '{"a": 1, "b": 2}'
    3

クライアントは HTTP にも利用可能です。

$ calc-cli --url "http://localhost:8000" calc add -a 1 -b 2
3

swagger-ui

サーバは swagger.json を返してくるだけにして、別途 docker で swagger-ui を立ち上げておきます。

docker-compose.yaml

    swagger-ui:
      environment:
        - API_URL=http://localhost:8000/openapi.json
      image: swaggerapi/swagger-ui
      ports:
        - "7777:8080"

docker-compose up -d すると localhost:7777 で swagger-ui が立ち上がるはずです。

CORS設定が必要なので、CORSをデザインに入れます。swagger-uiのCORS設定についての説明はここにあります。

https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/cors.md

v3では、CORS の DSLプラグインに切り出されています。

https://github.com/goadesign/plugins/tree/v3/cors

CORSのDSLは、APIServiceの下に設定できます。ここでは、localhost:7777からのアクセスを許容します。

    package design
    
    import (
       . "goa.design/goa/v3/dsl"
       cors "goa.design/plugins/v3/cors/dsl"
    )
    
    var _ = API("calc", func() {
       Title("Calculator Service")
       Description("Service for adding numbers, a Goa teaser")
       Server("calc", func() {
          Host("localhost", func() {
             URI("http://localhost:8000")
             URI("grpc://localhost:8080")
          })
       })
       cors.Origin("http://localhost:7777", func() {
          cors.Headers(" Content-Type", "api_key", "Authorization")
          cors.Methods("GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS")
       })
    })

f:id:ikawaha:20190907162415p:plain

swagger-ui が利用できるので gRPC を開発しているときもデバッグが楽になりそうです。

さいごに

最近、Goa v3 の開発が活発になってきて、いろいろ修正パッチがあたるようになってきました。 DSL が整理されて再設計されたことで、issue に対する対応も早くなっている気がします。 まだ細かな問題はあるかもしれませんが、積極的に使って育てていきたいと思えるフェーズには入っているのではないかと思います。

Happy hacking!