LoginSignup
0
0

More than 1 year has passed since last update.

ラップ?、ラッパー?、サランラップ?となっていた僕がラッパー関数に惚れた瞬間

Last updated at Posted at 2021-12-11

この記事は ITRC Advent Calendar 2021 の11日目の記事です.

前の記事は @teapod418 の 最近のAWS CDKを使用してECSを手軽に構築する です。

はじめに

  • 今年の自分は1年通してコツコツとGolangを触ってきました。WebServerを立ててchannelを使用したwebsocket通信をしたり、TwitterAPIを使用して擬似投票機能を作ったり、GoolgeMapAPIを使用してデートプランを提示したりと、、、。こんなに作ってもまだGolangという言語に対しては苦手意識があります。が、この1年でわからなかったことがわかるようになったということもありました。Websocket、channel、ラッパー関数,etc...

ということで今回はラッパー関数について少し紹介していこうと思います。

ラッパー関数とは

  • ラッパー関数。。。自分も以前どこかで聞いたことはありましたがどんな関数なのか全く分かりませんでした。ラップという言葉を聞いて一番最初に思い浮かぶんだのがサランラップでした。ラッパー関数はサランラップのように何かを包み込む関数なのか?(=間違ってはなかった)と思っていました。
  • そんなときにたまたまある1冊の本を読んでいたときにラッパー関数に出会いました。これはしっかり習得するいい機会と思いその本を購入し、実際に本を読み進めていきました。

=> ラッパー関数とは 関数の中に関数を入れる関数!!!まさにサランラップのような関数でした!!!

※あ、ちなみにラッパー関数というのはGolangに関わらず全てのプログラミンング言語で扱える1関数、1ロジックです。

ラッパー関数の利点

  • Closeすべき処理をCloseし忘れるといったことを防ぐことができる
  • 仕様変更に強い

ラッパー関数の注意点

  • ネストしすぎると読みにくいコードになってしまう。

=> 設計に合わせて使用しなければいけない。

ラッパー関数を使用したコード例

main.go
var (
        addr  = flag.String("addr", ":8080", "エンドポイントのアドレス")
        mongo = flag.String("mongo", "localhost", "MongoDBのアドレス")
    )
mux := http.NewServeMux()
mux.HandleFunc("/polls/", withCORS(withVars(withData(db, withAPIKey(handlePolls)))))
graceful.Run(*addr, 1*time.Second, mux)

func withCORS(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Expose-Headers", "Location")
        fn(w, r)
    }
}

func withVars(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        OpenVars(r)
        defer CloseVars(r)
        fn(w, r)

    }
}

func withData(d *mgo.Session, f http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        thisDb := d.Copy()
        defer thisDb.Close()
        SetVar(r, "db", thisDb.DB("ballots"))
        f(w, r)
    }
}

func withAPIKey(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if !isValidAPIKey(r.URL.Query().Get("key")) {
            respondErr(w, r, http.StatusUnauthorized, "不正なAPIキーです")
            return
        }
        fn(w, r)
    }
}

func handlePolls(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "GET":
        handlePollsGet(w, r)
        return
    case "POST":
        handlePollsPost(w, r)
        return
    case "DELETE":
        handlePollsDelete(w, r)
        return
    case "OPTIONS":
        w.Header().Add("Access-Control-Allow-Methods", "DELETE")
        respond(w, r, http.StatusOK, nil)
    }
}

解説

  • 部分的に見ていきましょう

1-1

var (
        addr  = flag.String("addr", ":8080", "エンドポイントのアドレス")
        mongo = flag.String("mongo", "localhost", "MongoDBのアドレス")
    )

mux := http.NewServeMux()
mux.HandleFunc("/polls/", withCORS(withVars(withData(db, withAPIKey(handlePolls)))))

graceful.Run(*addr, 1*time.Second, mux)
  • まず、こちらではHandleFunc関数を使用してユーザがhttp://localhost/polls/にアクセスした際にwithCORS関数を実行するようになっています。

1-2

func withCORS(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Expose-Headers", "Location")
        fn(w, r)
    }
}
  • withCORSではResponseHeaderにオリジンの設定、クライアントからのLocationヘッダーへのアクセス設定などをしたのちに引数で渡されたfn(w, r)関数(= withVars関数)を実行しています。

1-3

func withVars(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        OpenVars(r)
        defer CloseVars(r)
        fn(w, r)

    }
}
  • withVars関数では、まずOpenVars関数というこのプロジェクトで扱う変数を管理している関数を実行しています。
  • 次の行ではdeferを使用してCloseVars関数を実行しています。CloseVars関数ではこのサーバが終了した際に管理していた変数をdeleteしています。
  • 最後の行で引数で渡されたfn(w,r)関数(= withData関数)を実行しています。(1-2と同じですね。)

1-4

func withData(d *mgo.Session, f http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        thisDb := d.Copy()
        defer thisDb.Close()
        SetVar(r, "db", thisDb.DB("ballots"))
        f(w, r)
    }
}
  • withData関数ではMongoDBのセッション値をコピーしています。
  • このコピーを閉じる処理を defer thisDb.Close()で行っています。
  • SetVar関数ではkeyを"db"という名前で"ballots"データベースへの参照を格納しています。
  • そしてまた、最後の行で引数で渡されたfn(w,r)関数(= withAPIKey関数)を実行しています。(1-2と同じですね。)

ここまで

  • ここまででラッパー関数の意味はだいぶわかってきたと思います。
  • ですが、ラッパー関数の魅力についてはいまいち理解できていないのではないのでしょうか。

 ラッパー関数の魅力

簡単にいうと各々の関数で出てきたfn(w,r)関数以前に定義したものは次のラッパー関数以降でも常に引き継がれているということです。
例えば"1-2"で出てきたこの関数

w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Expose-Headers", "Location")

レスポンスにオリジン等の設定を入れている変数w。この変数ネストされている関数全てに引数として使用されていますよね。
ということはwという変数には常にオリジン等の設定が入っているということになります。
本来ならば1関数ごとにオリジン等の設定をしなければいけないのですが、関数をラップすることで一度記述するだけで良くなります。

補足

*deferを使用してクリーンアップ処理している箇所を紹介した方がよかったのかもしれないですが、、、。こちらもラップしていることによりサーバが終了した際にCloseすべき処理を見落すことなくClose処理することができます。

まとめ

ラッパー関数最高!!!
皆さんもぜひラッパー関数を使用してみてください。

参考資料

Go言語によるWebアプリケーション開発
マット・ライアーによる本

0
0
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
0
0