
t0mmy
学習履歴詳細
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
のように扱う
2023年06月09日(金)
1.5時間