7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

はじめに

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

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する形でもいいですね。
クラス図は前者のイメージ

class.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定義はこんな感じで行います。

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クラスを作成します。定義の有無を判別する為に、ちょっと回りくどい実装になってしまった。

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で生成します。

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で登録。

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がいらなくなりました。

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。

handle_passphraseInfo.go
type HandlePassphraseInfo struct {
    MsgHandler
}

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

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)
    }
7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?