技術ブログを開設する
ログイン
もっと気軽にアウトプットできる技術ブログプラットフォーム
この投稿は別サイトからのクロス投稿です(クロス元:https://qiita.com/developer-kiki...

はじめに

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

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

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

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

type Method int  
const (  
    METHOD_GET Method = iota  
    METHOD_POST  
    METHOD_PUT  
    METHOD_DELETE  
)  

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

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

//継承用の構造体定義。メンバーが必要なら追加します。  
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で生成します。

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

最後にmainで登録。

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

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。

type HandlePassphraseInfo struct {  
    MsgHandler  
}

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

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

関連記事

この記事へのコメント

まだコメントはありません
2
@dOpIa1PQNPi5jLrnの技術ブログ
2
このエントリーをはてなブックマークに追加