Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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) }

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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) }

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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) }

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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) }

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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) }

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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) }

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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) }

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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) } }

Slide 22

Slide 22 text

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) }

Slide 23

Slide 23 text

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