依赖注入:禁止 AI 硬编码实例化,提升可测试性与扩展性

AI 写代码时,非常喜欢一种"最快能跑"的方式,,例如:

python 复制代码
db = Database()
cache = RedisCache()
mailer = EmailService()
service = UserService()

哪里需要,就在哪里创建。

短期看很方便,但当项目越来越大以后,就会出现:

text 复制代码
数据库实例到处创建
缓存客户端到处创建
配置散落
Service 互相 new
测试时无法替换依赖

这类代码最明显的问题是:

对象把自己的依赖写死了。

DI(Dependency Injection,依赖注入) 要解决的,就是这件事。


📌 技术名片

DI / Dependency Injection / 依赖注入

指一个类或函数需要某个对象时,不由它自己创建,而是由外部把已经准备好的对象"注入"进来。

例如:

不推荐:

python 复制代码
class UserService:
    def __init__(self):
        self.db = UserDatabase()

推荐:

python 复制代码
class UserService:
    def __init__(self, db):
        self.db = db

区别只有一点:

UserService 不再决定数据库对象怎么创建。

它只负责:

使用数据库。


💡 一句话理解

可以把 DI 想象成一家餐厅。

厨师做菜需要:

text 复制代码
食材
锅
燃气
调料

正常情况下,是餐厅提前准备好这些东西。

厨师上班以后直接使用,而不是:

text 复制代码
每炒一道菜
 ↓
厨师自己去买锅
 ↓
自己开燃气账户
 ↓
自己找供应商买菜

软件也是一样。

业务类真正应该关心的是:

"我要使用什么能力?"

而不是:

"这个能力应该怎么创建?"

所以 DI 的核心可以概括为:

对象负责使用依赖,系统负责创建依赖。

这对 AI 编程尤其重要。因为如果不给规则,AI 很容易为了完成当前任务,直接在任何地方实例化它需要的对象。


一、反面教材:AI 的"自由发挥"

假设我们告诉 AI:

实现一个 AgentService,用于查询 Agent,并在 Agent 修改后清理缓存。

没有架构限制时,AI 很可能写成:

python 复制代码
class AgentService:

    def __init__(self):
        self.db = AgentDatabase()
        self.cache = RedisCache()

    async def get_agent(self, agent_id: int):
        return await self.db.get_agent(agent_id)

    async def update_agent(self, agent_id: int, data):
        agent = await self.db.update_agent(
            agent_id,
            data
        )

        await self.cache.delete(
            f"agent:{agent_id}"
        )

        return agent

乍一看完全合理,但问题已经出现了...


问题 1:数据库被写死了

python 复制代码
self.db = AgentDatabase()

意味着:

AgentService 永远只能使用这个 AgentDatabase

以后如果要:

text 复制代码
换数据库实现
使用 Mock Database 测试
增加数据库代理
更换连接池

都必须修改 AgentService


问题 2:缓存也被写死了

python 复制代码
self.cache = RedisCache()

以后如果项目想从:

text 复制代码
Redis

换成:

text 复制代码
内存缓存
其他缓存服务
测试用 Fake Cache

业务类也必须跟着修改。


问题 3:每个地方都可能重新创建连接

如果 AI 在很多 Service 中都写:

python 复制代码
Database()
Redis()
LLMClient()

可能导致:

text 复制代码
连接池重复创建
HTTP Client 重复创建
模型客户端重复创建
缓存实例重复创建

浪费系统资源。


问题 4:测试特别麻烦

假设现在要测试:

python 复制代码
AgentService.update_agent()

我们并不想真的:

text 复制代码
连接 SQLite
连接 Redis
初始化完整系统

只想给它一个假的数据库,但对象内部已经写死,测试就很难替换。


二、架构规则:不要让业务对象自己创建依赖

DI 最重要的规则其实很简单:

谁使用依赖,谁不要负责创建依赖。

创建工作应该集中到系统入口、容器或者框架的依赖管理机制中。

一个简单结构可以是:

text 复制代码
应用启动
   ↓
创建 Database
   ↓
创建 Cache
   ↓
创建 Repository
   ↓
创建 Service
   ↓
放入 DI Container
   ↓
业务代码按需获取

这里的:

DI Container

全称是:

Dependency Injection Container,依赖注入容器。

可以把它理解成:

统一管理对象创建和关系的"总仓库"。


规则 1:业务类不主动 new / 实例化基础设施

例如不要:

python 复制代码
class AgentService:

    def __init__(self):
        self.db = AgentDatabase()

而应该:

python 复制代码
class AgentService:

    def __init__(self, db):
        self.db = db

这样:

text 复制代码
AgentService

只知道:

我有一个 db 可以用。

至于这个 db:

text 复制代码
是不是 SQLite
是不是 PostgreSQL
是不是 Mock

都不是它关心的事。


规则 2:共享资源尽量集中创建

例如:

text 复制代码
Database Engine
Session Factory
HTTP Client
LLM Client
Cache Registry
Vector Store

这些对象通常不应该每个请求都重新创建。

更合理的是:

text 复制代码
应用启动
 ↓
创建一次
 ↓
统一复用

这既是 DI,也是资源生命周期管理。


规则 3:Service 的依赖从构造函数或容器获得

例如:

python 复制代码
class UserService:

    def __init__(self, user_db):
        self.user_db = user_db

或者:

python 复制代码
class AgentService:

    def __init__(self, container):
        self._agent_db = container.agent_db
        self._cache = container.cache

重点不是形式,而是:

依赖来自外部,而不是内部临时创建。


规则 4:API 不自己创建 Service

不要:

python 复制代码
@router.get("/agents")
async def list_agents():
    service = AgentService()

而应该:

python 复制代码
@router.get("/agents")
async def list_agents(
    service = Depends(get_service)
):
    ...

这里的 Depends 是 FastAPI 提供的:

Dependency Injection,依赖注入机制。

它负责在执行接口之前,把依赖准备好。


规则 5:禁止为了方便绕过容器

这一点对 AI 特别重要。

例如项目已经有:

python 复制代码
container.agent_service

AI 却为了方便重新写:

python 复制代码
service = AgentService(container)

技术上可能能运行,但会绕开统一生命周期管理。所以应该明确:

已有容器管理的对象必须复用,不允许随意重新实例化。


三、这样做有什么好处?

DI 经常被新人理解成:

"只是换一种传参数的方法。"

其实远不止如此。


1. 更容易替换实现

假设现在:

text 复制代码
AgentService
 ↓
SQLite Repository

以后需要换成:

text 复制代码
AgentService
 ↓
PostgreSQL Repository

如果依赖是注入的:

AgentService 本身可能根本不需要修改。

这就是:

低耦合。

也就是:

一个模块不应该死死绑定某个具体实现。


2. 更容易测试

正式运行时:

text 复制代码
AgentService
 ↓
Real Agent Database

测试时:

text 复制代码
AgentService
 ↓
Fake Agent Database

例如:

python 复制代码
fake_db = FakeAgentDatabase()

service = AgentService(
    db=fake_db
)

测试不需要真的启动数据库。

这就是 DI 对:

Testability,可测试性

最大的帮助之一。


3. 系统资源更容易统一管理

例如数据库 Engine:

text 复制代码
创建一次
 ↓
Repository 共用

而不是:

text 复制代码
UserService
 → 一个 Engine

AgentService
 → 又一个 Engine

ToolService
 → 再一个 Engine

尤其对于:

text 复制代码
数据库连接池
LLM Client
HTTP Client
向量数据库

统一生命周期非常重要。


4. 配置切换更简单

例如开发环境使用:

text 复制代码
Local LLM

生产环境使用:

text 复制代码
Cloud LLM

如果业务代码直接:

python 复制代码
client = OpenAIClient(...)

就会和具体实现绑定。

DI 后可以变成:

text 复制代码
开发环境
 ↓
LocalLLMClient

生产环境
 ↓
CloudLLMClient

业务逻辑只接收:

text 复制代码
LLM Client

而不用自己决定是谁。


5. AI 不容易偷偷制造新的基础设施

这是 AI 编程中特别有价值的一点。

如果没有 DI 规则,AI 遇到:

"这里需要数据库。"

很容易:

python 复制代码
db = Database(...)

遇到:

"这里需要缓存。"

又:

python 复制代码
cache = Redis(...)

最后整个项目出现很多重复对象。

有 DI 以后,AI 的第一反应应该变成:

项目里已经有这个依赖了吗?

而不是:

我怎么创建一个新的?


四、AI 最容易出现的"失控现场"

假设让 AI:

给 Agent 增加知识库检索能力。

没有 DI 规则时,很可能写:

python 复制代码
class AgentService:

    async def run(self, agent_id, query):

        llm = LLMClient(
            api_key=settings.api_key
        )

        vector_db = Chroma(
            persist_directory="./db"
        )

        retriever = RetrievalPipeline(
            vector_db=vector_db
        )

        agent = AgentRunner(
            llm=llm,
            retriever=retriever
        )

        return await agent.run(query)

它真的可能工作。但每调用一次:

text 复制代码
创建 LLMClient
创建 Vector DB
创建 RetrievalPipeline
创建 AgentRunner

同时:

text 复制代码
配置硬编码
对象生命周期混乱
缓存失效
测试困难

全部出现。

更麻烦的是,下一个 AI 看到这段代码以后可能继续模仿:

text 复制代码
Web Search
自己创建 Client

SQL Agent
自己创建 DuckDB

Knowledge Base
自己创建 Vector Store

最后系统彻底失去统一资源管理。


五、提示词落地:把 DI 规则告诉 AI

只写:

"使用依赖注入。"

仍然不够具体。可以直接写进项目规则:

text 复制代码
## 依赖注入规则

本项目使用依赖注入 (DI)。

规则:

- 业务类不得创建内部基础设施依赖项。
- 不得在业务方法内部直接实例化数据库、存储库、缓存、LLM 客户端、HTTP 客户端、向量存储或服务。
- 重用应用程序 ServiceContainer 管理的依赖项。
- 共享基础设施资源必须集中创建和管理。
- 服务应通过构造函数注入或现有应用程序容器接收所需的依赖项。
- FastAPI 路由必须使用项目现有的依赖项解析机制。
- 不得在 API 路由内部直接实例化 Service 类。
- 创建新对象之前,请检查 ServiceContainer 中是否已存在等效实例。
- 尊重容器管理的单例或共享资源的生命周期。
- 依赖项在测试中必须尽可能可替换。

然后具体任务可以这样写:

text 复制代码
为 Agent 增加新的业务能力。

严格遵守项目 Dependency Injection 规则:

1. 不要在 Router 或 Service 内创建数据库实例;
2. 不要直接实例化已有 Service;
3. 不要重新创建缓存、LLM Client、Vector Store 等共享资源;
4. 优先从 ServiceContainer 获取已有依赖;
5. FastAPI API 使用现有 Depends / get_service 方式获取 Service;
6. 如果确实需要新增共享依赖,应统一在 ServiceContainer 中创建并管理;
7. 业务类只负责使用依赖,不负责决定依赖如何构造。

实现前先检查:
app/core/service_container.py
相关 Service
相关 Router
现有 Repository / Runtime 实现。

这会明显改变 AI 的编码习惯。它不再问:

"怎么 new 一个?"

而是先问:

"项目已经把它放在哪里了?"


六、正面产出:miniagent 的 DI 是怎么做的?

miniagent 当前有一个非常明确的核心类:

text 复制代码
backend/app/core/service_container.py

文件本身的说明就是:

python 复制代码
# Application-level service container,
# Implement Dependency Injection.

即:

应用级 Service Container,用于实现依赖注入。


1. 数据库 Engine 只在容器集中创建

ServiceContainer 中先统一创建:

python 复制代码
self.engine = create_async_engine(
    database_url,
    echo=False,
    future=True,
)

self.session_factory = async_sessionmaker(
    bind=self.engine,
    ...
)

也就是说:

text 复制代码
Service A
Service B
Service C

不需要各自再:

python 复制代码
create_async_engine(...)

而是共享容器管理的数据库基础设施。


2. Repository 也统一创建

容器接着创建:

python 复制代码
self.user_db = AsyncUserDatabase(
    self.engine,
    self.session_factory
)

self.agent_db = AsyncAgentDatabase(
    self.engine,
    self.session_factory
)

self.tool_db = AsyncToolDatabase(
    self.engine,
    self.session_factory
)

以及知识库、Document、Chat、Role 等数据访问对象。

形成:

text 复制代码
ServiceContainer
       │
       ├── engine
       ├── session_factory
       │
       ├── user_db
       ├── agent_db
       ├── tool_db
       ├── kb_db
       └── ...

所有对象的来源非常清楚。


3. Service 继续由容器统一创建

例如:

python 复制代码
self.agent_service = AgentService(self)
self.llm_service = LLMService(self)
self.user_service = UserService(self)
self.tool_service = ToolService(self)
self.kb_service = KnowledgeBaseService(self)

于是整体关系变成:

text 复制代码
ServiceContainer
       │
       ├── Repository
       ├── Runtime
       ├── Cache
       ├── Registry
       └── Service

也就是说:

对象在哪里创建,由容器决定。


4. 例子:AgentService 自己不创建数据库

再看真实的:

text 复制代码
backend/app/services/admin/agent.py

AgentService 的构造函数是:

python 复制代码
def __init__(
    self,
    container: ServiceContainer,
) -> None:

    self._agent_db = container.agent_db
    self._user_agent_relation_db = (
        container.user_agent_relation_db
    )
    self._agent_tool_relation_db = (
        container.agent_tool_relation_db
    )
    self._tool_db = container.tool_db
    self._cache = container.object_cache_invalidator

这里没有:

python 复制代码
AsyncAgentDatabase(...)

也没有:

python 复制代码
CacheRegistry()

而是:

text 复制代码
容器已经准备好
        ↓
AgentService 获取并使用

这就是依赖注入。


5. 例子:FastAPI Router 也不自己创建 Service

再来看 Agent API。

它定义:

python 复制代码
def get_service(
    request: Request
) -> AgentService:

    return request.app.state.container.agent_service

接口则:

python 复制代码
async def create_agent(
    payload: AgentCreate,
    svc: AgentService = Depends(get_service),
    caller_id: int = Depends(_add),
):
    agent_out = await svc.create_agent(payload)

    return ApiResponse(
        data=agent_out
    )

这里的:

python 复制代码
Depends(get_service)

就是 FastAPI 的依赖注入机制。

可以理解成:

text 复制代码
HTTP Request
      ↓
FastAPI Depends
      ↓
get_service()
      ↓
ServiceContainer
      ↓
已经存在的 AgentService
      ↓
Router 使用

Router 没有:

python 复制代码
svc = AgentService(...)

6. 最终形成了怎样的依赖关系?

下面是 miniagent 的 依赖注入 / DI 实现原理图:

可以简化成:

flowchart TD START[&#34;Application Startup<br/>应用启动&#34;] CONTAINER[&#34;ServiceContainer<br/>依赖注入容器&#34;] DB[&#34;Database<br/>数据库&#34;] RUNTIME[&#34;Runtime<br/>运行时&#34;] CACHE[&#34;Cache<br/>缓存&#34;] REPO[&#34;Repository<br/>数据访问&#34;] REGISTRY[&#34;Registry<br/>注册中心&#34;] SERVICE[&#34;Service<br/>业务服务&#34;] DEPENDS[&#34;FastAPI Depends<br/>依赖注入&#34;] ROUTER[&#34;Router<br/>路由&#34;] START --> CONTAINER CONTAINER --> DB CONTAINER --> RUNTIME CONTAINER --> CACHE DB --> REPO RUNTIME --> REGISTRY CACHE --> REGISTRY REPO --> SERVICE REGISTRY --> SERVICE SERVICE --> DEPENDS DEPENDS --> ROUTER

这里最重要的一条原则就是:

依赖在外部创建,从外向内注入。

而不是:

text 复制代码
Router
 ↓
自己 new Service
 ↓
Service 自己 new Repository
 ↓
Repository 自己 new Database

七、 为什么这比内部实例化更好?

如果直接写成:

python 复制代码
class AgentService:

    def __init__(self):
        self._agent_db = AsyncAgentDatabase(...)
        self._tool_db = AsyncToolDatabase(...)
        self._cache = CacheInvalidator(...)

AgentService 就必须知道:

text 复制代码
Engine 怎么创建
SessionFactory 从哪里来
Cache Registry 怎么配置
Repository 怎么构造

它承担了太多本不属于自己的职责。而当前结构下:

text 复制代码
AgentService

只负责:

使用这些能力完成 Agent 业务。


八、这对 AI 编程意味着什么?

假设以后让 AI:

给系统增加 EmailService。

没有 DI 规则时,它可能:

python 复制代码
mailer = SMTPMailer(
    host=...,
    port=...
)

然后在很多地方重复出现。

有 DI 规则后,合理思路应该变成:

text 复制代码
1. 这是共享服务吗?
      ↓
2. 是否应该进入 ServiceContainer?
      ↓
3. 邮件 Client 在哪里创建?
      ↓
4. 哪些 Service 需要它?
      ↓
5. 通过容器注入使用

同样:

text 复制代码
Redis
LLM Client
Vector Store
Web Search Client
SQL Agent

都遵循相同原则。

于是 AI 的行为从:

"缺什么,我就现场造什么。"

变成:

"缺什么,我先检查系统是否已经提供。"

这其实是非常大的变化。


结语

DI 依赖注入经常被讲成一个很抽象的设计模式。

但对于 AI 编程,我们完全可以用一句话理解:

不要让业务代码自己创建它依赖的对象。

为什么?

因为一旦 AI 可以自由实例化:

text 复制代码
Database
Cache
LLM
HTTP Client
Repository
Service

它很容易为了当前功能快速创建新对象。几十次以后,系统就会出现:

text 复制代码
重复实例
重复配置
资源浪费
测试困难
替换困难
生命周期混乱

而 DI 给 AI 规定了一条非常重要的工程纪律:

text 复制代码
对象的创建
 ↓
集中管理

对象的使用
 ↓
按需注入

于是系统变成:

text 复制代码
ServiceContainer
负责"谁是谁、怎么创建"

Service
负责"怎么完成业务"

Router
负责"怎么接收请求"

AI 不再需要到处考虑:

"我要怎么创建 Database?"

它只需要关心:

"项目已经给我的 Database 应该怎么使用?"

所以:

DI 的真正价值,不只是少写几个 new,而是把"对象创建权"从业务代码手里收回来。

对于 AI 编程来说,这一点尤其重要。

禁止 AI 随意实例化,让依赖由架构统一供应。

这样才能获得真正的:

低耦合、可测试、可替换、可扩展。

开源代码


🪐祝您好运🪐

相关推荐
Dr.kangder3 小时前
嵌入式面试总结(二十一)——C语言关键字
c语言·开发语言·面试·职场和发展·架构·虚拟化
晚安code3 小时前
上下文工程是什么?从提示词工程到上下文,一文讲透 AI 不跑偏
ai编程
晓得迷路了3 小时前
栗子前端技术周刊第 142 期 - DeepSeek Harness、pnpm 12 RC、crypto‑js...
前端·javascript·ai编程
必须会一定会4 小时前
Node.js Agent Handoff 仓库扫描 MVP:忽略规则、include 通配与稳定输出实现
人工智能·node.js·ai编程
cxr8284 小时前
HyperMind Lab M1 架构地基 Implementation Plan <二>
开发语言·人工智能·架构
AINative软件工程4 小时前
Agent 上下文账本工程:别让工具结果把 128K 窗口塞成垃圾场
后端·架构·ai编程
卡布叻_星星4 小时前
后端架构笔记之Maven多模块与微服务
笔记·架构
程序员黑豆4 小时前
Java正则表达式详解
java·前端·ai编程
特立独行的猫a13 小时前
一切皆插件:DeepSeek Harness 的架构哲学,以及与主流 Agent 的对比
人工智能·架构·agent·deepseek·harness