スタディサプリ Product Team Blog

株式会社リクルートが開発するスタディサプリのプロダクトチームのブログです

gorilla/mux から chi に移行する

はじめに

こんにちは!スタディサプリで開発者をしている @pankona です。今日は、最近スタディサプリでも頻繁に使われるようになってきた Go 言語の話をします。

先日、GitHub 上の gorilla/mux リポジトリアーカイブされました。gorilla/mux は Go 言語での HTTP ルーター選択肢の一つで、シンプルながら必要十分なルーティング機能を提供するライブラリです。主観的な見解かもしれませんが、このライブラリは広く使われ、支持を得ていたと感じています。私のお気に入りのライブラリの一つでもあります。スタディサプリで稼働している一部のマイクロサービスでは現在も gorilla/mux を使用しています。

ライブラリがアーカイブされるということは、その開発が停止し、新機能の追加やセキュリティアップデートが提供されなくなるということです。また、新しい Go のバージョンとの互換性も保証されないという問題もあります。新しいプロジェクトで gorilla/mux を使用することは推奨できないと言えますし、現在 gorilla/mux を使用しているサービスについては他のライブラリへの代替を検討する必要があるでしょう。

私が関わっているマイクロサービスでは、gorilla/mux の代わりに chi を用いることにしました。この記事では、gorilla/mux から chi への移行のメリットと、実際にコードを書き換えるとどうなるかを、gorilla/mux と chi のコード比較を通じてご紹介します。

chi を移行先にする利点

gorilla/mux と chi は API が非常に似ています。gorilla/mux で書かれたコードの大部分を再利用しつつ chi に移行することができます。 具体的には以下の点が共通しています。

パスの記述スタイルが似ている

両方のライブラリとも、ルーティングを定義するための表現がよくに似ています。たとえば、動的パスパラメータはどちらも /{param} の形式 (中括弧で変数を表す) で表現されます。

ミドルウェアをセットする方法が似ている

両ライブラリとも Use メソッドを利用してミドルウェア関数を追加します。一見すると見た目上はまったく同じです。

HTTP ハンドラとして http.HandlerFunc をとる

gorilla/mux と chi は共に、http.HandlerFunc を HTTP ハンドラとして扱います。gorilla/mux でハンドラとして扱っていたオブジェクトは chi でもそのまま利用できます。

gorilla/mux を使ったコードと chi を使ったコードの比較

gorilla/mux なコードと chi なコードを比較してみます。 まず、以下に gorilla/mux を使ったサンプルコードを示します。

package main

import (
    "encoding/json"
    "io"
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

type Person struct {
    Name string `json:"name"`
}

// ミドルウェア
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println("Request received")

        next.ServeHTTP(w, r)

        log.Println("Request handled")
    })
}

// パスへパラメータを利用するハンドラ
func yourPathVarHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    age := vars["age"]
    w.Write([]byte("You are " + age + " years old.\n"))
}

// クエリパラメータを利用するハンドラ
// gorilla/mux と chi ではクエリパラメータを利用するハンドラの書き方が同じですが、一応載せておきます
func yourQueryHandler(w http.ResponseWriter, r *http.Request) {
    queryParams := r.URL.Query()
    name := queryParams.Get("name")
    if name != "" {
        w.Write([]byte("Hello, " + name + "!\n"))
    } else {
        w.Write([]byte("Hello, stranger!\n"))
    }
}

// リクエストボディを利用するハンドラ
// gorilla/mux と chi ではリクエストボディを利用するハンドラの書き方が同じですが、一応載せておきます
func yourBodyHandler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    defer r.Body.Close()

    var person Person
    json.Unmarshal(body, &person)

    w.Write([]byte("Hello, " + person.Name + "!\n"))
}

func main() {
    // gorilla/mux のルーターを作成
    r := mux.NewRouter()

    // ミドルウェアのセット
    r.Use(loggingMiddleware)

    // ハンドラのセット
    r.HandleFunc("/hello/{age}", yourPathVarHandler).Methods(http.MethodGet)
    r.HandleFunc("/hello", yourQueryHandler).Methods(http.MethodGet)
    r.HandleFunc("/hello", yourBodyHandler).Methods(http.MethodPost)

    http.ListenAndServe(":8000", r)
}

次に、このコードを chi に書き換えたものを示します。

package main

import (
    "encoding/json"
    "io"
    "log"
    "net/http"

    "github.com/go-chi/chi/v5"
)

type Person struct {
    Name string `json:"name"`
}

// ミドルウェア
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println("Request received")

        next.ServeHTTP(w, r)

        log.Println("Request handled")
    })
}

// パスへパラメータを利用するハンドラ
func yourPathVarHandler(w http.ResponseWriter, r *http.Request) {
    age := chi.URLParam(r, "age")
    w.Write([]byte("You are " + age + " years old.\n"))
}

// クエリパラメータを利用するハンドラ
// gorilla/mux と chi ではクエリパラメータを利用するハンドラの書き方が同じですが、一応載せておきます
func yourQueryHandler(w http.ResponseWriter, r *http.Request) {
    queryParams := r.URL.Query()
    name := queryParams.Get("name")
    if name != "" {
        w.Write([]byte("Hello, " + name + "!\n"))
    } else {
        w.Write([]byte("Hello, stranger!\n"))
    }
}

// リクエストボディを利用するハンドラ
// gorilla/mux と chi ではリクエストボディを利用するハンドラの書き方が同じですが、一応載せておきます
func yourBodyHandler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    defer r.Body.Close()

    var person Person
    json.Unmarshal(body, &person)

    w.Write([]byte("Hello, " + person.Name + "!\n"))
}

func main() {
    // chi のルーターを作成
    r := chi.NewRouter()

    // ミドルウェアのセット
    r.Use(loggingMiddleware)

    // ハンドラのセット
    r.Get("/hello/{age}", yourPathVarHandler)
    r.Get("/hello", yourQueryHandler)
    r.Post("/hello", yourBodyHandler)

    http.ListenAndServe(":8000", r)
}

いかがでしょうか。あまり差がないという気がしませんか。 これらの例からわかるように、gorilla/mux から chi への移行した場合は大部分のコードを流用できます。移行はそれほど難しくないと言えるでしょう。

まとめ

gorilla/mux から chi に移行する例を紹介しました。gorilla/mux と chi は書き味が似ているので、移行はそれほど難しくありません。gorilla/mux から何かのライブラリへの移行を検討している場合は、chi を検討してみてはいかがでしょうか。

スタディサプリでも Go を使う機会がぼちぼち多くなってきました。Go Conference 2023 にもシルバースポンサーとして参加します。Go Conference ではオフィスアワーもしていますので、よかったら遊びに来てください (株式会社リクルートの名前で参加しています)。 一緒に開発する仲間を募集しています。興味のある方は Web Application Engineer の募集要項 をぜひチェックしてみてください!

参考リンク