Share
Embed
Transcript
23 slides
-
自己紹介 鈴木 大介 • Go歴は3年弱 • 2017年4月にDeNAに入社 • AndAppの開発をしています ◦
https://www.andapp.jp ◦ 技術面は golang.tokyo #6 のスライド参照 • 好きなエディタはVim -
はじめに Webサーバの構成要素 1. サーバ ◦ http.Server 2. マルチプレクサ(ルータ) ◦ http.ServeMux
3. ハンドラ ◦ http.Handler, http.HandlerFunc 4. ミドルウェア ◦ panic処理やログ出力など色々 5. 外部インターフェース ◦ http.Client、 sql.DB、 など 今日は • ハンドラ • 外部インターフェース を実装する際のパターンを紹介します。 -
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) } -
★ メリット ◦ すぐに処理を書くことができる ◦ 同一スコープの変数を使用することができる ★ デメリット ◦ 処理が長くなると見通しが悪くなる
軽い処理に向いている。 • ヘルスチェック • モック • など 1. 無名関数 -
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) } -
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) } -
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 -
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) } -
4. メソッド ★ メリット ◦ 処理をメソッド内に閉じることができる ◦ フィールドを通して外からデータを渡すことができる ◦ 複数のメソッドでフィールドを共有することができる
★ デメリット ◦ 事前に型の初期化する必要がある 規模の大きなサーバでよく見られるパターン。 • consul • docker • etcd • prometheus • upspin -
• 対象のパッケージをimportして公開されている関数を直接呼び出す • 必要なパラメータなどは全て呼び出す際に引数で設定する 高レベルな関数が用意されている or した場合に使われる。 ➔ ハンドラの実装パターンによらずに適用できる ➢
細かい制御はしづらい ➢ 単体テストがやりにくくなることも... 1. パッケージ関数 -
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) } -
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) } -
• 使用する毎に初期化を行い、処理を呼び出す 初期化にリクエスト時の情報が必要な場合に使われる。 ◦ Context ◦ パラメータ ◦ など ➔
ハンドラの実装パターンによらずに適用できる ➢ 初期化コストが高いとパフォーマンスへの影響が大きい ➢ 制御のしやすさは『パッケージ関数』とほぼ同じ 3. ローカル変数 -
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) } -
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) } } -
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) }