@wneng/create-keel 0.3.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/index.js +206 -13
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/src/templates/ci-gitee/files/pipeline.yml +62 -0
  5. package/src/templates/ci-github/files/ci.yml +160 -0
  6. package/src/templates/db-go-elasticsearch/files/Makefile +18 -0
  7. package/src/templates/db-go-elasticsearch/files/apply_templates.go +80 -0
  8. package/src/templates/db-go-elasticsearch/files/db-README.md +57 -0
  9. package/src/templates/db-go-elasticsearch/files/go.mod +7 -0
  10. package/src/templates/db-go-elasticsearch/files/index-template-init.json +25 -0
  11. package/src/templates/db-go-elasticsearch/fragment.yaml +22 -0
  12. package/src/templates/db-go-migrate-mysql/files/000001_init.down.sql +3 -0
  13. package/src/templates/db-go-migrate-mysql/files/000001_init.up.sql +35 -0
  14. package/src/templates/db-go-migrate-mysql/files/Makefile +33 -0
  15. package/src/templates/db-go-migrate-mysql/files/db-README.md +77 -0
  16. package/src/templates/db-go-migrate-mysql/files/go.mod +8 -0
  17. package/src/templates/db-go-migrate-mysql/fragment.yaml +22 -0
  18. package/src/templates/db-go-migrate-postgres/files/000001_init.down.sql +3 -0
  19. package/src/templates/db-go-migrate-postgres/files/000001_init.up.sql +32 -0
  20. package/src/templates/db-go-migrate-postgres/files/Makefile +31 -0
  21. package/src/templates/db-go-migrate-postgres/files/db-README.md +71 -0
  22. package/src/templates/db-go-migrate-postgres/files/go.mod +8 -0
  23. package/src/templates/db-go-migrate-postgres/fragment.yaml +22 -0
  24. package/src/templates/db-java-elasticsearch/files/EsTemplateApplier.java +86 -0
  25. package/src/templates/db-java-elasticsearch/files/db-README.md +63 -0
  26. package/src/templates/db-java-elasticsearch/files/index-template-init.json +25 -0
  27. package/src/templates/db-java-elasticsearch/files/pom.xml +134 -0
  28. package/src/templates/db-java-elasticsearch/fragment.yaml +19 -0
  29. package/src/templates/db-java-flyway-mysql/files/V1__init.sql +44 -0
  30. package/src/templates/db-java-flyway-mysql/files/application.yaml +39 -0
  31. package/src/templates/db-java-flyway-mysql/files/db-README.md +102 -0
  32. package/src/templates/db-java-flyway-mysql/files/pom.xml +172 -0
  33. package/src/templates/db-java-flyway-mysql/fragment.yaml +19 -0
  34. package/src/templates/db-java-flyway-postgres/files/V1__init.sql +40 -0
  35. package/src/templates/db-java-flyway-postgres/files/application.yaml +37 -0
  36. package/src/templates/db-java-flyway-postgres/files/db-README.md +75 -0
  37. package/src/templates/db-java-flyway-postgres/files/pom.xml +166 -0
  38. package/src/templates/db-java-flyway-postgres/fragment.yaml +19 -0
  39. package/src/templates/db-node-elasticsearch/files/apply-templates.cjs +60 -0
  40. package/src/templates/db-node-elasticsearch/files/db-README.md +76 -0
  41. package/src/templates/db-node-elasticsearch/files/index-template-init.json +26 -0
  42. package/src/templates/db-node-elasticsearch/files/package.json +26 -0
  43. package/src/templates/db-node-elasticsearch/fragment.yaml +19 -0
  44. package/src/templates/db-node-knex-mysql/files/db-README.md +90 -0
  45. package/src/templates/db-node-knex-mysql/files/knexfile.cjs +72 -0
  46. package/src/templates/db-node-knex-mysql/files/migrations-init.cjs +42 -0
  47. package/src/templates/db-node-knex-mysql/files/package.json +31 -0
  48. package/src/templates/db-node-knex-mysql/files/seeds-dev-fixtures.cjs +38 -0
  49. package/src/templates/db-node-knex-mysql/files/seeds-prod-dictionaries.cjs +25 -0
  50. package/src/templates/db-node-knex-mysql/fragment.yaml +25 -0
  51. package/src/templates/db-node-knex-postgres/files/db-README.md +81 -0
  52. package/src/templates/db-node-knex-postgres/files/knexfile.cjs +67 -0
  53. package/src/templates/db-node-knex-postgres/files/migrations-init.cjs +42 -0
  54. package/src/templates/db-node-knex-postgres/files/package.json +31 -0
  55. package/src/templates/db-node-knex-postgres/files/seeds-dev-fixtures.cjs +36 -0
  56. package/src/templates/db-node-knex-postgres/files/seeds-prod-dictionaries.cjs +26 -0
  57. package/src/templates/db-node-knex-postgres/fragment.yaml +25 -0
  58. package/src/templates/db-python-alembic-mysql/files/0001_init.py +70 -0
  59. package/src/templates/db-python-alembic-mysql/files/alembic.ini +47 -0
  60. package/src/templates/db-python-alembic-mysql/files/db-README.md +87 -0
  61. package/src/templates/db-python-alembic-mysql/files/env.py +71 -0
  62. package/src/templates/db-python-alembic-mysql/files/pyproject.toml +52 -0
  63. package/src/templates/db-python-alembic-mysql/files/script.py.mako +26 -0
  64. package/src/templates/db-python-alembic-mysql/fragment.yaml +25 -0
  65. package/src/templates/db-python-alembic-postgres/files/0001_init.py +62 -0
  66. package/src/templates/db-python-alembic-postgres/files/alembic.ini +45 -0
  67. package/src/templates/db-python-alembic-postgres/files/db-README.md +70 -0
  68. package/src/templates/db-python-alembic-postgres/files/env.py +62 -0
  69. package/src/templates/db-python-alembic-postgres/files/pyproject.toml +52 -0
  70. package/src/templates/db-python-alembic-postgres/files/script.py.mako +25 -0
  71. package/src/templates/db-python-alembic-postgres/fragment.yaml +25 -0
  72. package/src/templates/db-python-elasticsearch/files/apply_templates.py +55 -0
  73. package/src/templates/db-python-elasticsearch/files/db-README.md +57 -0
  74. package/src/templates/db-python-elasticsearch/files/index-template-init.json +25 -0
  75. package/src/templates/db-python-elasticsearch/files/pyproject.toml +50 -0
  76. package/src/templates/db-python-elasticsearch/fragment.yaml +19 -0
  77. package/src/templates/docs-skeleton/files/governance-database.md +150 -0
  78. package/src/templates/docs-skeleton/fragment.yaml +3 -0
  79. package/src/templates/root-files/files/AGENTS.md +6 -2
  80. package/src/templates/server-python/files/README.md +75 -2
  81. package/src/templates/server-python/files/app-init.py +11 -1
  82. package/src/templates/server-python/files/config.py +40 -0
  83. package/src/templates/server-python/files/main.py +62 -0
  84. package/src/templates/server-python/files/pyproject.toml +14 -1
  85. package/src/templates/server-python/files/test_healthz.py +36 -0
  86. package/src/templates/server-python/fragment.yaml +10 -1
@@ -0,0 +1,45 @@
1
+ ; Alembic configuration for <%= it.options.projectName %>-server (PostgreSQL).
2
+ ;
3
+ ; Connection string is read from DATABASE_URL at runtime (see env.py).
4
+
5
+ [alembic]
6
+ script_location = db/migrations
7
+ prepend_sys_path = .
8
+ version_path_separator = os
9
+ sqlalchemy.url =
10
+
11
+ output_encoding = utf-8
12
+
13
+ [loggers]
14
+ keys = root,sqlalchemy,alembic
15
+
16
+ [handlers]
17
+ keys = console
18
+
19
+ [formatters]
20
+ keys = generic
21
+
22
+ [logger_root]
23
+ level = WARN
24
+ handlers = console
25
+ qualname =
26
+
27
+ [logger_sqlalchemy]
28
+ level = WARN
29
+ handlers =
30
+ qualname = sqlalchemy.engine
31
+
32
+ [logger_alembic]
33
+ level = INFO
34
+ handlers =
35
+ qualname = alembic
36
+
37
+ [handler_console]
38
+ class = StreamHandler
39
+ args = (sys.stderr,)
40
+ level = NOTSET
41
+ formatter = generic
42
+
43
+ [formatter_generic]
44
+ format = %(levelname)-5.5s [%(name)s] %(message)s
45
+ datefmt = %Y-%m-%d %H:%M:%S
@@ -0,0 +1,70 @@
1
+ # server/db/
2
+
3
+ 数据库迁移、种子数据、本地数据库工具的入口。
4
+
5
+ > 跨项目的数据库归属规则(哪类 SQL 放哪个目录、与 `contracts/` 如何对齐)见 [`docs/governance/database.md`](../../docs/governance/database.md)。
6
+
7
+ ## 工具与版本
8
+
9
+ | 项 | 值 |
10
+ |---|---|
11
+ | 数据库 | PostgreSQL 14+ |
12
+ | 迁移工具 | [Alembic](https://alembic.sqlalchemy.org) `1.13.3` |
13
+ | ORM | SQLAlchemy `2.0.36` |
14
+ | 驱动 | `psycopg[binary]` `3.2.3`(psycopg v3) |
15
+
16
+ 工具版本在 `pyproject.toml` 钉死,并镜像到 `docs/03-工程规范与研发基础设施/tech-stack-server.md`。
17
+
18
+ ## 目录布局
19
+
20
+ ```
21
+ server/
22
+ ├── pyproject.toml
23
+ ├── alembic.ini
24
+ └── db/
25
+ ├── README.md
26
+ └── migrations/
27
+ ├── env.py
28
+ ├── script.py.mako
29
+ └── versions/
30
+ └── <NNNN>_<description>.py
31
+ ```
32
+
33
+ ## 常用命令
34
+
35
+ ```bash
36
+ alembic revision -m "create orders table" # 新建迁移
37
+ alembic revision --autogenerate -m "..." # 配合 SQLAlchemy 模型
38
+ alembic upgrade head # 升到最新
39
+ alembic downgrade -1 # 回滚一版
40
+ alembic current # 查看当前版本
41
+ ```
42
+
43
+ 环境变量 `DATABASE_URL`(默认 `postgresql+psycopg://postgres:@127.0.0.1:5432/<project>_dev`)。
44
+
45
+ ## prod vs dev seeds
46
+
47
+ | 类型 | 位置 |
48
+ |---|---|
49
+ | 生产字典(必装) | 写进 `versions/` 的 revision 中(ON CONFLICT DO NOTHING) |
50
+ | 开发样例 | `server/db/seeds/dev/` 下手动执行 |
51
+
52
+ 字典类生产种子真值在 `contracts/dictionaries/enums.yaml`;当前手动同步。
53
+
54
+ ## 改动分级(参见 `docs/governance/change-tiers.md`)
55
+
56
+ | 改动 | Tier |
57
+ |---|---|
58
+ | 新建 revision 加表 / 加字段 | 3 |
59
+ | 修改既有 revision | **不允许** |
60
+ | 删字段 / 改类型(破坏性) | 4 |
61
+ | 改 dev seed | 1/2 |
62
+ | 改字典数据 | 3(同步 contracts/dictionaries/) |
63
+ | 升级 Alembic / SQLAlchemy / psycopg | 2,但同步改 tech-stack-server.md |
64
+
65
+ ## CI 行为
66
+
67
+ ```bash
68
+ alembic upgrade head
69
+ alembic downgrade base
70
+ ```
@@ -0,0 +1,62 @@
1
+ """Alembic environment for PostgreSQL (psycopg v3).
2
+
3
+ Reads DATABASE_URL from env (or falls back to a localhost Postgres DSN);
4
+ imports the application's SQLAlchemy metadata for autogenerate support.
5
+
6
+ Edit `target_metadata` to point at your app's Base.metadata once you
7
+ introduce ORM models.
8
+ """
9
+
10
+ import os
11
+ from logging.config import fileConfig
12
+
13
+ from alembic import context
14
+ from sqlalchemy import engine_from_config, pool
15
+
16
+ config = context.config
17
+
18
+ if config.config_file_name is not None:
19
+ fileConfig(config.config_file_name)
20
+
21
+ config.set_main_option(
22
+ "sqlalchemy.url",
23
+ os.environ.get(
24
+ "DATABASE_URL",
25
+ "postgresql+psycopg://postgres:@127.0.0.1:5432/" + "<%= it.options.projectName.replace(/-/g, '_') %>" + "_dev",
26
+ ),
27
+ )
28
+
29
+ target_metadata = None
30
+
31
+
32
+ def run_migrations_offline() -> None:
33
+ url = config.get_main_option("sqlalchemy.url")
34
+ context.configure(
35
+ url=url,
36
+ target_metadata=target_metadata,
37
+ literal_binds=True,
38
+ dialect_opts={"paramstyle": "named"},
39
+ )
40
+
41
+ with context.begin_transaction():
42
+ context.run_migrations()
43
+
44
+
45
+ def run_migrations_online() -> None:
46
+ connectable = engine_from_config(
47
+ config.get_section(config.config_ini_section, {}),
48
+ prefix="sqlalchemy.",
49
+ poolclass=pool.NullPool,
50
+ )
51
+
52
+ with connectable.connect() as connection:
53
+ context.configure(connection=connection, target_metadata=target_metadata)
54
+
55
+ with context.begin_transaction():
56
+ context.run_migrations()
57
+
58
+
59
+ if context.is_offline_mode():
60
+ run_migrations_offline()
61
+ else:
62
+ run_migrations_online()
@@ -0,0 +1,52 @@
1
+ [project]
2
+ name = "<%= it.options.projectName %>-server"
3
+ version = "0.1.0"
4
+ requires-python = ">=3.11"
5
+ dependencies = [
6
+ "fastapi==0.115.5",
7
+ "uvicorn[standard]==0.32.0",
8
+ "pydantic==2.9.2",
9
+ "pydantic-settings==2.6.1",
10
+ "alembic==1.13.3",
11
+ "sqlalchemy==2.0.36",
12
+ "psycopg[binary]==3.2.3",
13
+ ]
14
+
15
+ [project.optional-dependencies]
16
+ dev = [
17
+ "ruff>=0.6.8",
18
+ "mypy>=1.11.0",
19
+ "pytest>=8.3.0",
20
+ "pytest-asyncio>=0.24.0",
21
+ "httpx>=0.27.0",
22
+ "pip-audit>=2.7.0",
23
+ ]
24
+
25
+ [tool.ruff]
26
+ line-length = 100
27
+ target-version = "py311"
28
+ extend-exclude = ["generated", ".venv", "db/migrations/versions"]
29
+
30
+ [tool.ruff.lint]
31
+ select = ["E", "F", "W", "I", "B", "UP", "N"]
32
+ ignore = []
33
+
34
+ [tool.ruff.format]
35
+ quote-style = "double"
36
+ indent-style = "space"
37
+ line-ending = "lf"
38
+
39
+ [tool.mypy]
40
+ python_version = "3.11"
41
+ strict_optional = true
42
+ disallow_untyped_defs = true
43
+ warn_unused_ignores = true
44
+ exclude = ["generated", ".venv", "db/migrations/versions"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
48
+ asyncio_mode = "auto"
49
+
50
+ [tool.setuptools.packages.find]
51
+ include = ["app*"]
52
+ exclude = ["tests*", "generated*", "db*"]
@@ -0,0 +1,25 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ revision: str = ${repr(up_revision)}
15
+ down_revision: Union[str, None] = ${repr(down_revision)}
16
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
17
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
18
+
19
+
20
+ def upgrade() -> None:
21
+ ${upgrades if upgrades else "pass"}
22
+
23
+
24
+ def downgrade() -> None:
25
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,25 @@
1
+ name: db-python-alembic-postgres
2
+ version: 1.0.0
3
+ appliesWhen:
4
+ backend: python
5
+ database: postgres
6
+ priority: 40
7
+ files:
8
+ - from: files/pyproject.toml
9
+ to: server/pyproject.toml
10
+ render: true
11
+ - from: files/alembic.ini
12
+ to: server/alembic.ini
13
+ render: true
14
+ - from: files/env.py
15
+ to: server/db/migrations/env.py
16
+ render: false
17
+ - from: files/script.py.mako
18
+ to: server/db/migrations/script.py.mako
19
+ render: false
20
+ - from: files/0001_init.py
21
+ to: server/db/migrations/versions/0001_init.py
22
+ render: false
23
+ - from: files/db-README.md
24
+ to: server/db/README.md
25
+ render: true
@@ -0,0 +1,55 @@
1
+ """Idempotent Elasticsearch index-template applier (Python backend).
2
+
3
+ ES 没有事务性 DDL;"migration" 退化为把 db/index-templates/*.json 全部 PUT 一次。
4
+ ES 自身保证 PUT 同名 template 的幂等性。
5
+
6
+ 改动 tier(参见 docs/governance/change-tiers.md):
7
+ - 新增 template 文件:Tier 3
8
+ - 修改既有 template 的 mapping 字段:Tier 4(破坏性 - 可能 reindex)
9
+ - 仅改 priority / 新增 alias:Tier 2
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import os
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ from elasticsearch import Elasticsearch
20
+
21
+ TEMPLATE_DIR = Path(__file__).parent / "index-templates"
22
+
23
+
24
+ def main() -> None:
25
+ url = os.environ.get("ELASTICSEARCH_URL", "http://127.0.0.1:9200")
26
+ username = os.environ.get("ELASTICSEARCH_USERNAME")
27
+ password = os.environ.get("ELASTICSEARCH_PASSWORD")
28
+
29
+ kwargs: dict[str, object] = {"hosts": [url]}
30
+ if username and password:
31
+ kwargs["basic_auth"] = (username, password)
32
+
33
+ client = Elasticsearch(**kwargs)
34
+ client.cluster.health(wait_for_status="yellow", timeout="30s")
35
+
36
+ files = sorted(p for p in TEMPLATE_DIR.glob("*.json"))
37
+ if not files:
38
+ print(f"no index templates found in {TEMPLATE_DIR}")
39
+ return
40
+
41
+ for path in files:
42
+ body = json.loads(path.read_text(encoding="utf-8"))
43
+ name = path.stem # filename minus .json
44
+ client.indices.put_index_template(name=name, body=body)
45
+ print(f"✓ applied {name}")
46
+
47
+ print(f"done; {len(files)} template(s) applied to {url}")
48
+
49
+
50
+ if __name__ == "__main__":
51
+ try:
52
+ main()
53
+ except Exception as e: # noqa: BLE001
54
+ print(f"✗ failed: {e}", file=sys.stderr)
55
+ sys.exit(1)
@@ -0,0 +1,57 @@
1
+ # server/db/
2
+
3
+ Elasticsearch index-template 入口(Python 后端)。
4
+
5
+ > 跨项目的数据库归属规则见 [`docs/governance/database.md`](../../docs/governance/database.md)。
6
+
7
+ ## "Migration" 的语义
8
+
9
+ **Elasticsearch 没有事务性 DDL,本目录里的 JSON 不是迁移文件。**
10
+
11
+ 每个 `db/index-templates/*.json` 是一个 [Index Template v2](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html)。`apply_templates.py` 按文件名字典序遍历目录,对每个文件 PUT;ES 自身保证幂等。
12
+
13
+ ## 工具与版本
14
+
15
+ | 项 | 值 |
16
+ |---|---|
17
+ | Elasticsearch | 8.15+ |
18
+ | Python 客户端 | `elasticsearch` `8.15.1` |
19
+
20
+ ## 目录布局
21
+
22
+ ```
23
+ server/
24
+ ├── pyproject.toml
25
+ └── db/
26
+ ├── README.md
27
+ ├── apply_templates.py # 幂等 applier
28
+ └── index-templates/
29
+ └── <utc-ts>_<description>.json
30
+ ```
31
+
32
+ ## 常用命令
33
+
34
+ ```bash
35
+ python -m server.db.apply_templates
36
+ # 或
37
+ python server/db/apply_templates.py
38
+ ```
39
+
40
+ 环境变量 `ELASTICSEARCH_URL`(默认 `http://127.0.0.1:9200`)、`ELASTICSEARCH_USERNAME` / `ELASTICSEARCH_PASSWORD`(可选)。
41
+
42
+ ## 改动分级(参见 `docs/governance/change-tiers.md`)
43
+
44
+ | 改动 | Tier |
45
+ |---|---|
46
+ | 新增 template 文件 | 3 |
47
+ | 修改既有 template 的 mapping 字段 | 4(破坏性 - 可能 reindex) |
48
+ | 仅改 priority / 新增 alias / 改 settings.refresh_interval | 2 |
49
+ | 删除 template | 4 |
50
+
51
+ ## 与 `contracts/asyncapi/` 的同步
52
+
53
+ 事件 ingest 的 schema 真值在 `contracts/asyncapi/asyncapi.yaml`;index-template 的 mapping 应当与之**对齐**。当前手动同步。
54
+
55
+ ## CI 行为
56
+
57
+ CI 起一个 ES 8 service container;脚本失败 = CI 红。
@@ -0,0 +1,25 @@
1
+ {
2
+ "index_patterns": ["events-*"],
3
+ "priority": 100,
4
+ "template": {
5
+ "settings": {
6
+ "number_of_shards": 1,
7
+ "number_of_replicas": 1,
8
+ "refresh_interval": "5s"
9
+ },
10
+ "mappings": {
11
+ "properties": {
12
+ "@timestamp": { "type": "date" },
13
+ "tenant_id": { "type": "keyword" },
14
+ "event_type": { "type": "keyword" },
15
+ "user_id": { "type": "keyword" },
16
+ "payload": { "type": "object", "dynamic": true },
17
+ "trace_id": { "type": "keyword" }
18
+ }
19
+ }
20
+ },
21
+ "_meta": {
22
+ "description": "Sample event-stream template. Replace mappings with your domain fields.",
23
+ "managed_by": "@wneng/create-keel"
24
+ }
25
+ }
@@ -0,0 +1,50 @@
1
+ [project]
2
+ name = "<%= it.options.projectName %>-server"
3
+ version = "0.1.0"
4
+ requires-python = ">=3.11"
5
+ dependencies = [
6
+ "fastapi==0.115.5",
7
+ "uvicorn[standard]==0.32.0",
8
+ "pydantic==2.9.2",
9
+ "pydantic-settings==2.6.1",
10
+ "elasticsearch==8.15.1",
11
+ ]
12
+
13
+ [project.optional-dependencies]
14
+ dev = [
15
+ "ruff>=0.6.8",
16
+ "mypy>=1.11.0",
17
+ "pytest>=8.3.0",
18
+ "pytest-asyncio>=0.24.0",
19
+ "httpx>=0.27.0",
20
+ "pip-audit>=2.7.0",
21
+ ]
22
+
23
+ [tool.ruff]
24
+ line-length = 100
25
+ target-version = "py311"
26
+ extend-exclude = ["generated", ".venv"]
27
+
28
+ [tool.ruff.lint]
29
+ select = ["E", "F", "W", "I", "B", "UP", "N"]
30
+ ignore = []
31
+
32
+ [tool.ruff.format]
33
+ quote-style = "double"
34
+ indent-style = "space"
35
+ line-ending = "lf"
36
+
37
+ [tool.mypy]
38
+ python_version = "3.11"
39
+ strict_optional = true
40
+ disallow_untyped_defs = true
41
+ warn_unused_ignores = true
42
+ exclude = ["generated", ".venv"]
43
+
44
+ [tool.pytest.ini_options]
45
+ testpaths = ["tests"]
46
+ asyncio_mode = "auto"
47
+
48
+ [tool.setuptools.packages.find]
49
+ include = ["app*"]
50
+ exclude = ["tests*", "generated*", "db*"]
@@ -0,0 +1,19 @@
1
+ name: db-python-elasticsearch
2
+ version: 1.0.0
3
+ appliesWhen:
4
+ backend: python
5
+ database: elasticsearch
6
+ priority: 40
7
+ files:
8
+ - from: files/pyproject.toml
9
+ to: server/pyproject.toml
10
+ render: true
11
+ - from: files/apply_templates.py
12
+ to: server/db/apply_templates.py
13
+ render: false
14
+ - from: files/index-template-init.json
15
+ to: server/db/index-templates/20260101000000_init.json
16
+ render: false
17
+ - from: files/db-README.md
18
+ to: server/db/README.md
19
+ render: true
@@ -0,0 +1,150 @@
1
+ ---
2
+ last-reviewed: 2026-05-16
3
+ ---
4
+
5
+ # 数据库治理
6
+
7
+ > 本文件统一回答:"SQL / 索引 / 迁移 / 种子数据放哪个目录、与 `contracts/` 如何对齐"。
8
+ >
9
+ > 工具与命令的细节(哪个项目用 Knex / Flyway / Alembic / golang-migrate / ES applier)见各项目的 `server/db/README.md`,本文件不重复。
10
+ >
11
+ > 工具决策与 mongodb / sqlite 排除理由见 [`../02-系统方案与架构/adr-0005-database-conventions.md`](../02-系统方案与架构/adr-0005-database-conventions.md)。
12
+
13
+ ## 1. 五类 SQL 文件的归属
14
+
15
+ 数据库相关的产物分五类,每类只有一个合法位置:
16
+
17
+ | 类型 | 例子 | 归属 | 与契约关系 |
18
+ |---|---|---|---|
19
+ | **schema migration**(DDL,版本化) | `V1__create_users.sql` / `0001_init.py` / `000001_init.up.sql` | `server/db/migrations/`(Java 走 `src/main/resources/db/migration/`) | 与 `contracts/openapi/` 中的字段对齐 |
20
+ | **生产种子数据**(字典 / 码表) | 角色码表、状态枚举、错误码 lookup | `server/db/seeds/prod/`(Knex)/ 写入迁移文件(Flyway / Alembic / golang-migrate) | **真值在 `contracts/dictionaries/`**;当前手动同步 |
21
+ | **开发种子数据**(fixture) | 样例用户、demo 商品、调试数据 | `server/db/seeds/dev/` | 与契约无关;不进生产 |
22
+ | **存储过程 / 函数 / 视图 / 触发器** | `usp_calculate_amount.sql`、物化视图 | `server/db/procedures/` | 写入迁移文件,与代码同等对待 |
23
+ | **运维脚本**(一次性) | 数据回填、重建索引、清理过期数据 | `scripts/db/<date>-<topic>.sql` | 与契约无关;不进生产迁移历史 |
24
+ | **报表 / 分析 query**(BI 看板) | 仪表板背后的 SQL、临时调研 query | `docs/09-数据与埋点/`(启用 data 角色) | 与契约无关;与数据资产对齐 |
25
+
26
+ > Elasticsearch 的"migration"指**幂等的 index-template apply**,不在上述 RDBMS 表里;其归属规则:JSON template 在 `server/db/index-templates/`(Java 走 `src/main/resources/index-templates/`),applier 脚本在 `server/db/`。详见各项目 `server/db/README.md`。
27
+
28
+ ## 2. 三类特殊场景
29
+
30
+ ### 2.1 跨 service 共享同一个 DB(微服务但 DB 没拆)
31
+
32
+ DB schema 升格为契约:
33
+
34
+ ```
35
+ contracts/schemas/db/
36
+ ├── README.md # 谁是 schema 拥有者("owner service")
37
+ ├── ddl/ # 真值;其他 service 视为 read-only
38
+ │ ├── core_tables.sql
39
+ │ └── views.sql
40
+ └── CHANGELOG.md # 改 schema 必须 bump SemVer
41
+ server/apps/owner/db/migrations/ # 唯一执行迁移的 service
42
+ server/apps/consumer/db/ # 不存 DDL,只存 ORM 映射
43
+ ```
44
+
45
+ 只有 owner service 的 PR 能改 `contracts/schemas/db/ddl/`;consumer service 改 → CI 拒(按 contracts/ 现有规则)。
46
+
47
+ ### 2.2 多租户 schema-per-tenant
48
+
49
+ ```
50
+ server/db/migrations/
51
+ ├── shared/ # 公共 schema(账户、租户元数据)
52
+ └── tenant/ # 应用到每个租户 schema 的迁移
53
+ ```
54
+
55
+ 文档说清楚"新租户 onboarding 要跑哪些 migrations"。
56
+
57
+ ### 2.3 多 database per service
58
+
59
+ 例如:postgres 主库 + elasticsearch 用作搜索。
60
+
61
+ - 选第二个 fragment:手动从对应 `db-<backend>-<other>` 复制配置
62
+ - 在 `server/db/README.md` 里说明双库各管什么
63
+ - CI 起两个 service container
64
+
65
+ scaffolder 0.4.0 不自动支持双库;用户合并两个 fragment 的内容。
66
+
67
+ ## 3. 与 `contracts/` 的同步规则
68
+
69
+ ### 3.1 字典数据 → 生产种子
70
+
71
+ `contracts/dictionaries/enums.yaml` 是字典(角色 / 错误码 / 状态枚举)的真值。改字典的 PR 必须**同步**:
72
+
73
+ - 加字典项 → 同 PR 在迁移文件中加 INSERT IGNORE / ON CONFLICT DO NOTHING
74
+ - 改字典项的 name / description → 写新迁移更新对应行(不要改既有迁移)
75
+ - 删字典项 → Tier 4,需 ADR
76
+
77
+ > 当前是**手动同步**。自动派生(codegen)留给后续 `contract-derived-seeds` 特性。
78
+
79
+ ### 3.2 OpenAPI 字段 → DB 列
80
+
81
+ OpenAPI 中的实体字段与 DB 表的列**应当**对齐(同名、同类型映射)。手动维护;governance-lint 不强制(语义 diff 太复杂)。
82
+
83
+ ### 3.3 AsyncAPI 事件 → ES index template
84
+
85
+ `contracts/asyncapi/asyncapi.yaml` 中的事件 schema 与 `server/db/index-templates/` 中的 mapping **应当**对齐。当前手动同步。
86
+
87
+ ## 4. 改动分级(参见 `change-tiers.md`)
88
+
89
+ | 改动 | Tier | 备注 |
90
+ |---|---|---|
91
+ | 新增 migration 加表 / 加字段 | 3 | 同 PR 改 `contracts/CHANGELOG.md`(如果字段进契约) |
92
+ | 修改既有 migration 文件 | **不允许** | 写新 migration 代替;Flyway / Alembic / golang-migrate 都拒绝 |
93
+ | 删字段 / 改类型(破坏性) | 4 | ADR + 详细设计 + 数据迁移预案 |
94
+ | 改 dev seed | 1/2 | 不需要同步契约 |
95
+ | 改 prod seed(字典数据) | 3 | 同 PR 改 `contracts/dictionaries/` |
96
+ | 改 ES index-template 的 mapping | 4 | 破坏性 - 可能需要 reindex |
97
+ | 改 ES index-template 的 priority / settings | 2 | 局部行为调整 |
98
+ | 升级 Knex / Flyway / Alembic / golang-migrate / ES SDK | 2 | 同 PR 改 `tech-stack-server.md` 钉版本 |
99
+
100
+ ## 5. CI 行为
101
+
102
+ 每个 (backend, database) 组合的 CI 都跑 **migrate up + migrate down** smoke test:
103
+
104
+ - RDBMS:`db-migrate-smoke` job 起 service container,跑 `npm run db:migrate && npm run db:rollback`(或对应工具命令)
105
+ - ES:`db-es-template-apply` job 起 ES service container,跑 applier 脚本一次
106
+
107
+ CI **不**校验:
108
+ - schema 内容(用 DBA tool)
109
+ - 与 `contracts/` 字段对齐(语义 diff 太复杂)
110
+ - 数据回填脚本的正确性(一次性脚本不进 CI)
111
+
112
+ ## 6. 目录布局速查(非 multi-app 模式)
113
+
114
+ ```
115
+ server/
116
+ ├── db/
117
+ │ ├── README.md # 工具与命令(按 backend 不同)
118
+ │ ├── migrations/ # Knex / Alembic / golang-migrate 路径
119
+ │ │ └── <ts/N>_<description>.<ext>
120
+ │ ├── index-templates/ # ES applier 模式
121
+ │ │ └── <ts>_<description>.json
122
+ │ ├── seeds/
123
+ │ │ ├── prod/ # 字典数据;与 contracts/dictionaries 对齐
124
+ │ │ └── dev/ # 仅本地 / 测试
125
+ │ └── apply-templates.{cjs,py,go} # ES applier(按 backend)
126
+ ├── src/main/resources/db/migration/ # ← Java + Flyway 的迁移路径(与 server/db/ 分离)
127
+ └── src/main/resources/index-templates/ # ← Java + ES applier 的 template 路径
128
+
129
+ contracts/
130
+ ├── dictionaries/enums.yaml # 字典真值(→ prod seed 派生)
131
+ ├── openapi/api.yaml # API 字段(→ DB 列对齐)
132
+ └── asyncapi/asyncapi.yaml # 事件 schema(→ ES mapping 对齐)
133
+
134
+ scripts/db/ # 一次性运维(数据回填等)
135
+ └── 2026-05-backfill-tenants.sql
136
+
137
+ ops/db/ # DB 实例 / 权限 / 备份(IaC)
138
+ └── grants/
139
+ ```
140
+
141
+ ## 7. multi-app 模式
142
+
143
+ 每个 `server/apps/<app>/` 自己一份 `db/`。共享 schema 时(多 app 同 DB)schema 真值进 `contracts/schemas/db/`,由 owner app 唯一执行迁移。完整规则与示例见 §2.1。
144
+
145
+ ## 8. 不在本特性范围内的(推到下次 spec)
146
+
147
+ - mongodb 支持(详见 ADR-0005)
148
+ - sqlite 支持(详见 ADR-0005)
149
+ - contracts/dictionaries → seed migration 自动派生
150
+ - web/mobile/miniapp 本地数据库(IndexedDB / SQLite / Realm)
@@ -117,6 +117,9 @@ files:
117
117
  - from: files/governance-change-tiers.md
118
118
  to: docs/governance/change-tiers.md
119
119
  render: false
120
+ - from: files/governance-database.md
121
+ to: docs/governance/database.md
122
+ render: false
120
123
  - from: files/governance-checklists.md
121
124
  to: docs/governance/checklists.md
122
125
  render: true