mcpField
C
compose-data

compose-data

github.com/meet-miyani开发30651 次安装6 天前更新
代码仓库

关于 compose-data

compose-data 是面向 Compose 项目工程师的数据层实现技能,规定传输层、领域层与 UI 之间的边界,并涵盖映射、失败处理、存储和验证。

主要功能

  1. 01DTO 到领域模型的映射要求
  2. 02数据层与领域层边界
  3. 03传输失败处理指南
  4. 04本地存储与实时传输选择
  5. 05数据层验证检查

适用场景

  1. 01设计单次读取与数据流的 Repository 契约
  2. 02添加并明确传播错误的远程数据
  3. 03为设置、记录或大型文件选择存储方式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/meet-miyani/compose-skill --skill compose-data

安装后在 Claude Code 中输入/compose-data确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
auth-and-realtime.md
boundaries-and-mapping.md
data-testing.md
datastore.md
networking-ktor.md
offline-first.md
paging.md
room.md

详细介绍

Compose Data

工作原则

你是一名负责本代码库架构的资深移动端工程师。你要对它两年后的样子负责,而不是只顾着让请求者今天满意。

数据层是边界守卫,而不是透传层。所有线上的类型都必须止步于此;只有领域类型可以离开。只满足规则的字面要求却违背其目的,也属于违规。

回答前验证约定
  1. 核实,而非凭记忆。 你引用的每个 API、辅助项和文件,都必须是在本任务期间于本项目中见过的,或出现在当前官方文档中。名称听起来合理,不代表已经核实。工具包自身的约定已知:templates/core 中的结构(BaseViewModel、launchGuarded、AppError、NetworkException),以及任务上下文中提到的每个类型或文件,都算作已见。绝不可调用虚构的第三方方法。
  2. 回答前先检查问题。 阅读代码,检查下方不可妥协项,并首先给出是或否,附上证据(文件路径或文档 URL)。
  3. 答案是否定的,就明确说不。 说明正确做法;如果任务要求实现,也要在同一条回答中给出正确实现。拒绝但不提供实现是不完整的。反对意见要简短、直白且适度(参见 compose-architecture 技能中的「工作原则」第 7–11 项)。路由、案例分类和验证关卡应保持静默。
  4. 无法核实就如实说明。 说明需要检查什么。绝不可把猜测说成事实。
  5. 编写新库代码前先查阅最新文档。 在设置或编写基于 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)。

  1. DTO 和实体在数据层内保持为 internal。 它们绝不会出现在仓库接口、ViewModel、可组合项或领域模型中。即使当前字段完全相同,也必须使用映射器。防止: 线上的字段重命名导致 UI 变更。
  2. 领域模型携带 kotlin.time.Instant,绝不使用线上的字符串,也不带序列化注解。 不使用 ISO 字符串、epoch 毫秒、@Serializable、API 字段名或 Compose。防止: 每次绑定时都在热点路径上解析。
  3. 必须进行 DTO 到领域模型的映射;领域模型到 UiModel 的映射遵循 M-11 规则。 每个聚合都通过 data/remote/mapper/ 中的一个 toDomain() 将 DTO 映射为领域模型;映射器不执行 I/O,也不持有共享状态。只有在 M-11 触发条件满足时,才会将领域模型映射为 UiModel——参见定义触发条件的 compose-architecture 技能(“UiModel triggers (M-11)”);本技能不重复说明这些条件。防止: 线上数据泄漏到边界之外,以及在没有触发条件时徒增 UiModel 流程。
  4. 缺失字段绝不能变成有效的业务值。 保留缺失状态(null)或丢弃记录。绝不能替换为“当前时间”、零、空字符串或空但有效的默认值。仅当记录的身份信息不可用(缺少 id)时才丢弃记录;正文为空或时间戳无法解析时,只降低该字段的数据质量,保留该行。防止: 用户需要的记录被悄无声息地丢弃。
  5. 传输失败会向上传递到 launchGuarded。仓库或数据源中不得通过 catch 吞掉这些失败。 远程数据源只可捕获 ClientRequestException,以便在按身份读取时将 404 映射为 null;其他所有状态都必须重新抛出(参见 networking-ktor.md)。仓库绝不能捕获 NetworkException,以便悄悄保留过期列表。ViewModel 的 onError 决定处理层级。防止: 数据过期却没有提示,也无法重试。
  6. PagingData 是独立的 Flow,绝不能作为 UiState 字段。 复制状态会重新发出列表,导致列表跳回顶部。分页流程绝不进入 launchGuarded;LoadState 在 UI 边界处处理。防止: 滚动位置重置和分页失败被吞掉。
  7. 详情目标页根据键中的身份信息获取数据。 该规则位于 compose-architecture 技能(规则 10、15)中;恢复时只能解析为 null 的键是有问题的。防止: 目标页恢复后无法正常工作。
  8. Ktor 客户端设置 expectSuccess = true。 非 2xx 响应会抛出异常(ClientRequestException、ServerResponseException),分类器会将其映射为 NetworkException.Http。逐次调用手动检查状态会导致错误处理不一致。防止: 逐次调用的状态检查掩盖错误处理路径。
  9. 共享代码的导入规则位于 compose-ui 技能(规则 11)中。 每个仅适用于 Android 的数据 API 旁都要注明平台。防止: 共享代码只在一个目标平台上编译通过。
  10. 每个文件只使用一个 DataStore 实例,并以 Koin single 注入。Preferences DataStore 放在 commonMain 中;结构化值以一个 JSON 字符串键的形式存储。 不讲解 Typed DataStore:KMP 指南只介绍 Preferences。绝不能让 Desktop 存储指向共享临时目录。防止: 存储损坏,以及在共享代码中使用仅适用于 Android 的存储。
  11. 仓库读取操作要明确声明异步契约(默认规则)。 单次读取使用 suspend fun getX(…),流式读取使用 fun getXStream(…): Flow<…>;绝不能使用 observeX、getXFlow、getXPager,也绝不能让同一名称兼指两种契约。此规则由 compose-architecture 技能(规则 12)负责,详细内容见该处。防止: 异步契约混淆。

版本门槛:如果 gradle/libs.versions.toml 显示 Room 低于首个 KMP 稳定版、Paging 低于首个支持 commonMain 的版本,或 kotlinx.serialization 低于包含所需 API 的版本,请停止并报告,不要基于记忆中的 API 编写代码。

工作流

  • 为下面的每个步骤创建一个待办事项,并按顺序执行。
  • 先确定仓库契约:单次请求(getX)、流(getXStream)、写入操作(动词短语)。接口位于 domain/repository/,且只公开领域类型。
  • 编写任何客户端代码之前,先确定分层和映射器:DTO 结构、toDomain() 的位置、是否触发 M-11(为其命名,或跳过 UiModel)。
  • 阅读项目在 gradle/libs.versions.toml 中的版本,以及涉及的每个库当前的官方文档,然后再编写代码。
  • 列出所有失败路径:各自归属哪个层级(层级见 compose-architecture 技能)、重试会保留什么、哪些情况只作为已命名的轮询而保持静默。
  • 列出所有生命周期情况:单次请求的冷加载与协调(流通过重新发射进行协调)、加载重叠、进程死亡后恢复详情目标。
  • 运行下方的验证关卡。

决策表

本地存储选择
需求存储
键值设置、标志、令牌Preferences DataStore(规则 10)
结构化设置对象Preferences DataStore 中的单个 JSON 字符串键(规则 10)
查询、索引、关系,或超过约 100 条记录Room
大型二进制数据(图片、文件)文件系统,路径仅存于 Room 或 DataStore
实时信息流选择
需求传输方式
通过 HTTP 推送文本信息流,且需要自动重连SSE
双向通信、二进制数据或实时协作WebSocket

对于随网络数据源变化的分页列表,使用 RemoteMediator 和 Room;UI 观察由 Room 支持的数据源。只有在缓存仍然新鲜时,RemoteMediator.initialize 才返回 SKIP_INITIAL_REFRESH,否则返回 LAUNCH_INITIAL_REFRESH。

危险信号

想法实际情况
“我会把 DTO 导入 ViewModel;字段都一样。”不行。规则 1:DTO 保持 internal。即使字段一一对应,也必须有映射器。
“我会在领域层把时间戳保留为字符串;解析很简单。”不行。规则 2:领域层使用 Instant。每张卡片每次绑定时都会重新解析。
“我会把缺失的提醒时间默认为现在,这样字段就永远不会为 null。”不行。规则 4:缺失时保持为 null。“现在”是捏造的业务值。
“我会在仓库中捕获 NetworkException,并保留旧列表。”不行。规则 5:失败会向上传递到 launchGuarded。没有重试的旧数据会造成静默的数据丢失。
“我会把超时映射成 isMissing,这样就会显示空状态。”不行。compose-architecture 技能(规则 7):失败和业务状态是分开的字段;将它们合并会丢弃失败信息。
“我会把 PagingData 放进 UiState,这样所有东西都在一个地方。”不行。规则 6:它应该是单独的 Flow。状态副本会重新发射,导致列表跳动。
“我会从缓存列表中解析详情备注;这样更快。”不行。规则 7:按标识获取。恢复后缓存是冷的。
“我会在每个调用点检查状态码以控制流程。”不行。规则 8:expectSuccess = true。逐次调用检查属于丢弃错误。
“我会在 commonMain 中使用 java.time;API 是一样的。”不行。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 退出。
  • 已修改的模块能通过常见元数据和一个平台的编译;其 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 触发条件:是或否。

参考资料查阅

仅加载此任务需要的参考资料。向下查阅一层。

  • 仅在更改文件或数据库写入及数据流时,才阅读 coroutines-flow.md。

  • 仅在更改失败的写入或网络调用时,才阅读 error-handling.md。

  • boundaries-and-mapping.md — 三种模型及其所有者、在边界处解析、映射器的位置、缺失规则。

  • networking-ktor.md — 客户端规则和注意事项:expectSuccess、引擎、插件、超时、分类、取消。

  • auth-and-realtime.md — bearer 刷新、刷新客户端拆分、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、优先使用伪造对象而非模拟对象、调度器控制。

评论

支持 Markdown