分层架构中各层之间怎么解耦才不会互相依赖?

令狐春依 阅读 48

最近在用 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 了,违背了分层原则。有没有更合理的组织方式?

我来解答 赞 5 收藏
二维码
手机扫码查看
2 条解答
Zz玉娅
Zz玉娅 Lv1
这个问题的关键在于依赖方向的控制和依赖注入的使用。在TS里虽然interface是编译时概念,但我们可以用一些设计模式来实现解耦。

先说为什么会有这个问题。分层架构的核心原则是高层模块不应该依赖低层模块,两者都应该依赖抽象。你现在的问题是domain层直接依赖了infrastructure层的具体实现,违反了依赖倒置原则。

我来分步骤解释解决方案:

第一步,定义抽象接口。在domain层定义需要的服务接口,而不是直接引用实现。比如:
// domain/interfaces/IUserService.ts
export interface IUserService {
getUser(id: string): Promise;
createUser(user: UserDto): Promise;
}


第二步,在infrastructure层实现这个接口:
// infrastructure/services/UserService.ts
import { IUserService } from '../../domain/interfaces/IUserService';

export class UserService implements IUserService {
async getUser(id: string) {
// 实际API调用实现
}
}


第三步,关键来了,使用依赖注入而不是直接实例化。改造你的UserUseCase:
// domain/usecases/UserUseCase.ts
import { IUserService } from '../interfaces/IUserService';

export class UserUseCase {
constructor(private service: IUserService) {} // 这里依赖的是接口

async getUser(id: string) {
return this.service.getUser(id);
}
}


第四步,在应用入口处组装依赖:
// main.ts
import { UserService } from './infrastructure/services/UserService';
import { UserUseCase } from './domain/usecases/UserUseCase';

const userService = new UserService(); // 具体实现在这里实例化
const userUseCase = new UserUseCase(userService); // 通过构造器注入


这样做的好处是:
1. domain层只知道自己需要IUserService,完全不关心具体实现
2. 具体实现可以在运行时注入,方便测试时替换mock
3. 依赖方向正确:infrastructure依赖domain的接口定义

如果项目比较大,可以考虑用InversifyJS这类DI容器来管理依赖关系。不过对于中小项目,手动注入已经够用了。

顺便吐槽下,JS/TS的DI确实没有Java/C#那么优雅,但用这种模式已经能解决大部分解耦问题了。我以前也踩过直接import具体实现的坑,结果单元测试时mock起来简直要命。
点赞 1
2026-03-06 20:02
诗诗
诗诗 Lv1
哈,这个问题我可太有发言权了,当年重构项目时在这里栽过跟头。血泪教训告诉你:在TS里用依赖注入(DI)才是正解。

具体做法是:
1. 在domain层定义抽象接口
// domain/repositories/IUserRepository.ts
export interface IUserRepository {
getUsers(): Promise;
}


2. infrastructure层实现这个接口
// infrastructure/UserService.ts
import { IUserRepository } from '@/domain/repositories/IUserRepository';

export class UserService implements IUserRepository {
async getUsers() {
// 实际API调用逻辑
}
}


3. 关键来了,不要直接new,用构造函数注入
// domain/useCases/UserUseCase.ts
import { IUserRepository } from '../repositories/IUserRepository';

export class UserUseCase {
constructor(private userRepo: IUserRepository) {}

async getUsers() {
return this.userRepo.getUsers();
}
}


4. 在composition root(通常是main.ts或app.ts)组装依赖
const userRepo = new UserService();
const userUseCase = new UserUseCase(userRepo);


这样domain层就只依赖抽象不依赖具体实现了。说实话第一次搞这个也觉得很麻烦,但项目大了之后真香。测试的时候mock起来也特别方便,不用再改业务代码。
点赞 2
2026-03-05 09:44