Skip to main content

ローディング状態はドメインか?通信状態とドメイン状態の見分け方

TL;DR​

  • loading / pending のような取得中フラグは基本的にドメインではなく通信・アプリケーション層の関心事。
  • 見分け方は「その状態が業務ルール(ユビキタス言語)に登場するか」。「審査中」はドメイン、「fetch中」は違う。
  • 迷ったら「バックエンドを同期APIに置き換えても残る状態か?」と問う。残るならドメイン、消えるなら通信状態。
  • 構造としては RemoteData(外側・通信状態)がドメイン型(内側)を包む。ドメインモデルに isLoading を混ぜない。
  • TanStack Query 利用時は query 層が status / fetchStatus としてすでに所有しているので、自前の RemoteData 型はほぼ不要。

このドキュメントの射程​

  • フロントエンドで「ローディング中かどうか」のような状態をドメインモデルに含めるべきか迷ったときの判断基準。
  • 同じ boolean / 直和型でも、置き場所(ドメイン層 vs UI・通信層)が変わるケースの整理。

原因(なぜ混同が起きるか)​

  • どちらも「状態」であり、discriminated union で表現すると見た目が同じになるため。
  • isLoading: boolean をドメインモデルに直接生やすと、「items が空なのはロード中だから?本当に空?」のような不正状態が表現可能になる。RemoteData パターンの原典(Kris Jenkins, 2016)が指摘したのはまさにこの問題で、isInProgress: true かつ error あり かつ data あり、のような矛盾した組み合わせが型上作れてしまう。
  • Jenkins 自身の定式化は「REST リクエストには 4 状態ある(NotAsked / Loading / Failure / Success)」であり、これはリクエストのモデルであってデータそのもののモデルではない。ここを読み飛ばすと「状態=ドメイン」と混同する。

解決(判断基準と構造)​

判断基準​

問いYes → ドメインNo → 通信/UI層
ドメインエキスパートとの会話に出てくるか「審査中は編集不可」「fetch中」は出ない
業務ルール・不変条件がその状態に依存するか「承認済みのみ発送可」loading は何も規定しない
バックエンドを同期APIにしても残るか「下書き→提出→承認」は残るloading は消える

構造:包む、混ぜない​

// ❌ ドメインモデルに通信状態が漏れている
type Order = {
id: OrderId;
isLoading: boolean; // 注文自体に「ロード中」という業務的意味はない
items: OrderItem[];
};

// ✅ 通信状態(外側)がドメイン型(内側)を包む
type RemoteData<E, A> =
| { type: "idle" } // NotAsked:未取得と取得済みを区別する要
| { type: "loading" }
| { type: "failure"; error: E }
| { type: "success"; data: A };

type OrderQuery = RemoteData<ApiError, Order>;

// ✅ 業務上のライフサイクルはドメインとしてモデル化する(通信とは無関係に存在)
type Application =
| { status: "draft"; content: DraftContent }
| { status: "underReview"; submittedAt: Date }
| { status: "approved"; approvedAt: Date }
| { status: "rejected"; reason: RejectionReason };
  • success のときだけ完全な Order が存在することが型で保証される。「parse, don't validate」と同じく、境界を越えたら通信の事情を持ち込まない。
  • NotAsked(idle)の存在がパターンの核心。isLoading: false だけでは「未取得」と「取得済み」を区別できない。

2026年時点の実務的な着地点​

  • TanStack Query を使うなら、通信状態は query 層が status / fetchStatus の直積として所有済み。自前で RemoteData を定義してドメイン型に混ぜる必要はない。
  • コミュニティの語彙では「server state vs client state」として定式化されており、「API レスポンスを Redux/Zustand に入れて loading/error フラグを手動管理する」のがアンチパターンとされる。
  • 逆方向の注意:loggedIn や isPaidMember のようなバックエンド由来の値をそのままコンポーネントで使うのは業務知識の漏れ出し(フロントエンドのドメインモデル貧血症)。同じ boolean でも「有料会員かどうか」はドメインなのでモデル化する。業務ルールに登場するかで置き場所が変わる。

まとめ​

  • ローディング状態は「リクエストのモデル」、業務ステータスは「ドメインのモデル」。RemoteData で外から包み、ドメインモデルには混ぜない。

参考​