【Poteto】GolangでWebフレームワークはじめ②【ミドルウェアグループ】
はじめに
- 前回の記事
- レポジトリ
作り始めたWebフレームワーク(Poteto)の機能が充実してきて、簡単なWebAPIを実装できるようになりました(ドキュメントは追いついていないですが、、、)。
今回は、開発の自由度を高めるために、Middlewareグループを導入します。
いつも通り、Echoを参考にしていますが、設計思想は違います。
- Echo
*この記事に乗ってるコードのいくつかは、特に動作確認していないので、雰囲気を味わうイメージでお願いします。。。
middlewareGroupとは
middlewareGroupは、各ルーティングをまとめて、ミドルウェアを適用させる機能です。例えば、Echoの例を見てみましょう。
func main() {
e := echo.New()
admin := e.Group("/admin")
admin.Use(echojwt.WithConfig(config))
admin.GET("/check_admin", CheckAdmin)
e.Run()
}
これを設定することで、/admin/*
に対して、jwtWithConfig
ミドルウェアを適用しています。
これを自作フレームワークに実装することが目標です。
Echoの実装を見る
EchoではGroup
がEcho
へのポインタを持ちます。
type Group struct {
...
echo *Echo
middleware []MiddlewareFunc
}
また、Echo
はGroup()
によってmiddlewareGroupを登録しています。
func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
g = &Group{prefix: prefix, echo: e}
g.Use(m...)
return
}
Group
がEcho
へのポインタを持つおかげで、開発者は、適用されるミドルウェアを意識しながらルーティングを構成することができます。これはすごく良いですよね。
func main() {
e := echo.New()
admin := e.Group("/admin")
admin.Use(echojwt.WithConfig(config))
admin.GET("/check_admin", CheckAdmin) // Groupにルーティング追加するイメージ
e.Run()
}
ただ逆にフレームワーク側の実装はやや複雑になってしまっています。Echoには、そもそも全てのルーティングに対してミドルウェアを適用することができます。
func main() {
e := echo.New()
e.Use(<some middlewares>)
e.Run()
このミドルウェアはどこに入るかというと、Echoのメンバになります。
type Echo struct {
...
middleware []MiddlewareFunc
...
}
そのため、全体に適応しているミドルウェアはEcho
にあり、グループごとに適応するミドルウェアはGroup
に格納されます。もちろんこの複雑な設計がキレイに実装されているので、開発者フレンドリーな作りと言えますね。ただ、私はここをあきらめてシンプルな設計にしました。
Potetoでの実装
PotetoではPoteto
がMiddlewareGroup
を持ちます。つまりPoteto
は全てのルートに適用するミドルウェアを持っていないわけですね。
type poteto struct {
...
middlewareGroup MiddlewareGroup
}
type middlewareGroup struct {
children map[string]MiddlewareGroup
middlewares []MiddlewareFunc
key string
}
では、ミドルウェアを全てのルーティングに適用させるときはどうしているかというと、空文字のミドルウェアグループを作成することで対応しています。これによって、開発者はGroupを意識することなく開発を行うことができます。
func (p *poteto) Register(middlewares ...MiddlewareFunc) {
p.middlewareGroup.Insert("", middlewares...)
}
ミドルウェア自体は前回記事のルーティングと同様にTrie木を用いているので、結局木のトップノードにミドルウェアグループが出来ている訳ですね。
取り出すときには、以下のようにトップパスから見つかったミドルウェアを全て返しています。
func (mg *middlewareGroup) SearchMiddlewares(pattern string) []MiddlewareFunc {
middlewares := []MiddlewareFunc{}
currentNode := mg
middlewares = append(middlewares, mg.middlewares...)
patterns := strings.Split(pattern, "/")
for _, p := range patterns {
if p == "" {
continue
}
if nextNode, ok := currentNode.children[p]; ok {
currentNode = nextNode.(*middlewareGroup)
middlewares = append(middlewares, currentNode.middlewares...)
} else {
...
}
return middlewares
}
実装をシンプルにしたことによる弊害
開発者はミドルウェアグループのことを考えながらルーティングを作成する必要が出来てしまいました。
func main() {
p := poteto.New()
admin := p.Combine("/admin")
admin.Use(echojwt.WithConfig(config))
p.GET("/admin/check_admin", CheckAdmin) // Groupにルーティング追加するイメージ
p.Run()
}
ただ、ここはオリジナリティとして(笑)、フレームワーク自体のシンプルさを優先しました。
終わりに
今回はミドルウェアグループについての話でした。ぜひ興味出たら動かしてみていただけるとありがたいです。
検討
middlewareGroupという名前ですが、探索中に見つかった全てのミドルウェアを返しているため、名前を変えることも検討しています。またはtop以外は、自身のしか返さないとかでしょうか。
Discussion