Software Design 202510
ネットワーク基礎
プロトコルとは
プロトコルとは、通信手順やデータ形式の仕様のこと。
OSI参照モデルとは:
- プロトコルを策定するときに、この7階層に対応させようね、というきまり
- 普及しなかったが考え方は受け継がれている
- 現実的にはきれいに7層に分かれることは少ない (e.g. WiFiは物理層とデータリンク層を両方含む)
送信の過程では、上層から下層に行くにつれ、ペイロードにヘッダをどんどん追加していく。 受信の過程では、その逆を行う。
HTTP
- L7のプロトコル
- 主にアプリケーションが扱う対象
- HTTP1.0, HTTP1.1, HTTP2, HTTP3と、主にパフォーマンス性や効率性を中心に進化してきた。
- HTTP3は、UDP上で動くがTCP的な信頼性もある、すごい子。普及途上。
TCP
- L4のプロトコル
- 主にルーターやOSが扱う対象
- 信頼性の担保が主な役割
- ポートという概念がある
- 「どのアプリケーションか」を表す
- 複数の通信を同時進行可能にする
- ヘッダには、ACKやSYNといったフラグ、ウィンドウサイズ、送信元ポート番号、宛先ポート番号などが含まれる
- 順序制御、再送制御、流量制御、輻輳制御などを行う
- ヘッダに含まれる情報により制御を行う
- TCPがあることで、HTTP層(アプリ)やIP層(ルーター)は、これら制御について考えなくてよくなる
通信を待ち受けるサーバーは、well-known port(0-1023)で待ち受ける。 クライアントのポートはephemeral port(1024-65535)なら何でもいい。
UDP
- L4のプロトコル
- 基本はTCPと同じだが、TCPと違って送りっぱなし
IP
- L3のプロトコル
- 主にルーターやOSが扱う対象
- IPアドレスは「どのコンピュータか」を表す
- ヘッダには送信元IP、送信先IP、TTLなどが入る
- ルーターによるルーティングでバケツリレーしながら目的地まで運ぶ
- 静的ルーティングと動的ルーティングがあり、用途により使い分ける
NAPT
アドレス枯渇を避けるためにNAPTが使われる。L3とL4にまたがる技術。 中継機器で変換表を作り、IPヘッダとTCP/UDPヘッダを書き換える力技である。 外界と通信を始めるとき、ルーターは変換表に5つの情報を記録する。 つまるところ、何を何に付け替えたかの記録である。
- 内側(送信元の機器)のIPアドレスとポート
- 外側(ルーターのインターネット側の口)のIPアドレスと任意に払い出したポート
- プロトコル
P2PアプリのようなNAPT超えが必要な場合、最近はIPv4では厳しいので、そもそもNAPTが不要なIPv6を使うのがよい。
イーサネット
- L1とL2にまたがるプロトコル
- 主にスイッチングハブやNICが扱う対象
- フレームと呼ばれる単位でデータをやり取りする
- フレームも「ペイロード + ヘッダ」の一形態である
- フレームには宛先MAC、送信元MAC、データなどが含まれる
ORM
ORMを使うにはSQLの知識も必須である。
- メリット
- 直接SQLを書くのと比べて楽で簡潔(直接書くと単純なJOINですら扱いが面倒)
- DBスキーマ管理を効率化できる
- デメリット
- 非効率なSQL(N+1など)が知らずに発生しがち
- DB固有の複雑な機能を使いたいときの足かせになることもある
Prisma
Prismaの特徴
- クラスではなく、純粋なオブジェクトによりデータを表現する
- これはシステム成長に伴いクラスのメソッドが肥大化する「ファットモデル」問題を避けるため。
- 抽象的なAPIによりSQLに詳しくなくても簡単に使えるようにする設計思想
- 型安全性
- DSLによる宣言的なDBスキーマ管理
デメリット
undefined
を誤って渡すと条件が指定されていないと解釈される。これで事故る例が多々あり。
これを防ぐには、strictUndefinedChecks
機能を有効にする。
ただしこれはランタイムのチェックであり、型レベルでの検出はできないので、
あわせてTSのexactOptionalPropertyTypes
を有効にすることが推奨される。
もしくはPostgresの Row Level Security 機能などを利用する。
TSの構造的部分型の特性により、意図しないカラムが露出する事故も起きがち。 対策としては、取得時にselectを使用する、レスポンスオブジェクトを作成してマッピングする、 zodによる切り落としを行う、等がある。
DSLが表現の足かせになる。たとえばRLSはDSLでは表現できないので生SQLを書く必要がある。
独自の型やクラスをカラムにマッピングできない。
発行されるSQLが不透明。例えばincludeしたときのSQLはIN句なのでパフォーマンス問題が起きがち。
relationJoins
というPreview Featureもあるが、これを使ってもJOINじゃなくて
サブクエリが使われるため、クエリはさらに複雑化する。
Drizzle
Drizzleの特徴
- 大規模アプリや複雑なデータ操作に最適
- 生成されるSQLが予見可能
- TSのブランド型をカラムにマッピングできて便利。値の検証も可能。
- 概して、Prismaの苦手な領域を特異とする
- コードファースト(TSでスキーマを定義する)
- SQLライクなAPIも、抽象化されたAPIも、両方使える
- 事前のコード生成が不要
- まだv0系
Tips
DBコネクションプールの作成(Controller層)とその利用場所(Repository層)は、コード上での距離が遠い。
普通に考えると引数によるバケツリレーが必要になるが、これは可読性や保守性を著しく低下させる。
これを防ぐためには、Node.jsのasyncLocalStorage
と、Webフレームワークのミドルウェアを組み合わせるとよい。
DB接続プールやユーザー情報といったリクエスト固有の情報を、
呼び出しスタックの深い箇所からでもシンプルに行うことが可能になる。
つまみぐい関数型プログラミング
関数型専用の言語でなくても、以下のようなことに気をつければ、関数型プログラミングはできる。
- 副作用を意識する
- 可能な限り純粋関数に処理を切り出す(部分的でも全然OK)
- データをイミュータブルに扱う
- 小さな関数を合成して処理を組み立てていく
純粋関数を作るときのコツは、副作用のある操作を関数の外に追い出すこと。 そうすれば、テスト時に外から依存をInjectしたうえで、純粋関数としてテストが可能になる このテクニックはどの言語でも使える普遍的なものだ。
NIX
nix-shell
は、nixの管理するパッケージへのパスが通った特別なシェル。
例えばnix-shell -p cowsay lolcat
とすることで、cowsay
とlolcat
が利用できるシェルが起動する。
分離性が高くシステムを汚さない、再現性が高い、手軽、といったメリットがある。
パレートフロンティア
トレードオフの関係にある2つの指標において、 どちらか一つを改善しようとすると必ず他の指標が悪化してしまう解の集合のこと。 横軸と縦軸に指標をとり、それらを線で結ぶことでグラフ上に表すことができる。
線の左下側の領域は製品化する意味がない。 ある点(既存製品)より右上に新たな点(新製品)が出てきたら、前者は価値がなくなる。