学習履歴一覧

183件中の 51-75件 を表示

APIデザインパターン 10章 ロングランオペレーション 読了

やったこと APIデザインパターン 10章 ロングランオペレーション 読了 学んだこと ポイント LROとは どういった問題を解決できるか LROによる非同期APIの実装に必要なもの LROにメタデータ LROを取り扱う二種類の方法 学び LROはAPIで非同期処理を実現するための仕組み javascript の Promise や python の Futuresのようなもの HTTPのリクエスト/レスポンスとは異なる点(≒気を付けるべき点)が多々ある Operationリソース、LROを管理するAPIの設置、メタデータを持たせるなど LROの取り扱いには 「ポーリング」と「待機」という選択肢がある LROのキャンセル/一時停止/再開は必須ではない 提供することに価値があれば、実装する LROは保持期間を設けると良い 気づき LRO クライアント側が楽になる(同期処理)と、APIが辛くなる(待機) API側が楽になる(非同期処理)と、クライアント側が辛くなる(ポーリング) これは、クライアント側とAPI側のトレードオフなのかもしれない メモ 取り扱う問題 APIで、時間がかかる処理を扱いたい。 言い換えると、直ぐにレスポンスを返すことが出来ないような処理 そして、API設計において、レスポンスが早い処理と、レスポンスが遅い処理の両方に一貫性を持たせることは難しい。 概要 API で、時間がかかる処理を扱いたい。 そのために、非同期処理の仕組みを構築する。 非同期処理の仕組みとして、Javascript の Promise や、Python の Futures に相当する仕組みをWeb APIに実装する。 本書では、 この仕組みを LRO (Long-running Operation)と呼ぶ。 LRO は、 Javascript の Promise や、Python の Futures と比較して、以下の点が異なる LRO生成後、一リソースとして残り続ける Promise は処理完了後破棄される LROは、オペレーションの進捗状況を含むメタデータを含む 呼び出し側は、メタデータから、オペレーションの進捗を確認できる 実装 LROによる非同期APIを実装するために、以下の二つが必要。 Operation リソースとして、以下の二つを定義する 処理完了後に返却されるリソースの型 LROのメタデータ APIでLROを発見し、管理する方法を提供する LROは、作成された場所とは異なる環境に存在することがほとんどのため、検索が必要 取得には、 get や list といった標準メソッド 管理には、LROの「一時停止」「再開」「キャンセル」を要求できるカスタムメソッド LROには、以下を持たせる。 処理の成功/失敗を表現するresult フィールド 処理が完了したかどうかを示すフラグ 処理の進捗を含むメタデータ LRO リソースの保存場所 LROは非同期処理を管理するオブジェクトであり、どこかで管理する必要がある。 管理場所として最もよいのは、Operationリソースのトップレベルコレクション。 /operations/1 、operations/2など LROの取り扱い 受け取った LRO を解決する。 ◇ポーリング "処理が完了したかどうかを示すフラグ" が完了状態になるまでポーリングする。 計算時間の無駄が発生するが、理解しやすく、クライアント側にも責任を負わせることが出来る。 最大遅延時間と、無駄な時間やリソース消費のトレードオフ ポーリング間隔を長くすると、リソース消費は抑えられるが、結果を得るまで時間がかかる ポーリング間隔を短くすると、結果を得るまでの時間を短縮できるが、リソース消費が膨れ上がる ◇待機 クライアント側は、APIサービスとの接続を切らずに(≒セッションを確立したまま)待機する。 API側は、処理完了後にレスポンスを返す。 言い換えると、、処理が完了するまでクライアントへのレスポンスを行わない クライアント側のポーリング処理が不要となり、処理が単純になる(関数の同期呼び出しと、感覚は同じ)。 反面、API側は接続の管理、確実にレスポンスを返す(≒レスポンスに責任を持つ) 実装には、 waitカスタムメソッドを用いることになる。 /{id}/:wait エラー処理 LROのエラー処理にHTTPのエラーコードを用いると、HTTPのエラーか、RLO処理に伴うエラーなのか判別が困難になる。 そのため、エラーを伝える別の手段が必要。 別の手段として、処理の成功/失敗を表現するresult フィールドでエラーを表現する。 進捗のモニタリング RLOのメタデータを使用して、クライアント側に進捗状況を示すデータを渡す。 格納するデータは、実装側で任意に決めることが出来る。 例) タイムスタンプ、完了までの残り推定時間、総バイト数と処理したバイト数 ... など クライアントにとって価値あるデータを渡そう! オペレーションをキャンセルする CancelOperation カスタムメソッドを実装して、クライアントが処理のキャンセルをリクエスト出来るようにする。 LROの完全な停止を確認してから、クライアントに、キャンセル完了を返す。 キャンセル処理は、ポーリング(非同期)よりも、待機(同期)の方が良い。 キャンセル処理の目的は、「リクエスト自体がなかった」状態へ戻すこと。 そのため、LROによって生じた中間ファイルの削除(いわゆるクリーンアップ)も忘れないこと。 キャンセル機能の実装は任意。 クライアントにとって、キャンセル処理に価値があれば、実装すると良い。 LROの一時停止と再開 一時停止/再開も、カスタムメソッドを実装することで、クライアントがリクエスト出来るようにする。 一時停止リクエストは、一時停止に成功した旨をクライアントに伝える必要がある。 一時停止/再開といった状態を示すメタデータを持つと良い (paused:booleanなど )。 キャンセル機能同様、一時停止/再開機能の実装は任意。 クライアントにとって、キャンセル処理に価値があれば、実装すると良い。 オペレーションを検索する APIを処理する環境と、LROを実行する環境は分離することが多い。(AWS LambdaとAWS Batchに分離する...など) LROのクラッシュなどを追跡できるよう、別環境で実行したLROを管理・検索できる必要がある。 この問題は、Operationのコレクションを返却する、list 標準メソッドを実装することで解決できる。 このとき、「一時停止しているLRO」など、何らかのフィルタリング手段も併せて実装する。 LROの永続性 LROは、APIが生成する永続性のあるリソースとは異なる。 処理状況をを管理するために、扱いリソースで表現したものがLRO、というイメージ。 リソースの有効期限は、LROの最終的な結果の種類に依存してはならない。 永続性の選択肢にはいくつかある。 他のリソース同様、永続化する 有効期限を設け、有効期限が切れたLROを削除する 更に複雑なロジックで管理する 時間経過で段階的にアーカイブする、LROに結びつくリソースの削除に合わせてLROも削除する、など ロジックの実装・運用コストが発生するため非推奨 扱うこと 非同期APIでロングランオペレーションを使用する方法 オペレーションの進捗に関するメタデータを保持する オペレーションはリソース階層のどこに配置するべきか? ポーリングとブロッキングによるオペレーションの状態(エラーを含む)の確認 実行中のオペレーションの操作(一時停止、再開、キャンセルなど) まとめ LROは、Web API版の Promise や Futures で、 APIサービスがバックグラウンドで行っている作業をトラッキングするツールとして機能する LROはパラメータを持つI/Fで、具体的な結果と、LRO自身の進捗情報を保存するためのメタデータタイプを返却する LROは一定時間後に結果またはエラーを返す 利用者は、以下のどちらかの方法で、レスポンスの有無を知ることができる 定期的に状態をポーリング 待機中に結果が通知される LROは、APIの判断で「一時停止」「再開」「キャンセル」可能 なお、これらはカスタムメソッドで実施する LROは、理想は永久保存するべき 一般的には、標準的な時間(30日など)が経過したものは破棄する

WebAPI

2023年06月15日(木)

1.5時間

APIデザインパターン 11章 再実行可能ジョブ 読了

やったこと APIデザインパターン 11章 再実行可能ジョブ 読了 学んだこと ポイント ジョブとは ジョブの恩恵 ジョブの作成と実行 Executionリソースとは 学び ジョブとは、よくある「関数による何らかの処理の実行」を、以下の2ステップに分けたもの 「ジョブ」という処理単位を作成 「ジョブ」を実行する 上記2ステップにわけたことで、様々な恩恵が得られる ジョブは、重たい処理を想定し、実行結果をLROとして扱う LROなので生存期間が存在する ジョブの実行結果の生存期間を、LROに引っ張られたくない場合は、Executionリソースを定義する Executionリソースは、ジョブ実行結果のコレクションであり、LROとは別に生存期間を設定できる 「ジョブ」リソースも、Executionリソースも、れっきとしたリソースなので、標準メソッドを実装して扱いやすくすると良い メモ 焦点は、「カスタマイズ可能で、何度でも実行できる機能の実装」 対象とする問題 LROでは解決できない、以下のシナリオ。 非同期実行を行う、あれこれカスタム可能なメソッドの提供 設定可能なパラメータが増えるにつれ、パラメータ管理が非常に難しくなる このパラメータをAPIに保存できると、非常にありがたい 認可を、「APIを呼び出すことができる」と「特定のパラメータを設定できる」の二つで制御 「利用者Aは、この設定でしかAPIを呼び出せない」といった制御は難しい cron のように、定期的に実行したい場合 加えて、cronのような別のシステムではなく、APIの機能としてスケジューリングしたい場合 まとめると、以下をAPIの機能として提供したい。 設定パラメータを保存できる 必要に応じて(可能であればスケジューリングで)再実行可能 概要 上記「対象とする問題」を解決するために、【ジョブ】(リソースの「ジョブ」と紛らわしいので以降ジョブパターン)という概念を使用する。 ジョブパターンは、実行したい処理を「ジョブ」という単位で扱う。 システムは、以下の二つのステップで「ジョブ」を処理する 1. 任意の設定を入力しつつ、「ジョブ」を作成する 2. 「ジョブ」を実行する - WebAPIでは、Jobというリソースに対して、runカスタムメソッドを呼び出す形で「ジョブ」の実行をリクエストする ジョブパターンを用いることで、以下の恩恵を得ることが出きる。 処理の実行に必要な各種設定を、「ジョブ」作成時にのみ行うだけでよい パラメータの設定は「ジョブ」作成時に一度だけ行うだけで、以降管理の手間が減る 処理を再実行しやすくなる ステップ1で作成したジョブを実行するだけでよい 「ジョブを作成する権限」と「ジョブを実行する権限」を分離できる これにより、これらの認可を制御できる 実装 「ジョブ」リソースを定義し、「ジョブ」リソースに沿って作業を実行できる run メソッドを定義する。 「ジョブ」リソース 設定パラメータと、一意の識別子をもつ。 runカスタムメソッド 実行したい「ジョブ」リソースの識別子を受け取り、識別子に対応した「ジョブ」リソースに沿って処理を実行する。 ジョブパターンで実行したい処理は、重たい処理を想定してLROとして扱う。 この時、ジョブの実行結果もLROリソースとして扱うため、LROの生存期間という制約を受ける。 ジョブの実行結果を、LROの生存期間に縛られずに保持したい場合は、Executionリソースを使用する。 (そして、LROは、Execitonリソースの参照を返す) ◇Executionリソース runカスタムメソッドの実行結果のコレクションを保持するコレクション。 LROとは異なる生存期間を設定できる。 一つのExecutionリソースは、一つのジョブに紐づく。 そのため、Executonリソースは、紐づくジョブの配下で管理すると良い。 「ジョブ」リソースも、Executionリソースも、れっきとしたリソースなので、標準メソッドが必要。 取り扱うこと 再実行可能なジョブとは LROとの違い 再実行可能ジョブをリソースとして表現する方法 LROと比較した、再実行可能ジョブの利点 ジョブの実行結果をジョブ実行リソースで表現する方法 まとめ 再実行可能なジョブは、あるタスクを設定できる利用者と、同じタスクを実行できる利用者を分離できる、優れた選択肢 ジョブは、最初に作成され、設定されるリソース ジョブは、runカスタムメソッドを実装を使用して実行されることがある ジョブが既存のリソースを操作したり作成したりしない場合、Jobリソースの実行結果は、Executionリソースになる Executionリソースは読み取り専用 明示的な更新や作成は出来ない

WebAPI

2023年06月15日(木)

1.0時間

APIデザインパターン 9章 カスタムメソッド 読了

やったこと APIデザインパターン 9章 カスタムメソッド 読了 学んだこと ポイント 標準メソッドとカスタムメソッドの使い分け 標準メソッドととカスタムメソッドのどちらの、副作用のある処理を実装するべきか 特に、標準メソッドで副作用のある処理を実装した場合のデメリット 学び カスタムメソッドは、標準メソッドの枠に収まらないような、複雑な問題(特に、副作用を持つ処理)を解決する際に使用する 言い換えると、標準メソッドに、副作用を持つ処理を実装するべきではない カスタムメソッドには副作用を持つ処理を実装することが多いが、副作用のない処理を実装しても問題ない 複数のリソースを扱う際、「コレクションを扱うメソッド」として実装すると良い 「リソースに特化したメソッド」は、将来「単一のリソースを扱う」操作を実装するときのために残しておく メモ 標準メソッドの枠に収まりきらないような、複雑な問題(特に、何らかの状態管理が必要な、副作用を持つ処理)を解決するAPIを提供する場合に、カスタムメソッドは役立つ。 標準メソッドで副作用を持つ処理を実装することもできるが、以下のデメリットがある。 クライアント側で状態を指定することになるが、これは正しい状態とは言えない 多くの場合 update メソッドを使用することになるが、「データ更新」と「状態遷移」という二つの責務を負うことになる 標準メソッドが責務を二つ持つと、扱いにくいAPIとなってしまい、標準メソッドが掲げる「シンプルさ」を損なうことになる 標準メソッドで副作用を扱うことになる 標準メソッドを過度に複雑化する 概要 カスタムメソッドは、(標準メソッドと異なり)制限事項がほとんどない。 ただし、利用者の理解促進のために、カスタムメソッド全体で一貫性を保つ必要はある 言い換えると、ルールを自由に設けることができるが、(一貫性維持のため)設けたルールはカスタムメソッド全体で守ること 実装 カスタムメソッドは、以下の書式で実装する。 POST /book/123456(リソース):launch(アクション) リソースとアクションを: で区切る 大事なのは、リソースとアクションを確実に区別できること 多くの場合 POST メソッドで実装する 副作用 カスタムメソッドでは、任意の副作用を実装できる。 むやみな実装は避ける(≒過度に複雑にするべきではない) 副作用を実装する場合、その旨をドキュメントで利用者に説明するべき 複数のリソースを扱う 以下の選択肢を選べる場合、二つ目の選択肢(★)を選ぶと良い(どちらも、複数のEmailをエクスポートすることを想定している)。 リソースに特化したメソッド ( POST /users/1:exportEmails) ★コレクションを操作するメソッド ( POST /users/1/emails:export ) そして、一つ目の選択肢は、将来「単一のリソースを扱う」操作を実装するときのために残しておく。 ステートレスなカスタムメソッド 状態を持たず、標準メソッドのどれにも当てはまらないような処理も考えられる。 この場合は、ステートレスのままカスタムメソッドを実装すると良い。 扱うこと 標準のメソッドではすべてのケースをカバーできないのはなぜか 副作用が必要な場合のカスタムメソッドの使用 計算に特化したAPIメソッドでステートレスのカスタムメソッドを使用する方法 カスタムメソッドのターゲット(リソースかコレクション)を決める まとめ カスタムメソッドは、ほとんどの場合、HTTP POST メソッドで実装する PATCH メソッドで実装するべきではない 冪等性をもち、安全な場合はGETを使用することもある カスタムメソッドでは、コロン(:)を使ってリソースのターゲットと実行するアクションを分離する /missiles/1234:launch など カスタムメソッドでは、副作用を取り扱うことが出来る 標準メソッドではダメ 副作用の利用は計画的に、ドキュメントによる説明も行うこと 一般的に、カスタムメソッドが一つのコレクション内の複数のリソースを対象とする場合は、コレクションを丸ごと扱うこと ステートレスなカスタムメソッドで処理を実行するかは、よく検討すること 後からステートフルに変更する可能性は常に存在する そして、後からステートフルへ変更することはそれなりに大変

WebAPI

2023年06月11日(日)

2.0時間

APIデザインパターン 8章 部分的な更新と取り出し 読了

やったこと APIデザインパターン 8章 部分的な更新と取り出し 読了 学んだこと ポイント フィールドマスクとは 何か どういった問題を解決するか どこに設定すると良いか フィールドマスクのデフォルト値には何が適しているか 「特定のプロパティを削除する」といった部分更新処理は、どう実装すると良いか 無効なフィールドを指定されたときは、どう振る舞うべきか 学び 部分取得・更新という考え方 部分取得・更新を実装する、「フィールドマスク」の考え方 コレクションの、特定の要素だけ更新する、といった処理は提供するべきではない 部分取得・更新は、SQLレベルの細やかな制御には不向き この場合は、GraphQLの方が適任 メモ 対象とする問題 リソースの一部分だけを取得したい リソースの一部分だけを更新したい 「全リソースを更新する」という手段しかない場合、以下の問題が発生する 更新処理の衝突が発生する クライアントが古いバージョンのAPIクライアントを使用した場合、データの喪失が発生する 部分取得・更新を可能にする「フィールドマスク」 取得したい、特定のフィールド名を集めたリスト。 以下はイメージ。 fieldlMask:["title"] APIは、フィールドマスクに設定したフィールドのみクライアントに返却する。 これは、更新処理にも適用できる。 => フィールドマスクに設定したフィールドのみ更新する JSONのような動的データ構造の場合、 "fieldMask"フィールドが存在しなくても、JSONのプロパティから取得(または更新)したいフィールドに当たりをつけることができる。 実装 部分取得を実装する GETリクエストでフィールドマスクを送信することになる。 問題点 GETリクエストは、body を持つことが出来ない。 そのため、フィールドマスク値の送信を工夫する必要がある。 どう実装するか クエリパラメータを使用して、fieldMaskを渡す。 ?fieldMask=X&fieldMask=Y&... クエリパラメータは、サーバサイドフレームワークによって実装が微妙に異なるため、要確認。 部分更新を実装する PATCH リクエストでフィールドマスクを送信することになる。 問題点 PATCH リクエストは、通常、更新したいリソースのみ送信することを想定する。 言い換えると、リソースとは無関係な、部分更新を行うためだけに必要な設定データ(≒fieldMask プロパティ)をbodyに含めてはならない リソース指向の設計思想に反する 技術的には可能だが、 標準のupdateメソッドの前提を破っており、将来的に矛盾を引き起こしかねない どう実装するか 部分取得と同じく、クエリパラメータを使用する。 ネスト構造に対する部分取得/更新 JSONの中にJSONがあるような状況。 ネスト構造では、以下のルールに従うと良い。 フィールドの指定には、区切り文字としてドットを使用する ネストされたメッセージのすべてのフィールドは、アスタリスクを使用して参照できる マップのキーは常に文字列 キーに含まれる特殊文字をエスケープする場合は、バッククォートを使用 バッククォートをエスケープする場合は、バッククォートを使用 ◇サンプルリソース { "id":1, "title":"test" "settings": { "language":"ja" "volume":20 } } ◇サンプルリソースのフィールドマスク * // 全フィールド取得 "id" "title" "settings.*" "settings.language" "settings.volume" クエリパラメータに設定する例 ?fieldMask=* ?fieldMask=title&fieldMask=settings.* コレクションフィールド フィールドマスクでは、コレクションフィールドも扱うことが出来る。 ただし、コレクションのインデックスを指定するようなフィールドマスクはアンチパターン。 コレクションは順序が保障されない データの増加によって、インデックスが指し示すリソースが変化する インデックスを指し示すリソース更新は困難 コレクションの特定の要素だけ更新する、といった処理は困難なため、コレクション全体を置き換えることで更新する。 これでも、オブジェクト全体を置き換えるよりかはいくらかマシ デフォルト値 部分取得において、フィールドマスクが設定されていない場合。 ほとんどの場合、全フィールド取得を期待する 全フィールド取得が悪手の場合、デフォルトで除外する、といった戦略は有り この場合、デフォルトで除外するフィールドの名前、除外理由、代替の取得方法などをドキュメントに記載するべき 除外も含めた全フィールド取得方法の代表は、「すべて」を表すフィールドマスク「 *」 で要求すること 部分更新で全フィールド更新は、replace と役割が被るため、あまり意味がない。 利用状況から、よく更新するパターンを見極め、デフォルト値として設定することになる。 暗黙のフィールドマスク フィールドマスクを設定しない(≒全フィールド更新)は、replace と役割が被るため、あまり意味がない。 効率を上げるためには、何かしらの句数が必要。 一般には、 PATCH の body は、リソースの更新したいプロパティのみを含む。 このことから、body に含まれる(≒更新したい)フィールドから、フィールドマスクを推論すると良い。 この時、プロパティが存在しない(= undefined)と、プロパティの値が null は、区別すること。 undefined はフィールドマスクから除外する nullは、「データを null へ更新する」と解釈する場合が多い いずれにせよ、APIの設計に従うこと 動的なデータ構造の更新 PATCH処理にて、動的なデータ構造(JSONなど)のプロパティを削除したい。 どうしたらよいか? PATCHのBody に含めない ... は、更新対象から除外することを意味するため利用できない 書籍では、以下の選択肢を提供している。 削除したいプロパティを、フィールドマスクで宣言する PATCHのBody に、空のオブジェクト({})を設定する 無効なフィールド 無効なプロパティを指定されたら、そのプロパティの値を undefinedとして扱おう。 言い換えると、無効なプロパティを指定された時、むやみにエラーを投げるのは良くない選択肢。 プロパティの追加/削除はよくあること APIのアップデートなど 利用者が、古いAPIクライアントを使用して、現在提供していないプロパティを指定する、といったことは十分考えられる このとき、エラーを投げると、利用者に驚きと混乱を与えることになる トレードオフ フィールドマスクは、部分取得・更新を実装する強力な手段。 しかし、SQLレベルの細やかな制御は不向き。 技術的には可能かもしれないが、そうするべきではない この場合はGraphQLの方が適している。 部分取得・更新は、必ず実装する必要はない 扱うリソースが小さければ、全プロパティ置き換えでも問題はない。 言い換えると、全プロパティ置き換えが、無視できないほどパフォーマンスを悪化させる場合は、部分取得・更新を検討することになる。 そして、一貫性を保つため、リソース一つでも部分取得・更新を実装するのであれば、全リソースに部分取得・更新を実装すること。 パフォーマンスと、部分取得・更新開発コストのトレードオフ。 更新処理にPATCH以外を選択する JSON Patch 、JSON Merge Patchなど。 取り扱うこと なぜリソースの特定の部分だけ取得/更新したいか 対象となるフィールドをAPIサービスに伝える最善の方法 複雑なフィールド内のある特定の要素を指定可能にするかどうか デフォルト値の定義と暗黙のフィールドマスクの扱い 無効なフィールドの指定に対処する方法 まとめ 部分取得は、以下のような状況では特に重要 扱うリソースが大きい リソースを扱う側(≒クライアント)のハードウェアスペックが限られている 部分更新は、衝突を気にせずにきめ細かな更新を行うのに有効 フィールドマスクとは フィールド、ネストしたフィールド、マップキーを指定する方法をサポートし、検索または更新されるべきフィールドを示すために使用されるべきもの 配列フィールドに対して、特定の要素を指定する機能を提供すべきではない デフォルトでは、部分検索はフィールドマスクの全値を仮定する 部分更新では暗黙的なものを仮定する フィールドが無効な場合、それらは存在するが値が undefinedのように扱う

WebAPI

2023年06月09日(金)

1.5時間

API デザインパターン 7章 標準メソッド 読了

やったこと API デザインパターン 7章 標準メソッド 読了 学んだこと ポイント 冪等性/副作用 標準的なメソッド うまく設計されたAPIとは 標準化のトレードオフ 学び うまく設計された API は、一貫性があり、予測可能で扱いやすい 使用していないHTTPメソッドへのリクエストには、 405 を返すと良い 標準的な処理を整理すると、 Get , List , Create , Update , Delete , Replace の6つに分類できる Replace メソッドでは、新規作成を請け負わない方が良い 標準的なメソッドは一貫性を持つべき 結果整合性を持つデータを扱う場合、カスタムメソッドの実装を検討すること 標準的なメソッドを用いるという事は、柔軟性と学習コストのトレードオフ 基本は標準的なメソッド、標準的なメソッドで賄いきれない部分はカスタムメソッドを実装する 気づき この章の文脈における「標準」は「汎用」ぐらいに思っておく 「カスタムメソッド」は「特化メソッド」ぐらいに思っておく メモ うまく設計された API は、一貫性があり、予測可能で扱いやすい。 実装 どのメソッドをサポートするべきか? 使用していないメソッドで通信を試みると、404が返ってくる。 これでは、リソースが存在しないのか、メソッドがサポートされていないのか、利用者が判別できない。 利用者がこれらを判別できるよう、正当な理由がない限り、各リソース毎に、標準メソッドを実装する。 そして、使用しない理由をドキュメントで説明する 使用しないメソッドは 405 Method Not Allowedや403 を返す 書籍では、標準的なメソッドとして Get , List , Create , Update , Delete , Replace を挙げている。 状況標準メソッドを、HTTPメソッドにて実装していく 上記標準メソッドと同じ名前のHTTPメソッドを作れ、というわけではない 冪等性と副作用 標準メソッドが予測可能であるために、可能な限り副作用をもたないようにする。 メソッド次第では冪等性も満たすべき。 副作用を持たせるか、冪等性を持たせるかの判断は、場合による。 Get 識別子を受け取り、それに合致するリソース1つだけを返却する。 冪等性 ... 満たすべき 副作用 ... 顕著な副作用(※)があってはならない (※)顕著な副作用 稼働しているアプリケーションの動作に影響があるような副作用。 顕著ではない副作用としては、メソッド呼び出し回数を記録するカウンタの更新など List 特定の条件に合致したリソースのコレクションを返却する。 返却するコレクションは、「すべてのデータが揃った具体的なリソース」である必要はない。 恐らく、「条件を満たすリソースの識別子」など、リソースの一部分だけを抽出したコレクションでも良い、と言いたいのだと解釈 予測可能性を下げそうな気がする Create 与えられた情報を基に、API内に新しいリソースを作成するメソッド。 その後、レスポンスとして、作成したリソースを返却する。 作成した情報は即座に読み取ることが出来るべき(書籍では、一貫性と表現している)。 結果整合性を持つデータは、一貫性を満たさない。 標準メソッドは一貫性を満たしていることが期待される。 もし一貫性を満たさないデータを取り扱いたい場合は、カスタムメソッドにて取り扱うと良い。 Update 与えられた情報を基に、リソースを更新する。 その後、レスポンスとして、、更新後のリソースを返却する。 一部分を更新する際は PATCH メソッドを使用する。 全部更新する(もはや置き換え)際は PUT メソッドを使用する。 Update ではなく、カスタムメソッドを使用した方が良い場合もある。 更新処理で状態遷移を表現したい場合など どういった状態へ変更するのかがひと目でわかる名前のカスタムメソッドの方が良い Update以外にも、更新処理があっても問題ない。 Delete 与えられた情報を基に、リソースを削除する。 戻り値は無い。 Deleteの戦略には、動作重視と結果重視が存在する。 ◇動作重視 「リソースの削除処理に成功した」ことを重視する。 削除対象のリソースが存在しない、「削除する処理」を実行できず、エラーとなる。 例として、同じIDに対してDeleteを二回連続で要求すると、二回目はエラーとなる。 このため、動作重視の戦略では、冪等性は満たしていない。 ◇結果重視 「リソースが存在しなくなっている」状態であることを重視する。 たとえ削除対象のリソースが存在しなくても、「指定したリソースが存在しない」状態は達成できているので、Delete成功とみなす。 こちらの場合、冪等性は満たしている。 Replace 与えられた情報を基に、リソース全体を置き換える。 HTTP の PUTで実装する。 新規リソースの作成も担うことが出来るが、その場合、識別子問題が発生する。 ◇識別子問題 リソースを新規作成したい場合は、クライアント側で識別子を作成できる必要がある。 そして、クライアント側では識別子を作成できない方が良い。 このため、要件上必要な場合を除き、一般的なユースケースでは新規作成を担わなない方が良い。 トレードオフ 標準化された仕組みを利用することは、以下のトレードオフである 既存の仕組みに縛られるため、柔軟性が低下する 利用者が、「既存の仕組み」を既に知っている場合、学習コストが低くなる 標準メソッドは、あらゆる状況に対応できるわけはない。 その代わり、API利用者は、少ない学習でAPIを習得できる。 標準メソッドではどうしても賄いきれない処理は、カスタムメソッドを実装する。 取り扱うこと 冪等性とその副作用 標準メソッドの紹介 準標準メソッド 標準メソッドの利点と欠点 まとめ 標準メソッドを用いることで、APIは一貫性と予測可能性を高めることが出来る すべての標準メソッドが同じ動作原則に従うことが重要 例) すべての putメソッドは、同じように動作するべき 冪等性とは、メソッドを何回呼び出しても、常に同じ結果になるという特性 APIシステムのどこかに変更が生じるような副作用は持つべきではない deleteメソッドは冪等性を満たさない 標準メソッドは、できることがある程度決まっている 一方で、API利用者は、リソース指向APIに関する既存の知識を活用しやすい(つまり、学習コストが低い)

WebAPI

2023年06月04日(日)

1.5時間

APIデザインパターン 6章 リソース識別子 読了

やったこと APIデザインパターン 6章 リソース識別子 読了 学んだこと ポイント 良い識別子が満たすべき属性 良い識別子とは 識別子を実装する際の勘所 学び 良い識別子が満たすべき属性は、大きく7つある 上記属性を満たしたうえで、良い識別子の条件がいくつか存在する 識別子の生成に、Base32エンコーディングは有効 UUIDは2005年7月には仕様が策定されている UUIDは万能ではない メモ 識別子とは APIにおいて、特定のリソースを一意に識別するための値。 良い識別子が満たすべき属性 使いやすい事 RESTなどで簡単に扱うことが出来ること(記号を含まないことなど) 一意であること 不変であること 高速で簡単に生成できること 他人が予測不可能であること 読みやすく、共有しやすく、検証可能であること アナログな方法でやり取りする可能性を考慮し、不必要に難しくしないこと チェックサムを導入して、識別子の誤りを検出できること 情報密度が高いこと 小さいサイズで、できるだけ多くの情報を詰め込むこと 良い識別子とは データ型は文字列を推奨 汎用性が高く、プログラムやUIからも扱いやすい URL中で使用することを考慮し、'/'などURLで意味のある文字は含めないことを推奨 ASCIIで構成するのが最も安全 ASCIIの中でも、記号や誤解を招きやすい文字(IとL、Oと0など)は除外するのが無難 Base32エンコードすると、簡単に解決できる 使われていない識別子と、明らかに誤っている識別子を区別できる チェックサムを用いることで、「明らかに誤っている識別子」を検出できる リソースの種類が一目でわかる book/識別子のように、識別子の前にリソースの名前を持ってくると良い book/識別子/page/識別子のように、階層構造を構築する戦略もある 以下のような場合に、うまく適応する リソースが親子関係でる 子要素だけでは成り立たない そうではない場合、将来階層構造の変更を試みる時に識別子の不変性要件を満たせなくなる shelves/識別子book/識別子など bookリソースのプロパティに、本棚の識別子を持たせた方が合理的 実装の勘所 識別子の長さは固定にすると良い 識別子は疑似乱数生成器 & Base32 などで生成する 利用者側で生成させてはならない 機微な情報を含めてしまう、予測不可能ではなくなってしまうなど、弊害が多い 「生成した識別子が、既に生成されていないか」を検証できるようにする データを削除する際、論理削除を用いる データが増えると、計算時間が増えてしまう問題を抱える 衝突が考えられないほど、長い識別子を使用する UUIDはこの戦略 チェックサムはDBに保存するべきではない チェックサムの計算アルゴリズムを後から変更できる 識別子をDBに保存する場合の戦略 基本は文字列、文字列が不可能な場合はバイトで格納する 識別子には、情報密度が最大となる「2のN乗」となるような長さを選択する UUIDは万能ではない UUIDは、良い識別子が満たすべき属性を満たしている。 しかし、常にUUIDを使えばよいというわけではない。 UUIDは、以下の点で万能ではない。 一般的なユースケースではオーバースペックになりがち (Base32と比較して)情報密度が低い Base32は1文字列に5ビット詰め込めるのに対して、UUIDは1文字に4ビットまで 定義上、チェックサムを持っていない 使われていない識別子と、明らかに誤っている識別子を明確に区別できない それでも、多くのユースケースにおいて、UUIDは優れた選択肢である。 扱うこと 識別子とは何か 良い識別子の持つ属性 良い識別子とは何か 識別子を作成するシステムはどのように実装するか 本章の内容とUUID まとめ 識別子は、APIにおいて、特定のリソースを一意に識別するための値 良い識別子は使いやすく、一意性をもち、値が不変で、素早く簡単に生成でき、予測不可能で、読みやすく、コピー可能で、共有可能で、情報量が多いもの 識別子はASCII文字セットで構成された文字列であるべき 利用者が扱いやすい 識別子は、チェックサム文字を使用して、以下を区別できる必要がある 存在しないリソース リソースを指していない識別子

WebAPI

2023年06月03日(土)

1.0時間

API デザインパターン 5章 データ型とデフォルト値 読了

やったこと API デザインパターン 5章読了 # 学んだこと ポイント 「データが存在しない」をきちんと定義する 各データ型ごとに、デフォルト値には要注意 学び 「データが存在しない」状態は、何種類か考えられる APIにとって「データが存在しない」状態は統一する 意図をもって何種類か使い分けたい場合は、その意図をきちんと文書化する 数値型の強みは「四則演算可能なこと」であり、この恩恵を全く得られない用途(識別子など)で数値型は使うべきではない 数値は、コンピュータやプログラミング言語によって扱いが異なるため、APIでは数値型の値を色々気にかける必要がある デフォルト値も設計のうち 意味のあるデータ(マップの {})なのか 利用者に対して適した値を挿入してほしい旨を示すデータ(マップの null )なのか 気づき リスト要素数にも上限を設けるべき、について AWS dynamoDBの scan のイメージか 要素数が数千万や億単位となると、確かにAPI利用者がびっくりする メモ データの入力・保持・出力にあたって、データの型は常に考える必要がある。 データ型入門 APIを誰でも(≒どんな言語でも)利用できるよう、汎用フォーマットにシリアライズ/デシリアライズする。 JSONやXMLが代表 汎用フォーマットへ変換する過程で、特に型情報が欠落してしまう。 そのため、クライアント側と、送受信するデータの型やプロパティ名について、ドキュメントなどで認識を合わせる必要がある。 「データが存在しない」状態は、いくつか考えられる。 フィールドが存在しない フィールドは存在するが、値が null 文字列を文字列を期待するフィールドは存在するが、値が空文字 APIのデータについて、「データが存在しない」状態は統一する必要がある(でなければ、予測可能でないAPIとなってしまう)。 上記の状態それぞれに意味があるのであれば、その旨を利用者が理解できるよう、きちんとドキュメント化する必要がある。 ブール値 ポイント 一般に、肯定を示す変数の方が、理解しやすい 否定の方が良い場合もあるため、ケースバイケース 数値 色々な意味を持つ数値(長さ、大きさ、距離、通貨価値など)を、数値型としてまとめて扱う。 数値型の強みは「四則演算可能なこと」であり、この恩恵を全く得られない用途(識別子など)で数値型は使うべきではない。 数値の扱いは、コンピュータやプログラミング言語によって大きく異なる。 そのため、APIでは以下のような点に気を配る必要がある。 数値の上限と下限、最大値と最小値 64ビット整数値が安泰 ゼロの値(特に null とmissingと同じ感覚でデフォルト値にゼロを設定する場合) 安易に「0」を使用すると、計算の結果設定した「0」なのか、デフォルト値としての「0」なのか区別が付かない 非常に大きな値を扱うか否か 64ビットを超えるような、非常に大きな(あるいは高精度な)値を扱う場合、文字列で表現する方が無難 文字列 APIフィールドの大部分は文字列のため、文字列の扱いは重要。 文字列も、サイズ(≒文字列の長さ、API次第ではバイト数)を気にするべき 特に、RDBでサイズ制限付きの文字列を取り扱っている場合は考慮必須 サイズを超えるようなデータは拒否すると良い サイズを超えた分は切り捨てて処理を続行する...といった実装は、大抵よろしくない デフォルト値には、"default"という文字列を使用できる Nullや空文字は有効な値ではない場合があるため、デフォルト値としては使用しない エンコーディングは一種類に統一すること 最有力はUTF-8 列挙型 WebAPIの世界では、使用を避け、文字列などで代替する。 新しい値を後から追加する可能性がある場合は、特に列挙型の使用を避ける。 クライアントライブラリの再コンパイルが必要となり、非常に高コスト 列挙型は、強い型付けをサポートしているプログラミング言語では有効だが、WebAPIの世界ではデメリットが強い。 リスト 一括処理したいデータをまとめたいときに最適なデータ構造 言い換えると、「ある一つの要素だけ変更したい」というユースケースには不適当 複数の個所で保存・管理するべきではない リストの項目は、JSONエンコード可能な形で保持すると良い ["str",5,true] のように、 一つのリストに、複数のデータ型を格納するべきではない 要素数制限を設けた方が良い そして、サイズを超えるような入力は、拒否するべき 切り捨てて処理を続行するのはよろしくない API利用者側が、大量のデータに驚き混乱する...といった事態を防ぐことが出来る サイズの見積もりが困難であれば、インラインでリストを保持するのをやめよう リソースのサブコレクションとして扱う デフォルト値は空のリストが有力候補 マップ 構造が未確定な動的データを扱うのに適している 定義済みのカスタムスキーマという選択肢もある こちらは、データが事前に分かっており、より扱いやすく構造化したいときに使用する 要素数制限を設けた方が良い キーとバリューの長さにも制限を設けると良い いずれも、「マップのデータが肥大化し、歯止めがきかなくなる」状況を防いでくれる デフォルト値には、 nullと{} が選択できる {}はデータが全く存在しないことを示す値として、 nullは利用者が最善のデータを設定することを示す値として活用すると良い なお、カスタムスキーマはデータ構造が決まっている(≒要素数がはっきりしている)ため不要 扱うこと データ型とは何か 値が無いものと、NULLの違い ア様々プリミティブデータ型とコレクションデータ型 様々なデータ型に対するシリアライズ方法 まとめ 全ての値で、 null 、undefined (またはmissing)の両方を考慮する必要がある ブール値はフラグに最適 変数名は、肯定を示す名前の方が良い 数値型の強みは「四則演算可能なこと」であり、この恩恵を全く得られない用途(識別子など)で数値型は使うべきではない 適切な数値表現を持たない言語では、数値を文字列へシリアライズする 大きな数値や浮動小数点演算問題を避けるため 文字列はUTF-8でエンコードする 文字列を識別子として使用する場合は、標準化形式Cで正規化する 列挙型は使用せず、代わりに文字列を使用する この文字列の検証は、サーバ側で行う(言い換えると、クライアント側で検証しない) リストは一括処理したいデータをまとめる際に最適 ["str",5,true] のように、 一つのリストに、複数のデータ型を格納するべきではない リスト、マップは、要素数を制限する 更に、マップではキーとバリュー両方のサイズを制限する

WebAPI

2023年06月03日(土)

2.0時間

API デザインパターン 4章読了

やったこと APIデザインパターン 4章 リソースの範囲と階層 読了 学んだこと ポイント 階層関係を構築すべき場合/すべきでない場合 学び 不必要な参照は持たない 気づき メモ リソースレイアウトとは何か APIの特定の設計における実体(リソース)関係モデルを表す。 リソースの配置 リソースのフィールド定義 リソース間の関連 リソースレイアウト設計は、DB設計と似通ったモノがある。 書籍では、UMLのクラス図でリソース間の関係を表現している。 関係にも、いくつかの種類がある。 ◇参照関係 あるリソースが、別のリソースを指している状態。 Userリソースと、Messageリソースなど ◇多対多の関係 リソースが、複数の他のリソースを指している状態。 Userリソースと、CharRoomリソースなど ◇自己参照関係 ある型Aのインスタンスが、同じ型の、異なるインスタンスを参照している状態。 グラフ構造やリスト構造など ◇階層関係 包含関係や所有関係を保持しつつ、あるリソースが別のリソースを指している状態。 木構造など。 親要素に対して実行できる操作を、子要素にも実行できることが期待される。 これは、利点にもなるし、欠点にもなる。 正しい関係を選ぶ 不必要な参照は持たない リソース決定後、リソースの関係を決定することになる。 この時、不必要な参照は持たないよう心掛ける。 不必要な参照は、データ量増加に伴い、運用保守が困難になる、パフォーマンスの低下などを引き起こす インラインと参照 データの保持にはインライン保持と参照保持があり、それぞれ特徴がある。 保持方法 特徴 利点 欠点 インライン 複製したオブジェクトを保持する リソース取得時、一々APIを叩く必要がない データサイズが重くなる 参照 オブジェクトのポインタのみ保持する(idだけ持つなど) データサイズが軽くなる オブジェクトが必要な場合、リソース取得処理が必要になる ユースケースに応じて、インラインと参照どちらを使用するべきかが変化する。 常にデータを参照したい場合、リソース取得処理が少なくなるインラインに軍配が上がる あまり参照しない場合は、処理高速化のため参照に軍配が上がる 階層関係 例えば、親リソースを削除すると、子リソースも削除されることが期待される。 このように、処理や認可が連鎖するような関係は、利点にも欠点にもなる。 以下のどちらにも当てはまる場合、階層関係は有益。 「親リソースを削除した場合、子リソースも削除されるべき」という場合 「親リソースへアクセス可能となった場合、子リソースにもアクセスできる」という場合 以下に当てはまる場合、階層関係はNG。 子リソースの親が、常に一つの場合 言い換えると、既に親を持つ子リソースは、他の親を持つことが出来ない場合 アンチパターン あらゆるデータをリソース化する リソースとして扱うか、データ型(一プロパティ)として扱かは、 データAが、リソースBからしかアクセスされない場合、データAはデータ型で問題ない つまり、リソース化しない方が良い 複雑になるだけ データAについて、直接操作したく、かつインライン化可能であれば、インラインで問題ない つまり、リソース化しない方が良い 深い階層 過度に深い階層は、理解するのが困難で、管理が難しくなる。 ユースケースに応じて、同レベルに配置する、一つのデータにまとめるなどして、不必要に深くしないようにしよう。 全てのデータをインライン化する 「全てインライン化する」とは、つまり 一つのリソースにまとめること。 リソースの一部にだけアクセスしたい場合でも、不要な部分も含めてデータも呼び出すことになり、パフォーマンスが低下する。 あるデータAが、複数のリソースから参照される場合、データAはリソース化しよう。 - つまり、インライン化しない 扱うこと リソースレイアウトとは何か 様々な種類のリソース関係(参照、多対多など) 実体関係図によるリソースレイアウトの記述 リソース間の適切な関係を決める方法 避けるべきリソースレイアウトのアンチパターン まとめ リソースレイアウトとは、API内のリソースの配置と関係を指す APIに重要な機能を提供する場合にだけ関係を持たせるようにする API内のすべてのリソースをつなぎたくなる誘惑に打ち克つ ある概念について、「個別のリソース」とした方が良い場合と、そのデータをインライン化し、概念をデータ型として残した方が良い場合がある これは、概念とそのやりとりにアトミック性が必要かどうかで判断する 過度に深い階層的な関係は理解が困難で管理が難しくなるため避ける

WebAPI

2023年06月01日(木)

1.5時間

APIデザインパターン 2章 読了

やったこと APIデザインパターン 2章 読了 学んだこと ポイント API の特徴 APIデザインパターンを用いるメリット 気づき APIでは、最低限の情報だけ公開する 公開する情報が多いほど、変更が困難になる 可能な限り情報は非公開にすることで、いくらか変更できるようになる APIデザインパターンを用いるモチベーション APIは変更が非常に困難なため、公開前に可能な限り高品質に設計したい => 世の中で成功を収めているAPIデザインパターンを用いる メモ APIデザインパターンとは、APIに適用できるデザインパターン。 実装よりもI/Fに焦点を置く ◇APIの特徴 硬直性(≒柔軟性が低い) I/Fをちょっと変更するだけでも、API使用者にバグをもたらす つまり、APIは、公開しているため変更が非常に困難 => イテレーティブ開発(アジャイル開発など)による設計のブラッシュアップが困難 可視性 公開されているI/Fの変更は非常に困難 => 変更が発生しうる部分を利用者から隠匿することで、変更しやすくなる 特に、硬直的で、公開されているAPIは、変更が非常に困難。 出来るだけ変更したくない => 最初から、質の高いAPIを設計したい => 「質の高いAPIを設計」するのに役立つ、APIデザインパターンに価値が生まれる まとめ APIデザインパターンは、APIを設計し構造化するのに適応できる設計図のようなもの 一般的なAPIは「硬直的」で、変更が難しい APIデザインパターンを用いることで、大規模な構造変更の必要性を最小限に抑えることが出来る 言い換えると、大規模な構造変更を行わなくても目的を達成できるようになる

WebAPI

2023年05月27日(土)

1.0時間

API デザインパターン 3章 名前付け 読了

やったこと API デザインパターン 3章 名前付け 読了 学んだこと ポイント APIにおける、名前付けの重要性 「良い」名前の条件 コンテキストが名前に与える影響 名前に追加で情報を与える手段 学び 公開情報の名前付けは、プログラミングでの名前付けよりも、ずっと重要度が高い 名付けにおける表現力とシンプルさはトレードオフ コンテキストによって、名前が持つ意味は変わり得る 名前に単位を含めると、利用者に、より明確な意図を伝えることが出来る sec や msecは定番 名前に加えて専用のデータ型を定義すると、利用者に、より明確な意図を伝えることが出来ること 気づき 公開しているAPIの名前を変更するのは、電話番号や住所、主要メールアドレスを変更するのと同じぐらい大変。 とても良い例え。 メモ 名前の重要性 公開しているAPIの名前は非常に重要。 利用者の利便性に直結 後から変更することが非常に困難 公開しているAPIの名前を変更するのは、電話番号や住所、主要メールアドレスを変更するのと同じぐらい大変。 「良い」名前の条件 表現力があること、シンプルであること、予測可能であること。 ◇表現力があること 名前から、名前を付けたリソースが何なのか、読み手に明確に伝わること。 特に、文脈が変わると意味が変わるような名前は要注意。「アカウント」や「メッセージ」など。 ◇シンプルであること 表現力を失うことなく、最低限の文字数(単語数?)で読み手に情報を伝えることが出来ること。 名前は、長いほど表現力が上がりやすいが、理解に時間がかかる(≒複雑) 言い換えると、シンプルさと表現力はトレードオフ シンプルかつ表現力のある名前を模索することになる ◇予測可能であること APIを一つ利用すると、他のAPIの使い方が何となくわかる(≒予測できる)、そんな状態。 名付けルールや、名前が持つ役割が一貫していると、利用者は予測しやすい。 言語、文法、構文上の注意 名前付けには、自然言語を使用する。 言語はルールが緩く,曖昧なもの。 これは、高い汎用性をもたらすというメリットになり、理解を阻害する原因というデメリットにも成りえる。 ◇言語 APIの名前付けには、英語を用いることが標準 言い換えると、APIの名付けに英語以外のを使用すると、様々な弊害が生まれる ◇文法 RESTfull でないAPIの場合、アクションの名前付けに多様な選択肢が生まれる。 関数の名付けでは、よく命令形(createBook()など)や直接法(IsNullOrEmpty()など)が使用される。 APIの場合、命令形や直接法でもそのまま使えるとは限らない。 APIの名前で前置詞が使用される場合、APIの設計に根本的問題が潜んでいる可能性を示唆している。 前置詞の使用を禁止しているわけではない API名に前置詞を見かけたら、「問題を書けていないか調査する価値がある」ことを示している 複数のリソースを返す場合、末尾に s や es を付与するべきか? - person リソースの複数形は persons? それとも people? など - -> 安易に、「複数リソースを返す時はリソース名に s を付けよう」という発想があまりよくないことがわかる ◇構文 表記法は、「キャメルケース」「スネークケース」「ケバブケース」など。 表記法の使用には一貫性があることが望ましい。 - 何の単位でどの表記法を使用するかは統一する - 一貫性が重要であり、どの表記法を使うかはあまり重要ではない プログラミング言語の予約語は、可能な限り使用を避ける。 - つまり、 string String intといったフィールド名は避ける コンテキスト 名前は、名前が登場する文脈(コンテキスト)によって変化しうる。 言い換えると、名前は、コンテキストと密接に関係する より正確な意味を伝えるメリットを持つ一方で、誤解を招く可能性があるデメリットもある この点を踏まえ、APIの名前を決める際は、APIが使われるコンテキストを念頭に置くこと。 データ型と単位 自明な名前(firstNameなど)もあれば、文脈や単位によって意味が異なってくる名前(sizeなど)もある。 「文脈や単位によって意味が異なってくる名前」に明確な意味を持たせるには、以下のアプローチが考えられる。 名前に単位を含める 専用のデータ型を定義する(言語次第) 扱うこと なぜ名前を気にする必要があるのか 良い名前を付けたときの影響 言語、文法、構文の決め方 コンテキストが名前の意味に与える影響 ケーススタディ : 名前付けが良くないとどうなるか? まとめ 良い名前は、良いAPIと同様に、シンプルで表現力があり、予測可能 言語、文法、構文(およびその他の任意の選択)に関しては、多くの場合、何かを選んでそれを一貫して使用するのが正解 名前に前置詞を含めることは、APIにとってマイナスになることが多い 前置詞を含むAPIは、根本的な設計上の問題を抱えていることを暗示している コンテキストは、より分かりやすい情報はを提供すると同時に、誤解を生む原因になりえることも忘れてはならない 名前を選ぶときには、名前が使用されるコンテキストに注意を払うこと 型情報を活用して、名前には含まれない情報を伝える 特に、専用のデータ型は効果的

WebAPI

2023年05月27日(土)

1.5時間

API デザインパターン 1章読了

やったこと API デザインパターン 1章読了 学んだこと ポイント インターフェースとは? APIとは何か? リソース指向とは? 良いAPIの要因は何か? 学び ライブラリやパッケージもAPI 気づき Web APIは、機能に必要な特定の実装や計算要件を隠ぺいする 言い換えると、利用者は、これらを気にすることなく使用できる。 RPCもAPIの一つであり、リソース指向とは別物 ![[Drawing 2023-05-25 21.20.57.excalidraw]] 予測可能なAPI あるAPIを一つ学ぶと、他のAPIの使い方も何となくわかる(≒予測できる)、そんなAPI 「表現力があること」と「シンプルであること」は、近い概念のように思う なぜそう思うかは、うまく言葉にできなかった メモ APIは、システムとやり取りする方法を定義したもの ライブラリやパッケージもAPI Web APIも、APIの一種であり、以下の特徴を持つ 開発者側が大部分をコントロールできる 利用者がコントロールできる箇所は少ない 利用者は、APIの変更の影響をもろに受ける APIはコンピュータと相性が良い 人間用はGUIであり、GUIはコンピュータと相性が悪い 特に、自動化にはAPIが重要な役割を担う 色々なAPIがある RPC 指向 実行される処理に重点を置く考え方 リモートのプログラムを、メソッドのように呼び出す ステートレスな処理には強いが、ステートフルな処理に弱く、メソッド名の丸暗記が必要という欠点がある リソース指向 操作 × リソース で、APIを定義 6つの操作と5つのリソースの場合、操作名とリソース名の計11個を覚えておくことで、 最大30種類の処理を実現できる APIを開発する目的 ユーザが必要とする機能がある ユーザは、その機能をプログラムから使用したい 上記目的から、APIが持つべき性質を挙げる 実行可能であること レイテンシーや正確さなど、ユーザの要件を満たしつつ、問題を解決できること 表現力があること ユーザが必要とする処理を、明確かつ簡単に表現できること 処理内容と戻り値が容易に予測できる名前 分かりやすい名前の引数でオプションを指定できる シンプルであること 利用者が求める機能を、最も簡単な方法で公開すること よく利用される機能は単純にする一方で、上級者向けに複雑なAPIも用意する 境界がはっきりしている 予測可能であること 一例としては、名前に一貫性があること まとめ インターフェースとは、二つのシステムがどのようにやり取りをするかを定義した契約書 APIは、二つのシステムのやり取り方法を定義した特殊なインターフェース ダウンロード可能なライブラリ、Web APIなど、様々な形態で提供される Web APIは、以下の点が特殊 機能をネットワーク上に公開する 機能に必要な特定の実装や計算要件を隠ぺいする 言い換えると、利用者は、これらを気にすることなく使用できる リソース指向APIは、API設計手法の一つ 限られたリソースに対して、標準的なアクション群(メソッド)を定義することで、複雑さを軽減する 良いAPIの要因は明白ではないが、少なくとも以下は当てはまる 実行可能であること 表現力があること シンプルであること 予測可能であること

WebAPI

2023年05月25日(木)

1.0時間

単体テストの考え方/使い方 10章 読了

やったこと 単体テストの考え方/使い方 10章 データベースに対するテスト 読了 学んだこと ポイント データベースをテストするのに必要な事前準備 データベースのテストに関するベスト・プラクティス テストデータのライフサイクル テストでのトランザクションの扱い 学び DB操作を含む統合テストは、テストとして非常に価値がある スキーマ情報も、バージョン管理する 「単位作業」という考え方 統合テストにおけるトランザクションの扱い 「フェーズをまたいだトランザクションの使いまわし」は行わないこと 読み込み処理のテストは必須ではない リポジトリは、個別にテストするべきではない ビジネスロジックの統合テストと、確実に重複するため ビジネスロジックの統合テストの中で、一緒に検証するに留める 気づき DB操作を含む統合テストは、テストとして非常に価値がある(大事なことなので二回書きました) トランザクションは、遅延可能であれば、遅延させた方が良い パフォーマンスの向上が期待できる Snowflake の構文に、 create or replace という構文があったことを思い出した こういった構文を使うことが出来れば、DDLは状態ベースで管理できそう 問題は、古いデータを新しいテーブルへ移行する方法 「読み込み処理のテストは必須ではない」という結論には、コスパの面もありそう 書き込み処理は、高リスクのため、徹底的に検証したい => コスパが高い 読み込み処理は、リスクが低いため、そこまで重要ではない => コスパが悪い 「リポジトリをテストするべきか」という結論にも、コスパの面がありそう 改めて、大事なのは **「最低限の労力で、最大限の効果が得られるテストを作成する」こと メモ 管理下にある依存をいかにテストするか - 代表的なものはDB 事前準備 スキーマを、Gitなどのバージョン管理ツールにて管理する 開発者ごとに個別のデータベース・インスタンスを用意する 「移行ベース」を用いて、データベースの変更を本番環境へ反映させる バージョン管理ツールを用いたスキーマの管理 ソースコードと同じく、スキーマ情報をバージョン管理ツールにて管理する。 これにより、スキーマの変更を記録・追跡できるようになる。 - バージョン管理ツールで運用する場合は、隠れた変更が内容徹底する必要がある スキーマとして、テーブル、ビュー、インデックス、ストアドプロシージャに加えて、参照データも含めること。 ◇参照データ アプリケーションを適切に機能させるために事前に用意しなくてはならないデータのこと。 SQLにおいて、テーブル結合に必要なメタデータなど CREATE 文に加えて、参照データを格納するINSERT文もバージョン管理すると良い。 開発者ごとにデータベースインスタンスを用意する 開発者全員で一つのDBインスタンスを共有すると、以下の問題が発生する。 互いのテストに影響が出てしまう 互換性のない変更を加える際、他の開発者の手を止めてしまう この問題を解決策として、開発者一人一人に、専用のDBインスタンスを立ち上げると良い。 「移行ベース」を用いた本番環境への反映 DBの変更を本番環境へ反映する手段として、「状態ベース」と「移行ベース」の二つが考えられる。 ◇状態ベース 開発DBと本番DB の差分を取得する際、「状態の違い」に着目する手法。 ツールを用いて開発DBと本番DBの差分を取得し、本番DBが「最新の状態」になるようなスクリプトを実行することで、本番DBを最新化する。 「差分を埋めるスクリプト」が、バージョン管理ツールで管理する対象になる。 ◇移行ベース 開発DBと本番DBの差分を取得する際、「最新状態への移行に必要な操作」に着目する手法。 つまりはDDLということ? 状態ベース 移行ベース データベースの状態 明確になる 隠れる 移行の手順 隠れる 明確になる データベースの状態が明確になると、「マージ競合」に対処しやすくなる。 移行手順が明確になると、「データ・モーション」に取り組みやすくなる。 ◇データ・モーション 既存のデータを、新しくなったスキーマに合致するよう変形すること データベースへの変更が増えるほど、「データ・モーション」に取り組みやすい「移行ベース」に軍配が上がる。 データベーストランザクションの管理 データベース操作とトランザクションを分離する 責務を、以下の二つに分割する。 - データベースにアクセスし、CRUD処理を完遂するリポジトリクラス - トランザクションを管理する、トランザクションクラス 上記二つのクラスは、生存期間が異なっても良い。 ![[Drawing 2023-05-05 18.14.39.excalidraw]] 実装的には、コントローラ(またはその上位クラス)でトランザクションオブジェクトを生成し、リポジトリに渡す。 public class UserController { public string ChangeEmail()(int userId,string newEmail){ Transaction transaction = new transaction(); UserRepository userRepository = new UserRepository(transaction); //他、必要なリポジトリを生成 // ビジネスオペレーション userRepository.someAction(); ... //全DB操作が正常終了した場合、コミット transaction.commit(); } } commit処理(≒DBへ変更を反映するかどうかの判断)は、コントローラ側で実装する。 一方で、トランザクション破棄の処理は、インフラ層で実装すると良い。 単位作業(Unit of Work)の導入 ◇単位作業 一つのビジネス・オペレーションの中でデータの変更が発生するオブジェクトを全て保持し、オペレーション完了時に、変更を一単位にまとめてDBへ反映するパターン。 ![[Drawing 2023-05-05 18.36.52.excalidraw]] 単位作業を導入することで、DBに対する更新を後回しにできる。 これにより、トランザクションの生存期間を可能な限り短くすることが出来、更新データの輻輳緩和やパフォーマンスの向上ができる。 統合テストにおけるデータベーストランザクションの管理 統合テストも、三つのフェーズ(Arrange、Act、Assert)で構成される。 この時、トランザクションや単位作業がフェーズをまたいではならない。 - 言い換えると、フェーズをまたいで、トランザクションを使いまわしてはならない これは、可能な限り本番環境に近い環境でテストするため。 - 本番環境では、トランザクションや単位作業を使いまわさないよね? テストデータのライフサイクル データベースを使用する統合テストは、一ケースずつ実行すること 一ケースずつ実行し、テストケースに応じたテストデータを順次用意していくと効率が良い。 - 特に、統合テストのテストケースを同時実行しようと試みることは、労力に見合わない 一つのテストケースが完了したら、不要なデータを削除すること いわゆる後始末をきちんと行うこと。 テストケースの実行前に削除する(≒初期化する)のが望ましい。 テスト中のみ、in-memory DBを使う問題点 「テスト用に、SQLiteのような in-memory DBを使用する」といった戦略は推奨されない。 これは、「可能な限り本番環境に近い環境でテストする」という考え方に反するため。 特に、in-memory DBは、そうではないDBと比較して機能面で異なる部分も存在する。 そのため、in-memory DBでテストに成功したからと言って、本番環境で正しく動作するかはわからない。 - 言い換えると、in-memory DBでテストに成功したという結果は、「本番環境で正しく動作すること」を保証できない まとめると、可能な限り、本番環境で使用するDBと、同じDB製品を用いて統合テストに臨むことが推奨される。 そして、「テストの時だけin-memory DBを使用する」といった戦略は避けること。 - 本番環境でin-memoryDBを使用し、その部分の動作を検証したい場合を除く テストコードの再利用 テストコードも、保守性や拡張性が大事。 再利用しやすよう、ヘルパーメソッドやコンポジションを導入すると良い。 準備フェーズでのコード再利用 オブジェクトマザーパターンと、テストコードビルダーパターンが代表的。 ◇オブジェクトマザー テストフィクスチャの生成を手助けするクラスやメソッドのこと。 テストの準備フェーズを共通化でき、テストコードの再利用性を高めることができる。 ◇テストデータビルダー オブジェクトマザーと同じことを、BuilderパターンとFluent Interfaceにて実現する。 可読性が大きく向上する。 書籍では、オブジェクトマザーの利用を推奨している。 - テストデータビルダーは、ボイラー・プレートコードが増えるため非推奨 実行フェーズでのコードの再利用 トランザクションの生成処理が主な再利用対象。 トランザクションブロックをヘルパーメソッドメソッド化し、トランザクション内で実行したい処理を引数として受け取るようにする...など 確認フェーズでのコードの再利用 「確認のため、DBのデータをクエリする」ような処理は、十分共通化可能。 共通化部分は、Fluent Interface で実装すると、可読性が向上して良い。 データベースを使用したテストに関するよくある疑問 疑問1 読み込み処理をテストするべきか? 必須ではない。 - 読み込み処理は、書き込み処理ほどリスクが高くないため - 非常に複雑な読み込み処理や、ビジネスロジックにとって非常に重要な役割を担う場合はテストする価値あり また、読み込み処理はカプセル化しない(≒SQLを直接叩く)方が良いことがある。 - 本来カプセル化は、データが壊れないことを保証するための仕組みであり、書き込み処理のための仕組み - 読み込み処理の場合は、データを破壊することはないため、カプセル化は必須ではない - カプセル化しないことは、不必要な抽象化層を経由しないことを意味する - 不要な層を経由しない(≒処理が少なくて済む)ため、パフォーマンスの向上が見込める 疑問2 リポジトリをテストすべきか? リポジトリは、コントローラに分類される。 結論として、「リポジトリ専用のテスト」は作成するべきではない。 - 「外部のデータストア」、つまりプロセス外依存を専門に扱うため、保守コストが高くつく - テスト内容が、ビジネスロジックを検証する統合テストと確実に重複する そのため、統合テストの一部として検証するにとどめる。 同じ理由で、EventDispatcherクラス(ドメイン・イベントを基に、管理下にない依存への呼び出しを行うクラス)もテストしない方が良い。 まとめ スキーマはソースコードと共にGitなどのリポジトリで管理する スキーマ : テーブル、ビュー、インデックス、ストアドプロシージャなど 参照データ : アプリケーションを適切に機能させるために、事前準備する必要があるデータ スキーマの一部 対義語は通常データ 開発マシン上で、開発者個別のDBインスタンスをホストできると、テスト効率が向上する データベースの変更を本番環境へ反映する方法には、「状態ベース」と「移行ベース」がある 状態ベース データベースの比較ツールを用いた方法 マージ競合が発生しても、解消しやすい 移行ベース どのようにデータベースを新しい構造に移行するか、に焦点を当てた方法 データモーションを行いやすい データモーションの重要度が高いため、「移行ベース」がおすすめとのこと 可能な限り、単位作業パターンを採用する すべてのデータ更新をビジネスオペレーションの最後まで行わないようにする パフォーマンスの向上につながる DBが絡むテストでは、準備・実行・確認フェーズでは、それぞれ個別のデータベーストランザクションを持つこと 言い換えると、フェーズをまたいだトランザクションを使用しないこと テストの後始末は、テスト終了後ではなく、テスト開始時に行うと良い 個別に後始末のフェーズを設ける ... といったことが不要になる テストの際は、本番環境と同じDB製品を使用すること 本番環境ではMySQL 、テストではSQLite ... といったことはダメ SQLite のテストであり、MySQLを使用したコードのテストとはならない テストコードから、ビジネス的に重要ではない技術的なコードをプライベートメソッドやヘルパークラスに抽出する テストケースのコードを量を減らすことが出来る 準備フェーズでは、テストデータビルダーよりもオブジェクトマザーを使用する 実行フェーズでは、コントローラのメソッドへの呼び出しを委譲するメソッドを用意する 確認フェーズでは、Fluent Interface を導入する Read/Write テストは、 Write の方が重要 Readのテストは、複雑なクエリを含む読み込みや、ビジネス的に非常に重要なデータの読み込みを中心に行う リポジトリパターンを導入したコードは直接テストしない 労力に見合わない 統合テストの際に、テストの一部として間接的に検証する

テスト

2023年05月05日(金)

3.0時間

単体テストの考え方/使い方 11章 読了

やったこと 単体テストの考え方/使い方 11章 単体テストのアンチパターン 読了 学んだこと ポイント プライベートメソッドをテストする際のアンチパターン テストコードにドメイン知識が漏洩する状況 具象クラスに対してテストダブルを作成する際の注意点 学び プライベートメソッドは直接テストしない 観測可能な振る舞いを通じて、間接的にテストする 「具象クラスの一部分だけをモック化したい」という要求の元、具象クラスのモックを作成することはアンチパターン 具象クラスが責務を負い過ぎている可能性が高い テストのためだけにコードを変えてしまうと、プロダクションコードの検証ではなく、「テスト用にチューニングしたコードの検証」となってしまう テストコードに、ドメイン知識を持ち込まない ドメインロジックの中に移植すること 気づき 「具象クラスを一部分だけモック化したい」と感じたら、単一責任の原則違反を疑おう プライベートメソッドは直接テストしない リフレクション等で頑張っても、労力に見合わず、壊れやすい テストのためだけにコードを変えてしまうと、プロダクションコードの検証ではなく、「テスト用にチューニングしたコードの検証」となってしまう点 目から鱗 単体テストにて、Act のコードが多い場合、ドメイン知識の漏洩を疑ったほうがよさそう メモ プライベートなメソッドに対する単体テスト プライベートメソッドをテストするために、プライベートメソッドを公開してはならない - 「観察可能な振る舞いのみ検証する」という基本原則に反する プライベートメソッドをテストする場合は、公開されている観察可能な振る舞いを通してテストすること。 観察可能なふるまいを可能な限り検証したにもかかわらず網羅率が低い場合、以下の問題が考えられる。 - デッドコード - 絶対に通過しないコード(リファクタリングの際に消し忘れた...など) - 削除するべき - 抽象化の欠落 - 抽象化 テストのためだけに、private修飾子をpublicにしてしまう アンチパターン。 テストするべきコードは、プロダクションコードと同じコード。 - テストのためだけにコードを変えてしまうと、プロダクションコードの検証ではなく、「テスト用にチューニングしたコードの検証」となってしまう そもそも、公開するのは、観察可能な振る舞いのみ。 観察可能なふるまいを通じて、プライベートメソッドをテストする。 テストコードに、ドメイン知識が漏洩してしまう テストのために、何らかのアルゴリズムなど、テストコードを動かすための何らかの知識を記述する必要があるような場合。 - プロダクションコードのアルゴリズムが変化すると、テストコードが壊れてしまう これは、ドメイン知識がテストコードの中に漏洩していることを意味する。 ◇対応策 - リファクタリングを通じて、ドメイン知識を含むロジックを、ドメインクラス側(≒観察可能な振る舞いの中)に閉じ込める - テストコードに、「(ドメインロジックを走らせた結果である)期待する値」を直接書いてしまう プロダクションコードの汚染 プロダクションコードの汚染とは テストでのみ使用するコードを、プロダクションコードの中に実装してしまうこと 本番環境で動作するに際して、不要なコードが本番環境に持ち込まれることを意味する。 これは、以下二つの問題につながる。 - 不要なコードを実行することによるパフォーマンスの低下 - コード増加に伴う、可読性、および保守性の低下 テスト専用コードは、プロダクションコードに持ち込まないこと。 具象クラスに対するテストダブル 基本的に、テストダブルは、I/Fに対して作成する。 しかし、大多数のライブラリは、具象クラスに対してもテストダブルを作成できる。 具象クラスに対してテストダブルを作成すると、「プロダクションコードの内、一部分だけをモック化する」といったことが可能。 しかし、上記機能は使うべきではない。 - つまり、具象クラスに対してテストダブルを作成するべきではない 「具象クラスの一部分だけをモック化したい」という要望が生まれる場合、その具象クラスが責務を負い過ぎている(≒単一責任の原則に反している)可能性が高い。 ◇対応策 単一責任の原則に則り、責務が一つになるよう具象クラスを分割すること。 単体テストにおける現在日時の扱い 現在日時を扱う独自クラスを構築し、環境(本番/テスト)に応じて動作を変化させる(いわゆる環境コンテキストパターン) -> アンチパターン - コードが複雑になるだけ ◇対応策 現在日時情報を、明示的な依存として、メソッドに注入する。 - 可能な限り、値(または値オブジェクト)を注入する - 値の注入が不可能であれば、「現在日時を返す」サービス(I/F)を注入し、現在日時が必要になったらサービスから受け取るようにする まとめ 単体テストのために、プライベートメソッドを公開してはならない テストは、プロダクションコードと同じように取り扱ってこそ価値がある テストだけ特別扱いしたら、それは「テスト用にチューニングしたコード」の検証になってしまい、プロダクションコードの検証にならない 公開したメソッドが、テストの実装に結び付き、テストが壊れやすくなる プライベートメソッドを使用するパブリックメソッドを通してテストすること パブリックメソッドを通したテストが困難な状況は、抽象化の欠落が発生している可能性が高い 要リファクタリング プロダクションコードのアルゴリズムやロジックの知識を、テストに持ち込んではならない 言い換えると、ブラックボックステストを行うこと テストに持ち込まれる状況、というのは、ドメイン知識がテストに漏洩することを意味する プロダクションコードの汚染とは、テストでのみ使用するコードを、プロダクションコードに持ち込むこと プロダクションコードに不要なコード(≒処理)が増え、保守性とパフォーマンスの低下に繋がる 「具象クラスの一部分だけをモック化したい」という要求の元、具象クラスのテストダブルを作成することはアンチパターン 具象クラスが責務を負い過ぎている(≒単一責任の原則に反している)可能性が高い 現在日時を環境コンテキストとして表現することは、プロダクションコードの汚染である 現在日時は、依存として、明示的に注入すること

テスト

2023年05月03日(水)

2.0時間

単体テストの考え方/使い方 9章 読了

やったこと 単体テストの考え方/使い方 9章 モックのベストプラクティス 読了 学んだこと この章で取り扱うこと モックの価値を最大限に引き出す方法 モックからスパイへの移行 モックのベストプラクティス ポイント モック化すべき対象と、モック化するべきではない対象 モックとスパイの違い、およびスパイの利点 モックのベストプラクティス 学び 単体テストでは、モックを使ってはならない 言い換えると、モックは、統合テストでのみ使用する モックでは、以下の二つを確認する事 想定している呼び出しが行われていること 想定していない呼び出しが行われていないこと 汎用I/Fと、ドメインモデルに特化したI/Fを分離する 言い換えると、ひとつのI/Fに、複数の責務を負わせないようにする この時、一つの具象クラスのために一つのI/Fを定義しているのであれば、I/Fを使用せず直接具象クラスを扱うことを検討する 気づき 「管理下にない依存」用のI/Fは、ドメインロジックと管理下にない依存を抽象化する役割もある AWS SDKの呼び出しをラップしたりは、無意識にやっていた 改めて、効果的な手法であることを再確認した 単体テストにモックを使用してはならない理由をつかめた 自プロダクトのテストにも改善の余地ありとみた メモ 9.1 モックの価値を最大限に引き出す方法 モック化は、「管理下にない依存」に対してのみ実施すること。 - 「管理下にある依存」のモック化は、テストが実装の詳細に強く結びつき、テストが壊れやすくなる モック化は、「アプリケーションの境界」に近いコンポーネントに対して行うと良い。 - リファクタリング耐性が向上する - アプリケーションの境界に近いほど、実装の詳細との結びつきが弱くなるため - より強い退行保護(バグ検出)能力が得られる - 境界に近づくまでに、より多くのコンポーネントを経由することになる - これは、より多くのコードをテストできることを意味する モックからスパイへの置き換え ◇スパイ モックと同じ、テストダブルの一種。 モックはフレームワークの助けを得て作成するのに対して、スパイは開発者が実装する。 別名、「手書きのモック」 モックと比較して、スパイは、以下の点で優れている。 - 実行結果を確認するコードを記述できる - 記述したコードを再利用できる - テストケースのコード量が減り、可読性が向上する アプリケーションの境界に位置するテストダブルを作成する場合は、特に上記スパイの利点が大きくなる。 テストでは、プロダクションコードを信頼しない 監査人が、監査対象の人の言葉を鵜吞みにしないのと同じように、テストケースでは、プロダクションコードを信頼しない。 過度に信頼している例 あるメソッドの戻り値を検証せず、メソッドが呼び出されたことだけを検証している また、テストは、プロダクションコードの影響を受けずに検証できる場所で行う必要がある。 9.2 モックのストプラクティス モックの利用は統合テストに限定する 目的は、ビジネスロ ジッ クに関するコードと連携を指揮するコードとを分離すること。 - テストは、「プロセス外依存とのやりとり」または「ドメインの複雑さ」のどちらか一方だけをテストするべき - 両方をテストしてはならない モック化は、「管理下にない依存」に対してのみ行うべき。 「管理下にない依存」を扱うのはコントローラのみ。 つまり、モック化は、コントローラのテスト(≒統合テスト)でのみ行うべき。 モックは、「管理下にない依存」の数だけ用意する 言い換えると、「一テストにつきモックは一つだけ!」といった制約は存在しない。 モックに対して行われた呼び出し回数を常に確認する 言い換えると、モックに対して、以下の二つを必ず確認する。 - 想定する呼び出しが行われていること - 想定していない呼び出しが行われていないこと 目的は、管理下にない依存との互換性を維持すること。 サードパーティーライブラリをモック化しない サードパーティーライブラリ製のライブラリをモック化したい場合は、独自クラスでサードパーティーライブラリの処理をラップし、独自クラスに対してモック化すること。 この独自クラスは腐敗防止層として機能し、以下のメリットを享受できる。 - サードパーティーライブラリの複雑さを抽象化できる - 必要な機能のみ公開できるようになる - クラス名やメソッドを定義できる機会(≒名前を付ける機会)を得ることが出来る - ドメインに合わせた、ドメイン用語を使用できるようになる まとめ 「管理下にない依存」とのコミュニケーションを検証する場合、コントローラからその依存に向かう流れにて、最後のコンポーネントをモック化する スパイは別名「手書きのモック」 システムの境界にあるクラスをモック化する場合は、スパイの方が優れている スパイであれば確認コードを記述でき、更にスパイの記述を使いまわすことができる テストでは、プロダクションコードの影響を受けない場所を構築し、そこでテストする必要がある プロダクションコードにて定義したリテラルや定数を使用しない 同義語反復なテストケース(なにも検証していない、無意味な確認)となりかねない 管理下にない依存は、全て同じレベルでの後方互換性が必要なわけではない メッセージの正確さではなく、メッセージの存在および特定の情報が含まれているかだけを検証したい場合など 単体テストでは、モックを使ってはならない モックは、管理下にない依存との通信でのみ使用する 管理下にない依存を扱うのはコントローラのみ コントローラをテストするのは統合テストのみ => モックは、統合テストでのみ使用する 言い換えると、単体テストでは、モックを使ってはならない 一つのテストケースで、モックを複数使っても問題ではない モックの数は、「管理下にない依存」の数で決まる モックを用いたテストでは、以下の二つを確認する 想定している呼び出しが行われていること 想定していない呼び出しが行われていないこと サードパーティーライブラリをモック化してはならない サードパーティーライブラリの処理をモック化したい場合は、サードパーティーライブラリのアダプターを作成し、アダプターに対してモック化する

テスト

2023年05月02日(火)

2.0時間

単体テストの考え方/使い方 8章 読了

やったこと 単体テストの考え方/使い方 8章 読了 学んだこと ポイント 統合テストの役割 テスト・ピラミッドについての更なる考察 価値のある統合テストの書き方 学び 単体テスト 統合テスト 検証対象 ドメインモデル ドメインモデルとプロセス外依存を結びつけるコード ケース 出来るだけ多くの異常系を検証 最長のハッピーパス一件のと、単体テストでは検証できない異常系全て 単体テスト/統合テストの棲み分け テストピラミッドは、プロジェクトの複雑さによって形が変わり得る 価値の低い(または無い)テストケースは、作成しない方が良い 具象クラスがひとつしかないI/Fは、抽象化の恩恵が少ないだけでなく、コードの視認性が悪化する問題を抱える 管理下にない依存をモック化したい場合は、具象クラスが1つであってもI/Fが有効 管理下にある依存は、具象クラスとして実装し、コントローラに注入する すべての依存(ログ出力オブジェクトも含む)は、常に、コンストラクタやメソッドの引数を経由して、明示的に注入されるようにすべきである 気づき 手元の動作確認には 単体テストを、CI/CDテストでは 単体テスト & 統合テスト を、リリース直前ではE2Eテストを実行する ... という棲み分けか メモ 単体テストだけではなく、外部依存とのやり取りを含めた検証も必要。 このような検証が、統合テストに当たる。 8.1 統合テストとは? 単体テストが持つ、以下の性質を一つでも損なっているものが、統合テストに分類される。 - 一単位の振る舞いを検証する事 - 実行時間が短い事 - 他のテスト・ケースから隔離された状態で実行されること 具体的には、プロセス外依存を交えた動作を検証することが多い。 - これは、コントローラをテストすることを意味する プロセス外依存を全部 Mock に置き換えたコントローラのテストは、単体テストに分類できる。 まとめると、... - 単体テストでドメインモデルを検証 - 統合テストで「ドメインモデルとプロセス外依存を結びつけるコード」を検証 単体テストと統合テストのバランスを保つ 単体テストと統合テストは、それぞれ利点/欠点がある。 - 単体テストは、保守性が高く、リファクタリング容易で、すぐに動作完了するが、プロセス外依存を含めた動作は確認できない - 統合テストは、プロセス外依存を含めた動作を検証できるが、遅く、保守コストが高くなる これらを踏まえ、いかに必要最低限のテストケースで済ませることが出来るかを考えることになる。 単体テストでは、多くの異常系を検証する 統合テストでは、一件のハッピーパスと、単体テストでは検証できないすべての異常系を検証する 以上より、以下の指針が立つ。 * 出来るだけ多くのケースを単体テストで作成し、保守コストを下げる * ビジネス・シナリオ一つにつき、1,2件ほど統合テストを実施し、システムの機能に自信を持つ 設計原則 早期失敗 望まないエラーを検知した場合、すぐにその処理を停止させる、という原則。 以下の利点を享受でき、アプリケーションが堅牢になる。 - フィードバック・ループの短縮 : より速やかにバグを検出でき、修正につなげることが出来る - 保存される状態の保護 : 壊れたデータが、データストアに保存されにくくなる -> 例外処理や、事前条件の確認などが、早期失敗の原則に則った設計に該当 8.2 どのようなプロセス外依存をモックに置き換えるべきか? プロセス外依存の種類 管理下にある依存 : テスト対象のアプリケーションが、完全に制御可能な依存のこと ある処理専用のDB(テーブル)など -> 「実装の詳細」に該当する 管理下にない依存 : テスト対象以外のアプリケーションも、動作を制御可能な依存のこと 複数のビジネスロジックで共有するDB(テーブル)、メールなど -> 「観察可能な振る舞い」に該当する プロセス外依存をモックに置き換える時の指針 管理下にある依存は、「実装の詳細」のため、そのまま使用する 言い換えると、モックに置き換えたりしない 管理下にない依存は、「観察可能なふるまい」のため、モックに置き換える 統合テストにて、「管理下にある依存」のDBを使えない場合は、統合テストを作成しない。 - モックなどで無理矢理テストを書いても、コスパが悪いだけ 8.4 インターフェースを使った依存の抽象化 単体テストの文脈において、誤ったインターフェースの使用が目立つ。 これにより、インターフェースの過剰使用が多々発生している。 インターフェースの誤った認識を確認し、どのような場合にインターフェースを用いるべきか議論する。 I/Fの間違った認識 誤った認識 : I/F により、プロセス外依存を抽象化でき、疎結合化できる I/Fの実装が一つしかない場合、そのI/Fは抽象ではなく、疎結合化に寄与しない 既存のコードを変更することなく機能追加が可能になり、Open/Closed 原則を順守しやすくなる Open/Closed 原則以前に、 YAGNI原則に外れる Open/Closed 原則順守のために、不要なコードの作成にコストをかけるのは本末転倒 「一つのI/Fに対して一つの実装」が有効な場面 「管理下にない依存」のモックを作成する必要がある場合、I/Fの実装が有効。 * 「管理下にある依存」は具象クラス化し、コントローラに注入する形をとる * 単体テストではモッククラスを、統合テストではプロダクションコードで使用する具象クラスを注入し、動作確認を行う ドメインロジックで使用するようなクラスのI/Fが存在する(当然、一クラス一I/F)場合、問題を疑うこと。 * ドメインロジックの動作(≒実装の詳細)を検証しようとしている可能性が高い * 実装の詳細を検証するテストは壊れやすく、テストのリファクタリング体制を低下させる 8.5 統合テストのベストプラクティス 以下のベストプラクティスが存在する。 ドメインモデルの境界を明確にする アプリケーションを構成する層を減らす 循環依存を取り除く ドメインモデルの境界を明確にする ドメインモデルの境界を明確にする(≒ドメインモデルに関するコードを見つけやすくする)ことで、以下のメリットを享受できる。 - 単体テストで検証すべきコードが明確になる - ドメインモデルの検証 = 単体テスト - アプリケーションが解決したい問題が明確になる - ドメインモデルは、解決したい問題のドメイン知識を集めたもの アプリケーションを構成する層を減らす コードの抽象化や汎用化を行う際、間接参照の層を設けることは少なくない。 間接参照の層が厚くなるほど、コードの可読性が低下する。 そのため、間接参照の層は、可能な限り少なくなるよう努めよう。 - 多くの場合は、「アプリケーションサービス層(コントローラ)」「ドメイン層」「インフラ層」で十分 - 「インフラ層」では、プロセス外依存に関する操作を扱う 循環依存を取り除く 循環依存を持つコードには、以下の問題がある - 可読性が低下する - 単体テストの際、一つの振る舞いを切り取ることが困難になる 特に、コールバックで発生しやすい。 循環依存の原因がコールバック関数の場合、コールバック関数を呼び出すのではなく、コールバック関数が期待する値オブジェクトを定義することで解決できる。 before const callback = (result) => {...} const sample = (callback) => {...} // 関数callbackは、result 変数を期待する after const sample = () => { return ...} const result = sample() // result 変数を受け取る callback(result) // これで、sample() に callback を渡す必要がなくなった 1つのテストケースに、複数の実行フェーズを用いる場合 基本的には、一つのテストケースにつき、実行フェーズは一つ。 - 複数ある場合は、設計やテストケースの問題である可能性が高い - 設計の問題 : テスト対象のメソッド一つが、複数の責務を負っている可能性 - テストケースの問題 : 一つのテストケースで、複数の振る舞いを検証しようとしている 例外的に、「開発者が、プロセス外依存を制御できない場合」は、実行フェーズを複数設ける。 - 言い換えると、制御可能なプロセス外依存は「制御可能」であるため、テストケースを複数に分割できるはず 他には、E2Eテストも、複数の実行フェーズを定義する。 8.6 ログ出力に対するテスト ログ出力をテストするべきか、判断する指針 サポートログか、診断ログか サポートログ : 開発者以外(運用者、クライアント、ユーザなど)が見ることを意図した、特定のイベントを記録するログ 診断ログ : 開発者のみが見る、開発中のデバッグログ ログ出力がユーザの要望か否か(≒ビジネス要求か否か) ログ出力のテスト方法 サポートログの場合は、テストするべき。 テストの際は、DomainLogger のような、ビジネス要求を反映したLogger クラスの定義する。 そして、上記クラスに、記録したいイベントのログを吐き出す専用メソッドを定義する。 イメージ domainLogger.UserTypeHasChanged(args...) これにより、どのようなサポートログを出すべきかが、コードから読み取れるようになる。 これは、保守が容易になることを意味する。 DomainLogger のようなクラスは、ドメインロジックに直接記入しない。 ドメインロジック側でドメインイベントを返却するようにし、返却されたドメインイベントを基に、DomainLoggerのようなクラスを用いてログを吐き出す。 各種テストでは、以下を確認する。 - 単体テストの際は、目的のドメインイベントが生成されたか確認する - 統合テストでは、モックを用いて、想定したログ出力メソッドが呼ばれたか確認する ログ出力オブジェクトは、コンストラクタやメソッドの引数に渡す。 まとめ 統合テストは、テスト対象がプロセス外依存と統合した状態で行うテスト 統合テストでは、コントローラを検証する(一方で、単体テストはドメインモデルを検証する) 統合テストは、退行保護(バグ検出)とリファクタリングに優れる 一方で、単体テストは、保守容易性と、フィードバックの速さに優れる 統合テストでは、単体テストでは備えることのできない、退行保護とリファクタリングに重きを置く 必要なら、フィードバック速度や保守容易性は犠牲にする この辺りを図示したものがテストピラミッド テストピラミッドの形状は、プロジェクトの複雑さによって変化する 早期失敗(Fail Fast):「問題(バグ)が発生したら、すぐに処理を失敗させる」という原則 管理下にある依存(managed dependency) : テスト対象のアプリケーションを介してのみアクセス可能なプロセス外依存のこと 管理下にない依存(unmanaged depencency) : テスト対象以外からもアクセス可能なプロセス外依存のこと 統合テストでは、管理下にある依存には実際の依存を使用し、管理下にない依存にはモックを使用する 統合テストでは、「管理下にある依存」を扱う層をすべて経由kること 実装クラスを一つしか持たないようなI/Fは抽象ではない 「将来に備えて」設けられたこのようなI/Fは、TAGNI原則から外れる 一方で、テストの際にこのI/Fをモックに置き換えたいような場合は、効果あり ただし、基本は「管理下にない依存」に対するI/Fのみ もし、このようなI/Fが同一プロセス内の依存に使用されているのならば、設計を疑うこと モックを使ってドメインクラス間でのやり取りを検証しようとしている これは、実装の詳細をテストしようとしていることを意味する ドメインモデルは、コードベースから見つけやすいところに配置しよう ドメインクラスとコントローラの境界が明確になっていれば、単体テストと統合テストを分離しやすくなる 間接参照の層が増えると、コードが分かりにくくなるため、できるだけ少なくしよう バックエンドであれば、ドメイン層、アプリケーションサービス層、インフラ層で十分 循環依存は、コードが分かりにくくなる コールバック処理で発生しやすい コールバックの場合は、コールバックではなく、処理結果を詰めた値オブジェクトを返すことで、循環依存を解消できる 統合テストでは、以下のような場合に限り、Act フェーズが複数存在できる プロセス外依存に何らかの制約がある場合 システム運用者が見ることを意図したログ(サポートログ)は、観察可能な振る舞い 一方で、開発者が見ることを想定したログは、実装の詳細に分類される サポートログは、ビジネス要求であり、DomainLogger クラスのような実装が必要 すべての依存は、常に、コンストラクタやメソッドの引数を経由して明示的に注入されるようにすべきである

テスト

2023年05月01日(月)

3.0時間

Linuxのしくみ 増量改訂版 9章 ブロック層 読了

やったこと Linuxのしくみ 増量改訂版 9章 ブロック層 読了 学んだこと ポイント ブロック層 推測するな、計測せよ I/O メトリクス ブロック層の機能 I/O スケジューラ readahead(先読み) ストレージデバイス高速化と、ブロック層の機能の関係 学び I/O スケジューラによる、読み書き最適化 推測するな、計測せよ ハードウェア性能が上昇するほど、I/O スケジューラのデメリットが際立つようになる I/O スケジューラの「I/O要求をためて、最適化する」という仕組みがほとんど活きず、I/Oスケジューラ動作分のオーバーヘッドだけが残る NVMe SSD では、I/Oスケジューラがデフォルトで off に 通常SSD と NVMe の違い HDD と同じI/Fでアクセスできるか否かの違い 気づき ワークロードによって、注視すべき I/O メトリクスは変化する メモ HDD の特徴 元々ブロック層は、HDDをターゲットとしていた。 技術革新によってSSDが台頭すると、SSD向けにブロック層も変化していった。 HDD は、データの読み書きが機械的な処理であり、この処理が非常に遅い。 そのため、HDDのパフォーマンスは、データの読み書き(≒機械的な処理)をいかに減らせるかが焦点だった。 ブロック層の基本機能 代表的な機能は、「I/Oスケジューラ」と「readahead(先読み)」。 I/O スケジューラ データの読み書き命令を一定期間ストックし、最適化した後に読み書きする。 雑に言えばバッチ&最適化処理。 I/O スケジューラ側で最適化してくれるので、プログラマがHDDのI/O最適化を意識する必要が(ある程度)なくなった。 readahead (先読み) 「次はこの辺りのデータを読み込むはず」と当たりをつけて、事前にデータを読み込んでページキャッシュに保存する。 配列など、連続したデータ領域へのアクセスを試みる場合に効果がある。 ブロックデバイスの性能指標 大きく、次の指標がある。 - スループット - 単位時間当たりのデータ転送量 - データコピー時に注視される - レイテンシ - 一回の I/O に要する時間 - 小さなI/Oが大量に発生するような場合に注視される - IOPS - I/O per second。そのまま、一秒間に処理できるI/O数 並列処理が可能な場合、上記指標の計算に注意する事。 推測するな、計測せよ カタログスペックだけ見てスペックを判断するのではなく、実際に動かして、メトリクスを計測してスペックを判断しよう。 動かしてみて初めて分かることもある。 ただし、推測を全否定しているわけではない。 性能測定ツール fio ファイルシステム/デバイス の性能測定ツールとして使用できる。 fio の特徴 * I/Oのパターンや、並列数、その時に使うI/Oの仕組み(fioでは I/O engine と呼ぶ)を細かく決めることが出来る * レイテンシ、スループット、IOPS など、様々な種類の性能情報を採取できる 何のための性能測定 「性能測定を行う目的」をしっかり定めて、目的を達成できるメトリクスを採取しよう。 言い換えると、余計なメトリクスは採取せず、目的を達成できる最低限のメトリクスだけ採取しよう。 よくある失敗は、「目的無くやみくもに有名なベンチマークツールを使って、採取したら満足して終わり」という事態。 「どういうパターンで性能測定するか」「そのために、どのようなベンチマークツールを使うか」 技術革新に伴うブロック層の変化 大きく、以下の技術革新が、ブロック層の変化に影響を与えた。 SSD の登場 HDDと比較して機械的な動作が一切なく、高速に動作する SATA SSD , SAS SSD : HDD と同じI/Fでマシンに接続できるSSD NVMe SSD : HDD と全く異なるI/Fでマシンに接続できる CPUのマルチコア化 I/Oスケジューラは、マルチコア未対応だった 現在のIOスケジューラは、マルチキューを用いて複数CPU動作を実現している ハードウェア性能が上昇するほど、I/O スケジューラのデメリットが際立つようになる。 NVMe SSD では、I/Oスケジューラがデフォルトで off になっている。

linux

2023年04月30日(日)

2.0時間

なんでも図解 読了

やったこと 書籍「なんでも図解」読了 学んだこと 図解のメリット 文字の羅列よりも、図の方が理解しやすい 「図」という共通認識を基に話を進めることが出来る 認識がぶれにくい 後で見返すとき、図を見た瞬間「ああ、あの話か」と思い出すことが出来る 文字の羅列だと、文字を読んで理解しないと思い出せない 1章 文字を○や□で囲むだけでシンボル化でき、図として扱うことが出来る。 □は、どっしりした安定や、「具体的」な印象を与える。 事実や固有名詞の表現に向いている。 〇は、柔らかさや、「抽象的」な印象を与える。 変化するする事柄の表現に向いている。 同じグループや関心事は、同一の図形を用いることだけは注意したい。 意図を変える 同じ図形でも、表現方法を変えることで意図を差別化できる。 一部分だけ囲んで強調する 図を入れ子で表現することで、関係性を強調する 2章 矢印をマスター! 学び 矢印で表現できること(例) 人、システム、データ、金の流れ 何かしらの順番 図で表現したシンボルの関係性 依存関係 3章 「人」をマスター!学び 「人」が効果的な場面 以下の存在を強調したい ステークホルダー 明確な役割を持つ人 人に関わるサービス・状況・状態をわかりやすく伝えたいとき 「人」に吹き出しをつけるだけで、ちょっとした状態を表現できる 4章 文章を読み、最速で作図する! 文章を図解する三ステップ 文章全体の意味を読み取る キーワードを書き出す 「囲み」「矢印」「人」で表現する 文章を読み取る際は、以下の三つの型のどれに当てはまるかを考える。 状態・構造・構成(AはBである、AはBとCから成る、など) 因果関係・変化 (AとBがやりとりする、AからBへ変わる、循環など) 拡散・収束・収集(AからB,C,Dへ広がる、Aに向けて集まる、など) 5章 話を聞きながら図解するコツは「キーワード+余白」学び 余白を設ける利点 以下の効果がある。 後から文字を囲みやすい 後から時系列や因果関係の矢印が書きやすい 後から(余白に)情報を書き加えることが出来る 6章 会議や打ち合わせでも慌てず図解する 中心に線を引いて分割する。 強制的に改行する必要が生まれるため、一行が短くなり、ブロック分けしやすくなる。 三つの型があること 時系列型 セミナーや会議など、時系列のある形態で有効 発散型 ブレストなど、アイデア出しで有効 ランダム型 フリートークやインタビューなど、先が読めない形態で有効 図解の精度が上がる5つのテクニック 1. スペースが足りない時の対処法 ノートにて、話の内容が切り替わったと感じたら、次のページを使用 ホワイトボードにて、スペースが足りなくなったら、スクショを取って古い部分を消す 模造紙して、スペースが足りなくなったら、別の模造紙に書いてつなげる 2. 日付を必ず入れる 検索性が向上する 3. 色は足し過ぎない 色分けは、グループを強調する効果があるが、色が多すぎると以下の問題が発生する - 重要な個所が分からなくなる - 目がちかちかする。 4. 文字の太さを意識 使用するツールに合わせて、視認性の高い太さを選択。 また、タイトルには太めの線を選択して強調すること。 5. 文字の抑揚を押さえる 文字は、できれば水平に書こう。 - よく文字を書いていると右上がりになりがちだが、これは読みにくくなる

ソフトスキル

2023年04月29日(土)

3.0時間

Linuxのしくみ 増量改訂版 8章 記憶階層 読了

ポイント ダーティ状態 キャッシュ戦略 ライトスルー , ライトバック 学び レジスタの計算速度と、メモリアクセス速度は、数十倍違う 「キャッシュラインに変更があった」ことを、「ダーティである」「ダーティ状態」と表現する Simultaneous Multi Threading (SMT) ページキャッシュでファイルのデータを、バッファキャッシュでファイル以外のデータをキャッシュする 気づき 空間的局所性 の特徴を知っていれば、意図的にパフォーマンスを悪化させることが可能 キャッシュ関連情報は、 sar コマンドで大体取得できる パフォーマンスが大幅なに低下した場合、ページキャッシュおよびメモリ I/O のスラッシングが原因の可能性がある sarコマンドでスラッシングの有無を特定できるようになると、トラブルシュートの幅が広がりそう メモ キャッシュメモリ レジスタの計算速度と、メモリアクセス速度は、数十倍違う。 この差を埋めるのが、キャッシュメモリ。 cpuがキャッシュメモリを書き換えた場合、書き換えた情報をメモリにも伝える必要がある。 この時、メモリに情報を伝える方法には、以下の二つが存在する。 ライトスルー方式 : 都度メモリに伝える ライトバック方式 : 所定のタイミングで、まとめてメモリに伝える キャッシュメモリが一杯の時にデータ書き込みが発生する場合、キャッシュライン上のデータを一つ破棄する。 この破棄が頻繁に発生する状態をスラッシングと呼ぶ。スラッシング状態では、I/O性能が大幅に低下する。 階層型キャッシュメモリ 最近のCPU は、キャッシュメモリが階層化されている。 - 「L1キャッシュ」「L2キャッシュ」「L3キャッシュ」のように表現する 「L1キャッシュ」が、最もレジスタに近い。 階層の番号が増えるほど、レジスタから遠のき、大容量/低速 になる。 Simultaneous Multi Threading (SMT) 一つのCPUで複数スレッドを同時実行することで、疑似的な対照型マルチプロセッシング環境を構築できる技術。 待機中(≒空き)のCPUリソースを有効活用する手段の一つ。 同時実行するスレッドにて、リソースの競合が発生しない場合、非常に高い効果を得ることが出来る。 ページキャッシュ (ストレージデバイスの)ファイルから読み込んだデータを、メモリ上にキャッシュする技術。 ページキャッシュ領域は、カーネルのメモリ上に存在する。 つまり、ページキャッシュは、カーネル上で動作する。 仕組みはキャッシュメモリとほぼ同じ。 ページキャッシュでは、名前の通り、ページ単位でデータを扱う。 ページキャッシュの導入により、ストレージデバイスへの I/O 速度向上が期待できる。 /proc/sys/vm/drop_caches に3 を書き込むことで、ページキャッシュをクリアできる。 ページキャッシュをクリアすることで、「パフォーマンスの悪化の原因はページキャッシュか切り分ける」といった使い方が可能。 バッファキャッシュ ファイルデータ以外のデータをキャッシュする仕組み。 以下のようなときに使用する。 ファイルシステムを使用せずに、デバイスファイルを用いてストレージデバイスに直接アクセスするとき ファイルサイズやパーミッションといった、メタデータにアクセスする時 ページキャッシュにおける ライトバック処理のタイミング ※ライトバック : 変更のあったデータを、一定のタイミングでまとめて書き出す処理 ページキャッシュのライトバック処理は、以下のタイミングで動作する。 周期的。デフォルトでは5sに一回 周期は、 syctl の vm.dirty_writeback_centisecs で変更可能 「0」は、ライトバック処理を無効化する設定 ダーティページが増えてきたとき 物理メモリの内、ダーティページの占める割合が、特定の値を上回ると動作 デフォルトは10% 割合は、vm.dirty_backgroud_ratioパラメータで変更可能 割合ではなく、バイト単位による指定も可能 Dicect I/O 以下のような、ページキャッシュを動作させたくないような場合は、「Direct I/O」という仕組みを使用する。 一度しか読み書きしないデータ プロセスが、自分でページキャッシュ相当の仕組みを実装したい場合 ファイルの Open 時に、 O_Direct フラグを立てることで、「Direct I/O」を使用できる。 統計情報 sar -r コマンドで得られる、キャッシュ回りの統計データについてまとめる。 フィールド名 意味 kbmemfree 空きメモリの量(KiB)ページキャッシュ、バッファキャッシュ、スワップ領域はカウントしない kbavail 事実上の空きメモリ量(KiB) kbmemfree + kbbuffers + kbcached kbbuffers バッファキャッシュの量(KiB) kbcached キャッシュメモリの量(KiB) kbdirty ダーティなページキャッシュとバッファキャッシュの量(KiB) sar -B コマンドにて、ページイン、ページアウト情報が取得できる。 swapon --showコマンドにて、スワップ領域の容量などを確認できる。 sar -S コマンドにて、スワップ領域の利用状況を確認できる。 スワップ領域の使用量を示す %swpused の値を注視しよう。

linux

2023年04月23日(日)

1.5時間

単体テストの考え方/使い方 7章 読了

やったこと 単体テストの考え方/使い方 7章 単体テストの価値を高めるリファクタリング 読了 学んだこと この章で取り扱う事 プロダクション・コードの種類の識別 質素なオブジェクト 価値あるテストケースを作成する方法 ポイント 質素なオブジェクト コードの深さ、コードの広さ または「ドメインにおける重要性」と「協力者オブジェクトの数」 「ロジックに関する責務」と「コード間の連携に関する責務」を分離する 副作用は、ビジネスロジックで実行させない そのために、副作用を伴う処理を、可能な限り遅延させる 抽象化する対象をテストするよりも、抽象化された結果をテストする方が簡単である 学び ヘキサゴナルアーキテクチャ、関数型アーキテクチャは、質素なオブジェクトパターンを適用したアーキテクチャ 最小限の保守コストで最大限の価値を生み出すテストを作成するために、以下を実施できること 価値あるテストケースを識別できる 言い換えると、価値の少ない(または無い)テストケースを認識できる 価値あるテストケースを作成できる まずは、プロダクションコードから、リファクタリングが必要な部分を識別できること 「コードの複雑さ」と「協力者オブジェクトの数」の視点で識別できる Active Record パターン ドメイン・クラスが、DBから自身のデータを取得・保存できる設計パターン ビジネスロジックに副作用を持ち込みたくない 副作用を伴う処理とビジネスロジックを分離し、副作用を伴う処理を可能な限り遅延することで実現できる 気付き コントローラの責務 View と Model 、 Model と Model間など、二つ以上のコンポーネントを取り持つ(言い換えると、両者の処理の流れを制御する)ことに責務を持つ ドメインロジックと外部依存を分離することが大事 分離の際は、両者の橋渡しをするコントローラが必要になる 最終的には、「ビジネスロジック」「外部依存」「コントローラ」という分離になるか 役割 「ビジネスロジック」: 引数で受け取ったデータを基に、決定を下すだけ 「外部依存」: 外部APIを叩いたり、DBへアクセスしたり 「コントローラ」: ビジネスロジックと外部依存を橋渡しする 「ビジネスロジック」は、単体テストで集中的に 「コントローラ」は、統合テストに属する この時、「外部依存」は 全部 Mock となる予感 メモ リファクタリングが必要なコードの識別 「コードの複雑さ」と「協力者オブジェクトの数」の視点で分類できる。 ![[Pasted image 20230422130837.png]] * 協力者オブジェクトの例) 非同期、マルチスレッド処理、UI、プロセス外依存とのコミュニケーションなど 一般的に、以下が知られている。 * 協力者オブジェクトが多いほど、単体テストの保守コストが増大する * コードの複雑さが多いほど、単体テストの価値が高くなる 以上より、以下のことが言える。 「ドメイン・モデル アルゴリズム」の単体テストに時間をかける 「コントローラ」の単体テストに時間をかけない 「コントローラ」のテストは、統合テストで実施する 「取るに足らないコード」のテストに時間をかけない 過度に複雑なコードは、リファクタリングによって「ドメイン・モデル アルゴリズム」と「コントローラ」に分割する 「取るに足らないコード」や、質の悪いテストは、リファクタリングの過程で取り除くことになる。 質素なオブジェクトを用いた、過度に複雑なコードの分割 過度に複雑なコードは、「ドメイン・モデル アルゴリズム」と、協力者オブジェクト(簡単にはテストできない依存)から成る。 過度に複雑なコードを、以下の二つに分離する。 * 「ドメイン・モデル アルゴリズム」に相当するコード * テストが困難な依存で構成される質素なクラス 「ドメイン・モデル アルゴリズム」を、作成した質素なクラスで包みこむことで、プロダクションコードを動作させる。 * この時、質素なクラスには、ロジックをほぼ(または全く)持たせないこと。 このように、過度に複雑なコードを、ロジックに関する責務と質素なクラス(コントローラの責務)に分離するパターンを、質素なオブジェクトパターンと呼ぶ。 このようにして責務を分離すると、テストが容易になるだけでなく、プロダクションコードの保守性も向上する。 関数型アーキテクチャとヘキサゴナルアーキテクチャは、質素なオブジェクトパターンを適用したアーキテクチャ。 「コードの深さ」と「コードの広さ」 [[#質素なオブジェクトを用いた、過度に複雑なコードの分割]]で述べたように、「ロジックに関するコード」(ドメイン・モデル アルゴリズム)と「コード間の連携を指揮するコード」(コントローラ)の責務分離は、非常に重要。 この責務分離について、書籍では、以下のように定義している。 * ロジックに関する責務 :「コードの深さ」 (≒複雑さや重要性) * コード間連携の指揮に関する責務 :「コードの広さ」(協力者オブジェクトの数) 事前条件はテストするべきか 明確なルールは存在しない。 ドメインにとって重要な事前条件であれば、十分テストする価値はある。 コントローラにおける条件付きロジックの扱い 決定を下す過程で、ある処理で得た結果を基にプロセス外依存から新たにデータを取得するような場合を考える。 * この時、コントローラに、「プロセス外依存とのやり取りを決定する」ロジックを記述することになる この解決策は、以下の3つ。 外部依存に対する読み書きを、処理の開始/終了 部分に全て持っていく ドメイン・モデルに、プロセス外依存を注入する 決定を下す過程をさらに細かく分割する 上記の内、どの解決策を選択するべきかを判断したい。 この場合、以下に挙げる要素のバランスが取れているかを考えると良い。 ドメイン・モデルのテストしやすさ コントローラの簡潔さ パフォーマンスの高さ 上記要素は、二つまでしか備えることが出来ない。 各解決策に対する、書籍での見解 外部依存に対する読み書きを、処理の開始/終了 部分に全て持っていく 非推奨 : パフォーマンスが犠牲になるため非推奨 ドメイン・モデルに、プロセス外依存を注入する 非推奨 :ビジネス・ロジックをプロセス外依存とのコミュニケーションから分離できなくなる 決定を下す過程をさらに細かく分割する 推奨 : コントローラが複雑になるが、ある程度軽減可能 そもそも、コントローラから全ての複雑さを取り除くことは不可能 「決定を下す過程を更に細かく分割する」戦略をとる場合、[[#CanExecute/Execute パターン]] や[[#ドメインイベントの利用]]を使用することで、コントローラの複雑さをある程度軽減できる。 これらを使用すると、決定を下す過程を、ドメインモデルに移管できる。 CanExecute/Execute パターン あるコードを、「処理Aを行うか判断するコード」(CanExecute)と、「処理Aを行うコード」(Execute)の二つに分離するパターン。 CanExecute はビジネス・ロジックに相当 これにより、ビジネス・ロジック(≒CanExecute部分)が、コントローラへ流出することを防ぐ。 ドメインイベントの利用 ドメインにとって重要なイベントが発生したことを、プロセス外依存に伝えたい場合がある。 「プロセス外依存にイベントを伝えるべきか」の判断は、ドメインモデルに判断させたい。 このような場合、以下の手法で解決できる。 * 発生したドメインに関するイベントを、インスタンスで表現する * インスタンスの有無で、プロセス外依存にイベントを伝えるかどうかを判断する 「ドメインにとって重要なイベント」を表現したインスタンスを、ドメインイベントと呼ぶ。 ドメインイベントは、「ドメインエキスパートにとって意味のあるイベント」を指す。 イベント駆動(クリックイベントなど)の文脈で用いるイベントとは異なる(こちらは、いうなら技術的なイベント)。 ドメインイベントを導入することで、「プロセス外依存へイベントを伝えるか」を、ドメインモデルが判断できるようになる。 * ドメインイベントが存在しない場合、この判断はコントローラで実装することになりかねない ドメイン層から副作用を取り除く まとめ 抽象化したいこと 抽象化する手法 「外部依存へメッセージを送信する」処理 送信したいイベントを、ドメインイベントで管理する DBへの変更処理 ドメイン・クラスの状態を変更する 副作用は、ビジネスロジックで実行させない。 そのために、副作用が発生する処理は、ビジネスオペレーションの最後まで実行させないようにする。 * 言い換えると、副作用を伴う処理の実行を遅延させる そのための手段の一つが、「ドメインイベントの利用」、「CanExecure/Execute パターン」、「ドメイン・クラスによる状態の管理」。 そして、抽象化する対象をテストするよりも、抽象化された結果をテストする方が簡単である。 まとめ コードの複雑さは、コードにおける「決定を下す箇所(分岐)」の数によって判断できる 「決定を下す箇所」は、コードに記述してあることもあれば、コードが使用しているライブラリ内に存在することもある ドメインにおける重要性は、対象となるコードがドメインの問題領域においてどれだけ重要かを示すもの 複雑なコード、および、ドメインにおける重要性が高いコードは、単体テストを行う価値が高い 単体テストにおいて、協力者オブジェクトの多さは、テストの保守困難性の高さに直結する 「テストに必要な協力者オブジェクト」を準備するコードや、テスト後に協力者オブジェクトの状態を検証するコードが増えるため 全てのプロダクションコードは、コードの複雑さやドメインにおける重要性の観点、および協力者オブジェクトの数のか観点から、以下の4つに分類できる ドメイン・モデル/アルゴリズム ドメインにおける重要性が高く、協力者オブジェクトが少ない 単体テストの費用対効果が最も高い 取るに足らないコード ドメインにおける重要性が低く、協力者オブジェクトが少ない テストする価値が少ない(または無い) コントローラ ドメインにおける重要性が低く、協力者オブジェクトが多い 統合テストでテストするべきコード 過度に複雑なコード ドメインにおける重要性が高く、協力者オブジェクトが多い ドメイン・モデル/アルゴリズムとコントローラに分離するべき コードの複雑さや重要さが増すにつれて、協力者オブジェクトの数を減らすべき 質素なオブジェクト は、過度に複雑なコードからビジネスロジックを別のクラスに抽出し、テストを行いやすくする設計パターン ヘキサゴナルアーキテクチャ、関数型アーキテクチャは、実は質素なオブジェクトに該当する ビジネスロジックに関する責務は、コードの深さ(複雑さや重要性)としてみることが出来る 連携の指揮に関する責務は、コードの広さ(協力者オブジェクトの多さ)としてみることが出来る コードは、コードの深さと広さの両方を持ってはならない 言い換えると、コードの深さと広さの、どちらか一方に寄せるべき 「事前条件」は、ドメインにとって重要である場合に限ってテストする 費用対効果の問題 ビジネスロジックのコードと指揮するコードを分離する場合、バランスを考えなくてはならない3つの重要な性質がある ドメインモデルのテストのしやすさ : 協力者オブジェクトの数と種類に影響される コントローラの簡潔さ : コントローラの決定を下す箇所(分岐)の数に影響される パフォーマンスの高さ : プロセス外依存への呼び出しを行う回数に影響される 上記性質は、2つまでしか備えることが出来ない 決定を下す過程を更に細かく分離することは、上記性質の長所と短所を考慮した、最善のトレードオフ 次の二つの設計パターンを用いることで、コントローラの複雑さを緩和できる 確認後実行パターン ある処理について、「処理を行ってよいか確認するメソッド」と「処理を実行するメソッド」に分離する 「処理を実行するメソッド」を実行する際、必ず事前条件を満たしていること(≒処理を行ってよいか)を保障できる => コントローラから、決定を下すか確認する責務を切り離すことが出来る ドメインイベント ドメイン・モデルで発生する重要な状態の変更を追跡できる 発生したドメイン・イベントを基に、プロセス外依存を呼び出すようにする => コントローラから、状態変更を追跡する責務を切り離すことが出来る 抽象化する対象をテストするより、抽象化された結果をテストする方が簡単である ドメイン・イベントは、プロセス外依存への呼び出しに対する抽象化 ドメインクラスに対する状態の変更は、データ・ストレージへの状態の変更に対する抽象化

テスト

2023年04月22日(土)

3.5時間

単体テストの考え方・使い方 6章 単体テストの3つの手法

やったこと 単体テストの考え方・使い方 6章 単体テストの3つの手法 読了 学んだこと 学び 単体テスト 3つの手法(というより、性質に沿った分類) 参照透過性という考え方 カプセル化と関数の不変性は、どちらも不変条件の維持が目的 オブジェ九思考プログラミングは可変の部分をカプセル化することでコードを理解しやすくする。一方、関数型プログラミングは可変の部分を最小限にすることでコードを理解しやすくする。 関数型アーキテクチャとヘキサゴナルアーキテクチャは、副作用の扱いが異なる 関数型アーキテクチャは、「より厳しい制約を課したヘキサゴナルアーキテクチャ」と見ることが出来る 関数型アーキテクチャは、プロセス外依存の呼び出しが増えるため、アプリケーションコードのパフォーマンスが低下しやすい 気付き 純粋関数と副作用を、極限まで分離すると、関数型アーキテクチャになるイメージ 「関数的核」、「可変核」と、「指揮するアプリケーション・サービス」に分離する、というアプローチなら、オブジェクト指向言語でも十分実装できそう 感想 「可変核にて、関数適格に渡す全ての入力値が集められる」 入力値を集めるだけの関数が爆誕するイメージ。コードはとても読みにくそう この章でいう保守容易性は、「いかに壊れにくいテストを書くことが出来るか」に焦点が当たっているように読める 保守容易性は、テストの存在だけではない テストに全振りした結果、読みにくく、理解に時間のかかるコードが生まれては、保守が困難になりそう ここはトレードオフだと思う 極限まで関数型アーキテクチャを突き詰めると、テストは壊れにくくなるが、可読性(他にもあるかも)が損なわれる気がする メモ 単体テスト3つの手法 単体テストには、以下三つの手法がある。 出力値ベース・テスト(戻り値を確認するテスト) 状態ベース・テスト(状態を確認するテスト) コミュニケーション・ベース・テスト(オブジェクト間のやり取りを確認するテスト) 上記手法は、一つのテストケースに一つだけ適用する事も、複数適用することも可能。 ■出力値ベース・テスト テスト対象に入力を与え、得られる出力を検証するテスト。 冪等性が担保されること(≒テスト対象の関数・メソッドに、副作用がない事)が前提。 ■状態ベース・テスト 検証したい処理を実行した後、テスト対象の状態(テスト対象はもちろん、強力オブジェクトやDB等のプロセスが外依存)を検証する。 ■コミュニケーション・ベース・テスト モックを用いてテスト対象システムとその協力者オブジェクトとの間の通信を検証する。 単体テストの3つの手法の比較 単体テスト3つの手法を、単体テスト4本の柱の観点から評価する。 出力値ベーステスト 状態ベーステスト コミュニケーションベーステスト リファクタリング耐性の維持に必要なコスト 低い 普通 普通 保守容易性の維持に必要なコスト 低い 普通 高い 以上より、可能な限り、出力値ベーステストで実装すること。 以下補足 退行保護は、単体テストの手法に影響を受けない 退行保護に影響を与えるのは「テスト時に実行されるプロダクションコードの量」「コードの複雑さ」「ドメインの重要性」の3つ 迅速なフィードバックは、単体テストの手法に影響を受けない プロセス外依存を使わなければ、速度にそれほど差はでない リファクタリング耐性と、保守容易性は、単体テスト手法によって差が出る 保守容易性は、以下によって把握できる テストケースの理解がどの程度困難か : 一般に、コード量やコードの可読性の影響を受ける テストケースの実行がどの程度困難か : プロセス外依存の数の影響を受ける 「状態ベーステスト」は、保守性がやや低い ヘルパーメソッドの導入や、クラスの値オブジェクト化によって、ある程度軽減可能 関数型アーキテクチャについて 最も優位な「出力値ベーステスト」は、テスト対象が純粋関数であることが求められる。 テスト対象に隠れた入力や出力が存在すると、テストが困難になる。 隠れた出力には「副作用」「例外」が、隠れた入力には「内部または外部の状態の参照」がある。 参照透過性を持つかどうかで、テスト対象が純粋関数かどうかを判断できる 参照透過性 : 振る舞いを変えることなくメソッドを呼び出している部分を値に置き換えることが出来る能力 ロジック部分を無視して、メソッドに期待されている出力(リテラルなど)に置き換えた状態で、呼び出し元が正常に動作するか 関数型アーキテクチャでは、コードを以下の二種類に分離することで、ビジネスロジックと副作用の分離を達成する。 関数的核(functional core)または不変核: (副作用を持たない)決定を下すコード 可変核(mutable shell) : 決定に基づくアクションを実行するコード 入力値の準備や副作用を持つ処理は、可能な限り可変核で行う。 これにより、単体テストでは、出力値ベーステストを用いた関数的核だけ検証すればよい 可変核のテストは、統合テストで実施する カプセル化と関数の不変性は、どちらも不変条件の維持が目的。 カプセル化は、不変条件に絡むパラメータの変更に、特定の手続きの経由を強制することで、不変条件を維持する 関数の不変性は、副作用を持たないようにすることで、不変条件を維持する オブジェ九思考プログラミングは可変の部分をカプセル化することでコードを理解しやすくする。一方、関数型プログラミングは可変の部分を最小限にすることでコードを理解しやすくする。 関数型アーキテクチャとヘキサゴナルアーキテクチャの違いは、副作用の扱い方。 関数型アーキテクチャでは、副作用を、全て関数的核の外に出す ヘキサゴナルアーキテクチャでは、ドメイン層で完結するのであれば、副作用は許容する 言い換えると、あるドメインの副作用はドメイン内で収まる必要があり、ドメインの境界を超えてはならない 関数型アーキテクチャの欠点 ■ どのようなアプリケーションにも、関数型アーキテクチャのアプローチを導入できるわけではない 入力値を事前に集める事が出来ない場合など。 例) あるビジネスロジックの結果を基に、プロセス外依存から値を取得する場合 ■ アプリケーションコードのパフォーマンスが劣化しやすい プロセス外依存を呼び出す処理が増えてしまうため ■ コードベースが肥大化する 大体、関数的核と可変核へ分離するコードと、指揮を執るコードが要因。 複雑なシステムを構築する際は、コードの複雑さが減少し、保守容易性のメリットを享受できる。 しかし、そこまで複雑ではないシステムを構築する場合、保守容易性のメリットを享受できず、肥大化したコードだけが残る結果となる。 最後に 全てのテストケースを出力値ベーステストにする必要はない(というかできない)。 出来るだけ多く出力値ベーステストにすること。 言い換えると、関数の純粋性を厳格に維持する必要はない。 この章で扱う事 単体テストの手法の比較 関数型アーキテクチャとヘキサゴナルアーキテクチャとの関係 出力値ベース・テストへの移行 まとめ 出力値ベース・テストとは、テスト対象システムに入力値を与え、得られた出力を検証する単体テストのこと 冪等性のある関数やメソッドなど リファクタリングで壊れにくく、保守性が高いテストとなる傾向がある 状態ベース・テストとは、テスト対象システムと協力者オブジェクトの状態を検証する単体テストのこと 出力値ベース・テストと比較して、テストが壊れやすいため、注意が必要 テストのために、 private メンバを公開してはならない ヘルパーメソッドや値オブジェクトの活用により、ある程度軽減可能 コミュニケーション・ベース・テストとは、、モックを使ってテスト対象システムと協力者オブジェクトのコミュニケーションを検証する単体テストのこと 出力値ベース・テストと比較して、テストが壊れやすいため、注意が必要 以下の理由により、上記テストと比較して最も保守が難しい モックを使用することによる、実装への依存度が上がり、リファクタリングで壊れやすくなる モックを使用するための準備コードが必要であり、コード量が増えがち そのため、コミュニケーションベース・テストは、以下を両方満たす場合にのみ実装するよう、制限する 検証対象の通信がアプリケーションの境界を超えて行われること 外部から確認できる副作用を含むこと 関数型プログラミングを導入するモチベーションは、ビジネスロジックと副作用の分離である 関数型アーキテクチャでは、全てのコードを以下の二つに分類する 関数的核(functional core) : 決定を下すもの 可変殻(mutable shell) : 関数的核に入力値を提供したり、関数的核で下された決定に基づいて副作用を含む処理を実行する 関数型アーキテクチャとヘキサゴナルアーキテクチャの違いは、副作用の扱い 関数型アーキテクチャ : 全ての副作用をドメイン層の外に出す ヘキサゴナルアーキテクチャ : ドメイン層で完結するような副作用は許容する => 関数型アーキテクチャは、「副作用についてきつい制約を課したヘキサゴナルアーキテクチャ」と見ることが出来る 関数型アーキテクチャとヘキサゴナルアーキテクチャは、保守とパフォーマンスのトレードオフ 関数型アーキテクチャは、保守容易となる代わりにパフォーマンスが犠牲となる 関数型アーキテクチャの採用判断は慎重に あまり複雑ではないシステムだと、関数型アーキテクチャを導入する初期コストが無視できない事になる

テスト

2023年04月17日(月)

2.0時間

Linuxのしくみ 増量改訂版 7章 ファイルシステム 読了

やったこと Linuxのしくみ 増量改訂版 7章 ファイルシステム 読了 学んだこと Linuxのしくみ 増量改訂版 7章 ファイルシステム 学び 「メモリマップトファイル」という機能 ファイルを、system call 経由ではなく、メモリ上にマッピングして扱う ファイルシステムの特徴 ファイルシステムにアクセスする関数はPOSIX 準拠 これにより、ファイルシステム毎に異なるお作法を吸収できる Btrfs の機能(特にスナップショット) 特殊なファイルシステム関連 /proc や /sys は、特殊なファイルシステムを使用してマウントしている メモリ上にファイルシステムを構築する tmpfs メモリ上に構築するため、揮発性 気付き コピーオンライトは、「コピー時は参照を共有して、更新要求時に更新箇所だけコピーを取る」といった挙動全般を指す、汎用的な概念 /tmp ディレクトリは、 tmpfs を使用することで、「PCをシャットダウンすると、ディレクトリ内のファイルが全消去される」という挙動を実現している 万能なファイルシステムは存在しない ユースケース、目的によって、適したファイルシステムは異なる ext4 ... ext2,ext3 から迅速に移行したい場合 xfs ... inode を気にせずファイルを作成したい場合 Brtfs ... マルチボリューム管理、復旧といった、Brtfs の機能でしか解決できない課題を抱えている場合 メモ 冒頭 ファイルシステムは、以下を請け負ってくれる。 データを、ディスク上の何処に保存するか ディスク空き領域の管理 データ書き込み時、データを壊さずに書き込むために必要 ディスクに書き込んである情報の記憶 ディスクからデータを読み込む際に必要 言い換えると、ファイルシステムが存在しない場合、上記をユーザが自分で管理する必要がある。 ファイルへのアクセス方法 ファイルシステムへのアクセスは、POSIX準拠の関数を使用する。 POSIX準拠の関数を使用することで、ファイルシステムの違いを吸収できる。 メモリマップトファイル ファイルの内容をメモリに読み出しつつ、読みだしたメモリ領域を仮想アドレス空間にマップする機能。 通常、ファイルの読み書きは system call 経由で行うが、メモリマップトファイルで読みだしたファイルは、メモリを操作する感覚で読み書きする。 これにより、以下の利点がある。 読み書き速度向上(system call 経由の処理は重い) ページキャッシュの仕組みを活用出来る ユーザ空間にコピーを作成する必要がない デマンドページングの手法を用いて、大きなファイルでも「一部分を扱う」といった効率的な運用が可能 巨大なファイルを扱う際に、上記利点の恩恵が大きくなる。 言い換えると、小さなファイルを扱う場合は利点が少ない これは、ページサイズ(4KiB)でデータを扱うため 例)5KiBのファイルをマッピングすると8KiBが割り当てられ、3KiBが無駄となる => フラグメンテーションによる無駄が生じやすい 参考 : https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%9E%E3%83%83%E3%83%97%E3%83%88%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB 一般的なファイルシステム ファイルシステム 特徴 ext4 ext2,ext3という、昔からLinuxでよく使用されてきたファイルシステムから移行しやすい XFS スケーラビリティに優れる Btrfs 高機能 ファイルシステムの差別化点として、大きく以下の観点がある。 容量制限(クォータ) 特定のユーザ/プログラムが、ストレージ容量を使い過ぎないように、制限する機能 「重要な処理実行時に容量が足りない」といった事態を防ぐ措置。以下の種類が存在する ユーザクォータ : ファイルの所有者となるユーザ毎に容量を制限 ディレクトリクォータ : 特定のディレクトリに容量を制限する サブボリュームクォータ : ファイルシステム内のサブボリュームという単位ごとに容量を制限 ファイルシステムの整合性保持 ファイルシステムの不整合発生(電源ダウンなど)を防止する仕組み。以下の仕組みが主流 ジャーナリング : ジャーナリング領域という、特殊なメタデータ領域を用意して、整合性を保持する ext4とXFSにて採用 コピーオンライト : バージョン管理のような感覚で更新情報を保存し、問題が発生したら作りかけのデータを破棄することで整合性を保持する Brtfs にて採用 最強の整合性保持手段は「定期的なバックアップ」 Btrfs が提供するファイルシステムの高度な機能 スナップショット データを参照するメタデータを作成する マルチボリューム 複数のストレージデバイス/パーティションをまとめて一つの仮想的なストレージプールを構成し、そこからマウント可能なサブボリュームを切り出す ファイルシステム + LVMのような、ボリュームマネージャーと考えると分かりやすい(RAID構成も組める) データ破壊の検知と修復 全データについて、それぞれチェックサムを持つことで、データ破壊を検知 RAID1 構成と組み合わせることで、復旧も可能 その他のファイルシステム tmpfs メモリ上にファイルシステムを構築する メモリアクセスのみ(≒ストレージアクセスが発生しない)のため、読み書きが非常に高速 電源を落とすと消えてしまう 残す必要のない、 /tmp や /var/run で活用できる デマンドページングのように、メモリ領域が必要になった時に、必要な分だけ、ページ単位でメモリを獲得する [t0mmy@ ~]$ mount | grep tmpfs none on /mnt/wsl type tmpfs (rw,relatime) none on /mnt/wslg type tmpfs (rw,relatime) ... ネットワークファイルシステム(NFS) 同一ネットワークに存在する、リモートホストのデータに、ファイルシステムI/Fでアクセスする仕組み リモート上のファイルシステムを、手元のマシンで(ファイルシステムを扱う感覚で)操作できる 複数マシンのストレージデバイスを束ねて、大きなファイルシステムを構成するCephFSのような仕組みも存在する procfs OSの統計データを保存するファイルシステム 統計関連のコマンドは、procfsをマウントしている /proc 配下から情報を取得している sysfs procfs に配置しないような、雑多な情報を配置するfs /sys/ にマウントされる

linux

2023年04月09日(日)

2.0時間

Linuxのしくみ 増量改訂版 6章 デバイスアクセス 読了

やったこと Linuxのしくみ 増量改訂版 6章 デバイスアクセス 読了 学んだこと 学び デバイスファイルの役割 プロセスがデバイスファイルへアクセスしたいときに使用するファイル プロセスがデバイスファイルへ直接アクセスできないようにする デバイスファイルには、以下の二種類が存在する キャラクタデバイス ブロックデバイス デバイスファイルとデバイスのやり取り parsistant device name の存在 気付き /dev/ 配下のデバイスファイル名は仮名ぐらいに思っておく これらデバイスファイル名を指定する場合は、「label機能の使用」や「uuidの指定」を行うこと デバイスファイルは、デバイスの抽象化レイヤーと認識。以下の利点がある デバイスごとの違いを吸収し、ユーザから隠匿出来る 言い換えると、ユーザは、デバイス毎に異なる、デバイスのお作法を気にする必要がなくなる ユーザに、直接デバイスを操作させない ユーザとデバイスの間に入り込み、処理要求を制御できる メモ 概要 プロセスがデバイスにアクセスすると、競合が発生してしまい、予期せぬ動作を引き起こしてしまう。 そのため、カーネルを仲介する形でデバイスへアクセスする。 これにより、カーネルがデバイスへのアクセスを取り仕切る事ができるようになり、デバイスへのアクセスをコントロールでき、ひいては競合問題を解決出来る。 流れ) プロセス ==(カーネルに依頼)==> デバイスファイル ==> デバイスドライバ ==> デバイス操作 デバイスファイル プロセスがデバイスへアクセスする際は、デバイスファイルを使用する。 デバイスファイルも"ファイル"なので、ファイル操作系 system call で扱うことが出来る。 ls -l 行頭の文字が c の場合はキャラクタデバイス、 b の場合はブロックデバイスを示す。 キャラクタデバイス(キーボード、端末など)は、読み書きを実行できるが、書き込む場所の指定(いわゆるシーク操作)は出来ない。 ブロックデバイス(HDD,SSDなど)は、読み書きとシーク操作を実行できる。 デバイスドライバ 現代では、メモリマップトI/O(MMIO) という仕組みを用いて、デバイスのレジスタにアクセスする。 MMIO : デバイスを操作する場合、仮想アドレス空間上に、物理メモリに加えてレジスタ情報もマップする デバイスドライバが処理の完了を検知するには、以下のどちらかを使用する。 ポーリング : デバイスドライバが、デバイスに依頼した処理が完了したか定期的に確認する 割り込み : デバイスが、処理完了をCPUに通知する。CPUは、割り込みハンドラを用いて、デバイスドライバに処理完了を通知する CPUリソースを十分に活用(CPUをidle状態にして、他の処理に回すことが)できる、「割り込み」が主流。 一方で、「デバイスの処理が高速」かつ「処理が高頻度」な場合は、ポーリングを採用できる。 割り込み時に発生するオーバーヘッドが大きくなりすぎて、ポーリングの方が効率が良くなるような状況 デバイスファイル名は変わりうる デバイスファイル名は、一定の規則に則って名前が付与される。 特にブロックストレージは、接続した順番で名前が変わりうる。 このため、デバイスファイル名を指定する際は、 /dev/ 配下のデバイスファイル名ではなく、 「parsistant device name」を使用すること。 マウントする時にデバイスファイル名を指定する場合は、 mount コマンドの「label」機能や、デバイスの UUID を指定する事で問題を解決できる。

linux

2023年04月02日(日)

1.5時間

Linuxの仕組み 5章 プロセス管理 読了

やったこと Linuxの仕組み 5章 プロセス管理 読了 学んだこと 学び メモリの効率化に余念がない Copy on Write (CoW) パイプも、プロセス間通信手段の一つ ソケットには以下の二つが存在する Unixドメインソケット TCP/UDPソケット flock / fcntrl システムコールの存在 マルチスレッド/マルチプロセスの利点と欠点 気付き 共有メモリのやってることは、グローバル変数と変わらない 極力使用は避ける マルチスレッド処理を実装する場合は、言語が提供する便利な機能を活用しよう 自力でスレッドセーフなプログラムを組むのは大変 メモ コピーオンライトによる、プロセス作成処理(fork)の高速化 fork実行時、親ページのページテーブルのみコピーする(親プロセスが使用しているメモリはコピーせず、親子で共有する) 子プロセス側で更新処理を行った時に初めて、該当部分だけ子プロセス用メモリ領域を確保する デマンドページングによる、execve関数の高速化 execve関数実行時も、デマンドページングの仕組みを用いて、実行に必要な物理メモリを割り当てている プロセス間通信 ■共有メモリ 親プロセスと子プロセスで共有できるメモリ領域のこと 子プロセスで共有メモリを更新すると、親プロセスにも反映できる mmap システムコールを使用することで、共有メモリを確保できる ■シグナル 「シグナルが届いた」という情報を別プロセスに伝える仕組み データの受け渡しは出来ないため、データの受け渡しをしたい場合は別途仕組みを用意すること ■パイプ あるプロセスの出力を、別のプロセスに入力する仕組み 確かにプロセス間通信 ■ソケット プロセス間で通信する仕組みの一つ ソケットには以下の二つが存在する Unixドメインソケット 一つのマシン上のプロセス間で通信できる 言い換えると、マシンをまたいだプロセス間通信はできない TCP/UDPソケット TCP/IP を活用したプロセス間通信 TCP/IP を実装していれば、マシンをまたいだ通信が可能 排他制御 flock / fctcl システムコールを使用することで、ファイルのアトミック制御を実現できる マルチプロセス/マルチスレッドの利点と欠点 ■利点 プロセスと比較して、スレッドの生成時間が短い ページテーブルのコピーが不要のため リソースを節約しやすい リソースの共有が容易のため 協調動作が容易 全スレッドでメモリを共有するため ■欠点 一つのスレッドの障害が、全スレッドに波及する 各スレッドが呼び出す処理が、スレッドセーフか常に考慮する必要がある

linux

2023年03月26日(日)

1.5時間

単体テストの考え方・使い方 5章 モックの利用とテストの壊れやすさ 読了

やったこと 単体テストの考え方・使い方 5章 モックの利用とテストの壊れやすさ 読了 学んだこと 学び モックとスタブは考え方としては別物 スタブとのやり取りは、決して検証してはならない テストが実装に依存するようになり、リファクタリング時に壊れやすいテストとなってしまうため コマンドクエリ原則の考え方 「観測可能な振る舞い」か「実装の詳細」か、に加えて、「キチンと設計されたコード」の考え方 気付き モックとスタブは、目的を考えるとどちらも同じもの 目的 : テスト対象のテストをやりやすくする 手段 : 外部依存をテスト用オブジェクトに置き換える スタブとのやり取りは、決して検証してはならない テストすべきは、「外部依存からデータを取得して、アレコレ処理を行う」という、メソッドが行う一連の振る舞い public/private の考え方は、Design by Contract にも通ずる者があるように思う 公開しているメソッド(API)はクライアントと強く結びつくため、変更が困難 => 変更が困難なため、公開メソッドをテストするコードは、リファクタリングで壊れにくい 反対に、非公開メソッドは、開発者の一存でコロコロ変わる可能性が高い => 容易に変更できてしまうため、非公開メソッドをテストするコードは、リファクタリングで壊れやすい 単一責任の原則に従ってコードを書くと、自然と「キチンと設計されたコード」になる気がする。 「最も提供したい機能1つ」を提供するメソッドのみpublicへ 上記を補佐するメソッドは全てprivateへ 必要十分な「観測可能なふるまい」とならない原因は、設定ミスと設計ミスの二パターンが考えられる 設定ミス : private やパッケージプライベートとすべきところをpublic に設定してしまっている 設計ミス : private にしたくてもできないような状態 メモ モックとスタブは、考え方としては別物 モックは、外向きの通信の模倣・検証を指す 特に、外部依存の状態を変更するような処理(HTTP POSTなど や SQLの Insert Update Delete など) スタブは、内向きの通信の模倣を指す 特に、外部依存からデータを取得するような処理(HTTP GET や SQLのselectクエリなど) テストツールが提供するmock 機能を使用して、モック処理やスタブ処理を実装する スタブとのやり取りは、決して検証してはならない スタブとのやり取りは、実装の一部分 テストが実装に依存するようになり、リファクタリング時に壊れやすいテストとなってしまうため コマンドクエリ原則 メソッドは、コマンドかクエリのどちらかだけを満たすようにするべき(両方を満たすメソッドは極力作成しない) コマンド: 副作用有り、戻り値なし => モック対象 クエリ : 副作用無し、戻り値あり => スタブ対象 プロダクションコードを、「観測可能なふるまい」と「実装の詳細」の二つに分類して考える 「観測可能なふるまい」がテスト対象 「観測可能なふるまい」とは、以下のどちらかを満たすもの クライアントの目標達成に直接貢献する、公開された操作 クライアントの目標達成に直接貢献する、公開された状態 ここで言う「クライアント」とは、文脈によって変化する 同じコードベース上にあるコードだったり、外部アプリケーションだったり、UIだったり 例) setter , getter , コンストラクタ等は「観測可能な振る舞い」 setterやコンストラクタ内で実行するvalidationをまとめたprivateメソッドは「実装の詳細」 キチンと設計されたコードとは、観測可能な振る舞いが、公開されたAPIと一致しており、実装の詳細が、プライベートなAPIとして隠されている 例) パッケージを超えて使用したいメソッドを、観測可能な振る舞いとして public に設定 パッケージ内にのみ公開したいメソッドをパッケージプライベートに設定 クラス内でのみ使用したいメソッドを、実装の詳細として private に設定 一つの操作で、クライアントの目標を達成できる状態が良い 目標達成のために複数のpublicメソッドを叩く必要がある場合は、実装の詳細が漏洩している可能性を疑う 適切なカプセル化によって、実装の詳細を隠蔽する カプセル化は、「コードから、dataの整合性を破綻させる不変条件の侵害を守る」手段 ヘキサゴナルアーキテクチャ ドメインロジックを含む「ドメイン層」と、ドメイン層と外部アプリケーションを繋ぐ「アプリケーション・サービス層」で構成 アプリケーション・サービス層 => ドメイン層 という、一方向の依存関係 別の見方をすると、アプリケーション・サービス層は、取り換え可能である必要がある 「実装の詳細」を判断する、別の見方 システム内通信は、実装の詳細 システム間通信は、実装の詳細ではない システム間通信だと、public メソッドの契約(例:後方互換性)を考える必要がある これを検証するためにテストを実施する ... と考えると良いかも? システム間通信の検証で活躍するのが"モック" ■この章で扱う事 モックとスタブの違い 観察可能なふるまいと実装の詳細の定義 モックの利用とテストの壊れやすさとの関係 リファクタリングへの耐性を損なわずにモックを使う方法 ■まとめ テストダブル 「ダミー、スタブ、スパイ、モック、フェイク」の総称 モックグループとスタブグループに大別できる モックグループは外部に向かう通信(出力)の模倣・検証に使用する 外部APIの状態を変更する処理など スタブグループは内部に向かう通信(入力)の模倣に使用する 外部APIからデータを取得する処理など テストツールが提供するmock機能を使用することで、モックグループやスタブグループの処理を実装できる モックグループやスタブグループを明確に区別しているテストツールは存在しなさそう スタブとのやり取り部分の検証は、壊れやすいテストにつながる 例) 詳細なSQL文のテストは、テーブル定義が変わると破綻する HTTP Request のヘッダ・ボディの詳細な検証は、REST提供側の定義が変わると破綻する コマンド・クエリ分離 すべてのメソッドは、コマンドかクエリのどちらかの性質のみを満たすべき、という提唱 言い換えると、両方を満たすべきではない コマンドの置き換えがモック、クエリの置き換えがスタブに相当 クライアントに公開された操作や状態は「観察可能な振る舞い」であり、非公開の操作や状態は「実装の詳細」 ここで言う「クライアント」は、文脈によって変化する 同じコードベース上にあるコードだったり、外部アプリケーションだったり、UIだったり 「キチンと設計された」コードとは、観察可能な振る舞いが、公開されたAPIと一致しており、実装の詳細がプライベートなAPIとして隠されるコードのこと 「カプセル化」とは、不変条件の侵害からコードを守るための手段 ヘキサゴナルアーキテクチャ 性質 ドメイン(ビジネスロジック)層とアプリケーション・サービス(ドメイン層と外部アプリケーションの連携)層同士の関心が分離されている アプリケーション・サービス層 => ドメイン層 という、一方向の依存関係となっている 言い換えると、アプリケーション・サービスは付け替え可能でなければならない アプリケーション・サービス層はI/Fとして機能する ドメイン層を動かす場合は、必ずアプリケーション・サービス層を経由することになる ヘキサゴナルアーキテクチャでは、各層は、観察可能な振る舞いのみ公開するようにする システム内コミュニケーション : アプリケーション内のクラス間で行う通信のことであり、実装の詳細 システム間コミュニケーション : 外部アプリケーションと行う通信のことであり、観察可能なふるまいの一部となる システム内コミュニケーション の確認にモックを使用すると、テストが壊れやすくなる 以下を両方満たす場合、モックを使用できる システム間コミュニケーション 外部から副作用を観察できる場合

テスト

2023年03月25日(土)

3.0時間

183件中の 51-75件 を表示