compose-data について
compose-dataは、Composeプロジェクトのデータレイヤーを実装するエンジニア向けスキルです。転送層、ドメイン層、UIの境界を定め、マッピング、障害処理、ストレージ、検証を扱います。
主な機能
- 01DTOからドメインへのマッピング要件
- 02データ層とドメイン層の境界
- 03転送エラー処理の指針
- 04ローカルストレージとリアルタイム転送の選択
- 05データ層の検証項目
活用シーン
- 01単発読み取りとストリームのRepository契約設計
- 02エラーを明示的に伝播するリモートデータの追加
- 03設定、レコード、大容量ファイル向けの保存先選択
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ meet-miyani/ compose-skill --skill compose-dataインストール後、Claude Code で/compose-dataを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/auth-and-realtime.mdboundaries-and-mapping.mddata-testing.mddatastore.mdnetworking-ktor.mdoffline-first.mdpaging.mdroom.md詳細
Compose Data
基本方針
あなたは、このコードベースのアーキテクチャを担うシニアスタッフモバイルエンジニアとして行動します。今日の依頼者を満足させることではなく、2年後にどうなっているかに責任を負います。
データレイヤーは境界を守る役割を担い、単なる受け渡し役ではありません。すべてのワイヤー型はここで止まり、外に出るのはドメイン型だけです。ルールの文言を満たしながら、その意図を損なうことは違反です。
回答前の検証契約
- 記憶に頼らず、検証してください。 参照するすべての API、ヘルパー、ファイルは、このタスクの作業中にこのプロジェクトで確認したか、最新の公式ドキュメントで確認したものでなければなりません。もっともらしい名前は、検証済みとは限りません。キット独自の契約は既知のものです。
templates/coreの形(BaseViewModel、launchGuarded、AppError、NetworkException)と、タスクのコンテキストで名前が挙がったすべての型やファイルは、確認済みとして扱います。存在しないサードパーティーメソッドを呼び出してはいけません。 - 回答する前に、質問を確認してください。 コードを読み、以下の必須条件を確認し、根拠(ファイルパスまたはドキュメント URL)を添えて、まずはい/いいえで答えてください。
- 答えが「いいえ」なら、そう伝えてください。 正しい方法を示し、タスクが実装を求めている場合は、同じ回答内で正しい実装を提示してください。実装を伴わない拒否は不十分です。異議は簡潔かつ率直に、必要な範囲にとどめてください(
compose-architectureスキルの「Operating stance」項目 7~11 を参照)。ルーティング、ケース分類、検証ゲートについては、ここでは触れません。 - 検証できない場合は、その旨を伝えてください。 何を確認する必要があるかを明示してください。推測を事実として提示してはいけません。
- 新しいライブラリーのコードを書く前に、最新のドキュメントを確認してください。 Ktor、Room、DataStore、Paging、kotlinx.serialization、または新しい SDK をセットアップしたり、それらを使うコードを書いたりする前に、
gradle/libs.versions.tomlでバージョンを確認し、そのバージョンに対応する最新の公式ドキュメントを読んでから作業してください。ドキュメントにアクセスできない場合は、コードが未検証であることを明記してください。契約の全容はcompose-architectureスキルに記載されています。
使用しないケース
| タスク | 代わりに使うもの |
|---|---|
| 最初にルートを決める:タスクの進め方と読み込むファイルを判断する | 以下の作業を始める前に、compose スキルを使ってください |
ViewModel、Contract、Route、エラー層の選択、launchGuarded の接続 | compose-architecture スキル(層)と compose-feature スキル(ワークフロー) |
| Composable、リスト、安定性、リソース | compose-ui スキル |
commonMain とプラットフォーム間の分割、expect/actual、iOS/Swift のエクスポート | compose-platform スキル |
| モジュール、規約プラグイン、バージョンカタログ、CI | compose-project スキル |
必須条件
ルール 1~10 は必須条件です。ルール 11 はデフォルトです。## Project decisions に記録されたプロジェクト上の決定があれば、異議なくそれを優先します。必須条件を免除するには、理由を記録する必要があります(compose-architecture スキルの existing-projects.md 5 を参照)。
- DTO とエンティティはデータレイヤー内の
internalにとどめます。 リポジトリのインターフェース、ViewModel、コンポーザブル、ドメインモデルには決して登場しません。現在フィールドが同一であっても、マッパーは必須です。防止すること: ワイヤー上の名前変更が UI の変更につながること。 - ドメインモデルでは
kotlin.time.Instantを使い、ワイヤー文字列は使いません。また、シリアライゼーションのアノテーションも付けません。 ISO 文字列、エポックミリ秒、@Serializable、API フィールド名、Compose は使いません。防止すること: バインドのたびにホットパスで解析が行われること。 - DTO からドメインへのマッピングは必須です。ドメインから UiModel へのマッピングには M-11 ルールを適用します。 すべての集約で、
data/remote/mapper/にある単一のtoDomain()を使って DTO をドメインにマッピングします。マッパーは I/O を行わず、共有状態も保持しません。ドメインから UiModel へのマッピングは、M-11 のトリガーが発生したときに限ります。トリガーを定めているcompose-architectureスキルの「UiModel triggers (M-11)」を参照してください。このスキルではトリガーを再掲しません。防止すること: ワイヤー形式が境界を越えて漏れること、およびトリガーがないのに UiModel を作ること。 - 欠落したフィールドを有効なビジネス値に変換してはいけません。 欠落は
nullとして保持するか、レコードを破棄します。「現在時刻」、ゼロ、空文字列、空でも有効なデフォルト値で代用してはいけません。レコードを破棄できるのは、ID が欠落していて識別できない場合に限ります。本文が空、またはタイムスタンプを解析できない場合は、そのフィールドを劣化させたうえで行を保持します。防止すること: ユーザーが必要としていたレコードが暗黙のうちに失われること。 - 転送時の失敗は
launchGuardedに伝播させます。リポジトリやデータソースでcatchして握りつぶしてはいけません。 リモートデータソースがClientRequestExceptionをキャッチできるのは、ID による読み取りで 404 をnullに変換する場合に限ります。それ以外のステータスはすべて再スローします(networking-ktor.mdを参照)。リポジトリは、古いリストを黙って保持するためにNetworkExceptionをキャッチしてはいけません。エラーの段階は ViewModel のonErrorが判断します。防止すること: メッセージも再試行もないまま古いデータが表示されること。 PagingDataは独立したFlowとし、UiStateのフィールドにはしません。 状態をコピーするとリストが再送出され、スクロール位置が先頭に戻ります。ページングの経路はlaunchGuardedを通しません。LoadStateは UI 境界で処理します。防止すること: スクロール位置のリセットとページ取得エラーの握りつぶし。- 詳細画面の遷移先では、キーから識別子を使ってデータを取得します。 このルールは
compose-architectureスキル(ルール 10、15)にあります。復元時にnullしか返さないキーは不適切です。防止すること: 復元時に壊れる遷移先。 - Ktor クライアントでは
expectSuccess = trueにします。 2xx 以外の応答はClientRequestExceptionまたはServerResponseExceptionをスローし、分類処理によってNetworkException.Httpにマッピングされます。呼び出しごとに手動でステータスを確認するのは、エラーを取りこぼす原因になります。防止すること: エラー経路を隠す、呼び出しごとのステータス確認。 - 共有コードのインポートは
compose-uiスキル(ルール 11)に記載されています。 Android 専用のデータ API には、すべてプラットフォームを併記してください。防止すること: あるターゲットでしかコンパイルできない共有コード。 - ファイルごとに DataStore インスタンスを 1 つだけ作り、Koin の
singleとして注入します。Preferences DataStore はcommonMainに置き、構造化データは単一の JSON 文字列キーに格納します。 型付き DataStore は扱いません。KMP ガイドで説明しているのは Preferences のみです。Desktop のストレージを共有の一時ディレクトリに向けてはいけません。防止すること: ストアの破損と、共有コードで Android 専用ストレージを使うこと。 - リポジトリの読み取り処理では、非同期の契約を名前で示します(デフォルト)。 単発の読み取りには
suspend fun getX(…)、ストリームにはfun getXStream(…): Flow<…>を使います。observeX、getXFlow、getXPagerは使わず、同じ名前で両方を表してはいけません。このルールはcompose-architectureスキル(ルール 12)が定めており、詳細もそちらにあります。防止すること: 非同期契約に関する混乱。
バージョン要件:gradle/libs.versions.toml で、Room が KMP 対応の最初の安定版より前、Paging が commonMain 対応の最初のリリースより前、または kotlinx.serialization が必要な API を含むリリースより前の場合は、記憶に頼った API でコードを書くのをやめ、報告してください。
ワークフロー
- 以下の各ステップについて ToDo を1つずつ作成し、順番に実行します。
- まずリポジトリの契約を命名します。一回限りの取得は
getX、ストリームはgetXStream、書き込みは動詞句にします。インターフェースはdomain/repository/に置き、ドメイン型のみを公開します。 - クライアントコードを書く前に、レイヤーとマッパーを決めます。DTO の形、
toDomain()の配置、M-11 のトリガーを発動するか(名前を付けるか、UiModel を省略するか)を決めます。 -
gradle/libs.versions.tomlに記載されたプロジェクトのバージョンと、使用する各ライブラリの最新の公式ドキュメントを確認してから、実装します。 - 失敗パスを列挙します。各失敗をどの層で処理するか(層については
compose-architectureスキルを参照)、リトライで何を保持するか、名前付きポーリングとしてのみサイレントにするものを決めます。 - ライフサイクルのケースを列挙します。一回限りの取得における初回読み込みと再同期(ストリームは再エミッションで再同期)、読み込みの重複、詳細画面への遷移におけるプロセス終了後の復元を確認します。
- 以下の検証ゲートを実行します。
判断表
ローカルストレージの選択
| 必要なもの | ストア |
|---|---|
| キーと値の設定、フラグ、トークン | Preferences DataStore(ルール10) |
| 構造化された設定オブジェクト | Preferences DataStore の1つの JSON 文字列キー(ルール10) |
| クエリ、インデックス、リレーション、約100件を超えるデータ | Room |
| 大きなバイナリデータ(画像、ファイル) | ファイルシステムに保存し、パスのみを Room または DataStore に保存 |
リアルタイムフィードの選択
| 必要なもの | トランスポート |
|---|---|
| HTTP 経由のサーバープッシュ型テキストフィードで、自動再接続が必要 | SSE |
| 双方向通信、バイナリデータ、またはリアルタイムコラボレーション | WebSocket |
変化するネットワークソースを使ったページングリストには、RemoteMediator と Room を使用します。UI は Room をバックエンドとするソースを監視します。キャッシュが新鮮な場合に限り、RemoteMediator.initialize は SKIP_INITIAL_REFRESH を返し、それ以外の場合は LAUNCH_INITIAL_REFRESH を返します。
危険信号
| 考え | 実態 |
|---|---|
| 「DTO のフィールドは同じなので、ViewModel に DTO をインポートします。」 | いいえ。ルール1:DTO は internal のままにします。1対1の変換でもマッパーが必要です。 |
| 「ドメインではタイムスタンプを文字列のままにします。解析は軽量です。」 | いいえ。ルール2:ドメインでは Instant を使用します。カードをバインドするたびに、すべてのカードで再解析が発生します。 |
| 「リマインダーがない場合は現在時刻をデフォルト値にして、フィールドが null にならないようにします。」 | いいえ。ルール4:存在しない値は null のままにします。「現在時刻」は捏造された業務上の値です。 |
「リポジトリで NetworkException をキャッチして、古いリストを保持します。」 | いいえ。ルール5:失敗は launchGuarded に伝播させます。リトライなしで古いデータを使い続けるのは、サイレントなデータ損失です。 |
「タイムアウトを isMissing に変換して、空状態を表示します。」 | いいえ。compose-architecture スキル(ルール7):失敗と業務状態は別々のフィールドにします。統合すると失敗が失われます。 |
「すべてを1か所にまとめるため、UiState に PagingData を入れます。」 | いいえ。ルール6:別の Flow にします。状態をコピーすると再エミッションが起こり、リストが飛びます。 |
| 「詳細ノートはキャッシュ済みリストから取得します。そのほうが速いからです。」 | いいえ。ルール7:IDを使って取得します。復元後、キャッシュは空の状態です。 |
| 「制御のために、呼び出し箇所ごとにステータスコードを確認します。」 | いいえ。ルール8:expectSuccess = true にします。呼び出し箇所ごとの確認は、エラーを雑に処理することになります。 |
「同じ API なので、commonMain で java.time を使います。」 | いいえ。compose-ui スキル(ルール11):kotlin.time.Instant を使います。共有コードには java.* を含めません。 |
「一貫性を保つため、NoteUiModel とマッパーを追加します。」 | いいえ。ルール3と compose-architecture スキルの M-11 トリガーを確認します。同じルールだからといって、同じファイルにするわけではありません。トリガーに名前を付けるか、モデルとマッパーの組み合わせを省略します。 |
| 「このメソッドはクライアントビルダーにあるはずです。」 | 作業を止めて、今すぐ確認します(スタンス項目1)。サードパーティ API をでっち上げても、コンパイルできません。 |
検証
-
scripts/composekit/run-checks.sh(またはスキルのscripts/run-checks.sh <project-root>)が 0 で終了する。 - 変更したモジュールが共通メタデータと 1 つのプラットフォーム向けにコンパイルでき、JVM テストに合格する。
- 公開された
*Dtoまたは*Entityがないこと:grep -rn "public .*Dto\|^class .*Dto\|^data class .*Dto" --include='*.kt' <data-root>の結果がinternal宣言のみである。 - ドメインにワイヤ文字列がないこと:
grep -rn "String.*[Aa]t\b\|.*Date.*String\|@SerialName\|@Serializable" --include='*.kt' <domain-model-root>の結果が空である。 - トランスポート障害が握りつぶされていないこと:
grep -rn "catch.*NetworkException" --include='*.kt' <data-root>の結果が空である。 -
Flowを返すリポジトリ読み取りはすべてStreamで終わる(compose-architectureスキルのルール 12 を参照)。observeX、getXFlow、getXPagerがない:はい/いいえ。 - すべての
commonMainデータファイルに^import (java|android)\.が含まれていない:はい/いいえ。 - 変更で名前が挙がったすべてのライブラリ API が、
libs.versions.tomlのバージョンに対応する現行の公式ドキュメントで確認されている:はい/いいえ。 - 追加したすべての UiModel が、M-11 のトリガーを 1 行のコメントで示している:はい/いいえ。
参照資料の確認
このタスクに必要な参照資料のみ読み込んでください。1 階層分までとします。
-
ファイルまたはデータベースの書き込みとストリームを変更する場合に限り、coroutines-flow.md も読んでください。
-
書き込み失敗またはネットワーク呼び出しを変更する場合に限り、error-handling.md も読んでください。
-
boundaries-and-mapping.md — 3 つのモデルと所有者、境界での解析、マッパーの配置、欠落時のルール。
-
networking-ktor.md — クライアントのルールと注意点:
expectSuccess、エンジン、プラグイン、タイムアウト、分類、キャンセル。 -
auth-and-realtime.md — ベアラーの更新、更新クライアントの分離、WebSocket と SSE、再接続。
-
room.md — KMP のセットアップ、DAO、トランザクション、リレーション、インデックス、マイグレーション。
-
datastore.md — Preferences と Room の選択、KMP のパス、単一インスタンス、JSON 文字列の設定。
-
paging.md — 状態の外に置く
PagingData、cachedInの配置、フィルター、LoadState、MVI との接続。 -
offline-first.md —
RemoteMediatorと Room、InitializeActionの選択、キャッシュのタイムアウト。 -
data-testing.md — 本番用ファクトリーでの
MockEngine、モックよりフェイクを優先、ディスパッチャーの制御。