Inkwell 执行边界说明
本文档用于说明在 Inkwell 中,什么时候应该使用:
- server action
- route handler (
app/api/**) - CLI script (
scripts/*.ts) - 共享业务服务层 (
lib/**)
未来维护时,很多混乱都来自“把逻辑放错层”。本页的目标就是避免这种情况。
1. 总原则
优先把业务规则放在共享服务层,再由不同入口调用。
推荐分工:
- 页面组件:读数据、渲染 UI
- server action:表单提交、认证、redirect、revalidation
- route handler:HTTP 协议边界、header/token 校验、JSON response
- CLI script:命令行参数解析、环境加载、机器可读输出
lib/admin/**/lib/**:真正的业务逻辑与数据库读写
2. 什么时候该用 server action
适合场景:
- 后台表单提交
- 只服务于当前站点 Web UI 的 mutation
- 需要和
useActionState()搭配 - 成功后通常需要
redirect()与revalidatePath()
当前典型例子:
app/(admin)/[adminPath]/(protected)/posts/actions.tsapp/(admin)/[adminPath]/(protected)/categories/actions.tsapp/(admin)/[adminPath]/(protected)/settings/actions.ts
server action 负责:
- 读取
adminPath - 检查登录态
- 未登录跳转
- 调用服务层
- 根据结果 revalidate / redirect
不适合放在 action 中的内容:
- 大量重复业务规则
- 底层数据库组合逻辑
- 独立于 Web UI 的运维入口
3. 什么时候该用 route handler
适合场景:
- 需要提供 HTTP 接口
- 需要 header / token / request body 边界处理
- 需要返回稳定 JSON 协议
- 需要让站外调度器或其他系统调用
当前最典型例子:
app/api/internal/posts/publish-scheduled/route.tsapp/api/health/route.ts
内部运维 API 当前模式:
- 从 header 中取 Bearer token
- 对
INTERNAL_CRON_SECRET做校验 - 调用共享服务层
- 在 route 中负责公开 JSON 响应与必要的 revalidation
不要把 route handler 当成:
- 随便替代 server action 的写操作层
- 把大量业务逻辑直接塞进去的地方
4. 什么时候该用 CLI script
适合场景:
- 宿主机 cron / 手动命令 / 运维脚本入口
- 不依赖浏览器或 HTTP 请求
- 希望能输出 JSON 或稳定命令行结果
- 需要在 Next.js runtime 外运行
当前典型例子:
scripts/publish-scheduled-posts.tsscripts/reindex-search-posts.tsscripts/export-backup.tsscripts/import-backup.tsscripts/create-admin.ts
CLI 层负责:
dotenv加载.env.local- 解析 argv
- 调用服务层或 CLI-safe 数据访问逻辑
- 输出稳定结果
- 设置退出码
参考:
scripts/reindex-search-posts.tsscripts/import-backup.ts
5. 为什么共享服务层很重要
Inkwell 当前很多关键链路都有多个入口:
5.1 定时发布
- service:
lib/admin/posts.ts - CLI:
scripts/publish-scheduled-posts.ts - internal API:
app/api/internal/posts/publish-scheduled/route.ts
5.2 搜索重建
- service:
lib/search/reindex-posts.ts - CLI:
scripts/reindex-search-posts.ts
5.3 备份恢复
- services:
lib/backup/export.ts、lib/backup/import.ts - CLI:
scripts/export-backup.ts、scripts/import-backup.ts
这说明:
- 真正的业务规则不应该只属于某个入口
- 不同入口只是不同的执行边界
6. 谁负责 auth、参数解析、revalidation
6.1 auth
- 后台 Web 提交:通常在 server action 处理
- internal API:通常在 route handler 校验 token
- CLI:通常依赖本地环境,不走 Web auth
6.2 参数解析
- 表单字段:server action
- HTTP headers/body:route handler
- argv:CLI script
- 业务校验:服务层
6.3 revalidation
当前仓库模式下,一般由入口层负责:
- server action 成功后负责
revalidatePath() - internal API 在业务成功后负责
revalidatePath() - CLI 通常不负责 Next.js 页面 revalidation,除非明确运行在同一应用上下文中
示例:
- 文章后台 action 会刷新后台页与公开文章页:
app/(admin)/[adminPath]/(protected)/posts/actions.ts - internal publish API 会刷新文章页、sitemap 与 RSS:
app/api/internal/posts/publish-scheduled/route.ts:56-60
7. CLI-safe 的额外规则
CLI 运行时不应该依赖只适用于 Next.js 应用服务端上下文的入口。
这是搜索重建链路已经踩过的坑。
当前正确模式:
- 在 CLI 或 CLI-shared 路径中使用显式数据库上下文
- 不重新依赖
server-only的 Web DB 入口
参考:
lib/search/reindex-posts.tsscripts/reindex-search-posts.ts
8. 如何做选择:决策树
场景 A:后台表单保存
优先:server action + lib/admin/*
场景 B:给外部调度器提供入口
优先:route handler + shared service
场景 C:给宿主机 cron 提供入口
优先:CLI script + shared service
场景 D:多个入口共享同一业务规则
优先:先抽到 lib/**
场景 E:只是页面读取展示
优先:page / server component 调用查询层,不要为了“统一”硬做 API
9. 常见反模式
- 在 page.tsx 里直接写 mutation
- 在 server action 里重复实现完整业务逻辑
- 在 route handler 里堆数据库细节
- 让 CLI 依赖 Web-only 运行时模块
- 没有抽共享服务,导致 HTTP/CLI/后台 action 三份逻辑各写一遍
- 业务成功后忘了做正确范围的 revalidation
10. 文档同步要求
如果你调整了执行边界,至少检查:
docs/development.mddocs/architecture.mddocs/release-checklist.md- 必要时
docs/deployment.md
11. 推荐阅读顺序
如果你接下来要新增运维入口、后台 action 或 internal API,建议按顺序读:
docs/development.md- 本文档
docs/execution-boundaries.md docs/admin-extension-workflow.mddocs/testing-strategy.md