Revelからechoへの移行メモ

## モチベーション
- ここ半年以上Revelが死んでいるので折を見て移行したかった
- 未熟なGopherとしてRevelの充実した機能に乗っかり続けることへの危機感があった

## 移行するアプリ

- 個人で作っているplayground的プロジェクト
- コードベースはそれほど大きくない
    - 簡単なcontrollerが5つくらい
- クーロンjobとlogger以外はRevel標準の仕組みを使用
- かといってRevelにズブズブというわけでもない
    - 小さいアプリなのでRevelの基本的な機能しか使っていなかった

echoを触り始めて数日で書いているので勘違い（echoにこういう機能はない、とか）あるかもしれない。

`minimalist Go web framework`を標榜するechoでは自分で記述を追加する部分が多かった印象（良く言えばカスタマイズ性が高い）。

## ホットリロード

### :ghost: BEFORE
revelがデフォルトでやってくれていた。

### :angel_tone2: AFTER
echoにはない。
いちいちビルドするのもさすがに面倒なのでgodoというGo製タスクランナーを使う。JavascriptでいうところのGulpという認識。
https://github.com/go-godo/godo

アプリは以下のようなディレクトリ(appディレクトリ)で開発している。appディレクトリ内のgo, gohtmlファイルの変更をwatchさせる。

- :file_folder: app
    - :file_folder: modelsとかhandlersとか
    - :file_folder: views: 中に.gohtmlファイル
    - :page_facing_up: main.go

appディレクトリ直下にGododirというディレクトリを作成し、その中にmain.goファイルを作成する。とりあえずホットリロードができればいいのでシンプル。

```go:Gododir/main.go
package main

import do "gopkg.in/godo.v2"

func tasks(p *do.Project) {
	p.Task("server", nil, func(c *do.Context) {
		c.Start("main.go", nil)
	}).Src("**/*.{go,gohtml}").
		Debounce(3000)
}

func main() {
	do.Godo(tasks)
}
```

あとはappディレクトリで`godo server -w`コマンドを実行すればok

注意点としてはファイル名パターンの指定でカンマ,の後にスペースを空けるとgohtmlファイルの変更が認識されなくなる。

o `Src("**/*.{go,gohtml}")`
x `Src("**/*.{go, gohtml}")`

参考
[Docker内のGo製Webアプリをソース変更後リロードするだけで確認できるようにする](http://blog.flup.jp/2016/02/18/golang_docker_hot_reload/)

## コンフィグ

### :ghost: BEFORE
Revelのapp.confファイルに記述。
https://revel.github.io/manual/appconf.html

### :angel_tone2: AFTER
echoにはない。
前に見つけて良さそうと思っていたviperを使用することにした。

https://github.com/spf13/viper

Revelのコンフィグは一つのファイルにdevelop, productionごとの設定値、環境変数まで一緒くたに書ける便利さがあった反面、ただ順番に値を書いていくだけ、階層構造も（たぶん）作れなかったので気持ち悪さのようなものがあった。あとRevel側で使用する設定もいろいろあってごちゃごちゃしていた。

viperはymlなりjsonなり好きな記法で階層構造を作って設定を記述できるのが良く、値を取り出す処理もシンプルでいい。

参考
[[翻訳+α] Go言語の設定ファイルライブラリ Viper](http://qiita.com/hachi8833/items/fe3d5d6b1285c5ef1821)

## 静的ファイル
### :ghost: BEFORE
Revelのroutesファイルに記述
`GET     /public/*filepath                       Static.Serve("public")`

### :angel_tone2: AFTER
echoでは

```go
e := echo.New()
e.Static("/public", "public")
```

## ルーティング

### :ghost: BEFORE

routesファイルにRevelの独自記法

```:routes
GET　　　/　　　　　　　　　　Home.Index
```

### :angel_tone2: AFTER

```go:main.go
e.GET("/", handlers.HomeIndex)
```

## コントローラ

### :ghost: BEFORE

ORMにGormを使っているのでこういうController structを作っていた。

```go
type GormController struct {
	*revel.Controller
	txn *gorm.DB
}
```

コントローラファイル

```go:homeController.go
type Home struct {
	GormController
}

func (c Home) Index(id int) revel.Result {
	c.RenderArgs = "テンプレートで使うデータ"
	return c.RenderTemplate("home/index.gohtml")
}
```

### :angel_tone2: AFTER
Revelではcontrollerと呼んでいたがGoっぽくhandlerと呼ぶことに。

```go
func HomeIndex(c echo.Context) error {
	id := c.Param("id")
	return c.Render(http.StatusOK, "home", "テンプレートで使うデータ")
}
```

上記の`return c.Render(...`は[echoのドキュメント](https://echo.labstack.com/guide/templates)通りの記述だがここで`err != nil`となってもそのエラー内容はどこも表示してくれない。（:pencil2: 次項に追記あり ）
Revelでは丁寧にエラーを表示してくれていたがechoは以下のメッセージをブラウザに表示するだけだった。

> {"message": "Internal Server Error"}

~~なのでこのようなラッパーを作って各handlerから使うようにした。~~ →あまりよくなかった

```go
func render(c echo.Context, template string, vv viewvars) error {
	err := c.Render(http.StatusOK, template, vv)
	if err != nil {
		...
	}
	return err
}

```

#### :pencil2: echoのエラーハンドリングについて

エラーをどこも表示してくれないのでラッパーを作った、という点に関してコメントで指摘をいただいたので追記。

echoではhandlerが返すエラーがnilでなかったときecho.HTTPErrorHandlerを呼んでエラーハンドリングしている。

https://github.com/labstack/echo/blob/master/echo.go#L531

HTTPErrorHandlerにはデフォルトで[DefaultHTTPErrorHandler](https://github.com/labstack/echo/blob/master/echo.go#L286)がセットされる。
上記でエラー発生時に画面に`{"message": "Internal Server Error"}`しか表示されないと書いたがそれはDefaultHTTPErrorHandlerが返していたものだった。

DefaultHTTPErrorHandlerは詳細なエラー内容を表示してくれないのでラッパーを作ったがそれよりはHTTPErrorHandlerをカスタマイズしたほうがいいのだろう。

```go
e.HTTPErrorHandler = func(err error, c echo.Context) {
	fmt.Println(err) // 標準出力へ
	c.JSON(http.StatusInternalServerError, err.Error()) // ブラウザ画面へ
}
```

[echoのガイド](https://echo.labstack.com/guide/error-handling)に

> Echo advocates centralized HTTP error handling by returning error from middleware and handlers

とある通り、ラッパーを作るのはエラーハンドリングを一箇所に集約しているechoの構成に反している。そもそもラッパーを作っても裏ではHTTPErrorHandlerが動いているので処理の重複になってしまっている。

## テンプレート

一番移行に苦労した点。
テンプレートファイルを入れているviewsフォルダは以下のような構成（各ハンドラに対応したディレクトリに分ける）をしている。

- :file_folder: views
    - :file_folder: home
        - :page_facing_up: index.gohtml
    - :file_folder: work
        - :page_facing_up: index.gohtml

### :ghost: BEFORE

controllerで階層を含めてファイル指定。これだけ。
`return c.RenderTemplate("home/index.gohtml")`

### :angel_tone2: AFTER

基本は[echoのドキュメント](https://echo.labstack.com/guide/templates)に従う。

テンプレートをパースするところ

```go
t := &Template {
	template.Must(template.ParseGlob("views/**/*.gohtml")),
}
e.Renderer = t
```

handler内でレンダーをするところで渡している home はテンプレートの名前。

```go
func HomeIndex(c echo.Context) error {
	return c.Render(http.StatusOK, "home", "テンプレートで使うデータ")
}
```

この名前はテンプレートの中で define で定義した文字列と対応する。

```:home/index.gohtml
{{ define "home" }}
...
{{ end }}
```

こういう名前をつける以外にも直接ファイル名で指定することもできる。この場合ディレクトリ階層を指定することはできないのでファイル名はviewsディレクトリ全体で一意にする必要がある（と思う）。

```
return c.Render(http.StatusOK, "home.gohtml", "")
```

## テンプレートメソッド

### :ghost: BEFORE

RevelのCustom Functionを使用
https://revel.github.io/manual/templates.html#CustomFunctions

### :angel_tone2: AFTER

テンプレートをパースするところに記述を追加する必要。

```go
f := template.FuncMap{}
t := &Template{
	templates: template.Must(template.New("").Funcs(f).ParseGlob("views/**/*.gohtml")),
}
e.Renderer = t
```

[Template and custom function; panic: function not defined](http://stackoverflow.com/questions/17843311/template-and-custom-function-panic-function-not-defined)

Revelにデフォルトで入っていたfunctionで使いたいものはこのあたりを参考に移植した
https://github.com/revel/revel/blob/master/template.go#L45

## セッション

### :ghost: BEFORE

Revelにデフォルトで機能がある
https://revel.github.io/manual/sessionflash.html

### :angel_tone2: AFTER

echoにはデフォルトでない。のでミドルウェアを使用
https://github.com/ipfans/echo-session

## インターセプター

Revelにあるコントローラの実行前後にメソッド実行を挿入できるやつ

### :ghost: BEFORE

https://revel.github.io/manual/interceptors.html

### :angel_tone2: AFTER

echoにはもちろんないがcontextのカスタマイズで対応できる（と思う）。

https://echo.labstack.com/guide/context

```go
e.Use(func(h echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		// handler実行前の何かしらの処理
		defer after()
		return h(cc)
	}
})

func after() {
	// handler実行後の何かしらの処理
}
```