ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Epic Stack 架构决策实录:移除 cleanupDb 工具,用文件复制与 prisma migrate reset 重构数据库重置流程

Epic Stack 架构决策实录:移除 cleanupDb 工具,用文件复制与 prisma migrate reset 重构数据库重置流程 Epic Stack 架构决策实录移除 cleanupDb 工具用文件复制与 prisma migrate reset 重构数据库重置流程【免费下载链接】epic-stackThis is a Full Stack app starter with the foundational things setup and configured for you to hit the ground running on your next EPIC idea.项目地址: https://gitcode.com/GitHub_Trending/ep/epic-stack本篇技术文章围绕 Epic Stack 仓库中的架构决策文档 docs/decisions/038-remove-cleanup-db.md 展开深入剖析项目为何移除cleanupDb数据库清理工具以及如何用复制 base.db 文件和prisma migrate reset两条更简单的路径分别取代测试与 seed 场景中的数据库重置。读完本文你将理解 Prisma 迁移表为何是实现细节、文件级数据库复制为什么能做到纳秒级重置以及 seed 脚本依赖空数据库时的正确命令序列。决策背景cleanupDb 存在的原因与痛点它原本解决什么问题在 Epic Stack 早期的测试架构中存在一个名为cleanupDb的工具函数它会删除数据库中除 Prisma 迁移表之外的所有表从而让每个测试用例都能从一个干净的数据集开始。这个工具的存在有明确动机低层测试如服务层、工具函数测试需要频繁重置数据库状态而当时的标准方案prisma migrate reset需要完整执行回滚 → 重建表 → 重新应用迁移 → 运行 seed的流程对测试场景来说太慢了无法在每个测试之间反复执行。同时seed 脚本也依赖它在 prisma/seed.ts 正式向数据库写入数据之前先用cleanupDb清空旧数据确保 seed 在一个空库前提下进行——从源码可以看到seed 脚本使用prisma.user.create、prisma.note.create直接创建记录并硬编码了id如d27a197e这决定了它必须从一个空库开始否则会因主键冲突或数据残留而失败。为什么它是坏味道决策文档指出cleanupDb内部引用Prisma 迁移表来保留迁移记录、删除其余表。问题恰恰出在这里迁移表_prisma_migrations属于 Prisma 的实现细节业务代码和测试代码本不该关心它的存在。让一个测试工具去感知迁移表的结构等于把 ORM 的内部机制泄漏到了测试基础设施中一旦 Prisma 改变迁移表的命名或结构cleanupDb就会悄悄失效测试代码被迫理解迁移机制增加了不必要的认知负担重置逻辑是命令式删除表操作既不幂等也不够声明式错误地删错表会直接破坏开发环境。这正是项目在大量测试重构后决定摆脱这个实现细节的核心理由。决策内容两条路径替换一个工具决策文档给出了最终方案可用一张表概括场景旧方案新方案关键差异测试间重置数据库调用cleanupDb删表复制base.db到test.db纳秒级文件复制无 Prisma 参与seed 前清空数据库seed 脚本内调用cleanupDb直接运行prisma migrate resetreset 自带重建 运行 seed一步到位需要强调的是决策文档删除了cleanupDb工具本身当前仓库中已不存在该工具的任何源码并同步更新 CI以prisma migrate reset替代原先的prisma db seed。方案一测试用 base.db 文件复制重置数据库工作原理决策文档揭示了一个关键洞察项目早就在所有测试运行之前复制一份全新数据库即base.db作为起点既然如此为什么不把同一思路用到每次测试之间答案是直接把全新的base.db复制为当前测试进程的test.db。从源码可以验证这套机制的完整实现tests/setup/global-setup.ts 定义并生成基准库BASE_DATABASE_PATH指向./tests/prisma/base.db。setup()会先检查该文件是否存在并对比其修改时间与prisma/schema.prisma的修改时间——若 schema 更新了就重新执行npx prisma migrate reset --force --skip-seed --skip-generate指向file:${BASE_DATABASE_PATH}来重建基准库tests/setup/db-setup.ts 在beforeEach中执行核心逻辑beforeEach(async () { await fsExtra.copyFile(BASE_DATABASE_PATH, databasePath) })同时db-setup.ts在模块加载时根据VITEST_POOL_ID为每个测试进程分配独立的数据库文件./tests/prisma/data.${poolId}.db实现测试并行隔离afterAll中则动态 import Prisma 客户端并清理临时数据库文件。为什么纳秒级决策文档明确说复制文件takes nanoseconds。底层原理是SQLite 本身就是单文件数据库而base.db是经过完整迁移的全新空库含全部表结构、RBAC 角色种子数据但不含业务数据。文件复制不涉及 SQL 执行、索引重建、迁移校验只是一次磁盘拷贝因此比任何连接数据库执行 DROP/CREATE的方式都快几个数量级。这一方案能成立的前提正是本项目选用 SQLite 单文件存储见 docs/database.md 与决策文档 003-sqlite.md。方案二seed 前用 prisma migrate reset 清空并重建seed 脚本为何要求空库如前所述prisma/seed.ts 中的 seed 逻辑以数据库为空为前提它通过prisma.user.create创建 5 个随机用户并为其生成笔记与图片再创建管理员kody连接 GitHub OAuth 账号最后硬编码id写入 12 篇 kody 笔记。若数据库里已有数据重复执行会抛唯一约束错误。migrate reset 如何一步到位Prisma 的prisma migrate reset会完整执行删除数据库 → 重新创建 → 应用全部迁移 → 执行 seed 脚本seed 命令在 package.json 的prisma: { seed: tsx prisma/seed.ts }中声明。因此它天然满足空库 seed的组合需求完全替代了旧方案中先cleanupDb清空、再 seed的两步操作npx prisma migrate reset在 CI 中.github/workflows/deploy.yml 的 Playwright 任务正是这样使用的——先用actions/cachev4缓存数据库缓存未命中时执行npx prisma migrate reset --force完成建库与 seed- name: Seed Database if: steps.db-cache.outputs.cache-hit ! true run: npx prisma migrate reset --force保留迁移表的正确姿势prisma migrate reset会自动重建 Prisma 的迁移记录表_prisma_migrations并重新应用 prisma/migrations 下全部迁移当前仓库包含20250221233640_init初始迁移。这正是决策文档强调的对比点旧cleanupDb需要手写逻辑保留迁移表、删除其余表把实现细节暴露给调用方而migrate reset由 Prisma 官方管理迁移状态调用方完全不需要关心迁移表是否存在、叫什么名字。决策后果prisma db seed 的直接行为变化决策文档明确记载了最重要的一个后果移除cleanupDb之后直接在非空数据库上运行npx prisma db seed将会失败因为 seed 脚本期待一个空数据库。对此项目给出了两条出路推荐做法改用npx prisma migrate reset来 seed 数据库——它先重置再 seed效果等同于旧方案先清理再 seed备选做法改造 seed 脚本用 upsert 代替 create 使其幂等但这会显著增加脚本复杂度项目明确不倾向此路线。需要说明的是这一后果主要影响重复执行db seed的本地开发场景对首次搭建环境而言docs/getting-started.md 中给出的初始化流程npm run setup执行prisma migrate deploy与 seed 命令npx prisma6 db seed在空库上仍可正常工作。而生产环境的迁移与初始化另有机制部署时由主实例执行npx prisma migrate deploy见 other/litefs.yml生产 seed 的推荐做法是直接修改migration.sql以保证可复现详见 docs/database.md 的Seeding Production章节。迁移经验总结这条决策能给其他项目什么启发从 Epic Stack 移除cleanupDb的决策中可以提炼出几条可迁移到任何 Prisma SQLite 项目的经验测试重置数据库的首选是文件快照复制而非SQL 清理当数据库是单文件SQLite时复制一份经过完整迁移的空库比任何删表逻辑都快且无副作用前提是保证测试进程的文件隔离本项目用VITEST_POOL_ID实现不要让业务/测试代码感知 ORM 的内部表凡是要保留某张系统表的清理逻辑都是坏味道应交给官方工具管理迁移状态seed 与 reset 强绑定若 seed 脚本依赖空库就应通过prisma migrate reset重置 seed 一步完成作为唯一入口而不是在 seed 内部做清理CI 中善用缓存与 reset 组合如 .github/workflows/deploy.yml 所示用 schema/migration 的哈希作为缓存键仅在缓存未命中时执行migrate reset既保证环境一致性又节省 CI 时间。本决策的完整上下文记录在 docs/decisions/README.md相关工具链的进一步使用方式可参考 docs/database.md数据库与迁移与 docs/skills/epic-database/SKILL.md数据库技能指南。【免费下载链接】epic-stackThis is a Full Stack app starter with the foundational things setup and configured for you to hit the ground running on your next EPIC idea.项目地址: https://gitcode.com/GitHub_Trending/ep/epic-stack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进