t0mmy

2020年12月30日に参加

学習履歴詳細

API デザインパターン 23章 インポートとエクスポート 読了

やったこと

  • API デザインパターン 23章 インポートとエクスポート 読了

学んだこと

ポイント

  • importexport カスタムメソッドの責務
  • APIと外部ストレージシステムを直接やり取りさせる利点
  • リソースとバイト列の変換をどう制御するか
  • importexportの一貫性
  • import 時、識別子が衝突する可能性をどうやって回避するか
  • import で複数のリソースタイプを扱うにはどうするか
  • importexport でフィルタリングをサポートするべきか

学び

  • importexport カスタムメソッドは、データ転送にのみ責務を持つ
    • 細かな変換、フィルタリングなど、ビジネスロジックレベルの処理に責務は負わない
    • バックアップ & リストアを意図して設計されていない
  • APIと外部ストレージシステムを直接やり取りすることで、以下の利点がある
    • ETL処理を作成する必要がない
    • データ転送量が減る
  • リソースとバイト列の変換では、専用の設定インターフェースを用いて、処理を制御する
  • importexportは一貫性の責務を負わない
    • 特に export では、最新のデータを転送できるとは限らない
  • import 処理する時、 リソースを新規作成扱いで作成し、識別子も新しい値を割り当てる
    • import 処理失敗時の再試行で重複する恐れがある
    • これは、リクエスト重複回避(26章)にて回避できる
  • import 処理では単一のリソースタイプだけサポートする
    • 複数のリソースタイプを扱う複合リソースはサポートしない(それはデータ転送の範疇を超える、重要なビジネスロジックの領域)
  • フィルタリング
    • exportではサポートするべき
    • import ではサポートするべきではない
      • 利用者の責務で、インポート前に、 インポート対象のリソースをフィルタリングしてもらう

気づき

  • 外部ストレージシステムとのやり取りをAPIサーバー側でサポートするかは、恐らくトレードオフ
    • 利用者側に責務を転嫁して、APIサーバー側はバイト列だけ受け取る形でも良いと思う
    • 利用者の利便性と、運用負荷のトレードオフ

メモ

import / export カスタムメソッドを用いて、リソースを柔軟かつ安全にAPI内外へ移動する手段を提供する。
更に、外部ストレージシステムと直接(≒仲介クライアントを介さない)通信する手段を提供する。

API と外部ストレージシステムが直接やり取りする利点

  • データ転送回数が少なくなる
  • 「データ取得・整形・APIへ送信」というカスタムコードを記述する必要がなくなる

![[Drawing 2023-12-09 13.46.26.excalidraw]]

概要

APIにリソースを入出力するために、 importexport というカスタムメソッドを利用する。
API側で外部ストレージシステムとやり取りする以上、データのやりとりはAPI側で責任を持つ。

外部ストレージシステムやシリアライズフォーマットは無数に存在する。
importexport メソッドには、 将来登場するモノも含め、対応できる柔軟性が要求される。

この柔軟性を満たすために、包括的な設定インターフェースを設計する必要がある。

  • import には InputConfig を、 export には OutputConfig を設けるイメージ

設定インターフェースでは、以下を分離すると良い。

  • 外部ストレージシステムにアクセスするための設定
  • API リソースのシリアライズ/デシリアライズ設定

インターフェースが決まると、処理の制御を考えていく。

実装

import / export メソッド

大きく、実装のポイントは以下の2つ

  1. importexport メソッドは、単一のリソースタイプのみ扱う
    • 例)
      • Message リソース専用の ImportMessage / ExportMessage を定義し、 これらカスタムメソッドは、 Messageリソースのみ扱う
      • 何でも扱える ImportMisc / ExportMisc は定義しない
  2. 戻り値は LRO (10章参照) を返却し、同期的な処理は実装しない
    • 扱うリソースによっては、 Import / Export 処理に時間がかかるため

ストレージシステムとのやりとり

◆ポイント

  1. ベースとなるインターフェースを用意し、このインターフェースを継承する形で個別のストレージシステムごとにインターフェースを定義する
    • アンチパターン : ベースとなるインターフェースに、全個別ストレージの設定プロパティを定義する形
      • 利用したいストレージシステムについて、必須なプロパティが何かわかりにくくなる
        • 似た名前が複数登場するなど
  2. Import と Export のインターフェースは必ず分離すること
    • この二つは別の概念を表現しているため
    • 扱うプロパティが似通うことになっても、必ず分離する

リソースとバイト列の変換

import / export 用の設定インターフェースである InputConfigOutputConfig を使用して、各種設定を制御する。
設定の例を示す。

  • ContentType : "json", "csv" など、シリアライズ/デシリアライズ のフォーマット
  • compressionFormat : "zip" , "bz2" など、 import /export 処理後にリソースを圧縮したい場合、そのフォーマットを指定できるプロパティ
  • filenameTemplate: ファイル名のテンプレート。複数のリソースをまとめてexport する場合に使用するものと思われる
  • maxFileSizeMb : 最大ファイルサイズ

一貫性

エクスポートするデータのサイズや個数によっては、エクスポート処理に時間がかかる。
この時、エクスポート中にデータが更新される可能性も十分考えられる。

これには、以下の解決策がある。

  1. 基盤となるストレージシステムが提供するスナップショット機能、またはトランザクション機能を使用する
    • ある時点でのリソースを一括で読み込むことで、一貫したデータを確保できる
    • なお、エクスポート中に発生したデータの変更は、反映されない
  2. エクスポートされるデータが最新であることを保証しない(言い換えるとベストエフォート)
    • ある時点でのデータを正確に表現できないことを意味する
  3. エクスポート処理中は、変更処理を一切受け付けない
    • スナップショット機能、トランザクション機能未サポートかつ、最新であることを保証しなければならない場合の選択肢
    • ユーザーに不便を強いることになるので、あまりとりたくない選択肢

基本的には、選択肢2を取ることになる。

また、エクスポートとバックアップは異なる。
export カスタムメソッドを、バックアップ目的で使用しないよう、利用者に示すこと。

識別子と衝突

エクスポート処理にて、識別子ごとエクスポートすると、タイミング次第でインポート時に識別子の衝突が発生し得る。

「エクスポートは、外部からデータを新規に取得する」と考え、識別子ごとリソースを(必要ならバッチで)新規作成する。

この場合、重複したリソースが複数誕生する。
そもそも、 importexport カスタムメソッドは、一貫性が重要となるバックアップ & リストアを意図していない。
そのため、重複を許さない(または一貫性が重要な)リソースに対して、 importexport カスタムメソッドを提供しない(または保証しないことを明記する)こと。

関連リソースを扱う

importexport カスタムメソッドは、APIサービスと外部ストレージシステム間のデータの中継処理を取り除くことを目的としている。
子リソースごとに処理を選択できるような、高度な機能の提供は想定していない。

そのため、 importexport カスタムメソッドは、「子リソースを持たず、関連リソースが無くても有用なリソース」に限定して提供する。
複合リソースタイプのインポートおよびエクスポートの話が出たら、要件を疑おう。

  • 複合リソースをインポート/エクスポートできたとして、本当に価値はあるの?

失敗と再試行

◆インポートの失敗

検証をはじめとした何らかのロジックによるエラーか、一過性のエラーかで対応が変化する。

前者(何らかのロジックによるエラー)の場合、何度再試行しても失敗するため、利用者にエラーを通知する必要がある。

後者の場合、再試行処理によって、インポートに失敗した分のデータが重複してしまう。
これに対処するため、

  • インポート処理をトランザクション処理にて実行する
    • 途中で失敗したら、変更をロールバックする
  • リクエスト重複回避(26章)を実装する
    • インポートするデータ単位で識別子を生成
    • インポートに成功した識別子をキャッシュすることで、再試行時にスキップするか判断できるようになる
◆エクスポートの失敗

エクスポート処理は、基本的には一貫性を保証しない。
そのため、再試行したエクスポート処理は、再試行前のエクスポート処理と同じ結果になるとは限らない。

また、失敗したエクスポート処理に失敗したデータは、以下の理由により、どのエクスポートプロセスで失敗したかに関わらず、残すこと(言い換えると、勝手に削除しないこと)を推奨する。

  • API が、外部ストレージシステムのデータを削除する権限を持つとは限らないため
  • エクスポートに失敗したデータに価値があるか、API側では判断がつかないため
    • 100%でなくても、例えば98%でもエクスポートに成功できれば十分かもしれない

フィルタリングとフィールドマスク

エクスポートはフィルタリング機能を提供できた方がよい。
この時、フィルタリング文字列は、 エクスポート用設定ファイルではなく、 export カスタムメソッドのリクエストボディに含める。

  • エクスポート用設定ファイルは、リソースの束をバイト列の束に変えることだけに焦点を当てている
  • 他のことに焦点を当てたくないため、フィルタリング文字列を含めないようにする

インポートのフィルタリングは、API側でサポートするべきではない。

  • API側で、専用のフィルタリングルールを実装する必要がある
  • API側で各種フィルタリングルールの実装に責任を持つ必要があり、労力が伴う

インポート前に、利用者の責務で、リソースをフィルタリングしてもらう。

トレードオフ

このパターンの目的は、「APIサービスと外部ストレージシステム間のギャップを埋める」こと。
もっと言うと、データ転送にだけ責務を持つ。
これにより、以下の欠点を持つ。

  • 複数のリソースタイプを扱うよう設計されていない
    • 言い換えると、データ転送にのみ責任を持つため、細かなデータ変形には責務を持たない
    • 複数のリソースタイプを扱うような処理は、ただのデータ転送ではなく、重要なビジネスロジック
      • 別途カスタムメソッドを定義しよう
  • インポートとエクスポート機能を、バックアップ & リストア機能と混同されがち

扱う事

  • インポートおよびエクスポートの具体的な定義
  • リモートストレージシステムとの直接的なやり取り
  • シリアライズされたバイト列のAPIリソースへの変換
  • 変わりやすいデータセットをエクスポートする際の一貫性の処理
  • インポートおよびエクスポート時の一意な識別子の処理方法
  • 関連リソースをインポート、エクスポートするときの対処法
  • インポート / エクスポート操作が失敗した時の処理
  • インポートとエクスポートのバックアップやリストアとの違い
  • 入力時でのデータと出力データのフィルタリング

まとめ

  • カスタムの import , export メソッドによって、 APIサービスと外部ストレージシステム間で直接データを移動できるようになる
  • これらのメソッドはバックアップ/リストア機能を意図していない
    • バックアップ / リストアを意図して使用した場合、思わぬ結果につながる
  • 本書籍でのAPI定義では、以下に焦点を当てている
    • 外部ストレージシステムにバイト列を出し入れする
    • バイト列をAPIのリソース表現に / から変換する
  • import , export メソッドで扱うデータは、常に最新のデータとは限らない
    • システムがポイントインタイムデータの読み込みをサポートしていない場合を除く
  • データのインポートとエクスポートは、一度に単一のリソースタイプに制限されるべき
    • 子リソースやその他参照リソースなど、複数のリソースタイプを扱いたい場合は、代わりにバックアップとリストア機能を使用すること
WebAPI

2023年12月09日(土)

2.0時間