Skip to content

Neton Migration Boundary Spec

Schema Governance — runtime 不参与 schema 演进

状态:边界冻结(Boundary Frozen) 实现状态neton-migrate v0.1 已交付(status / up / verify) 当前权威路径neton-migrate CLI 或手动执行 sql/{dialect}/V*.sql应用启动语义:永远不变 — startup 不执行 schema 变更 明确禁止:运行时自动 schema 变更 标签:No runtime ALTER — CLI is the standard path — Manual SQL still valid


目录

  1. 设计原则
  2. 模块边界
  3. 当前权威路径
  4. ensureTable() 定位
  5. 未来 neton-migrate CLI 边界
  6. 版本表规范
  7. 明确禁止事项
  8. 冻结约束

一、设计原则

1.1 为什么 Neton 不做运行时迁移

Schema 变更的本质是 部署决策,不是 运行时行为。把它放进 app 启动流程意味着:

风险说明
不可审阅DDL 由代码隐式生成,无法在 PR/CR 阶段被人工 review
不可灰度多实例并发启动时,谁先抢到锁、谁执行成功、谁失败回滚,皆不可控
不可回滚应用启动失败 ≠ schema 已回滚;schema 已变更 ≠ 应用必然成功
数据库差异MySQL/PostgreSQL/SQLite 的 DDL 方言、约束语义、ALTER 行为不一致,自动化掩盖差异
复杂演进无解表拆分、列改名、数据回填、双写双读、灰度发布等场景,运行时自动迁移完全表达不了

1.2 核心立场

  • Schema 演进 = 人工审阅 + 显式执行 + 版本化记录
  • Neton 框架运行时只关心"连接 DB、读写数据",不关心"DB 长什么样"
  • 框架可以检查 schema 状态(连接探活、版本一致性校验),但不执行变更

二、模块边界

模块职责不做
neton-database运行时 DB 访问(连接、查询、事务、Entity ↔ Row 映射)不做 schema 变更,不做版本管理
neton-migrate(未来)迁移命令、版本表、SQL 脚本扫描与执行不嵌入运行时,不在 app 启动期被调用
neton-app(业务应用)启动 HTTP/数据库/任务/路由等组件不 ALTER、不自动 migrate、不依赖 schema 变更

关键约束neton-databaseneton-migrate互不依赖的两个模块。运行时代码不允许 import migrate 能力;migrate CLI 也不应启动业务应用上下文。


三、当前权威路径

3.1 目录约定

neton-application/sql/
├── mysql/
│   ├── V001__create_tables.sql
│   ├── V002__init_data.sql
│   └── V003__add_indexes.sql
├── postgresql/
│   └── ...
└── sqlite/
    └── ...

3.2 命名规范

V<version>__<description>.sql

  • V 大写前缀
  • <version> 三位以上零填充数字(001002、…),保证字典序 = 执行序
  • 双下划线 __ 分隔
  • <description> 用 snake_case,简短描述

命名格式与 Flyway 兼容,但不引入 Flyway 依赖。未来 neton-migrate 自己解析。

3.3 执行规则

  • 本地开发:开发者自行执行(mysql < V001__create_tables.sql 或 IDE 工具)
  • 测试环境:CI pipeline 显式执行 migration 步骤
  • 生产环境:由 CI/CD 部署流水线或 DBA 显式执行
  • 顺序:严格按版本号升序,不允许跳跃或乱序

3.4 与应用启动的关系

应用启动只做

  • 数据库连接探活(连不上则 fail-fast)
  • (可选)读取版本表,校验当前 schema 版本是否在应用所需的兼容范围内

应用启动不做

  • 创建表、修改表、删除表
  • 写入版本表
  • 任何形式的 DDL 执行

四、ensureTable() 定位

Table.ensureTable()neton-database 中保留,但严格限定用途。

4.1 仅用于

  • demo 工程
  • 本地开发的临时调试
  • 单元测试 / 集成测试中的 ephemeral 数据库(如 sqlite::memory:

4.2 禁止用于

  • 生产环境的 schema 创建
  • 生产环境的 schema 演进(它根本做不了 ALTER)
  • CI release 部署
  • 任何带"持久化"语义的数据库

4.3 能力清单

ensureTable() 只能做:

  • CREATE TABLE IF NOT EXISTS,仅含主键列与从 EntityMeta 推导的基础列

ensureTable() 永远不会做:

  • 新增/删除/修改字段
  • 索引、唯一约束、外键
  • 数据迁移、回填
  • 表已存在时的任何 schema 调整

4.4 文档与代码标注

  • 该方法的 KDoc 必须包含 dev/demo only, not for production migration
  • Main.ktModuleInitializer不允许调用 ensureTable()
  • examples 工程中调用时应附注释说明"仅 demo 用途"

五、未来 neton-migrate CLI 边界

本节是预留设计,不是立即实现承诺。当前不要写代码。

5.1 命令集(最小集)

命令行为备注
neton migrate status显示已执行 / 未执行脚本列表,与 checksum 一致性只读
neton migrate up按版本顺序执行所有未执行的脚本,失败中断默认行为
neton migrate verify校验已执行脚本的 checksum 是否与磁盘一致检测脚本被篡改

5.2 down 不在最小集内

  • 生产环境通常不允许自动回滚(数据可能已写入新结构,回滚会丢数据)
  • 如果未来要支持 down,必须设为 opt-in 且默认禁用
  • 可以提供"生成 down SQL 模板"的能力,由人工执行

5.3 执行机制

1. 连接数据库
2. 确保版本表存在(neton_schema_history)
3. 扫描 sql/{dialect}/V*.sql,按版本号排序
4. 与版本表对比,找出未执行脚本
5. 顺序执行:
   a. 计算脚本 checksum
   b. 执行 SQL(在 transaction 内,如果方言支持 DDL transaction)
   c. 写入版本表(version, checksum, executed_at, duration_ms, success=true)
6. 任意一步失败 → 中断、写入失败记录、返回非 0 exit code

5.4 退出码契约

Exit Code含义
0全部成功(或已无需执行)
1有未执行脚本(用于 status 命令的 dry-run 模式)
2执行中失败
3checksum 校验失败(脚本被篡改)
4数据库连接失败

CI/CD 可基于退出码判断是否阻塞部署。


六、版本表规范

6.1 表名

neton_schema_history

6.2 表结构

字段类型说明
versionVARCHAR(50) PRIMARY KEY版本号,如 001002
descriptionVARCHAR(200)脚本描述(取自文件名)
scriptVARCHAR(255)脚本文件名
checksumVARCHAR(64)SHA-256 hex
executed_atTIMESTAMP执行时间
duration_msBIGINT执行耗时
successBOOLEAN是否成功
error_messageTEXT NULL失败时的错误信息

6.3 写入规则

  • 每个脚本执行前后写一条记录
  • 失败的执行也要记录(success=false + error_message
  • 已记录 success=true 的版本不再重复执行
  • 已记录 success=false 的版本:默认要求人工介入(不自动重试)

七、明确禁止事项

以下事项是架构红线,任何 PR 都不允许引入。

禁止:

- App startup 时自动执行 migration
- 运行时自动 ALTER TABLE / DROP / RENAME
- ensureTable() 隐式升级已存在的 schema
- 在 ModuleInitializer 中调用 ensureTable()
- 将 Flyway / Liquibase / sqlx::migrate! 等运行时迁移工具内置进 neton-database
- 从远程 URL / 配置中心下载 SQL 脚本执行
- 多节点并发抢跑 migration(即使有了 CLI 也应在部署流程中保证单点执行)
- 把 migration 能力放在 neton-database 模块内
- 用 ORM 反向工程(reverse engineering)从 entity 推导出"应有"的 schema 并自动应用

八、冻结约束

维度冻结内容
运行时行为Neton app 启动永远不执行 schema 变更
ensureTable()能力不再扩展,文档明确"非生产"
当前路径sql/{dialect}/V*.sql 是 schema 唯一权威
未来扩展只能通过独立 neton-migrate CLI 模块;不进 neton-database
默认命令集status / up / verifydown 不承诺
版本表neton_schema_history,结构如 §6.2

附录 A:常见误区

Q:现在前端 Vue 项目都能 hot-reload,为什么数据库不能 hot-migrate? A:前端 hot-reload 影响的是单个浏览器 session;数据库 schema 变更影响的是所有现存与未来的应用实例 + 已存数据。两者风险量级完全不同。

Q:开发环境很方便啊,启动就建表,为什么不延伸到生产? A:开发环境的便利来自"数据可以随时丢"。生产数据不能丢。区分开发/生产是有意为之,不是缺陷。

Q:用 Flyway/Liquibase 不就解决了?为什么不直接集成? A:可以用,但不要内置进 neton-database。如果团队选用 Flyway,应在部署流水线中独立调用,与 neton-app 启动解耦。本 spec 描述的 neton-migrate CLI 是 Neton 自有的轻量替代,避免引入 Java 生态依赖。

Q:那 Rails、Django 都有 rake db:migrate / manage.py migrate,它们也是运行时执行? A:不是。rake db:migrate独立 CLI 命令,由部署人员或 CI 显式调用,与 web server 启动是两件事。Neton 的设计与之一致。


附录 C:neton-migrate CLI 用法(v0.1)

C.1 命令一览

bash
neton-migrate status   # 列出已执行 / 未执行 / changed 脚本(read-only)
neton-migrate up       # 顺序执行未执行脚本,写入 history
neton-migrate verify   # 校验已执行脚本的 checksum(read-only)

C.2 配置来源

优先级:CLI flag > config/database.conf [default]

toml
# config/database.conf
[default]
driver = "mysql"
uri    = "mysql://root:secret@127.0.0.1:3306/myapp"

C.3 典型场景

全 CLI flag(CI/CD 推荐)

bash
neton-migrate up \
  --driver mysql \
  --uri "mysql://root:secret@db.internal:3306/myapp" \
  --dir sql/mysql

复用 database.conf

bash
# 在工作目录下有 config/database.conf
neton-migrate status --dir sql/mysql
neton-migrate up     --dir sql/mysql
neton-migrate verify --dir sql/mysql

三方言示例

bash
neton-migrate up --driver sqlite     --uri /var/lib/myapp/data.db        --dir sql/sqlite
neton-migrate up --driver postgresql --uri "postgresql://u:p@h:5432/db"  --dir sql/postgresql
neton-migrate up --driver mysql      --uri "mysql://u:p@h:3306/db"       --dir sql/mysql

C.4 退出码

Exit含义典型用法
0OK / nothing to do部署可继续
1status: 有未执行脚本CI dry-run 提示
2up: 执行失败部署中断
3checksum 不一致 / verify 时 history 表不存在阻塞部署
4数据库连接失败检查网络/凭据
64命令行参数错误修正调用

C.5 v0.1 不做清单

与 spec §5 边界一致:

  • down(生产回滚需人工 SQL)
  • ❌ dry-run / baseline
  • ❌ dialect 自动推断(--dir 必须显式指向 sql/{dialect}/
  • ❌ 多节点并发锁(部署流程保证单点执行)
  • ❌ 远程 SQL 下载

C.6 部署流程示例(CI/CD)

bash
#!/bin/bash
set -euo pipefail

# 1. dry-run: 仅检查是否有未执行脚本
neton-migrate status --dir sql/mysql || PENDING=$?

if [ "${PENDING:-0}" = "1" ]; then
    echo "Pending migrations detected, applying..."
    neton-migrate up --dir sql/mysql      # 失败立即非 0 退出
    neton-migrate verify --dir sql/mysql  # 兜底校验
fi

# 2. 启动应用 — 应用启动本身永不执行 migration
exec /usr/local/bin/myapp --env=prod

Neton Framework Documentation