
t0mmy
学習履歴詳細
API デザインパターン 23章 インポートとエクスポート 読了
やったこと
- API デザインパターン 23章 インポートとエクスポート 読了
学んだこと
ポイント
import
とexport
カスタムメソッドの責務- APIと外部ストレージシステムを直接やり取りさせる利点
- リソースとバイト列の変換をどう制御するか
import
とexport
の一貫性import
時、識別子が衝突する可能性をどうやって回避するかimport
で複数のリソースタイプを扱うにはどうするかimport
とexport
でフィルタリングをサポートするべきか
学び
import
とexport
カスタムメソッドは、データ転送にのみ責務を持つ- 細かな変換、フィルタリングなど、ビジネスロジックレベルの処理に責務は負わない
- バックアップ & リストアを意図して設計されていない
- APIと外部ストレージシステムを直接やり取りすることで、以下の利点がある
- ETL処理を作成する必要がない
- データ転送量が減る
- リソースとバイト列の変換では、専用の設定インターフェースを用いて、処理を制御する
import
とexport
は一貫性の責務を負わない- 特に
export
では、最新のデータを転送できるとは限らない
- 特に
import
処理する時、 リソースを新規作成扱いで作成し、識別子も新しい値を割り当てるimport
処理失敗時の再試行で重複する恐れがある- これは、リクエスト重複回避(26章)にて回避できる
import
処理では単一のリソースタイプだけサポートする- 複数のリソースタイプを扱う複合リソースはサポートしない(それはデータ転送の範疇を超える、重要なビジネスロジックの領域)
- フィルタリング
export
ではサポートするべきimport
ではサポートするべきではない- 利用者の責務で、インポート前に、 インポート対象のリソースをフィルタリングしてもらう
気づき
- 外部ストレージシステムとのやり取りをAPIサーバー側でサポートするかは、恐らくトレードオフ
- 利用者側に責務を転嫁して、APIサーバー側はバイト列だけ受け取る形でも良いと思う
- 利用者の利便性と、運用負荷のトレードオフ
メモ
import / export カスタムメソッドを用いて、リソースを柔軟かつ安全にAPI内外へ移動する手段を提供する。
更に、外部ストレージシステムと直接(≒仲介クライアントを介さない)通信する手段を提供する。
API と外部ストレージシステムが直接やり取りする利点
- データ転送回数が少なくなる
- 「データ取得・整形・APIへ送信」というカスタムコードを記述する必要がなくなる
![[Drawing 2023-12-09 13.46.26.excalidraw]]
概要
APIにリソースを入出力するために、 import
と export
というカスタムメソッドを利用する。
API側で外部ストレージシステムとやり取りする以上、データのやりとりはAPI側で責任を持つ。
外部ストレージシステムやシリアライズフォーマットは無数に存在する。
import
と export
メソッドには、 将来登場するモノも含め、対応できる柔軟性が要求される。
この柔軟性を満たすために、包括的な設定インターフェースを設計する必要がある。
import
にはInputConfig
を、export
にはOutputConfig
を設けるイメージ
設定インターフェースでは、以下を分離すると良い。
- 外部ストレージシステムにアクセスするための設定
- API リソースのシリアライズ/デシリアライズ設定
インターフェースが決まると、処理の制御を考えていく。
実装
import / export メソッド
大きく、実装のポイントは以下の2つ
import
とexport
メソッドは、単一のリソースタイプのみ扱う- 例)
Message
リソース専用のImportMessage
/ExportMessage
を定義し、 これらカスタムメソッドは、Message
リソースのみ扱う- 何でも扱える
ImportMisc
/ExportMisc
は定義しない
- 例)
- 戻り値は LRO (10章参照) を返却し、同期的な処理は実装しない
- 扱うリソースによっては、 Import / Export 処理に時間がかかるため
ストレージシステムとのやりとり
◆ポイント
- ベースとなるインターフェースを用意し、このインターフェースを継承する形で個別のストレージシステムごとにインターフェースを定義する
- アンチパターン : ベースとなるインターフェースに、全個別ストレージの設定プロパティを定義する形
- 利用したいストレージシステムについて、必須なプロパティが何かわかりにくくなる
- 似た名前が複数登場するなど
- 利用したいストレージシステムについて、必須なプロパティが何かわかりにくくなる
- アンチパターン : ベースとなるインターフェースに、全個別ストレージの設定プロパティを定義する形
- Import と Export のインターフェースは必ず分離すること
- この二つは別の概念を表現しているため
- 扱うプロパティが似通うことになっても、必ず分離する
リソースとバイト列の変換
import / export 用の設定インターフェースである InputConfig
と OutputConfig
を使用して、各種設定を制御する。
設定の例を示す。
- ContentType : "json", "csv" など、シリアライズ/デシリアライズ のフォーマット
- compressionFormat : "zip" , "bz2" など、 import /export 処理後にリソースを圧縮したい場合、そのフォーマットを指定できるプロパティ
- filenameTemplate: ファイル名のテンプレート。複数のリソースをまとめてexport する場合に使用するものと思われる
- maxFileSizeMb : 最大ファイルサイズ
一貫性
エクスポートするデータのサイズや個数によっては、エクスポート処理に時間がかかる。
この時、エクスポート中にデータが更新される可能性も十分考えられる。
これには、以下の解決策がある。
- 基盤となるストレージシステムが提供するスナップショット機能、またはトランザクション機能を使用する
- ある時点でのリソースを一括で読み込むことで、一貫したデータを確保できる
- なお、エクスポート中に発生したデータの変更は、反映されない
- エクスポートされるデータが最新であることを保証しない(言い換えるとベストエフォート)
- ある時点でのデータを正確に表現できないことを意味する
- エクスポート処理中は、変更処理を一切受け付けない
- スナップショット機能、トランザクション機能未サポートかつ、最新であることを保証しなければならない場合の選択肢
- ユーザーに不便を強いることになるので、あまりとりたくない選択肢
基本的には、選択肢2を取ることになる。
また、エクスポートとバックアップは異なる。
export
カスタムメソッドを、バックアップ目的で使用しないよう、利用者に示すこと。
識別子と衝突
エクスポート処理にて、識別子ごとエクスポートすると、タイミング次第でインポート時に識別子の衝突が発生し得る。
「エクスポートは、外部からデータを新規に取得する」と考え、識別子ごとリソースを(必要ならバッチで)新規作成する。
この場合、重複したリソースが複数誕生する。
そもそも、 import
と export
カスタムメソッドは、一貫性が重要となるバックアップ & リストアを意図していない。
そのため、重複を許さない(または一貫性が重要な)リソースに対して、 import
と export
カスタムメソッドを提供しない(または保証しないことを明記する)こと。
関連リソースを扱う
import
と export
カスタムメソッドは、APIサービスと外部ストレージシステム間のデータの中継処理を取り除くことを目的としている。
子リソースごとに処理を選択できるような、高度な機能の提供は想定していない。
そのため、 import
と export
カスタムメソッドは、「子リソースを持たず、関連リソースが無くても有用なリソース」に限定して提供する。
複合リソースタイプのインポートおよびエクスポートの話が出たら、要件を疑おう。
- 複合リソースをインポート/エクスポートできたとして、本当に価値はあるの?
失敗と再試行
◆インポートの失敗
検証をはじめとした何らかのロジックによるエラーか、一過性のエラーかで対応が変化する。
前者(何らかのロジックによるエラー)の場合、何度再試行しても失敗するため、利用者にエラーを通知する必要がある。
後者の場合、再試行処理によって、インポートに失敗した分のデータが重複してしまう。
これに対処するため、
- インポート処理をトランザクション処理にて実行する
- 途中で失敗したら、変更をロールバックする
- リクエスト重複回避(26章)を実装する
- インポートするデータ単位で識別子を生成
- インポートに成功した識別子をキャッシュすることで、再試行時にスキップするか判断できるようになる
◆エクスポートの失敗
エクスポート処理は、基本的には一貫性を保証しない。
そのため、再試行したエクスポート処理は、再試行前のエクスポート処理と同じ結果になるとは限らない。
また、失敗したエクスポート処理に失敗したデータは、以下の理由により、どのエクスポートプロセスで失敗したかに関わらず、残すこと(言い換えると、勝手に削除しないこと)を推奨する。
- API が、外部ストレージシステムのデータを削除する権限を持つとは限らないため
- エクスポートに失敗したデータに価値があるか、API側では判断がつかないため
- 100%でなくても、例えば98%でもエクスポートに成功できれば十分かもしれない
フィルタリングとフィールドマスク
エクスポートはフィルタリング機能を提供できた方がよい。
この時、フィルタリング文字列は、 エクスポート用設定ファイルではなく、 export
カスタムメソッドのリクエストボディに含める。
- エクスポート用設定ファイルは、リソースの束をバイト列の束に変えることだけに焦点を当てている
- 他のことに焦点を当てたくないため、フィルタリング文字列を含めないようにする
インポートのフィルタリングは、API側でサポートするべきではない。
- API側で、専用のフィルタリングルールを実装する必要がある
- API側で各種フィルタリングルールの実装に責任を持つ必要があり、労力が伴う
インポート前に、利用者の責務で、リソースをフィルタリングしてもらう。
トレードオフ
このパターンの目的は、「APIサービスと外部ストレージシステム間のギャップを埋める」こと。
もっと言うと、データ転送にだけ責務を持つ。
これにより、以下の欠点を持つ。
- 複数のリソースタイプを扱うよう設計されていない
- 言い換えると、データ転送にのみ責任を持つため、細かなデータ変形には責務を持たない
- 複数のリソースタイプを扱うような処理は、ただのデータ転送ではなく、重要なビジネスロジック
- 別途カスタムメソッドを定義しよう
- インポートとエクスポート機能を、バックアップ & リストア機能と混同されがち
扱う事
- インポートおよびエクスポートの具体的な定義
- リモートストレージシステムとの直接的なやり取り
- シリアライズされたバイト列のAPIリソースへの変換
- 変わりやすいデータセットをエクスポートする際の一貫性の処理
- インポートおよびエクスポート時の一意な識別子の処理方法
- 関連リソースをインポート、エクスポートするときの対処法
- インポート / エクスポート操作が失敗した時の処理
- インポートとエクスポートのバックアップやリストアとの違い
- 入力時でのデータと出力データのフィルタリング
まとめ
- カスタムの import , export メソッドによって、 APIサービスと外部ストレージシステム間で直接データを移動できるようになる
- これらのメソッドはバックアップ/リストア機能を意図していない
- バックアップ / リストアを意図して使用した場合、思わぬ結果につながる
- 本書籍でのAPI定義では、以下に焦点を当てている
- 外部ストレージシステムにバイト列を出し入れする
- バイト列をAPIのリソース表現に
/
から変換する
- import , export メソッドで扱うデータは、常に最新のデータとは限らない
- システムがポイントインタイムデータの読み込みをサポートしていない場合を除く
- データのインポートとエクスポートは、一度に単一のリソースタイプに制限されるべき
- 子リソースやその他参照リソースなど、複数のリソースタイプを扱いたい場合は、代わりにバックアップとリストア機能を使用すること
2023年12月09日(土)
2.0時間