文章目录
-
- [📌 技术名片](#📌 技术名片)
-
- [💡 一句话理解](#💡 一句话理解)
- 通俗理解:数据库就像物流公司
- 一、跨数据源架构
-
- [1. 业务层不能直接依赖数据库](#1. 业务层不能直接依赖数据库)
- [2. ORM 的意义不只是"少写 SQL"](#2. ORM 的意义不只是“少写 SQL”)
- [3. 但 ORM 也不是"万能兼容层"](#3. 但 ORM 也不是“万能兼容层”)
- [二、miniagent(https://github.com/liupras/miniagent) 的主业务数据库](#二、miniagent 的主业务数据库)
-
- [1. 创建](#1. 创建)
- [2. 统一 Session 管理,避免 AI 到处 commit/rollback](#2. 统一 Session 管理,避免 AI 到处 commit/rollback)
- [3. 多数据源不等于"所有数据库强行统一"](#3. 多数据源不等于“所有数据库强行统一”)
- [4. 为什么分析型数据库可以单独存在?](#4. 为什么分析型数据库可以单独存在?)
- [5. 一个更完整的多数据源架构](#5. 一个更完整的多数据源架构)
- [6. 跨数据源的核心不是"兼容",而是"隔离"](#6. 跨数据源的核心不是“兼容”,而是“隔离”)
- [7. Repository 层就是数据库的"防火墙"](#7. Repository 层就是数据库的“防火墙”)
- 三、多数据源架构
-
- [1. 应该怎么分层?](#1. 应该怎么分层?)
-
- Repository
- Adapter
- [Client / Manager](#Client / Manager)
- [2. 什么时候应该抽统一接口?](#2. 什么时候应该抽统一接口?)
- [3. 有什么好处?](#3. 有什么好处?)
-
- 数据库迁移成本更低
- 可以让不同数据库发挥各自优势
- 更容易测试
- 数据库能力不会污染业务语义
- [更适合 AI 编程](#更适合 AI 编程)
- [四、给 AI 建立多数据源架构规则](#四、给 AI 建立多数据源架构规则)
-
- [提示词落地:不要只告诉 AI"支持 PostgreSQL"](#提示词落地:不要只告诉 AI“支持 PostgreSQL”)
- [添加新数据源时也先问 AI 几个问题](#添加新数据源时也先问 AI 几个问题)
- [五、以后 miniagent(https://github.com/liupras/miniagent) 可以怎样自然演进?](#五、以后 miniagent 可以怎样自然演进?)
- 总结
- 开源代码
很多项目一开始只有一个数据库。于是 AI 很自然地写出:
python
async def get_user(user_id: int):
async with sqlite_session() as session:
...
或者更直接:
python
conn = sqlite3.connect("app.db")
功能当然能跑,但问题会在以后出现。
今天是 SQLite ,明天可能要换成 PostgreSQL ...
随着代码规模的增加,项目很快就会变成:
业务代码和具体存储技术绑死。
这正是 Multi-Data-Source Architecture(多数据源架构) 要解决的问题。
📌 技术名片
Multi-Data-Source Architecture / 多数据源架构
指一个系统同时使用多个不同的数据源,并通过统一的架构边界管理它们,而不是让业务代码直接依赖具体数据库实现。
常见数据源包括:
text
SQLite
PostgreSQL
MySQL
DuckDB
这里最重要的不是:
"支持多少种数据库。"
而是:
业务代码是否需要知道底层到底是什么数据库。
💡 一句话理解
可以把多数据源架构理解成:
业务层只负责"我要什么数据",基础设施层负责"去哪里、怎么拿"。
通俗理解:数据库就像物流公司
假设你经营一家电商。业务部门只关心:
text
把商品送到客户手里。
至于运输方式:
text
顺丰
京东物流
DHL
海运
空运
不应该写进订单业务本身。错误设计可能是:
text
订单是国内:
调顺丰 API
订单是欧洲:
调 DHL API
订单是大件:
调海运 API
更加合理的是,订单系统只说:
"帮我发货。"
物流层再决定具体怎么做。
数据库也是一样。业务层应该说:
text
查询用户
保存 Agent
读取知识库
执行分析 SQL
而不是:
text
SQLite 用这个语法
PostgreSQL 用那个语法
DuckDB 再单独判断
一、跨数据源架构
1. 业务层不能直接依赖数据库
一个健康的调用关系应该是:
text
Router
↓
Service
↓
Repository / Data Access
↓
Database Driver / ORM
↓
Database
其中:
Repository(仓储层)
负责封装数据访问。
业务 Service 不应该知道:
text
SQLAlchemy
sqlite3
aiosqlite
psycopg
DuckDB connection
它只应该知道:
python
user = await user_repository.get_by_id(user_id)
而不是:
python
async with session.execute(...):
2. ORM 的意义不只是"少写 SQL"
这里需要引出一个重要术语:
ORM / Object-Relational Mapping / 对象关系映射
ORM 可以把 Python Object 和 Relational Database Table 建立映射;比如 User 对应 users 表 。
很多人理解 ORM 时,只想到:
少写 SQL。
其实在多数据库架构里,它还有一个更重要的价值:
隔离不同数据库之间的大量语法和驱动差异。
例如 SQLAlchemy 可以通过不同:
Dialect(方言)
适配不同数据库。
应用层写:
python
select(User).where(User.id == user_id)
底层可以根据数据库转换为相应 SQL。
3. 但 ORM 也不是"万能兼容层"
这里需要特别说明,不同数据库之间仍然存在大量差异,例如:
text
数据类型
JSON 支持
全文检索
分页语法
UPSERT
自增主键
日期函数
锁机制
事务隔离
索引
窗口函数
如果 AI 大量写:
python
text("某数据库专属 SQL")
那么虽然用了 ORM:
项目依然被绑在某个数据库上。
因此规范应该是:
优先使用 ORM 的通用表达能力,数据库专属语法只能留在基础设施层。
二、miniagent 的主业务数据库
1. 创建
miniagent 当前主业务数据库采用:
text
SQLite
+
SQLAlchemy Async
应用启动时,在 ServiceContainer 中统一创建:
python
database_url = f"sqlite+aiosqlite:///{db_path}"
self.engine = create_async_engine(
database_url,
echo=False,
future=True,
)
self.session_factory = async_sessionmaker(
bind=self.engine,
...
)
然后再统一把:
text
engine
session_factory
注入不同的数据访问对象:
python
self.user_db = AsyncUserDatabase(
self.engine,
self.session_factory
)
self.chat_db = AsyncChatDatabase(
self.engine,
self.session_factory
)
self.kb_db = AsyncKnowledgeBaseDatabase(
self.engine,
self.session_factory
)
这个结构非常关键。
因为业务 Service 并没有各自 create_engine() ,而是统一复用数据库基础设施。
2. 统一 Session 管理,避免 AI 到处 commit/rollback
数据库访问还有一个非常容易被 AI 写乱的问题:
Transaction(事务)
AI 很容易在不同地方分别写:
python
await session.commit()
另一个地方:
python
await session.rollback()
再一个地方忘记 close。
于是 Session 生命周期到处散落。
miniagent 提供统一的 AsyncBaseDatabase,其中:
python
@asynccontextmanager
async def get_session(self):
async with self.AsyncSessionLocal() as session:
try:
yield session
await session.commit()
except SQLAlchemyError:
await session.rollback()
raise
finally:
await session.close()
这里的:
Transaction(事务)
可以理解为:
一组数据库操作要么全部成功,要么全部失败。
这样 Repository 层可以统一使用:
python
async with self.get_session() as session:
...
而不用让每个业务函数重新考虑:
text
什么时候 commit?
失败要不要 rollback?
什么时候 close?
3. 多数据源不等于"所有数据库强行统一"
这是这篇文章最重要的一点之一。
miniagent 本身就是一个很好的例子。
它的:
text
用户
Agent
知识库配置
权限
聊天记录
系统设置
属于典型业务数据。
适合:
text
SQLAlchemy
↓
SQLite
但 SQL Agent 面向的是另一种场景:
分析型数据查询。
所以 miniagent 还单独存在 DuckDBManager:
python
self.conn = duckdb.connect(db_path)
def execute(self, sql, params=None):
return self.conn.execute(
sql,
params or []
).fetchall()
也就是说,miniagent 并没有强行要求:
所有数据必须通过同一种数据库访问方式。
而是:
text
Operational Data
业务数据
↓
SQLAlchemy / SQLite
Analytical Data
分析数据
↓
DuckDB
这是非常合理的。
4. 为什么分析型数据库可以单独存在?
因为:
text
SQLite
PostgreSQL
MySQL
这类数据库更擅长:
OLTP(Online Transaction Processing,联机事务处理)
例如:
text
创建用户
修改 Agent
保存聊天记录
权限管理
而 DuckDB 更偏向:
OLAP(Online Analytical Processing,联机分析处理)
例如:
text
分析 CSV
聚合百万行数据
GROUP BY
统计报表
临时分析
所以正确的多数据源架构并不是:
"找一种数据库解决所有问题。"
而是:
不同的数据源承担不同职责。
5. 一个更完整的多数据源架构
下面是 miniagent 的完整数据架构:

从上图可见,miniagent 主要有两条明显不同的数据访问路线。
第一条是业务数据:
text
Router
↓
Service
↓
Async*Database / Repository
↓
AsyncBaseDatabase
↓
SQLAlchemy
↓
SQLite
ServiceContainer 统一创建 Engine 和 SessionFactory,再注入多个业务数据库对象,而不是让每个模块自行创建数据库连接。
AsyncBaseDatabase 则进一步统一管理 Session、Commit、Rollback 和 Close 生命周期。
第二条是分析数据:
text
SQL Agent
↓
DuckDBManager
↓
DuckDB
DuckDBManager 直接封装 DuckDB 连接与 SQL 执行。
因此 miniagent 当前真正体现的是:
#mermaid-svg-RDxE2cgCSEW13IVk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RDxE2cgCSEW13IVk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RDxE2cgCSEW13IVk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RDxE2cgCSEW13IVk .error-icon{fill:#552222;}#mermaid-svg-RDxE2cgCSEW13IVk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RDxE2cgCSEW13IVk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RDxE2cgCSEW13IVk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RDxE2cgCSEW13IVk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RDxE2cgCSEW13IVk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RDxE2cgCSEW13IVk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RDxE2cgCSEW13IVk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RDxE2cgCSEW13IVk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RDxE2cgCSEW13IVk .marker.cross{stroke:#333333;}#mermaid-svg-RDxE2cgCSEW13IVk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RDxE2cgCSEW13IVk p{margin:0;}#mermaid-svg-RDxE2cgCSEW13IVk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RDxE2cgCSEW13IVk .cluster-label text{fill:#333;}#mermaid-svg-RDxE2cgCSEW13IVk .cluster-label span{color:#333;}#mermaid-svg-RDxE2cgCSEW13IVk .cluster-label span p{background-color:transparent;}#mermaid-svg-RDxE2cgCSEW13IVk .label text,#mermaid-svg-RDxE2cgCSEW13IVk span{fill:#333;color:#333;}#mermaid-svg-RDxE2cgCSEW13IVk .node rect,#mermaid-svg-RDxE2cgCSEW13IVk .node circle,#mermaid-svg-RDxE2cgCSEW13IVk .node ellipse,#mermaid-svg-RDxE2cgCSEW13IVk .node polygon,#mermaid-svg-RDxE2cgCSEW13IVk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RDxE2cgCSEW13IVk .rough-node .label text,#mermaid-svg-RDxE2cgCSEW13IVk .node .label text,#mermaid-svg-RDxE2cgCSEW13IVk .image-shape .label,#mermaid-svg-RDxE2cgCSEW13IVk .icon-shape .label{text-anchor:middle;}#mermaid-svg-RDxE2cgCSEW13IVk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RDxE2cgCSEW13IVk .rough-node .label,#mermaid-svg-RDxE2cgCSEW13IVk .node .label,#mermaid-svg-RDxE2cgCSEW13IVk .image-shape .label,#mermaid-svg-RDxE2cgCSEW13IVk .icon-shape .label{text-align:center;}#mermaid-svg-RDxE2cgCSEW13IVk .node.clickable{cursor:pointer;}#mermaid-svg-RDxE2cgCSEW13IVk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RDxE2cgCSEW13IVk .arrowheadPath{fill:#333333;}#mermaid-svg-RDxE2cgCSEW13IVk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RDxE2cgCSEW13IVk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RDxE2cgCSEW13IVk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RDxE2cgCSEW13IVk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RDxE2cgCSEW13IVk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RDxE2cgCSEW13IVk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RDxE2cgCSEW13IVk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RDxE2cgCSEW13IVk .cluster text{fill:#333;}#mermaid-svg-RDxE2cgCSEW13IVk .cluster span{color:#333;}#mermaid-svg-RDxE2cgCSEW13IVk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RDxE2cgCSEW13IVk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RDxE2cgCSEW13IVk rect.text{fill:none;stroke-width:0;}#mermaid-svg-RDxE2cgCSEW13IVk .icon-shape,#mermaid-svg-RDxE2cgCSEW13IVk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RDxE2cgCSEW13IVk .icon-shape p,#mermaid-svg-RDxE2cgCSEW13IVk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RDxE2cgCSEW13IVk .icon-shape .label rect,#mermaid-svg-RDxE2cgCSEW13IVk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RDxE2cgCSEW13IVk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RDxE2cgCSEW13IVk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RDxE2cgCSEW13IVk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} miniagent
Business / Runtime
Business Data
Analytics Data
Repository Layer
AsyncBaseDatabase
SQLAlchemy
SQLite
SQL Agent
DuckDBManager
DuckDB
它不是:
"所有数据库必须统一使用 SQLAlchemy。"
而是:
不同数据源采用适合自己的访问方式,但具体数据库实现被限制在基础设施边界之后。
这才是多数据源架构更实际的落地方式。
6. 跨数据源的核心不是"兼容",而是"隔离"
很多人会把目标写成:
我要兼容 MySQL、SQLite、PostgreSQL。
但更好的目标应该是:
当数据库改变时,改动尽量限制在基础设施层。
也就是说:
text
Database Changed
↓
Infrastructure
↓
Repository
而不是:
text
Database Changed
├── Router 修改
├── Service 修改
├── Tool 修改
├── API 修改
└── 前端修改
这就是:
Change Isolation(变化隔离)
架构真正想解决的就是变化成本。
7. Repository 层就是数据库的"防火墙"
可以把 Repository 理解成:
业务世界和数据库世界之间的一堵墙。
左边:
text
Business
说:
text
给我一个 User
保存 Agent
查询 KB
右边:
text
Database
考虑:
text
SQL
Join
Session
Transaction
Dialect
Index
Connection
中间 Repository 负责翻译。
例如:
text
Service
↓
user_repository.get_by_id(10)
↓
Repository
↓
SQLAlchemy
↓
SQLite / PostgreSQL
所以一个非常重要的 AI Rule 是:
Service 不允许跨过 Repository 直接操作数据库。
三、多数据源架构
1. 应该怎么分层?
可以采用这样的规则:
#mermaid-svg-lfYJjfRRD1p9uMHe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lfYJjfRRD1p9uMHe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lfYJjfRRD1p9uMHe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lfYJjfRRD1p9uMHe .error-icon{fill:#552222;}#mermaid-svg-lfYJjfRRD1p9uMHe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lfYJjfRRD1p9uMHe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lfYJjfRRD1p9uMHe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lfYJjfRRD1p9uMHe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lfYJjfRRD1p9uMHe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lfYJjfRRD1p9uMHe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lfYJjfRRD1p9uMHe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lfYJjfRRD1p9uMHe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lfYJjfRRD1p9uMHe .marker.cross{stroke:#333333;}#mermaid-svg-lfYJjfRRD1p9uMHe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lfYJjfRRD1p9uMHe p{margin:0;}#mermaid-svg-lfYJjfRRD1p9uMHe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lfYJjfRRD1p9uMHe .cluster-label text{fill:#333;}#mermaid-svg-lfYJjfRRD1p9uMHe .cluster-label span{color:#333;}#mermaid-svg-lfYJjfRRD1p9uMHe .cluster-label span p{background-color:transparent;}#mermaid-svg-lfYJjfRRD1p9uMHe .label text,#mermaid-svg-lfYJjfRRD1p9uMHe span{fill:#333;color:#333;}#mermaid-svg-lfYJjfRRD1p9uMHe .node rect,#mermaid-svg-lfYJjfRRD1p9uMHe .node circle,#mermaid-svg-lfYJjfRRD1p9uMHe .node ellipse,#mermaid-svg-lfYJjfRRD1p9uMHe .node polygon,#mermaid-svg-lfYJjfRRD1p9uMHe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lfYJjfRRD1p9uMHe .rough-node .label text,#mermaid-svg-lfYJjfRRD1p9uMHe .node .label text,#mermaid-svg-lfYJjfRRD1p9uMHe .image-shape .label,#mermaid-svg-lfYJjfRRD1p9uMHe .icon-shape .label{text-anchor:middle;}#mermaid-svg-lfYJjfRRD1p9uMHe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lfYJjfRRD1p9uMHe .rough-node .label,#mermaid-svg-lfYJjfRRD1p9uMHe .node .label,#mermaid-svg-lfYJjfRRD1p9uMHe .image-shape .label,#mermaid-svg-lfYJjfRRD1p9uMHe .icon-shape .label{text-align:center;}#mermaid-svg-lfYJjfRRD1p9uMHe .node.clickable{cursor:pointer;}#mermaid-svg-lfYJjfRRD1p9uMHe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lfYJjfRRD1p9uMHe .arrowheadPath{fill:#333333;}#mermaid-svg-lfYJjfRRD1p9uMHe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lfYJjfRRD1p9uMHe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lfYJjfRRD1p9uMHe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lfYJjfRRD1p9uMHe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lfYJjfRRD1p9uMHe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lfYJjfRRD1p9uMHe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lfYJjfRRD1p9uMHe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lfYJjfRRD1p9uMHe .cluster text{fill:#333;}#mermaid-svg-lfYJjfRRD1p9uMHe .cluster span{color:#333;}#mermaid-svg-lfYJjfRRD1p9uMHe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lfYJjfRRD1p9uMHe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lfYJjfRRD1p9uMHe rect.text{fill:none;stroke-width:0;}#mermaid-svg-lfYJjfRRD1p9uMHe .icon-shape,#mermaid-svg-lfYJjfRRD1p9uMHe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lfYJjfRRD1p9uMHe .icon-shape p,#mermaid-svg-lfYJjfRRD1p9uMHe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lfYJjfRRD1p9uMHe .icon-shape .label rect,#mermaid-svg-lfYJjfRRD1p9uMHe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lfYJjfRRD1p9uMHe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lfYJjfRRD1p9uMHe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lfYJjfRRD1p9uMHe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Application / Service
Data Access Abstraction
Repository
Adapter
Client
SQL DB
DuckDB
Vector DB
其中:
Repository
适合 业务数据库 CRUD。
Adapter
适合 不同协议或特殊数据源适配。
Client / Manager
适合:
text
Vector Store
外部 Search Engine
重点是:
上层看到的是"能力",而不是"产品名字"。
2. 什么时候应该抽统一接口?
例如业务层只需要:
python
await user_repository.get_by_id(id)
那么可以有:
text
UserRepository
│
├── SQLAlchemyUserRepository
└── FutureOtherRepository
但不要为了所谓"未来可能支持十个数据库",一开始就创建:
text
AbstractDatabaseFactoryProviderManagerAdapter
这样的巨大体系。
架构需要留扩展点,但不要过度设计。
对于很多 Python 项目:
text
Service
↓
Repository
↓
SQLAlchemy
已经足够隔离 SQLite → PostgreSQL 这类变化。
3. 有什么好处?
数据库迁移成本更低
假设:
text
开发环境:SQLite
以后生产改成:
text
PostgreSQL
如果业务大量使用 SQLAlchemy 的通用能力,改动主要集中于:
text
Database URL
Driver
Migration
少量 Dialect 差异
而不是重新修改所有 Service。
可以让不同数据库发挥各自优势
例如:
text
业务事务
→ PostgreSQL / SQLite
分析查询
→ DuckDB
向量搜索
→ Vector Database
缓存
→ Redis
而不是用一把锤子处理所有问题。
更容易测试
Service 如果只依赖:
text
Repository
测试时可以替换成:
text
Fake Repository
而不需要真的启动数据库。
数据库能力不会污染业务语义
业务代码仍然是:
python
await agent_service.create(...)
而不是:
text
SQLite INSERT
PostgreSQL ON CONFLICT
MySQL UPSERT
业务代码更容易理解。
更适合 AI 编程
AI 最危险的一点是:
看见一个能工作的写法,就复制到整个项目。
如果项目规定:
text
Router → Service → Repository → Database
AI 就有明确路径。
否则它很容易:
text
哪里需要数据
→ 哪里直接连数据库
四、给 AI 建立多数据源架构规则
可以直接加入 Project Rules:
text
## 多数据源架构
业务代码禁止直接依赖具体数据库实现。
调用关系:
Router
→ Service
→ Repository / Data Adapter
→ Database Client / ORM
→ Data Source
规则:
- Router 禁止创建数据库连接;
- Service 禁止出现数据库专属 SQL;
- 普通业务数据库访问统一放在 Repository;
- Connection、Session、Transaction 和 Driver 生命周期属于基础设施层;
- 普通 CRUD 优先使用 ORM 通用表达;
- 特定数据库 SQL 只能留在数据访问层;
- 禁止在业务层散落 database_type 判断;
- 不同数据源允许使用不同访问技术;
- 不强制 DuckDB、向量数据库、Redis 等套进关系型 ORM。
核心目标:
统一的是"访问边界",
不是强迫所有数据源使用同一种实现。
提示词落地:不要只告诉 AI"支持 PostgreSQL"
错误提示词:
把这个项目改成同时支持 SQLite 和 PostgreSQL。
AI 很可能开始:
python
if db_type == "sqlite":
...
else:
...
然后到处改。
更加合理的提示词:
text
请为当前项目增加 PostgreSQL 兼容能力。
要求:
1. 先检查现有数据库访问边界;
2. Router 和 Service 不允许感知数据库类型;
3. 普通 CRUD 继续复用现有 Repository;
4. SQLAlchemy Engine 和 SessionFactory 由基础设施层统一创建;
5. 优先使用 SQLAlchemy 通用表达;
6. 找出 SQLite 专属 SQL 或数据类型;
7. 数据库差异只允许封装在 infrastructure / repository;
8. 禁止在业务代码新增 db_type if/else;
9. 保持现有 Service API 不变;
10. 给出 SQLite 与 PostgreSQL 的兼容差异清单。
这样 AI 的目标就从:
"哪里报错就加 if。"
变成:
"把数据库差异关进正确的架构层。"
添加新数据源时也先问 AI 几个问题
例如要引入 DuckDB、Redis 或向量数据库,可以要求 AI 先判断:
text
在实现之前,请回答:
1. 新数据源承担什么职责?
2. 它是业务事务数据还是分析数据?
3. 是否应该复用现有 Repository?
4. 是否需要独立 Adapter / Manager?
5. Service 应看到什么统一能力?
6. 哪些数据库专属逻辑必须被隔离?
7. Connection 生命周期由谁管理?
8. 是否需要事务?
9. 是否需要连接池?
10. 是否真的应该和现有数据库统一实现?
尤其是最后一个问题非常重要。
因为:
多数据源架构不意味着把所有数据源做成一样。
五、以后 miniagent 可以怎样自然演进?
如果以后业务数据库从 SQLite 切换:
text
SQLite
↓
PostgreSQL
只要继续保持:
text
Service
↓
Repository
↓
SQLAlchemy
多数业务逻辑不需要知道这个变化。
而系统又可以继续保留 DuckDB 用于分析,同时 Chroma / Vector Store 承担向量检索,未来还可能有 Redis 承担共享 Value Cache。
最终形成:
#mermaid-svg-d2xIu9Zu7hCd1PGE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-d2xIu9Zu7hCd1PGE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-d2xIu9Zu7hCd1PGE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-d2xIu9Zu7hCd1PGE .error-icon{fill:#552222;}#mermaid-svg-d2xIu9Zu7hCd1PGE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-d2xIu9Zu7hCd1PGE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-d2xIu9Zu7hCd1PGE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-d2xIu9Zu7hCd1PGE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-d2xIu9Zu7hCd1PGE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-d2xIu9Zu7hCd1PGE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-d2xIu9Zu7hCd1PGE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-d2xIu9Zu7hCd1PGE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-d2xIu9Zu7hCd1PGE .marker.cross{stroke:#333333;}#mermaid-svg-d2xIu9Zu7hCd1PGE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-d2xIu9Zu7hCd1PGE p{margin:0;}#mermaid-svg-d2xIu9Zu7hCd1PGE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-d2xIu9Zu7hCd1PGE .cluster-label text{fill:#333;}#mermaid-svg-d2xIu9Zu7hCd1PGE .cluster-label span{color:#333;}#mermaid-svg-d2xIu9Zu7hCd1PGE .cluster-label span p{background-color:transparent;}#mermaid-svg-d2xIu9Zu7hCd1PGE .label text,#mermaid-svg-d2xIu9Zu7hCd1PGE span{fill:#333;color:#333;}#mermaid-svg-d2xIu9Zu7hCd1PGE .node rect,#mermaid-svg-d2xIu9Zu7hCd1PGE .node circle,#mermaid-svg-d2xIu9Zu7hCd1PGE .node ellipse,#mermaid-svg-d2xIu9Zu7hCd1PGE .node polygon,#mermaid-svg-d2xIu9Zu7hCd1PGE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d2xIu9Zu7hCd1PGE .rough-node .label text,#mermaid-svg-d2xIu9Zu7hCd1PGE .node .label text,#mermaid-svg-d2xIu9Zu7hCd1PGE .image-shape .label,#mermaid-svg-d2xIu9Zu7hCd1PGE .icon-shape .label{text-anchor:middle;}#mermaid-svg-d2xIu9Zu7hCd1PGE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-d2xIu9Zu7hCd1PGE .rough-node .label,#mermaid-svg-d2xIu9Zu7hCd1PGE .node .label,#mermaid-svg-d2xIu9Zu7hCd1PGE .image-shape .label,#mermaid-svg-d2xIu9Zu7hCd1PGE .icon-shape .label{text-align:center;}#mermaid-svg-d2xIu9Zu7hCd1PGE .node.clickable{cursor:pointer;}#mermaid-svg-d2xIu9Zu7hCd1PGE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-d2xIu9Zu7hCd1PGE .arrowheadPath{fill:#333333;}#mermaid-svg-d2xIu9Zu7hCd1PGE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-d2xIu9Zu7hCd1PGE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-d2xIu9Zu7hCd1PGE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d2xIu9Zu7hCd1PGE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-d2xIu9Zu7hCd1PGE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d2xIu9Zu7hCd1PGE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-d2xIu9Zu7hCd1PGE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-d2xIu9Zu7hCd1PGE .cluster text{fill:#333;}#mermaid-svg-d2xIu9Zu7hCd1PGE .cluster span{color:#333;}#mermaid-svg-d2xIu9Zu7hCd1PGE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-d2xIu9Zu7hCd1PGE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-d2xIu9Zu7hCd1PGE rect.text{fill:none;stroke-width:0;}#mermaid-svg-d2xIu9Zu7hCd1PGE .icon-shape,#mermaid-svg-d2xIu9Zu7hCd1PGE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d2xIu9Zu7hCd1PGE .icon-shape p,#mermaid-svg-d2xIu9Zu7hCd1PGE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-d2xIu9Zu7hCd1PGE .icon-shape .label rect,#mermaid-svg-d2xIu9Zu7hCd1PGE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d2xIu9Zu7hCd1PGE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-d2xIu9Zu7hCd1PGE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-d2xIu9Zu7hCd1PGE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Application
Service Layer
Data Access Boundaries
Relational DB
DuckDB
Vector DB
Redis
PostgreSQL
Analytics
Retrieval
Cache
上层业务不需要直接处理这些差异。
总结
跨多数据源架构真正要解决的问题,并不是:
"怎样写代码同时连接五种数据库?"
而是:
"怎样让五种数据库存在时,业务代码仍然保持干净?"
真正重要的原则是:
text
业务定义需要什么数据
↓
数据访问层决定如何取得
↓
基础设施决定使用什么数据库
对于普通业务 CRUD:
text
Service
↓
Repository
↓
ORM
↓
Relational Database
对于特殊数据源:
text
Service / Tool
↓
Dedicated Adapter / Manager
↓
DuckDB / Vector DB / Redis
所以,多数据源架构最值得写进 AI 项目规则的一句话不是:
"项目要支持多数据库。"
而是:
禁止 AI 把具体数据库写进业务逻辑;统一的是访问边界,不是强迫所有数据源使用同一种实现。
这样以后不管底层是:
text
SQLite
PostgreSQL
DuckDB
Redis
Vector Database
真正变化的,应该主要是基础设施,而不是整个项目。
开源代码
🪐祝您好运🪐