Closed8

『詳解Go言語Webアプリケーション開発』をやる

yoshinoyoshino

Webサーバーを起動する

  • http.Handler(インターフェース型)はServeHTTPメソッドを実装する必要がある
  • http.Handle
    • 関数のシグネチャ: func Handle(pattern string, handler Handler)
    • 第2引数にhttp.Handlerインターフェースを実装した値を取る
    • 指定したURLパターンに対し、http.Handlerを登録する
  • http.HandleFunc
    • 関数のシグネチャ: func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
    • 第2引数にfunc(ResponseWriter, *Request)型の関数を取る
    • 指定したURLパターンに対し、ハンドラ関数を登録する
    • 内部でハンドラ関数をhttp.HandlerFuncにキャストしてhttp.Handlerとして扱う
  • http.HandlerFunc
    • 型の定義: type HandlerFunc func(ResponseWriter, *Request)
    • func(ResponseWriter, *Request)型に対するアダプタ型
    • ServeHTTPメソッドを実装しているため、http.Handlerとして扱える
    • 通常の関数をhttp.Handlerとして扱えるようにする(登録するわけではない)
    • https://journal.lampetty.net/entry/understanding-http-handler-in-go
  • ListenAndServe関数は2つの方法で呼び出せる。
    • http.Server構造体の値を生成し、そのListenAndServeメソッドを呼び出す
    • トップレベルのhttp.ListenAndServe関数を直接呼び出す
  • net/httpパッケージにおいて、マルチプレクサとは、受信したHTTPリクエストを適切なハンドラに振り分ける、いわゆるルーティングの機能を担うもの
    • そもそもマルチプレクサとは、複数の入力信号を受け取り、それらを選択したり組み合わせたりして、1つの出力信号として出力する装置や回路のこと
yoshinoyoshino

リファクタリングとテストコード

  • main関数
    • プログラムのエントリーポイント(実行の開始点)となる
    • main関数を明示的に呼び出す必要はなく、自動的に呼び出される
  • init関数
    • パッケージの初期化時に呼び出される
    • 暗黙的に宣言されるため、他の場所から参照することはできない
    • 同じプログラム内で複数のinit関数を定義でき、定義された順に実行される
    • main関数より前に実行されるため、main関数の実行には依存しない
// トランスポート層の通信。データを適切なアプリケーションに振り分ける
// ローカルネットワークを、ポート番号pでリッスンする
// システムコール(OSのカーネルが持つサービスを要求するための命令)を使ってソケットを準備している
p := os.Args[1]
l, err := net.Listen("tcp", ":"+p)

// アプリケーション層の通信の準備。HTTPサーバーの構造体を作る
s := &http.Server{
    Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s", r.URL.Path[1:])
    }),
}

// リスナーlでHTTPコネクションの着信を受け付ける
// 無限ループ内でl.Accept()を実行。accept()システムコールを使い、クライアント側からの通信接続を待つ。リクエスト(コネクション)が来るたびに異なるgoroutineで処理する
// Accept()はコネクションが来るまで、呼び出し元のプログラムを待機状態にする
s.Serve(l)

https://www.itmanage.co.jp/column/tcp-ip-protocol/

https://ks888.hatenablog.com/entry/2018/03/19/122612

https://research.nii.ac.jp/~ichiro/syspro98/server.html

yoshinoyoshino

開発環境を整える

マルチステージビルドとは?

Goでアプリケーションを開発する際、Dockerを使ってビルドと実行環境を構築することがよくあります。その際、マルチステージビルドという手法を使うと、最終的なDockerイメージのサイズを小さく抑えることができます。
マルチステージビルドでは、Dockerfileの中で複数のFROM命令を使って、複数のステージ(中間コンテナイメージ)を定義します。

  • 最初のステージ(中間ビルドステージ)では、Goのビルド環境を構築し、ソースコードをビルドして実行ファイルを生成します。
  • 2つ目のステージ(最終ステージ)では、最小限のベースイメージ(例えばalpine)を使い、1つ目のステージでビルドした実行ファイルだけをコピーします。

このようにすることで、ビルドに必要だったGoのSDKやソースコードなどを最終イメージに含めずに済むので、イメージサイズを大幅に削減できます。
マルチステージビルドのメリットをまとめると以下のようになります:

  • 最終的なDockerイメージのサイズが小さくなる
  • Dockerfileの記述が簡潔になり、ビルドプロセスが分かりやすくなる
  • 中間イメージを個別に生成する必要がなくなる

以上のように、マルチステージビルドを使うことで、無駄なデータを含まない最適化されたDockerイメージを作成できるのです。

https://docs.docker.jp/develop/develop-images/multistage-build.html

  • マルチステージビルド
    • Dockerfileの中に複数のFROM文が並ぶ
    • 上から順にビルドされる
    • asでステージに名前をつけられる
    • ビルドを特定のステージで止めたい場合は--targetオプションを使う
  • Goはビルドをすると、アプリケーションの実行に必要なすべての依存関係を含む単一の実行可能バイナリファイルが生成される。シングルバイナリ内にGoのランタイムも含まれるため、シングルバイナリを実行するときに別途Goのランタイムを用意する必要はない。
  • GitHub Actions の actions/checkout は何をしているか?
    • GitHub Actionsのワークフロー内でリポジトリのソースコードをチェックアウト(取得)するための公式アクション。ワークフローを実行するランナー(仮想マシン)上に、リポジトリのソースコードをクローンする。取得するブランチの指定などカスタマイズ可能。
yoshinoyoshino

HTTPサーバーを疎結合な構成に変更する

  • Configuration, Credentials, and Code
    • 「接続する API のエンドポイント情報」や「データベースの接続情報」などの環境に依存する設定は環境変数から呼び出す。環境変数は外部化し、可能なら外部サービスに入れるべき。
  • t.Skip()
    • そのテストをスキップする
  • アプリケーションを実行中のコマンドライン上で Ctrl + C を押下した場合は割り込みシグナルがアプリケーションに送信される。コンテナ環境ならば、コンテナに終了指示として終了シグナル(SIGTERM)が送信される。
  • func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc)
    • リストされたシグナルのいずれかが到着したとき、返されたstop関数が呼ばれたとき、または親コンテキストのDoneチャネルが閉じられたとき、doneとマークされた(そのDoneチャネルが閉じられた)親コンテキストのコピーを返す。
  • コンテナ実行環境では、コンテナの健全性を監視するためにエンドポイントのポーリング(=定期的にリクエストを送信すること)が使われることがある。
  • httptestパッケージを使えばサーバーを立てずにhttp.Handlerの機能だけテストできる
  • gofmt -d [ファイル名] 差分の確認
  • gofmt -w [ファイル名] フォーマットして上書き保存
yoshinoyoshino

エンドポイントを追加する

  • Defined Typeを使うメリット
    • 異なる型の値を誤って代入することを防げる
  • テーブルドリブンテスト
    • 複数の入力や期待値の組み合わせを共通化した実行手順で実行させるテスト
  • ゴールデンテスト
    • テストの入力や期待値を別ファイルとして保存したテスト
  • http.HandlerFuncは、関数型であり、ServeHTTPメソッドを持つため、http.Handlerインターフェースを実装している
    • type HandlerFunc func(ResponseWriter, *Request)
      • The HandlerFunc type is an adapter to allow the use of ordinary functions as HTTP handlers. If f is a function with the appropriate signature, HandlerFunc(f) is a Handler that calls f.
    • func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)
      • ServeHTTP calls f(w, r).
  • Goの関数型の引数では型の完全一致は必要なく、シグネチャの一致だけで十分
    • 関数は引数の数と型、戻り値の数と型が同じであれば、同じ型となる。これを関数のシグネチャーと呼ぶ。
yoshinoyoshino

RDBMSを使ったデータの永続化処理の実装

  • Goはアンダースコア(_)で始まるディレクトリおよびtestdataという名前のディレクトリをパッケージとして認識しない
  • Goは標準パッケージやGo自体にRDBMSのマイグレーションを管理する機能は提供されていないのでOSSを利用することになる
  • mysql.cnf
    • MySQL クライアントプログラム (mysql コマンドなど) の設定ファイル
    • [mysql] や [client] セクションで設定を記述
    • 文字コードや、ログイン時のデフォルトデータベースなどを指定
  • mysqld.cnf
    • MySQL サーバー (mysqld) の設定ファイル
    • [mysqld] セクションで設定を記述
    • ポート番号、ログファイルのパス、各種パフォーマンス設定などを指定
  • RDBMSの操作
    • 標準パッケージはdatabase/sql
      • *sql.DB.QueryContextメソッドを実行したあと、1行ずつその結果を構造体に詰め込む処理が必要になる
    • sqlxパッケージ
      • 構造体のフィールドにタグでテーブルカラム名に対応したメタデータを設定しておけば、SQLクエリを実行する部分では構造体への変換を自前で行わずにすむ
yoshinoyoshino

責務別にHTTPハンドラーの実装を分割する

  • リファクタリング前は、下記の機能がすべてhandlerパッケージに集中していた
    • HTTPリクエストから必要な情報を読み取る
    • HTTPレスポンスを構築しレスポンスを返す
    • アプリケーションロジック、ビジネスロジックを実行する
    • storeパッケージを呼び出して永続化処理を行う
yoshinoyoshino

RedisとJWTを用いた認証・認可機能の実装

  • JWT
    • Base64URLエンコードされたJSONを使って二者間で情報をやり取りするための手段
    • RFC7519で仕様が定義されている
    • トークンの中身の改ざんを防ぐため、JWTに対して署名を行う
    • 署名に利用されるアルゴリズムはいくつかあるが、今回は秘密鍵と共通鍵を利用したRS256形式を採用する
  • go:embedディレクティブを使って実行バイナリにファイルを埋め込む
    • ファイルパスを指定してファイルを読み取る実装にすると、実行バイナリの他にファイルも適切なファイルパスで実行環境に展開する運用が必要になってしまう
    • ディレクティブとは?
      • コンパイラに対する特別な指示を与えるためのコメント形式の命令
このスクラップは2024/08/24にクローズされました