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

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

F3dd644c8cfb83c94d46cf3c44604cda?s=47 Daisuke Suzuki
September 29, 2017

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

F3dd644c8cfb83c94d46cf3c44604cda?s=128

Daisuke Suzuki

September 29, 2017
Tweet

Transcript

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

  2. 自己紹介 鈴木 大介 • Go歴は3年弱 • 2017年4月にDeNAに入社 • AndAppの開発をしています ◦

    https://www.andapp.jp ◦ 技術面は golang.tokyo #6 のスライド参照 • 好きなエディタはVim
  3. はじめに Webサーバの構成要素 1. サーバ ◦ http.Server 2. マルチプレクサ(ルータ) ◦ http.ServeMux

    3. ハンドラ ◦ http.Handler, http.HandlerFunc 4. ミドルウェア ◦ panic処理やログ出力など色々 5. 外部インターフェース ◦ http.Client、 sql.DB、 など 今日は • ハンドラ • 外部インターフェース を実装する際のパターンを紹介します。
  4. ハンドラ ハンドラ = HTTPのリクエストを受け付け、レスポンスを返す処理 主な実装パターン 1. 無名関数 2. ハンドラ関数 3.

    クロージャ 4. メソッド
  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) }
  6. ★ メリット ◦ すぐに処理を書くことができる ◦ 同一スコープの変数を使用することができる ★ デメリット ◦ 処理が長くなると見通しが悪くなる

    軽い処理に向いている。 • ヘルスチェック • モック • など 1. 無名関数
  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) }
  8. ★ メリット ◦ 処理を関数内に閉じることができる ★ デメリット ◦ 関数の外にあるデータとの接点が少ない もっともオーソドックスなスタイル。 迷ったらコレ。

    2. ハンドラ関数
  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) }
  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
  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) }
  12. 4. メソッド ★ メリット ◦ 処理をメソッド内に閉じることができる ◦ フィールドを通して外からデータを渡すことができる ◦ 複数のメソッドでフィールドを共有することができる

    ★ デメリット ◦ 事前に型の初期化する必要がある 規模の大きなサーバでよく見られるパターン。 • consul • docker • etcd • prometheus • upspin
  13. 外部インターフェース 外部インターフェース = スコープに存在しないリソースへのアクセサ 主な実装パターン 1. パッケージ関数 2. グローバル変数 3.

    ローカル変数 4. DI
  14. • 対象のパッケージをimportして公開されている関数を直接呼び出す • 必要なパラメータなどは全て呼び出す際に引数で設定する 高レベルな関数が用意されている or した場合に使われる。 ➔ ハンドラの実装パターンによらずに適用できる ➢

    細かい制御はしづらい ➢ 単体テストがやりにくくなることも... 1. パッケージ関数
  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) }
  16. • アクセサをトップレベルに宣言しておく • 初期化や設定などが必要であれば使用する前に行っておく 初期化した後に使い回すことができる場合に使われる。 ➔ ハンドラの実装パターンによらずに適用できる ➢ 規模が大きくなるにつれて管理が難しくなる 2.

    グローバル変数
  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) }
  18. • 使用する毎に初期化を行い、処理を呼び出す 初期化にリクエスト時の情報が必要な場合に使われる。 ◦ Context ◦ パラメータ ◦ など ➔

    ハンドラの実装パターンによらずに適用できる ➢ 初期化コストが高いとパフォーマンスへの影響が大きい ➢ 制御のしやすさは『パッケージ関数』とほぼ同じ 3. ローカル変数
  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) }
  20. • 別の場所で用意したアクセサを使用するところに注入する アクセサのスコープを限定したり、使い分けをしたい時に使われる。 ➔ 規模が大きくなっても管理しやすく、単体テストも書きやすくなる ➢ ハンドラが適用可能なパターンで実装されている必要がある ▪ 『クロージャ』 ▪

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