Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 1
Go言語でのWeb APIの作り方3選
虎の穴ラボ 藤原佳顕
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 2
アジェンダ
● 動機
● 前提事項
● 生Go(net/http)
● Gin
● Open API(Swagger)
● まとめ
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 3
自己紹介
● 名前:藤原佳顕
● 仕事:新規サービス系(Fantia等)
● 好きなもの:シューティングゲーム、格闘ゲーム、音楽ゲーム
● 好きな言語:RustとかClojureとか
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 4
動機
普段Rails使ってるけど簡単な APIを作るんだったら違う言語
も使ってみたいなぁ
社内で一部Go言語使ってるし、最近流行ってるから良さそう
けどWeb APIの作り方色々あってどれが良いのかわからな
いから作って比較してみよう!
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 5
前提事項
● 書いてる人はGo初心者です
● Go1.16.3
● Intel版 Mac Book Pro
● Web APIを作ることを目的にするのでHTMLレンダリングなどは度外視
○ 同様にフルスタック系のフレームワークも選外
● APIの形式はJSON
● 時間の都合で今回はGET系のみ
● 2021/04時点での情報
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 6
前提事項
type Person struct {
Name string `json:"name"`
Height int `json:"height"`
Mass int `json:"mass"`
HomeWorld string `json:"home_world"`
Films []string `json:"filmes"`
}
type Payload struct {
Data Person `json:"data"`
}
type Response struct {
Status int `json:"status"`
Result string `json:"result"`
Payload Payload `json:"payload"`
}
{
"status": 200,
"result": "ok",
"payload": {
"data": {
"name": "Luke Skywalker",
"height": 172,
"mass": 77,
"home_world": "https://swapi.dev/api/planets/1/",
"filmes": [
"https://swapi.dev/api/films/2/",
"https://swapi.dev/api/films/6/",
"https://swapi.dev/api/films/3/",
"https://swapi.dev/api/films/1/",
"https://swapi.dev/api/films/7/"
]
}
}
}
GETのレスポンスは以下 (SWAPIの抜粋+α)
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 7
その1:net/http
func getSwPersonHandler(w http.ResponseWriter, r *http.Request) {
// point: HTTPメソッドをチェックしたければ自前で実装が必要
if r.Method != "GET" {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
person := Person{
"Luke Skywalker",
172,
77,
"https://swapi.dev/api/planets/1/",
[]string{
"https://swapi.dev/api/films/2/",
"https://swapi.dev/api/films/6/",
"https://swapi.dev/api/films/3/",
"https://swapi.dev/api/films/1/",
"https://swapi.dev/api/films/7/",
}}
payload := Payload{Data: person}
ping := Response{http.StatusOK, "ok", payload}
// point: JSONのマーシャル/アンマーシャルも自前
dump, err := json.Marshal(ping)
if err != nil {
http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, string(dump))
}
func main() {
var httpServer http.Server
http.HandleFunc("/", getSwPersonHandler)
log.Println("start http listening :18888")
httpServer.Addr = ":18888"
log.Println(httpServer.ListenAndServe())
}
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 8
その1:net/http
● 標準ライブラリだけで作れるので依存が無い
● 最低限のルーティングなどは直感的でわかりやすい
● HTTPメソッドの判別は自前でやらないと行けない
○ ルーティングライブラリを使うなどもありかも?
● 単なるAPIであれば良いかも
● 依存がまったくないというメリットが大きい
○ フレームワーク等のアプデに振り回されることがない
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 9
その2:Gin
func buildResponse() Response {
person := Person{
"Luke Skywalker",
172,
77,
"https://swapi.dev/api/planets/1/",
[]string{
"https://swapi.dev/api/films/2/",
"https://swapi.dev/api/films/6/",
"https://swapi.dev/api/films/3/",
"https://swapi.dev/api/films/1/",
"https://swapi.dev/api/films/7/",
}}
payload := Payload{Data: person}
return Response{http.StatusOK, "ok", payload}
}
func getSwPersonHandler(c *gin.Context) {
res := buildResponse()
// point: 自前でハンドラーは実装→データをどこから取るかはシステム次第なので
c.JSON(http.StatusOK, res)
}
func main() {
r := gin.Default()
// point: HTTPメソッドを固定できるメソッドが用意されている
r.GET("/", getSwPersonHandler)
r.Run()
}
パフォーマンスを売りにしてるフレームワーク
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 10
その2:Gin
● 内部的にはhttprouterというルーティング(マルチプレクサ)ライブラリを使って
る
● HTTPメソッド名がそのまま関数になってるので直感的
● ドキュメントが揃っている
○ https://gin-gonic.com/ja/docs/
● かんたんに使えそう
● ドキュメントを見た限りはWebアプリに必要そうな機能は揃ってそう
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 11
その3:go-swagger
---
swagger: '2.0'
info:
version: 1.0.0
title: SWAPI
paths:
/:
get:
produces:
- application/json
operationId: GetPerson
responses:
200:
description: returns a person
schema:
$ref: "#/definitions/Response"
definitions:
Person:
type: "object"
properties:
name:
type: "string"
height:
type: "number"
format: "int64"
mass:
type: "number"
format: "int64"
home_world:
type: "string"
films:
type: "array"
items:
type: "string"
Open API 2.0に準拠したswaggerを扱うフレームワーク
Payload:
type: "object"
properties:
data:
$ref: "#/definitions/Person"
Response:
type: "object"
properties:
status:
type: "number"
format: "int64"
result:
type: "string"
payload:
$ref: "#/definitions/Payload"
swagger.ymlを用意
↓
swagger generate server -t gen -f ./swagger/swagger.yaml --exclude-main -A swapi
コマンドでソースを生成
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 12
その3:go-swagger
func main() {
var portFlag = flag.Int("port", 3003, "Port to run this service on")
// load embedded swagger file
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
if err != nil {
log.Fatalln(err)
}
// create new service API
api := operations.NewSwapiAPI(swaggerSpec)
server := restapi.NewServer(api)
defer server.Shutdown()
// parse flags
flag.Parse()
// set the port this service will be run on
server.Port = *portFlag
// point: 自前でハンドラーは実装
→データをどこから取るかはシステム次第なので
api.GetPersonHandler = operations.GetPersonHandlerFunc(
func(params operations.GetPersonParams) middleware.Responder {
person := models.Person{/** 略 **/}
payload := models.Payload{Data: &person}
ping := models.Response{Status: http.StatusOK, Result: "ok", Payload: &payload}
return operations.NewGetPersonOK().WithPayload(&ping)
})
// serve API
if err := server.Serve(); err != nil {
log.Fatalln(err)
}
}
main.goを実装
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 13
その3:go-swagger
● swaggerの書き方に慣れていれば良さそう
● ドキュメントと実装が対になるのは良い
● 逆にドキュメントを作る→ソース実装となるのでなれなと時間がかかりそう
● Open API 3.0は実装されていないのでそちらが使いたければ違うものにする
必要がある (https://github.com/go-swagger/go-swagger/issues/1122)
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 14
まとめ
● 作りたいのが単純なAPIかつ依存が少ないほうが良い
○ net/http
● 単純なAPIを作りたいがもう少し機能がほしい
○ gin(、echo)
○ ginと似てたので紹介してませんが、echoというFWも調査してます
● ドキュメントを必ず残しつつAPIを作りたい
○ go-swagger

【とらラボLT】go言語でのweb apiの作り方3選

  • 1.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 1 Go言語でのWeb APIの作り方3選 虎の穴ラボ 藤原佳顕
  • 2.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 2 アジェンダ ● 動機 ● 前提事項 ● 生Go(net/http) ● Gin ● Open API(Swagger) ● まとめ
  • 3.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 3 自己紹介 ● 名前:藤原佳顕 ● 仕事:新規サービス系(Fantia等) ● 好きなもの:シューティングゲーム、格闘ゲーム、音楽ゲーム ● 好きな言語:RustとかClojureとか
  • 4.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 4 動機 普段Rails使ってるけど簡単な APIを作るんだったら違う言語 も使ってみたいなぁ 社内で一部Go言語使ってるし、最近流行ってるから良さそう けどWeb APIの作り方色々あってどれが良いのかわからな いから作って比較してみよう!
  • 5.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 5 前提事項 ● 書いてる人はGo初心者です ● Go1.16.3 ● Intel版 Mac Book Pro ● Web APIを作ることを目的にするのでHTMLレンダリングなどは度外視 ○ 同様にフルスタック系のフレームワークも選外 ● APIの形式はJSON ● 時間の都合で今回はGET系のみ ● 2021/04時点での情報
  • 6.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 6 前提事項 type Person struct { Name string `json:"name"` Height int `json:"height"` Mass int `json:"mass"` HomeWorld string `json:"home_world"` Films []string `json:"filmes"` } type Payload struct { Data Person `json:"data"` } type Response struct { Status int `json:"status"` Result string `json:"result"` Payload Payload `json:"payload"` } { "status": 200, "result": "ok", "payload": { "data": { "name": "Luke Skywalker", "height": 172, "mass": 77, "home_world": "https://swapi.dev/api/planets/1/", "filmes": [ "https://swapi.dev/api/films/2/", "https://swapi.dev/api/films/6/", "https://swapi.dev/api/films/3/", "https://swapi.dev/api/films/1/", "https://swapi.dev/api/films/7/" ] } } } GETのレスポンスは以下 (SWAPIの抜粋+α)
  • 7.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 7 その1:net/http func getSwPersonHandler(w http.ResponseWriter, r *http.Request) { // point: HTTPメソッドをチェックしたければ自前で実装が必要 if r.Method != "GET" { http.Error(w, "Not Found", http.StatusNotFound) return } person := Person{ "Luke Skywalker", 172, 77, "https://swapi.dev/api/planets/1/", []string{ "https://swapi.dev/api/films/2/", "https://swapi.dev/api/films/6/", "https://swapi.dev/api/films/3/", "https://swapi.dev/api/films/1/", "https://swapi.dev/api/films/7/", }} payload := Payload{Data: person} ping := Response{http.StatusOK, "ok", payload} // point: JSONのマーシャル/アンマーシャルも自前 dump, err := json.Marshal(ping) if err != nil { http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, string(dump)) } func main() { var httpServer http.Server http.HandleFunc("/", getSwPersonHandler) log.Println("start http listening :18888") httpServer.Addr = ":18888" log.Println(httpServer.ListenAndServe()) }
  • 8.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 8 その1:net/http ● 標準ライブラリだけで作れるので依存が無い ● 最低限のルーティングなどは直感的でわかりやすい ● HTTPメソッドの判別は自前でやらないと行けない ○ ルーティングライブラリを使うなどもありかも? ● 単なるAPIであれば良いかも ● 依存がまったくないというメリットが大きい ○ フレームワーク等のアプデに振り回されることがない
  • 9.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 9 その2:Gin func buildResponse() Response { person := Person{ "Luke Skywalker", 172, 77, "https://swapi.dev/api/planets/1/", []string{ "https://swapi.dev/api/films/2/", "https://swapi.dev/api/films/6/", "https://swapi.dev/api/films/3/", "https://swapi.dev/api/films/1/", "https://swapi.dev/api/films/7/", }} payload := Payload{Data: person} return Response{http.StatusOK, "ok", payload} } func getSwPersonHandler(c *gin.Context) { res := buildResponse() // point: 自前でハンドラーは実装→データをどこから取るかはシステム次第なので c.JSON(http.StatusOK, res) } func main() { r := gin.Default() // point: HTTPメソッドを固定できるメソッドが用意されている r.GET("/", getSwPersonHandler) r.Run() } パフォーマンスを売りにしてるフレームワーク
  • 10.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 10 その2:Gin ● 内部的にはhttprouterというルーティング(マルチプレクサ)ライブラリを使って る ● HTTPメソッド名がそのまま関数になってるので直感的 ● ドキュメントが揃っている ○ https://gin-gonic.com/ja/docs/ ● かんたんに使えそう ● ドキュメントを見た限りはWebアプリに必要そうな機能は揃ってそう
  • 11.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 11 その3:go-swagger --- swagger: '2.0' info: version: 1.0.0 title: SWAPI paths: /: get: produces: - application/json operationId: GetPerson responses: 200: description: returns a person schema: $ref: "#/definitions/Response" definitions: Person: type: "object" properties: name: type: "string" height: type: "number" format: "int64" mass: type: "number" format: "int64" home_world: type: "string" films: type: "array" items: type: "string" Open API 2.0に準拠したswaggerを扱うフレームワーク Payload: type: "object" properties: data: $ref: "#/definitions/Person" Response: type: "object" properties: status: type: "number" format: "int64" result: type: "string" payload: $ref: "#/definitions/Payload" swagger.ymlを用意 ↓ swagger generate server -t gen -f ./swagger/swagger.yaml --exclude-main -A swapi コマンドでソースを生成
  • 12.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 12 その3:go-swagger func main() { var portFlag = flag.Int("port", 3003, "Port to run this service on") // load embedded swagger file swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "") if err != nil { log.Fatalln(err) } // create new service API api := operations.NewSwapiAPI(swaggerSpec) server := restapi.NewServer(api) defer server.Shutdown() // parse flags flag.Parse() // set the port this service will be run on server.Port = *portFlag // point: 自前でハンドラーは実装 →データをどこから取るかはシステム次第なので api.GetPersonHandler = operations.GetPersonHandlerFunc( func(params operations.GetPersonParams) middleware.Responder { person := models.Person{/** 略 **/} payload := models.Payload{Data: &person} ping := models.Response{Status: http.StatusOK, Result: "ok", Payload: &payload} return operations.NewGetPersonOK().WithPayload(&ping) }) // serve API if err := server.Serve(); err != nil { log.Fatalln(err) } } main.goを実装
  • 13.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 13 その3:go-swagger ● swaggerの書き方に慣れていれば良さそう ● ドキュメントと実装が対になるのは良い ● 逆にドキュメントを作る→ソース実装となるのでなれなと時間がかかりそう ● Open API 3.0は実装されていないのでそちらが使いたければ違うものにする 必要がある (https://github.com/go-swagger/go-swagger/issues/1122)
  • 14.
    Copyright  (C) 2021Toranoana Inc. All Rights Reserved. 14 まとめ ● 作りたいのが単純なAPIかつ依存が少ないほうが良い ○ net/http ● 単純なAPIを作りたいがもう少し機能がほしい ○ gin(、echo) ○ ginと似てたので紹介してませんが、echoというFWも調査してます ● ドキュメントを必ず残しつつAPIを作りたい ○ go-swagger