API の境界での検証は、どこか一箇所で行えば済むものではありません。入力を受け取る場所、業務の規則を適用する場所、外部に問い合わせる場所で、それぞれ役割が違います。この記事では、その分担をどう決めるか、とくに待ち時間と失敗の扱いを中心に整理します。
検証を境界に置くのはなぜか?
境界とは、自分が管理していない相手からデータを受け取る地点です。ここを素通りさせると、壊れた値が内側に広がります。
内側に入ってからの対処は、境界での対処よりはるかに高くつきます。理由は三つあります。
- 問題が見つかる時点が遅くなります。原因の値が、複数の処理を経た後の結果として現れます。
- 影響範囲が広がります。壊れた値が保存され、集計され、別の利用者に見えます。
- 直すのに移行が必要になります。すでに保存された値を後から直す作業は、入力時に止める作業より重くなります。
したがって、境界で受け取った直後に、受け入れ可能かどうかを判定する段階を置きます。判定に通らなかった値を内側へ入れないことが原則です。
クライアントとサーバーのどちらで検証するのか?
どちらか一方ではなく、両方で行います。ただし、役割は同じではありません。
| 場所 | 主な役割 | 失敗したときの意味 |
|---|---|---|
| クライアント側 | 入力の形を整え、明らかな誤りを早く知らせる | 送信前に利用者が直せる |
| サーバー側 | 受け入れてよいかを最終的に決める | 値は保存されない |
クライアント側の検証は、応答を速くするための仕組みです。通信を待たずに誤りを示せるため、利用者の手戻りが小さくなります。しかし、クライアント側の検証は迂回できます。直接 API を呼ぶ経路や、古い版の画面が残っている経路があるためです。
サーバー側の検証は、迂回できません。したがって、受け入れの可否を決めるのはサーバー側です。両方に同じ判定を書くと、判定の規則が二箇所に増えます。片方だけを直した状態が生まれ、画面上は通るのに送信すると弾かれる、という食い違いが起きます。
この食い違いを避けるには、判定の規則を一つの定義から両方に配る形にするか、クライアント側は明らかな誤りだけを扱う軽い確認にとどめるかのどちらかです。後者のほうが単純で、ずれが生まれにくくなります。
境界で行う確認は三つに分かれる
境界での検証は、性質の違う三つの層に分けて考えると整理できます。
- 形式の確認。桁数、文字種、区切り記号の扱いを確かめます。外部に問い合わせる必要はなく、応答は即時です。
- 算法による確認。公開された手順があれば、末尾の位を計算して確かめます。これも外部に問い合わせる必要がなく、応答は即時です。
- 帰属の確認。その番号が実在するか、有効な状態かを外部に問い合わせます。これは待ち時間がかかり、失敗もします。
最初の二つは、同じ場所で続けて行えます。三つ目は性質がまったく異なります。待ち時間があり、相手の都合で失敗し、呼び出しの回数に制限があることが多いからです。
この三つを一つの処理にまとめると、待ち時間の長い確認が、待ち時間の短い確認の結果まで巻き込んで失敗させます。分けて設計すれば、三つ目が失敗しても、最初の二つの結果は保てます。
外部照会の失敗をどう扱うのか
三つ目の層で失敗したときの扱いは、最も誤りやすい箇所です。照会がタイムアウトした場合、その番号は有効なのでしょうか、無効なのでしょうか。
答えは、どちらでもありません。確認できなかった、という第三の状態です。この状態を「無効」に丸めると、相手の一時的な障害が、利用者の番号の否定に変わります。利用者は自分の番号が間違っていると思い、正しい値を入れ直そうとします。実際には何も間違っていません。
この状態を「有効」に丸めるのも同じくらい危険です。確認していない値を確認済みとして扱うことになり、あとから取り返しがつきません。
そこで、照会の失敗は次のように切り分けます。
- 一時的な失敗。時間を置けば成功する見込みがあります。再試行の対象です。
- 恒久的な失敗。呼び出しの形式が誤っているなど、やり直しても同じ結果になります。
- 相手が応答しない。待ち時間を超えた場合です。判定を保留します。
いずれの場合も、利用者には「確認できなかった」と伝えます。この整理はチェックディジットがない番号で扱った四つの結論と同じ考え方です。確認していないことを、確認した結論に混ぜないことが原則になります。
待ち時間と回数制限にどう備えるか
外部照会を伴う設計では、待ち時間と回数の両方に上限を設けます。
待ち時間については、上限を決めて、それを超えたら打ち切ります。上限を設けないと、相手の遅延が自分の側の遅延になります。一つの要求が遅いために、他の要求が処理されない状態は避けなければなりません。
同時に走らせる本数にも上限を設けます。上限がないと、遅い相手に対して要求が積み上がり、自分の資源を使い切ります。
回数については、同じ値を何度も照会しない設計にします。短い期間の結果を保持し、同じ入力に対しては保持した結果を使います。保持の期間は、対象の性質によって決めます。頻繁に変わらない情報なら長めに、変わりやすい情報なら短めに設定します。
失敗した結果を保持しないことも重要です。一時的な失敗を保持すると、相手が復旧した後も失敗を返し続けます。失敗の保持は短くするか、そもそも保持しない設計にします。
利用者に何を返すべきか
境界で返す内容は、判定の結論だけでは足りません。
- どの層まで確認したか。形式だけか、算法までか、帰属までか。
- 結論。有効、無効、形式のみ、規則なし、確認できなかった、のいずれか。
- 修正の手がかり。形式のどこが合わなかったか、値のどの部分を疑うべきか。
- 再試行の可否。一時的な失敗なら、その旨と目安の時間。
とくに最初の項目が重要です。利用者は、返ってきた結論がどの程度の確かさを持つのかを知りたいはずです。層を明示せずに「有効」とだけ返すと、帰属まで確認したと読まれます。
また、内部の例外をそのまま返さないようにします。相手の障害の詳細は、利用者にとって意味がなく、外部に見せる必要もありません。利用者に必要なのは、何が起きたかと、次に何をすればよいかです。
本文で述べたのは境界設計の考え方に関する一般的な整理であり、例として想定した値はすべて説明のための作り物です。実在の番号、利用者、事業者に対応するものではなく、個々の値の真偽を示すものではありません。
次の一手
自分の API が返している結論を数えてみてください。二種類しかないなら、確認できなかった場合の扱いが抜けている可能性があります。返すべき状態の整理は、一括検証のワークフローで扱った区分と同じです。まず番号検証ツールで個別の値を試し、画面側でどの層までの結果が示されるかを確認してから、API の応答を設計すると、両者のずれを避けられます。あわせて公的番号のチェックディジットの考え方も参考にしてください。