跨多数据源架构:让 AI 不再绑死数据库

文章目录

    • [📌 技术名片](#📌 技术名片)
    • 一、跨数据源架构
      • [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 层就是数据库的“防火墙”)
    • 三、多数据源架构
    • [四、给 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 ObjectRelational 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 统一创建 EngineSessionFactory,再注入多个业务数据库对象,而不是让每个模块自行创建数据库连接。

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

真正变化的,应该主要是基础设施,而不是整个项目。


开源代码


🪐祝您好运🪐

相关推荐
程序员小八7773 小时前
MySQL 事务:一文把 ACID、隔离级别、MVCC、锁全串起来
数据库·mysql
lzhdim3 小时前
提高 SQL 语句执行速度的方法
java·开发语言·数据库·sql·oracle
咔咔学姐kk3 小时前
小白程序员必收藏:轻松入门AI Agent开发,大厂校招新风口!
人工智能·深度学习·ai·程序员·大模型·就业·大模型学习
有颜有货3 小时前
一文搞懂企业五大架构
架构·数据架构·业务架构·技术架构·代码架构
2601_967097224 小时前
园区巡检机器人推荐:4项评估维度与主流产品横评
大数据·人工智能·信息可视化
ltl4 小时前
ClickHouse Distributed 引擎与分布式查询路由
数据库
Wang's Blog4 小时前
PostgreSQL笔记60: 权限与角色管理——从层级体系到最佳实践
数据库·笔记·postgresql
stuartevil4 小时前
零基础怎么用AI文生漫剧做出一条完整视频
人工智能·音视频
极客猴子4 小时前
能提取抖音视频文案的APP推荐:短视频文案工具合集
人工智能·智能手机·音视频·语音识别
sel_94 小时前
【强化学习】Hands-on Modern RL项目实践|OPD 算法完整解析
人工智能·深度学习·算法·机器学习·语言模型