golangのechoでのREST API実装を整理する

## はじめに

Webサービス開発 && REST APIの勉強にgolangのechoを使っているのですが、API仕様としてURL+メソッド毎に関数登録出来るので、REST APIとの相性がいいのではないかと思います。

```golang:regist_api.go
group := e.Group("/api/")
group.GET(uri, get_handler_fnc)
group.POST(uri, post_handler_fnc)
group.PUT(uri, put_handler_fnc)
group.DELETE(uri, delete_handler_fnc)
```

ただこういう関数登録系のAPIって、何も考えずに作ると関数の量が凄いことになるので、APIの仕様をチーム間で調整しながら開発してると結構カオスなことになりそう。
なので、ある程度汎用的な形で実装してみました。

## 設計

基本方針はこんな感じで考えました。

1. GET/POST/PUT/DELETEはURI毎にHandleXXXで実装する。
2. HandleXXXの実体はFactory class的なものに生成させ、main処理に依存しないようにする。
3. 1と2をつなぐためのインターフェースを定義する。

3の定義はechoの処理を活かして必要なものだけ実装・登録する形でもいいし、GET/POST/PUT/DELETEのデフォルト関数を用意して必要に応じてoverrideする形でもいいですね。
クラス図は前者のイメージ

<img width="800" alt="class.png" src="https://qiita-image-store.s3.amazonaws.com/0/252277/823cf55b-af3d-0043-7668-791d15530915.png">

ファイル構成はこんな感じ。

```
main.go
controller/
 |
 +- handle_xxx.go -- REST APIの実処理はこれで統一
 +- handle_Logout.go
 +- handle_passphrase.go
 +- handle_passphraseInfo.go
 +- handle_userAccount.go
 |
 `- msg_handler.go -- interface, factory classの定義
```

## 実装

### echoの処理を活かした形

goのinterface定義はこんな感じで行います。

```golang:controller/msg_handler.go
type Method int
const (
    METHOD_GET Method = iota
    METHOD_POST
    METHOD_PUT
    METHOD_DELETE
)

type MsgHandler interface {
    GetHandlerFunc(method Method) echo.HandlerFunc
}
```

これを継承して各Handleクラスを作成します。定義の有無を判別する為に、ちょっと回りくどい実装になってしまった。

```golang:controller/handle_passphraseInfo.go
//継承用の構造体定義。メンバーが必要なら追加します。
type HandlePassphraseInfo struct {
}

//頭にthis *構造体を付けてあげると、構造体がthis参照できます。
func (this *HandlePassphraseInfo) GetHandlerFunc(method Method) echo.HandlerFunc{
   //実装したメソッドを返してあげる
   switch method {
    case METHOD_GET: return this.get
    case METHOD_PUT: return this.put
    case METHOD_DELETE: return this.deleteMethod
    default: return nil
    }
}

//bodyがJSONフォーマットなら、c.JSON(ステータスコード, res)で応答
func (this *HandlePassphraseInfo) get(c echo.Context) error {
    /*実処理*/
    return c.JSON(http.StatusOK, res)
}

//他のメソッドも同じような感じで定義
```

上記のようにガツガツ実装したhandleをfactoryで生成します。

```golang:controller/msg_handler.go
func MsgHandlerFactory() map[string]MsgHandler {
    var results map[string]MsgHandler = map[string]MsgHandler{"passphrase":&HandlePassphrase{}, "passphraseInfo":&HandlePassphraseInfo{}, "userAccount":&HandleUserAccount{}, "logout":&HandleLogout{}}
    return results
}
```

最後にmainで登録。

```golang:main.go
    e = echo.New()

    // regist handler
    group := e.Group("/api/")
    msg_handler := controller.MsgHandlerFactory()
    for uri, instance := range msg_handler {
        //Set method GET
        if handler_fnc := controller.GetEchoHandler(instance, controller.METHOD_GET); handler_fnc != nil {
            group.GET(uri, handler_fnc)
        }

        if handler_fnc := controller.GetEchoHandler(instance, controller.METHOD_POST); handler_fnc != nil {
            group.POST(uri, handler_fnc)
        }

        if handler_fnc := controller.GetEchoHandler(instance, controller.METHOD_PUT); handler_fnc != nil {
            group.PUT(uri, handler_fnc)
        }

        if handler_fnc := controller.GetEchoHandler(instance, controller.METHOD_DELETE); handler_fnc != nil {
            group.DELETE(uri, handler_fnc)
        }
    }
```

### override形式

interface定義は少し増えますが、その分他のコードがすっきりします。
interfaceはこんな感じ。GetHandlerFuncがいらなくなりました。

```golang:controller/msg_handler.go
type MsgHandlerIf interface {
    Get(c echo.Context) error
    Post(c echo.Context) error
    Put(c echo.Context) error
    Delete(c echo.Context) error
}

type MsgHandler struct {
}
func (this MsgHandler) Get(c echo.Context) error {return c.NoContent(http.StatusMethodNotAllowed)}
func (this MsgHandler) Put(c echo.Context) error {return c.NoContent(http.StatusMethodNotAllowed)}
func (this MsgHandler) Post(c echo.Context) error {return c.NoContent(http.StatusMethodNotAllowed)}
func (this MsgHandler) Delete(c echo.Context) error {return c.NoContent(http.StatusMethodNotAllowed)}

//return: map[uri]MsgHandler
func MsgHandlerFactory() map[string]MsgHandlerIf {
    var results map[string]MsgHandlerIf = map[string]MsgHandlerIf{"passphrase":&HandlePassphrase{}, "passphraseInfo":&HandlePassphraseInfo{}, "userAccount":&HandleUserAccount{}, "logout":&HandleLogout{}}
    return results
}
```

継承用のコードは構造体の先頭に親を記載します。後はechoの処理を活かした形で実装したget/post/put/deleteをGet/Post/Put/Deleteに変えればOK。

```golang:handle_passphraseInfo.go
type HandlePassphraseInfo struct {
    MsgHandler
}
```

そしてmain。めっちゃすっきりしました。

```golang:main.go
    e = echo.New()

    // regist handler
    group := e.Group("/api/")
    msg_handler := controller.MsgHandlerFactory()
    for uri, instance := range msg_handler {
        group.GET(uri, instance.Get)
        group.POST(uri, instance.Post)
        group.PUT(uri, instance.Put)
        group.DELETE(uri, instance.Delete)
    }
```