分层架构中各层之间怎么解耦才不会互相依赖?
最近在用 Vue 3 + TypeScript 重构一个项目,想按分层架构拆成 presentation、domain、infrastructure 三层。但写着写着发现 domain 层经常要引用 infrastructure 的接口实现,比如调用 API 的 service,感觉完全没解耦。
我试过用抽象接口隔离,但在 JS/TS 里没有真正的 interface 实现机制,最后还是直接 import 了具体类。比如:
import { UserService } from '@/infrastructure/UserService';
export class UserUseCase {
constructor() {
this.service = new UserService();
}
}
这样 domain 层就强依赖 infrastructure 了,违背了分层原则。有没有更合理的组织方式?
先说为什么会有这个问题。分层架构的核心原则是高层模块不应该依赖低层模块,两者都应该依赖抽象。你现在的问题是domain层直接依赖了infrastructure层的具体实现,违反了依赖倒置原则。
我来分步骤解释解决方案:
第一步,定义抽象接口。在domain层定义需要的服务接口,而不是直接引用实现。比如:
第二步,在infrastructure层实现这个接口:
第三步,关键来了,使用依赖注入而不是直接实例化。改造你的UserUseCase:
第四步,在应用入口处组装依赖:
这样做的好处是:
1. domain层只知道自己需要IUserService,完全不关心具体实现
2. 具体实现可以在运行时注入,方便测试时替换mock
3. 依赖方向正确:infrastructure依赖domain的接口定义
如果项目比较大,可以考虑用InversifyJS这类DI容器来管理依赖关系。不过对于中小项目,手动注入已经够用了。
顺便吐槽下,JS/TS的DI确实没有Java/C#那么优雅,但用这种模式已经能解决大部分解耦问题了。我以前也踩过直接import具体实现的坑,结果单元测试时mock起来简直要命。
具体做法是:
1. 在domain层定义抽象接口
2. infrastructure层实现这个接口
3. 关键来了,不要直接new,用构造函数注入
4. 在composition root(通常是main.ts或app.ts)组装依赖
这样domain层就只依赖抽象不依赖具体实现了。说实话第一次搞这个也觉得很麻烦,但项目大了之后真香。测试的时候mock起来也特别方便,不用再改业务代码。