Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Goの実装パターン ~ Webサーバ編 ~

Goの実装パターン ~ Webサーバ編 ~

Daisuke Suzuki

September 29, 2017
Tweet

More Decks by Daisuke Suzuki

Other Decks in Programming

Transcript

  1. の実装パターン
    ~ Webサーバ編 ~
    golang.tokyo #9

    View Slide

  2. 自己紹介
    鈴木 大介
    ● Go歴は3年弱
    ● 2017年4月にDeNAに入社
    ● AndAppの開発をしています
    ○ https://www.andapp.jp
    ○ 技術面は golang.tokyo #6 のスライド参照
    ● 好きなエディタはVim

    View Slide

  3. はじめに
    Webサーバの構成要素
    1. サーバ
    ○ http.Server
    2. マルチプレクサ(ルータ)
    ○ http.ServeMux
    3. ハンドラ
    ○ http.Handler, http.HandlerFunc
    4. ミドルウェア
    ○ panic処理やログ出力など色々
    5. 外部インターフェース
    ○ http.Client、 sql.DB、 など
    今日は
    ● ハンドラ
    ● 外部インターフェース
    を実装する際のパターンを紹介します。

    View Slide

  4. ハンドラ
    ハンドラ = HTTPのリクエストを受け付け、レスポンスを返す処理
    主な実装パターン
    1. 無名関数
    2. ハンドラ関数
    3. クロージャ
    4. メソッド

    View Slide

  5. 1. 無名関数
    package main
    import (
    "net/http"
    )
    func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello, world!\n"))
    })
    http.ListenAndServe(":8080", nil)
    }

    View Slide

  6. ★ メリット
    ○ すぐに処理を書くことができる
    ○ 同一スコープの変数を使用することができる
    ★ デメリット
    ○ 処理が長くなると見通しが悪くなる
    軽い処理に向いている。
    ● ヘルスチェック
    ● モック
    ● など
    1. 無名関数

    View Slide

  7. 2. ハンドラ関数
    package main
    import (
    "net/http"
    )
    func HelloHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello, world!\n"))
    }
    func main() {
    http.HandleFunc("/", HelloHandler)
    http.ListenAndServe(":8080", nil)
    }

    View Slide

  8. ★ メリット
    ○ 処理を関数内に閉じることができる
    ★ デメリット
    ○ 関数の外にあるデータとの接点が少ない
    もっともオーソドックスなスタイル。
    迷ったらコレ。
    2. ハンドラ関数

    View Slide

  9. 3. クロージャ
    package main
    import (
    "net/http"
    )
    func HelloHandler() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello, world!\n"))
    }
    }
    func main() {
    http.HandleFunc("/", HelloHandler())
    http.ListenAndServe(":8080", nil)
    }

    View Slide

  10. 3. クロージャ
    ★ メリット
    ○ 『ハンドラ関数』と同様、処理を関数内に閉じることができる
    ○ 引数を通して外からデータを渡すことができる
    ★ デメリット
    ○ 引数が増えすぎると面倒
    AndAppではそこそこ使われているが、他ではあまり見ない?
    ※middlewareは除く
    goの内部では以下で使われている。
    ● net/http/pprofパッケージ
    func Handler(name string) http.Handler
    ● go tool traceコマンド
    func serveSVGProfile(prof func(w io.Writer) error) http.HandlerFunc

    View Slide

  11. 4. メソッド
    package main
    import (
    "net/http"
    )
    type handler struct{}
    func (h *handler) HelloHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello, world!\n"))
    }
    func main() {
    h := &handler{}
    http.HandleFunc("/", h.HelloHandler)
    http.ListenAndServe(":8080", nil)
    }

    View Slide

  12. 4. メソッド
    ★ メリット
    ○ 処理をメソッド内に閉じることができる
    ○ フィールドを通して外からデータを渡すことができる
    ○ 複数のメソッドでフィールドを共有することができる
    ★ デメリット
    ○ 事前に型の初期化する必要がある
    規模の大きなサーバでよく見られるパターン。
    ● consul
    ● docker
    ● etcd
    ● prometheus
    ● upspin

    View Slide

  13. 外部インターフェース
    外部インターフェース = スコープに存在しないリソースへのアクセサ
    主な実装パターン
    1. パッケージ関数
    2. グローバル変数
    3. ローカル変数
    4. DI

    View Slide

  14. ● 対象のパッケージをimportして公開されている関数を直接呼び出す
    ● 必要なパラメータなどは全て呼び出す際に引数で設定する
    高レベルな関数が用意されている or した場合に使われる。
    ➔ ハンドラの実装パターンによらずに適用できる
    ➢ 細かい制御はしづらい
    ➢ 単体テストがやりにくくなることも...
    1. パッケージ関数

    View Slide

  15. 1. パッケージ関数
    import (
    "io"
    "net/http"
    )
    func UserHandler(w http.ResponseWriter, r *http.Request) {
    res, _ := http.Get("http://localhost:8080/users/1")
    io.Copy(w, res.Body)
    }

    View Slide

  16. ● アクセサをトップレベルに宣言しておく
    ● 初期化や設定などが必要であれば使用する前に行っておく
    初期化した後に使い回すことができる場合に使われる。
    ➔ ハンドラの実装パターンによらずに適用できる
    ➢ 規模が大きくなるにつれて管理が難しくなる
    2. グローバル変数

    View Slide

  17. 2. グローバル変数
    import (
    "database/sql"
    "fmt"
    "net/http"
    )
    var DB *sql.DB
    func setupDB(driver, dsn string) error {
    var err error
    DB, err = sql.Open(driver, dsn)
    return err
    }
    func UserHandler(w http.ResponseWriter, r *http.Request) {
    var name string
    DB.QueryRow("SELECT name FROM user WHERE id = ?", 1).Scan(&name)
    fmt.Fprintf(w, "hello, %s\n", name)
    }

    View Slide

  18. ● 使用する毎に初期化を行い、処理を呼び出す
    初期化にリクエスト時の情報が必要な場合に使われる。
    ○ Context
    ○ パラメータ
    ○ など
    ➔ ハンドラの実装パターンによらずに適用できる
    ➢ 初期化コストが高いとパフォーマンスへの影響が大きい
    ➢ 制御のしやすさは『パッケージ関数』とほぼ同じ
    3. ローカル変数

    View Slide

  19. 3. ローカル変数
    import (
    "fmt"
    "net"
    "net/http"
    )
    func HelloUserHandler(w http.ResponseWriter, r *http.Request) {
    var dialer net.Dialer
    conn, _ := dialer.DialContext(r.Context(), "tcp", ":8000")
    buf := make([]byte, 32)
    conn.Read(buf)
    fmt.Fprintf(w, "hello, %s\n", buf)
    }

    View Slide

  20. ● 別の場所で用意したアクセサを使用するところに注入する
    アクセサのスコープを限定したり、使い分けをしたい時に使われる。
    ➔ 規模が大きくなっても管理しやすく、単体テストも書きやすくなる
    ➢ ハンドラが適用可能なパターンで実装されている必要がある
    ■ 『クロージャ』
    ■ 『メソッド』
    4. DI

    View Slide

  21. 4. DI(クロージャ)
    import (
    "database/sql"
    "fmt"
    "net/http"
    )
    func UserHandler(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
    var name string
    db.QueryRow("SELECT name FROM user WHERE id = ?", 1).Scan(&name)
    fmt.Fprintf(w, "hello, %s\n", name)
    }
    }

    View Slide

  22. 4. DI(メソッド)
    import (
    "database/sql"
    "fmt"
    "net/http"
    )
    type handler struct {
    db *sql.DB
    }
    func (h *handler) UserHandler(w http.ResponseWriter, r *http.Request) {
    var name string
    h.db.QueryRow("SELECT name FROM user WHERE id = ?", 1).Scan(&name)
    fmt.Fprintf(w, "hello, %s\n", name)
    }

    View Slide

  23. おわりに
    Webサーバの基本的な実装パターンを紹介しましたが、ベストなパターンは
    ● プロジェクト(何を作るのか、いつまでに作るのか)
    ● チーム(誰が作るのか)
    によって変わってきます。
    ❖ メリット・デメリットを踏まえて実装パターンを選択することが重要!

    View Slide