はじめに
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の仕様をチーム間で調整しながら開発してると結構カオスなことになりそう。
なので、ある程度汎用的な形で実装してみました。
設計
基本方針はこんな感じで考えました。
- GET/POST/PUT/DELETEはURI毎にHandleXXXで実装する。
- HandleXXXの実体はFactory class的なものに生成させ、main処理に依存しないようにする。
- 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)
}