Go Web Frameworks 比較

Go言語にはいろいろなWebフレームワークが存在して、はっきりとしたデファクトスタンダードが決まっていません。

しいて言えば標準パッケージの `net/http` がデファクトですが、世の中ではそこに機能不足を感じた人たちが多くのフレームワークを開発しています。

そこで、いくつかのフレームワークを取り上げて、簡単なベンチマークと、それぞれのフレームワークでのいわゆるHello Worldの書き方をまとめておきます。

これによって、フレームワーク選びの参考になればと思います。

## 対象

- [Bone](https://github.com/go-zoo/bone)
- [Echo](https://github.com/labstack/echo)
- [Gin](https://github.com/gin-gonic/gin)
- [Gocraft](https://github.com/gocraft/web)
- [Goji](https://github.com/zenazn/goji)
- [Gorilla](https://github.com/gorilla/mux)
- [Kami](https://github.com/guregu/kami)
- [Martini](https://github.com/go-martini/martini)

Revel、Beego、Kochaなど、見かけたが入れていないものがいくつかあります。コマンドでスケルトンを作るもの、`net/http` の Handler interface を満たさないものは除外しました。

追加してくれる方はコメントか編集リクエストをお願いします！

Negroni あたりは入れたいですね。

## 個人的な感想

後半を読み飛ばしたい人のために、個人的な好みでオススメをあげておきますと、GinかEchoです。パフォーマンスも良く、いろいろ便利メソッドもあるのでよさそうです。

もっと薄いものが欲しい人は `net/http` が良いでしょう。

## ベンチマーク

`GET /gopher` のリクエストを送って `Hello, gopher!` を返す単純なコードを書いて計測します。

ベンチマークは https://github.com/najeira/go-frameworks-benchmark にあります。フレームワークを追加したい方、ベンチマークの種類を増やしたい方、ぜひ Pull Request をお願いいたします。

なお、大量のハンドラがある場合のルーティングについて知りたい人は https://github.com/julienschmidt/go-http-routing-benchmark も参照してください。

### 時間

||ns/op|
|:--|--:|
|Gin|734|
|Echo|879|
|Kami|1237|
|Goji|1299|
|Bone|1463|
|http|1854|
|Gocraft|1915|
|Gorilla|3944|
|Martini|5823|

GinとEchoが優秀です。Martiniは内部でリフレクションを使っているためか遅いですが、それでもナノ秒の単位ですので、ここがアプリケーションのボトルネックになることはなさそうです。

### allocs

||allocs/op|
|:--|--:|
|Gin|3|
|Echo|5|
|Goji|6|
|Bone|7|
|http|7|
|Kami|9|
|Gocraft|12|
|Gorilla|15|
|Martini|22|

ここでもGinとEchoが優秀です。とはいえ、GoのGCは進歩して性能が上がっていること、アプリケーション内でのアロケーションが支配的になるでしょうから、ここも大きな差はないといってよいでしょう。

### GitHub

||Stars|Contributors|
|:--|--:|--:|
|Martini|7995|93|
|Gin|4857|64|
|Goji|2888|17|
|Echo|2698|28|
|Gorilla|1808|27|
|Gocraft|918|11|
|Bone|823|11|
|Kami|127|4|

Martiniは歴史もあり、初期に多くのStarsを獲得、またContributeも多いようです。次いでGin。また、Echoは2015年スタートですが、かなりの勢いでStarsを獲得しています。

## コード

コード全体は https://github.com/najeira/go-frameworks-benchmark を見てください。ここでは雰囲気を把握してもらえれば。

### Bone

```go
m := bone.New()
m.Get("/:name", handler)
```

```go
func handler(w http.ResponseWriter, r *http.Request) {
  name := bone.GetValue(r, "name")
  fmt.Fprintf(w, "Hello, %s", name)
}
```


### Echo

```go
m := echo.New()
m.Get("/:name", handler)
```

```go
func handler(c *echo.Context) error {
  name := c.Param("name")
  return c.String(200, "Hello, %s", name)
}
```

### Gin

```go
m := gin.New()
m.GET("/:name", handler)
```

```go
func handler(c *gin.Context) {
  name := c.Param("name")
  c.String(200, "Hello, %s", name)
}
```

### Goji

```go
m := goji.New()
m.Get("/:name", handler)
```

```go
func handler(c goji.C, w http.ResponseWriter, r *http.Request) {
  name := c.URLParams["name"]
  fmt.Fprintf(w, "Hello, %s", name)
}
```

### Gocraft

```go
m := gocraft.New(ctx{})
m.Get("/:name", (*ctx).handler)
```

```go
func (c *ctx) handler(w gocraft.ResponseWriter, r *gocraft.Request) {
  name := r.PathParams["name"]
  fmt.Fprintf(w, "Hello, %s", name)
}
```

### Gorilla

```go
m := goriila.NewRouter()
m.HandlerFunc("/{name}", handler)
```

```go
func handler(w http.ResponseWriter, r *http.Request) {
  name := gorilla.Vars(r)["name"]
  fmt.Fprintf(w, "Hello, %s", name)
}
```

### Kami

```go
m := kami.New()
m.Get("/:name", handler)
```

```go
func handler(c context.Context, w http.ResponseWriter, r *http.Request) {
  name := kami.Param(c, "name")
  fmt.Fprintf(w, "Hello, %s", name)
}
```


### Martini

```go
m := martini.Classic()
m.Get("/:name", handler)
```

```go
func handler(w http.ResponseWriter, p *martini.Params) string {
  name := p["name"]
  return fmt.Sprintf("Hello, %s", name)
}
```