「SQLアンチパターン ジェイウォーク(信号無視)」を読んで
学びの備忘録
- 「多対多」の関連を表現する交差テーブルの作成を避けてカンマ区切りのリストを作成するのはアンチパターン(作中ではジェイウォーク(信号無視)と表現している)
- 全ての外部キーが文字列連結されて一つのフィールドに格納されていると、等価性の比較ができなくなり文字列に対するパターンマッチが必要になり、クエリの作成が難しくなる
- パターンマッチ構文はデータベース製品によって異なる
- インデックスを使うメリットを得られない
- ユーザーがさまざまな値を入力できるのでデータがゴミだらけになる危険性がある
- 全ての外部キーが文字列連結されて一つのフィールドに格納されていると、等価性の比較ができなくなり文字列に対するパターンマッチが必要になり、クエリの作成が難しくなる
- ジェイウォークを避ける解決策は、多対多の関連をあらわす交差テーブルの作成
- メリット
- 外部キー制約によって参照整合性を保持できる
- SQLのデータ型によって入力内容を制限可能
- 外部キーにインデックスを貼ることでパフォーマンス向上(postgresでは暗黙でインデックスが作成されないので注意)
- メリット
参考
oapi-codegen に関して備忘
OpenAPI で Go コードを生成するために使用している oapi-codegen に関して備忘
- oapi-codegen は OpenAPI 定義ファイルからサーバーサイドのボイラープレート、API クライアント、API モデルを生成する
- サーバーは Chi や Fiber などをサポートしている
- oapi-codegen がここで全ての型を生成するため、可能な限り
#/components/schemas内で定義することが推奨されている - API モデルは
cfg.yamlに下記を追加することで生成される
package: onlymodels output: only-models.gen.go generate: models: true
- 参照されてないオブジェクトはデフォルトでは生成されないが、yaml の設定ファイルに下記を追加することで、生成可能になる
package: onlymodels output: only-models.gen.go generate: models: true output-options: skip-prune: true // add
- OpenAPI 定義が大規模になった際、Import Mapping を利用することで複数ファイルに分割することが可能になる
- 下記のように定義することで、他のファイルのスキーマが参照可能になる
components:
schemas:
User:
$ref: '../common/api.yaml#/components/schemas/User'
cfg.yamlに Import Mapping の設定がされてないのに他ファイルの定義を参照しようとすると、oapi-codegen からplease provide the known import for this reference using option --import-mappingのような生成エラーメッセージが返却されるので、下記のように定義する必要がある
# yaml-language-server: $schema=https://raw.githubusercontent.com/deepmap/oapi-codegen/HEAD/configuration-schema.json package: admin output: server.gen.go generate: models: true chi-server: true output-options: # to make sure that all types are generated skip-prune: true import-mapping: # for a given file/URL that is $ref'd, point `oapi-codegen` to the Go package that this spec is generated into, to perform Go package imports ../common/api.yaml: github.com/oapi-codegen/oapi-codegen/v2/examples/import-mapping/common
- nullable な型を定義したい場合は下記で指定可能で、ポインタ型として生成されるが、フィールドが送信されてない場合と null として送信された場合を区別することができなかった
S:
type: object
properties:
Field:
type: string
nullable: true
required: []
type S struct {
Field *string `json:"field,omitempty"`
}
- 上記の課題の対応として、oapi-codegen v2.1.0 から nullable.Nullable 型が導入された。
// add output-option output-options: nullable-type: true
// generated
type S struct {
// note that there's no pointer here, just `omitempty`
Field nullable.Nullable[string] `json:"field,omitempty"`
}
参考
想定外の relation does not exit に遭遇した
ローカルマシンで Docker コンテナで postgres を起動していて コンテナから起動したサービスで postgres のコンテナにアクセスしリスト取得する箇所で、relation {table_name} does not exist. が発生した。
postgres コンテナに入って psql コマンドで確認したところ、該当のテーブルは存在しており、謎な状況だった。結論としては凡ミスで、postgres のコンテナと同じ port でローカルマシンで postgres を起動しており、サービスからコンテナの postgres ではなくローカルマシンの postgres を参照してしまっていたのが原因だった。視野が狭くなっているとこういう事にすぐ気付かなかったりするので、備忘として記録に残す。
Go の基本復習 ~interface~
Go 言語の基本を復習のため記載していく。インターフェース編。
Go のインターフェースは持つべきメソッドを表現するための言語機能。Go ではインターフェースの型を持つ構造体を定義することで、構造体に持たせたいメソッドを表現することが可能。他の言語のように、インターフェースを満たすために implements のようなキーワードは不要。
Go では構造体がインターフェースを満たすメソッドを実装しているかどうかは、インターフェースの型を持つ変数に構造体のポインタを代入すると自動的に確認される。
// インターフェース type TestWriter interface { Write(body string) } // インターフェースを満たす構造体 type testWriterImpl struct{} // 構造体のインスタンスのポインタをインターフェース型の変数に代入 var _ TestWriter = &testWriterImpl{} // インターフェースで定義されているメソッドを構造体に定義することで、インターフェースを満たす func (t testWriterImpl) Write(body string) { // TODO impl }

kubernetes 入門
技術書の備忘録を記録
- k8s クラスタ
- k8s のさまざまなリソースを管理する集合体
- Node
- Master を構成する管理コンポーネント
- Namespace
- Pod
- コンテナの集合体の単位。少なくとも一つのコンテナを持つ。同一 Pod 内のコンテナは、全て同一の Node に配置される
- ReplicaSet
- 同じ Pod を複数生成、管理するためのリソース
- 指定された Pod 数の確保や新しいバージョンへの Pod の入れ替え、以前のバージョンへの Pod のロールバックといった重要な役割を担う
- Deployment
- アプリケーションのデプロイの基本単位となるリソース。ReplicaSet は同一仕様の Pod を管理・制御するためのリソースで、Deployment は ReplicaSet を管理・操作するために提供されているリソース
- Service
- ClusterIP Service
- デフォルトで作成される Service が ClusterIP Service。クラスタ上の内部 IP アドレスに Service を公開できる。ある Pod から別の Pod 群へのアクセスは Service を介して行うことが可能。外からのリーチは不可
- NodePort Service
- Ingress
参考 Docker/Kubernetes 実践コンテナ開発入門
Docker復習 Dockerfileからイメージを生成しコンテナを起動
以下、備忘録。
- Dockerfile作成
# Ubuntuベースイメージを指定
FROM ubuntu:latest
# パッケージの更新とhttpdのインストール
RUN apt-get update && \
apt-get install -y apache2
# コンテナが起動したときに実行されるコマンドを指定
CMD ["apache2ctl", "-D", "FOREGROUND"]
- Dockerfileからイメージ生成
docker build -t sample:01 ./ -f Dockerfile
- コンテナの起動
# host OSの8900ポートを指定 docker run -d -p 8900:80 sample:01
- CMDとENTRYPOINTはコンテナ起動時の挙動に違いがある。CMDで指定したコマンドは
docker run実行時にコマンドを指定した場合にそれで上書きされるが、ENTRYPOINTだとコンテナ起動時に常に実行される。
Docker イメージに関して復習
以下、備忘録。
- Dockerイメージは複数のレイヤーから構成されている。レイヤーとは、イメージ内の変更や操作を表現するためのスナップショット。Dockerイメージ構築の際、ファイルの追加やコマンドの実行といった操作は新しいレイヤーとして保存される。各レイヤーは個別にキャッシュされるため、同じ操作を行う場合はキャッシュから再利用される。結果、重複する操作が最小限に抑えられ、ビルドの高速化につながる。
docker historyコマンドでイメージの履歴情報を確認可能。各レイヤーが作成された際に実行されたコマンドを確認可能。- DockerのRUNコマンドはベースイメージに対して操作を行う。RUNコマンドは新しいレイヤーを作成し、そのレイヤー上でコマンドを実行する。ベースイメージは変更されず、新しいレイヤーに変更内容が追加される。
- RUNコマンドではShell形式での記述とExec形式での記述が可能だが、Exec形式を利用するとシェルを介さずに実行できるので余分なプロセスを発生させずに済む。
# Dockerfile # Shell形式 RUN echo hello # Exec形式 RUN ["echo","hello"]
- RUNはイメージを作成するために実行するコマンド。イメージから生成したコンテナでコマンドを実行するには、CMDを利用する。
参考
プログラマのためのDocker教科書 インフラの基礎知識&コードによる環境構築の自動化 電子書籍(WINGSプロジェクト 阿佐 志保 山田 祥寛)|翔泳社の本