プロフィール作成は成功したのに、再読み込みで匿名へ戻る設計だった話

当ページのリンクには広告が含まれています。
この記事の結論
複数の処理から成る操作は、どこまで成功したかを分けて扱う必要があると考えた話です。プロフィール作成後にブラウザ保存だけが失敗した場合、作成APIを再送せず、未完了の保存だけを再試行する形へ直しました。

お疲れ様です!IT業界で働くアライグマです!

個人開発している開発関連ニュースのキュレーションサービス「DevPick」で、プロフィール登録後の状態管理を見直しました。サーバー側では作成に成功しているのに、そのプロフィールをブラウザへ保存できなかった場合、画面上では登録できたように見えながら、再読み込みすると匿名へ戻る設計になっていたためです。

目次

1回の登録に見えて、実際は2段階だった

DevPickのプロフィール登録は、利用者から見ると「登録する」ボタンを1回押す操作です。しかし内部では、サーバーへプロフィールを作成する処理と、返ってきたプロフィールのIDをブラウザへ保存する処理が順番に動いていました。

ブラウザへの保存にはWeb Storageの一種である localStorage を使っています。保存したIDがあれば、次にページを開いたときも同じプロフィールを復元できます。ところが、プライベートモードやサイトデータの制限などによって、保存処理が例外を返す場合があります。

問題だったのは、サーバー側の作成が成功した後に保存だけが失敗しても、画面は登録成功時と同じように進んでいたことです。その場では作成済みのプロフィールをメモリ上に持っているため、ログイン中の表示へ切り替えられます。設定シートも閉じるので、利用者には異常が見えません。

しかしIDはブラウザに残っていません。ページを再読み込みすると復元できず、匿名の表示へ戻ります。メールアドレスも登録していなければ、作成済みのプロフィールへ後から戻る手段がありませんでした。

1つのボタンにまとめられていても、処理は一枚岩とは限りません。今回は「プロフィールを作れたか」と「次回も使えるようブラウザへ保存できたか」を、別々の成功条件として扱う必要がありました。

IT女子 アラ美
登録完了に見えるのに、再読み込みで消えるのは利用者には見抜けないよね。

ITアライグマ
はい。画面が成功を示す前に、次回復元できる状態まで確認する必要がありました。

保存失敗を作成失敗と呼ぶと二重登録を促す

保存中に例外が起きたなら、フォームへ「プロフィールの作成に失敗しました」と表示する方法も考えられます。ただし今回は、その案内自体が事実と違います。失敗したのはブラウザへの保存であり、プロフィールはすでにサーバー上へ作成されているからです。

作成失敗と案内された利用者は、同じフォームからもう一度登録しようとします。すると、最初のプロフィールを残したまま新しいプロフィールを作る方向へ誘導してしまいます。エラーメッセージが、二重登録のきっかけになる状態です。

この問題は、例外をどこでまとめて扱うかによって起きていました。フォームは作成APIの呼び出しから、その後の画面更新までを一続きの処理として見ていました。そのため、作成後に呼ばれる処理が例外を投げても、フォーム側では作成処理の失敗に見えます。

そこで、作成APIから正常な応答を受け取った時点を失敗境界にしました。それより後のブラウザ保存や画面更新で起きた問題は、作成エラーへ戻しません。プロフィールを作る責任と、作成済みプロフィールをブラウザへ記憶する責任を分けた形です。

この考え方は、以前の復旧リンクの再試行で先の成功を壊さないようにした話と似ています。ただ今回は送信結果が不明なのではなく、前半の成功が確定しています。確定済みの処理を、後半の失敗に巻き戻して見せないことが中心でした。

IT女子 アラ美
失敗という言葉を雑にまとめるだけで、次の操作まで間違わせてしまうのね。

ITアライグマ
はい。どの処理が失敗したかを正確に伝えることも、重複を防ぐ設計の一部です。

作成APIを再送せず、保存だけを再試行する

失敗境界を分けた後は、再試行の単位も同じ境界へ合わせました。プロフィール作成に成功して保存だけが失敗したときは、作成済みのプロフィールを画面上に保持します。そして「保存して続ける」ボタンから、ブラウザへのID保存だけをもう一度実行します。

実装の流れを単純化すると、次のような形です。実際の識別子ではなく、処理の役割だけを表した例にしています。

const created = await createProfile(formData);

if (saveProfileId(created.id)) {
  closeSettings();
} else {
  showStorageError(() => saveProfileId(created.id));
}

保存処理は、成功したときだけ true を返し、ブラウザに拒否されたときは false を返すようにしました。例外をそのまま上へ投げないことで、呼び出し側は「作成後の保存に成功したか」を明示的に分岐できます。

再試行ボタンが呼ぶのも saveProfileId に相当する処理だけです。作成APIは最初の1回しか送らないことをテストで固定しました。保存が再び失敗した場合は案内を残し、成功した場合だけ案内を消して設定シートを閉じます。

重要だったのは、再試行機能を付けること自体ではなく、何を再試行するかを限定することでした。利用者の1操作を丸ごとやり直すのではなく、完了済みの処理を除き、未完了の1段だけを動かします。

IT女子 アラ美
再試行ボタンでも最初から全部やり直すとは限らないってことなのね。

ITアライグマ
はい。成功済みの段階を保存したまま、未完了の処理だけを再実行しています。

シートを閉じても失敗の案内を消さない

保存に失敗したときは、設定シートを自動では閉じません。シート内に「プロフィールの登録は完了しているが、このブラウザには保存できていない」と表示し、その場で保存だけを再試行できるようにしました。

ただし利用者は、再試行せずに自分でシートを閉じることもできます。この操作で案内まで消えると、ページ上はログイン中に見えるため、保存できていない事実を再び見失います。そこでシートを閉じている間は、同じ案内を本文側へ移して表示します。

案内する回復経路は、メールアドレスの登録有無でも変えています。メールアドレスが登録済みなら、後からアカウント復旧リンクを受け取って戻れます。未登録なら、保存できないままページを離れると戻る手段がありません。単に「保存できませんでした」で終わらせず、今離れた場合に何が起きるかまで伝える必要がありました。

保存に失敗した後でメールアドレスを追加した場合は、案内も復旧可能な内容へ切り替えます。そのため、保存失敗時点のプロフィール情報を複製して固定せず、現在のプロフィールを参照して表示する形にしました。

支援技術への伝わり方も確認しています。失敗案内には role="alert" を使い、再試行が再び失敗したときも読み上げられるよう、案内を更新します。見た目に残すだけでなく、失敗した事実と次の操作が利用者へ届くところまでを状態管理に含めました。

IT女子 アラ美
閉じる操作で警告まで片付けたら、まだ終わっていないことを忘れちゃうよね。

ITアライグマ
はい。画面の置き場所を変えても、未完了という状態は残す必要がありました。

Web Storage全体の失敗へ範囲を広げる

今回のきっかけは、プロフィール作成後の書き込み失敗でした。しかし localStorage は、書き込みだけが失敗するとは限りません。保存済みIDの読み出しや、プロフィール削除後のID削除でも例外が起こり得ます。

読み出しが拒否された場合、ブラウザ内に保存済みIDがあるかどうかすら確認できません。この状態ではプロフィールを復元できないため、記事一覧を匿名として表示し、閲覧自体は続けられるようにしました。復元できないことを、フィード全体の表示失敗へ広げない判断です。

IDの削除が拒否された場合も、プロフィール削除後の画面初期化は続けます。サーバー側では削除が完了しているため、ブラウザに古いIDが残っても、そのIDでプロフィールを復元することはできません。ブラウザ側の後片付けに失敗したからといって、削除後の匿名表示へ戻る処理まで止めないようにしています。

この2つは、書き込み失敗時の再試行とは対応が異なります。読み出せなければ匿名として閲覧を続け、削除できなければサーバー側の削除結果を優先して初期化を続けます。同じWeb Storageの例外でも、その時点で確定している事実と、利用者が続けるべき操作に合わせて縮退先を変えました。

作成時の1箇所を直すだけでなく、同じ外部機能を使う読み書き・削除の境界まで見直したことで、Web Storageが使えない環境でも未処理の例外で画面全体を止めない形になりました。

IT女子 アラ美
同じ保存機能のエラーでも、読む・書く・消すで正しい逃げ道が違うのね。

ITアライグマ
はい。その時点で確定した結果を守りながら、続けられる処理を選んでいます。

まとめ

DevPickのプロフィール登録は、サーバー側の作成とブラウザへのID保存という2段階の処理でした。作成後に保存だけが失敗した状態を作成失敗とまとめてしまうと、利用者へ二重登録を促す案内になります。

そこで、作成APIの成功を境界として確定させ、保存に失敗した場合は作成済みプロフィールを保持したまま、保存だけを再試行する形へ変えました。設定シートを閉じても案内を本文側へ残し、メールアドレスの登録状況に応じて、後から戻れるかどうかも伝えます。

今の私の結論は、複数段階の操作でエラーが起きたとき、操作全体を成功か失敗の2択にしないことです。どこまで確定し、何だけが未完了なのかを状態として持てば、成功済みの処理を壊さず、必要な部分だけを再試行できます。

IT女子 アラ美
一部だけ失敗したなら、成功した部分まで無かったことにしないのが大切なのね。

ITアライグマ
はい。部分成功を正しく残すことが、安全な再試行の出発点だと考えています。

作者が開発したサービス「DevPick」

この記事をシェアする
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

ITアライグマのアバター ITアライグマ ITエンジニア / PM

都内で働くPM兼Webエンジニア(既婚・子持ち)です。
AIで作業時間を削って実務をラクにしつつ、市場価値を高めて「高年収・自由な働き方」を手に入れるキャリア戦略を発信しています。

目次