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

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

Daisuke Suzuki
September 29, 2017

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

Daisuke Suzuki

September 29, 2017
Tweet

More Decks by Daisuke Suzuki

Other Decks in Programming

Transcript

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

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

    3. ハンドラ ◦ http.Handler, http.HandlerFunc 4. ミドルウェア ◦ panic処理やログ出力など色々 5. 外部インターフェース ◦ http.Client、 sql.DB、 など 今日は • ハンドラ • 外部インターフェース を実装する際のパターンを紹介します。
  3. 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) }
  4. 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) }
  5. 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) }
  6. 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
  7. 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) }
  8. 4. メソッド ★ メリット ◦ 処理をメソッド内に閉じることができる ◦ フィールドを通して外からデータを渡すことができる ◦ 複数のメソッドでフィールドを共有することができる

    ★ デメリット ◦ 事前に型の初期化する必要がある 規模の大きなサーバでよく見られるパターン。 • consul • docker • etcd • prometheus • upspin
  9. 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) }
  10. 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) }
  11. • 使用する毎に初期化を行い、処理を呼び出す 初期化にリクエスト時の情報が必要な場合に使われる。 ◦ Context ◦ パラメータ ◦ など ➔

    ハンドラの実装パターンによらずに適用できる ➢ 初期化コストが高いとパフォーマンスへの影響が大きい ➢ 制御のしやすさは『パッケージ関数』とほぼ同じ 3. ローカル変数
  12. 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) }
  13. 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) } }
  14. 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) }