グラフとチャートが表示されたデスクの2台のモニター
現場の実践

状態機械設計とURL正規化:小さなPWAから学ぶ障害耐性の作り方

目次を見る

個人開発のPWA(プログレッシブウェブアプリ、ブラウザで動くアプリをネイティブアプリのように使える仕組み)を題材に、状態設計の甘さがどう障害につながるかを整理します。対象は複数端末・複数サービスを連携させるシステムを設計・運用するエンジニアです。小規模な事例の方が、根本原因が見えやすいという利点があります。

題材は「TrainFlow」という、YouTube動画のタイムスタンプ(動画内の特定の再生位置を示す秒数)を使ったトレーニング管理アプリです。デスクトップでメニューを作り、モバイルで実行するという2端末構成を取っています。この構成自体は、クラウド移行やマルチデバイス連携を扱う現場でも頻出するパターンです。

何が問題になりやすいか

YouTubeのURLは、共有元によって形式がばらばらです。標準の視聴URL、短縮リンク、モバイル共有リンク、すでにtパラメータでタイムスタンプを含むリンクなど、少なくとも4パターンが混在します。

これを各コンポーネントがそれぞれ独自にパースすると、表記ゆれによる不整合が発生します。たとえば90という秒数表記と1m30sという表記が混在した場合、片方のコンポーネントだけ変換ロジックを持っていないと、同じ動画のはずが違う再生位置を指してしまいます。

これはマイクロサービス間でデータ形式の解釈がずれる問題と本質的に同じです。オンプレからクラウドに移行する際、複数のサービスがAPI経由で同じ外部データ(決済情報、住所、日時など)を扱う場面で頻繁に起きる不具合パターンでもあります。

境界での正規化という設計原則

この問題への対処法は、システムの境界(データが外部から入ってくる場所)で一度だけ正規化することです。TrainFlowの設計では、ユーザーがURLを登録した瞬間に、以下の処理をまとめて行っています。

  • プラットフォームのURL APIでパースする
  • 対応済みのYouTubeホストのみ受け入れる
  • 動画IDを抽出し妥当性を検証する
  • 901m30sなど複数のタイムスタンプ形式を秒数に統一する
  • 負の値や不正な数値を拒否する

この正規化を経たあとは、後続のコンポーネントは「検証済みの動画IDと秒数」だけを受け取ればよくなります。プレイヤー側のコードが単純化され、バグの発生源が1箇所に集約されます。

クラウド運用の文脈で言い換えると、これはAPI Gateway(複数のバックエンドサービスへのリクエストを一元的に受け付ける入り口)でのバリデーション設計と同じ考え方です。各マイクロサービスが個別に入力チェックを行う構成は、チェック漏れのサービスが1つでもあると、そこが障害の起点になります。境界を1箇所に絞ることで、監視すべきポイントも1箇所に絞れます。

状態機械という監視設計の土台

もう一つの重要な設計判断が、セッション状態を明示的な状態機械(ステートマシン、取りうる状態と遷移条件を厳密に定義した仕組み)として管理している点です。

type SessionStatus =
  | "ready"
  | "playing"
  | "resting"
  | "paused"
  | "completed";

イベントごとに遷移先を明示します。たとえばSTARTイベントはreadyからplayingへの遷移のみを許可し、COMPLETE_ACTIONplayingからrestingまたは次のアクションへの遷移のみを許可します。

これと対照的なのが、状態を複数の独立したブール値(真偽値のフラグ)で管理するやり方です。isPlayingisPausedisRestingのようなフラグを別々に持つと、理論上はisPlaying = trueかつisPaused = trueという矛盾した状態が発生し得ます。

この矛盾状態は、障害発生時の原因調査を著しく難しくします。ログを見ても「今どの状態だったか」が一意に определить(特定)できず、再現手順も組み立てにくくなるためです。

状態機械を採用すると、監視すべき対象がシンプルになります。「今どの状態にいるユーザーが何人か」「不正な遷移が発生していないか」という2つの指標だけで、システムの健全性をかなり把握できます。SRE(サイト信頼性エンジニアリング、システムの信頼性を運用面から支える職能)の文脈では、こうした明示的な状態遷移をログに残すことが、根本原因分析(インシデントの真因を特定する作業)の速度に直結します。

2端末構成が運用に与える影響

TrainFlowはデスクトップ用のダッシュボードとモバイル用のPWAを分離しています。これはレスポンシブCSSで画面サイズだけ切り替える設計とは異なります。

デスクトップは「メニューを組み立てる」役割に特化し、複数の情報を並べて比較できる密度重視の画面にしています。モバイルは「今のアクションだけ見せる」役割に絞り、操作数を最小限にしています。

共有のデータモデル(アクションとルーティンの定義)は同じでも、画面ごとに異なるレンダリングをする設計です。これは、バックエンドAPIを共通化しつつフロントエンドの責務を分離する、一般的なクラウドアーキテクチャの考え方とも重なります。

運用監視の観点では、この分離によって「どちらの端末での操作が障害の起点か」を切り分けやすくなるという利点があります。エラーログにクライアント種別(desktop/mobile)を必ず含めておけば、障害調査の初動が速くなります。

今日確認できること

自分のプロジェクトが同種の課題を抱えているかどうか、以下の観点で確認できます。

確認項目確認方法該当する場合のリスク
外部URL・外部データの正規化入力を受け取るコンポーネントが複数あるか、コードを検索する表記ゆれによるデータ不整合
状態管理の方式ブール値の組み合わせで状態を表現していないか確認する矛盾状態の発生、障害調査の長期化
ログへのコンテキスト付与エラーログにクライアント種別・状態名が含まれているか確認する根本原因の特定に時間がかかる

特に外部サービスのURLやIDを複数箇所でパースしている場合は、正規化処理を1箇所の関数やAPI Gatewayのミドルウェアに集約できないか検討する価値があります。TypeScriptを使っている場合、正規化後の型(例ではNormalizedVideoRefのような型)を定義し、後続のコードがその型しか受け取れないようにすると、境界の突破を防ぎやすくなります。

状態機械については、既存のブール値フラグ管理をいきなり全部書き換える必要はありません。まずは障害が起きやすい1つの機能(決済フロー、注文処理、セッション管理など)を対象に、取りうる状態を紙やMermaid図で書き出してみることから始められます。

障害調査を速くする鍵は、複雑な監視ツールの導入より先に「状態を一意に定義できているか」を見直すことです。

まとめ

個人開発の小規模なPWAであっても、境界での正規化と明示的な状態機械という2つの設計原則は、大規模なクラウドシステムの障害対応力に直結します。

  • 外部データを受け取る箇所を洗い出し、正規化処理が1箇所に集約されているか確認する
  • ブール値の組み合わせで状態管理していないか、既存コードを見直す
  • ログにクライアント種別や状態名を含め、障害調査の初動を速くする

まずは自分のプロジェクトで最も障害が多い機能を1つ選び、その状態遷移を図に書き出すところから着手できます。

参考

Designing a Cross-Device Workout Workflow Around YouTube Timestamps

この記事について: 本記事は AI を活用して作成し、forva AI 編集部が内容を確認・監修しています。

AI 駆動開発のご相談は forva AI へ。まずはお気軽にどうぞ。