こちらはGoアドベントカレンダー17日目の記事です。
背景
自分のチームでの開発では、(ある意味で行き過ぎた時もあった)Microservicesだったり、少なくともMonolithicではなく複数サービスの分割で開発することが多いのですが、Microservicesで開発するにはそれに向いたツールを使う必要が多々あります。
調べてみるとGoには他の言語と比べて比較的Microservices指向なツールが多々あるようです。
過去にはこういった記事も書いてみました。
go言語でのマイクロサービスフレームワークの雑な比較メモ
こちらのスライドも一応ご参考に。
Microservicesに必要とされる要件
Microservicesで開発する上では、およそ下記の要件を踏まえておく必要があるのではないかと思っております。
要素 | 概要 |
---|---|
Service discovery | 他のMicroservicesのエンドポイントを発見しやすくする、スケールアウト時も互いのエンドポイントに追従出来る、など。 |
Logging | サービス運用上のログを集約できる |
Tracing | どのサービスレイヤでパフォーマンス劣化が発生しているか発見 |
Monitoring | 各サービスの状態の監視 |
Load balancing | 負荷を各サーバに分散 |
Message Queue | イベント発生から非同期に処理実行 |
実際にMicroservicesを作る場合の話
必要な要件を並べてみましたが、自分のチームの場合、実際は上記の多くはApplication側で解決せずAWSやGCP、その他SaaS, PaaSの提供する機能で解決するようにしており、例えば下記のように対処可能です。
- Load balancingはAWS ALB
- Message QueueはAWS SQS
- TracingはAWS Xray
などなど。
とすると、Microservicesを開発する上で大切になってくるのは、チームが多くのAPIとやり取りするその過程を円滑にすることだと考えており、過去はそこをswaggerで記述してAPIデザインをレビューし合うことで解決しておりました。
ただし、課題としてAPIドキュメントを作成するフローと実際にAPIを作るフローが一連の流れにならない、ツールとして別々な世界の話になっているため、ドキュメントが腐らないよう配慮する必要が多々出てきます。
このAPIデザイン観点での連携しやすさというのがMicroservicesでは特に求められるのではないでしょうか。
goaとは
goaは、 Design first
を謳うWebフレームワークで、DSLからgeneratorを活用してWebAPIを作り上げていくスタイルを取っています。
goaのDSLは下記のように、リソースやアクション、データの型を定義していくものです。
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")
})
})
Swagger連携
goagenはDSLをベースにして、swaggerのドキュメントを生成してくれます。
swaggerとは、go-swaggerの紹介でも紹介したのですが、Restful APIの仕様記述に関する標準化を目指して作られたフォーマット、およびそのツール群です。
指定のフォーマットでyaml, jsonを記述していくと、swagger-uiなどの利用でドキュメントが生成されます。
goaはswagger
パッケージを利用することで、簡単にswaggerのjsonをAPI経由で返せる様になります。
var _ = Resource("swagger", func() {
Origin("*", func() {
Methods("GET") // Allow all origins to retrieve the Swagger JSON (CORS)
})
Files("/swagger.json", "swagger/swagger.json")
})
上記でdesignに記載した後、goagenし、main.goに下記を記載すると /swagger.json
APIが生え、swagger-uiに食わせて可視化できます。
cs := NewSwaggerController(service)
app.MountSwaggerController(service, cs)
なぜgoaが良いと感じたか
最終的に生成されるパッケージ自体は非常にシンプルで、先に上げた必要とされる要件のうちの多くは提供されません。
これはWebフレームワークです。
ですが、実際にMicroservicesを開発、運用する中で必要要件に関しては言語ではなくAWSなどが提供する仕組みで十分に解決可能で、
大切なのはチーム内でのドキュメント共有とそれが腐りにくい状況を作ることだと感じています。
その観点から見ると、
- デザインを記述
- goagenでコード生成
- swaggerで各位がAPIドキュメントの確認、レビューをする
- 実際のロジックを実装する
- リリースする
というフローが容易に構築できるgoaはMicroservicesに向けたフレームワークとして利用しやすいと感じました。
大切なのはチーム内、チーム間でドキュメントベースでAPIデザインが破綻しないことだと思います。
最後に
デザインファーストなAPI開発というツールは、実のところ go-swaggerなどもあるのですが、goaは生成されたコードも個人的に使いやすく、microservicesを志向している旨の記述もあるため、こうした用途に向けてより進化していくのではないかと期待しています。