NestJS依赖注入优化:移除Resolver构造器冗余代码遇阻
问题:NestJS+DDD架构下依赖注入优化与报错解决
开发基于DDD架构的NestJS+GraphQL+PostgreSQL应用,已实现用户入库功能。当前存在两个问题:
- 表现层
UserResolver构造器需手动实例化UserService和UserApplication,代码冗余 UserModule的providers中必须加入Object才能编译
尝试通过自定义注入令牌优化后,运行时报错:
TypeError: Cannot read properties of undefined (reading 'create') at UserApplication.create (C:\path\api-service\src\application\user.ts:8:39) at UserResolver.createUser (C:\path\src\presentation\user\user.resolver.ts:20:43)...
现有代码结构
表现层 user.resolver.ts
@Resolver(of => UserBaseWithPassword) export class UserResolver { private readonly userApplication: UserApplication constructor(private readonly userRepo: UserPostgresql) { const userService = new UserService(userRepo) this.userApplication = new UserApplication(userService) } @Query(returns => String) users() : string { return ""; } @Mutation(returns => String) async createUser(@Args('createUserDTO') createUserWithPasswordDTO: CreateUserWithPasswordDTO): Promise<string> { return await this.userApplication.create(createUserWithPasswordDTO=createUserWithPasswordDTO) } }
应用层 user.ts
export class UserApplication{ constructor(private userService: UserService) {} async create(createUserWithPasswordDTO: CreateUserWithPasswordDTO): Promise<string> { return await this.userService.create(createUserWithPasswordDTO=createUserWithPasswordDTO); } }
领域层 user.service.ts
@Injectable() export class UserService { constructor(private readonly userRepo: IUserRepo) {} async create(createUserWithPasswordDTO: CreateUserWithPasswordDTO): Promise<string> { this.validateEmail(createUserWithPasswordDTO.email) this.validatePassword(createUserWithPasswordDTO.password) try{ createUserWithPasswordDTO.password = await this.hashPassword(createUserWithPasswordDTO.password); return await this.userRepo.create(createUserWithPasswordDTO); }catch(e){ throw new Error("DEFAULT_USER_ERROR"+e) } } private async hashPassword(password: string): Promise<string> { const saltRounds = process.env.PASSWORD_SALT_ROUNDS; return await bcrypt.hash(password, saltRounds);; } private validateEmail(email: string): boolean { if(email.length < MIN_EMAIL_LENGTH) throw new Error(EMAIL_MIN_LENGTH_TEXT) if(email.length > MAX_EMAIL_LENGTH) throw new Error(EMAIL_MAX_LENGTH_TEXT) if(!EMAIL_REGEX.test(email)) throw new Error(EMAIL_REGEX_TEXT) return true; } private validatePassword(password: string): boolean { if(password.length < MIN_PASSWORD_LENGTH || password.length > MAX_PASSWORD_LENGTH) throw new Error(PASSWORD_LENGTH_ERROR_TEXT) if(!PASSWORD_HAS_NUMBER_REGEX.test(password)) throw new Error(PASSWORD_NOT_INCLUDES_NUMBER_TEXT) if(!PASSWORD_HAS_LETTER_REGEX.test(password)) throw new Error(PASSWORD_NOT_INCLUDES_LETTER_TEXT) return true; } }
领域层 user.repo.ts(接口)
import { CreateUserWithPasswordDTO } from "./dtos/create-user.dto"; export interface IUserRepo { create(createUserWithPasswordDTO: CreateUserWithPasswordDTO): Promise<string> }
基础设施层 repo.ts
@Injectable() export class UserPostgresql implements IUserRepo{ constructor(@InjectDataSource() private dataSource: DataSource) {} async create(createUserWithPasswordDTO: CreateUserWithPasswordDTO): Promise<string> { const parameters = [uuidv4(), createUserWithPasswordDTO.email, createUserWithPasswordDTO.password] const reply = await this.dataSource.query(CREATE_USER, parameters) return reply[0].id } }
当前 UserModule.ts
import * as dotenv from 'dotenv'; const ENV_FILE_PATH = './ci/postgres/.env' dotenv.config({path: ENV_FILE_PATH}); @Module({ imports: [TypeOrmModule.forFeature([User])], controllers: [], providers: [ Object, UserPostgresql, UserService, UserApplication, UserPostgresql, UserResolver ] }) export class UserModule {}
解决方案
1. 给UserApplication添加@Injectable()装饰器
报错核心原因是UserApplication未被Nest的依赖注入容器管理,导致其构造器中的userService无法被自动注入,出现undefined。
修改应用层user.ts:
import { Injectable } from '@nestjs/common'; import { UserService } from '../domain/user.service'; import { CreateUserWithPasswordDTO } from '../domain/dtos/create-user.dto'; @Injectable() // 必须添加此装饰器,让Nest管理该类的依赖 export class UserApplication{ constructor(private userService: UserService) {} async create(createUserWithPasswordDTO: CreateUserWithPasswordDTO): Promise<string> { return await this.userService.create(createUserWithPasswordDTO); } }
2. 完善自定义注入令牌配置
在领域层定义注入令牌(可单独创建文件或在user.repo.ts中添加):
// domain/user.repo.ts import { CreateUserWithPasswordDTO } from "./dtos/create-user.dto"; export const USER_REPO = Symbol('USER_REPO'); // 定义注入令牌 export interface IUserRepo { create(createUserWithPasswordDTO: CreateUserWithPasswordDTO): Promise<string> }
修改UserService的构造器,使用令牌注入:
// domain/user.service.ts import { Injectable, Inject } from '@nestjs/common'; import { IUserRepo, USER_REPO } from './user.repo'; @Injectable() export class UserService { constructor(@Inject(USER_REPO) private readonly userRepo: IUserRepo) {} // 其他代码保持不变 }
3. 清理并优化UserModule配置
移除冗余的Object和重复的UserPostgresql,配置令牌与实现类的映射:
import * as dotenv from 'dotenv'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './entities/user.entity'; import { UserPostgresql } from './infrastructure/repo'; import { UserService } from './domain/user.service'; import { UserApplication } from './application/user'; import { UserResolver } from './presentation/user/user.resolver'; import { USER_REPO } from './domain/user.repo'; const ENV_FILE_PATH = './ci/postgres/.env'; dotenv.config({ path: ENV_FILE_PATH }); @Module({ imports: [TypeOrmModule.forFeature([User])], controllers: [], providers: [ { provide: USER_REPO, useClass: UserPostgresql, // 令牌映射到PostgreSQL实现 }, UserService, UserApplication, UserResolver, // 若无需直接注入UserPostgresql,可移除该类的直接声明 ], }) export class UserModule {}
4. 简化UserResolver构造器
直接注入UserApplication,移除手动实例化逻辑:
@Resolver(of => UserBaseWithPassword) export class UserResolver { constructor(private readonly userApplication: UserApplication) {} @Query(returns => String) users(): string { return ""; } @Mutation(returns => String) async createUser(@Args('createUserDTO') createUserWithPasswordDTO: CreateUserWithPasswordDTO): Promise<string> { return await this.userApplication.create(createUserWithPasswordDTO); } }
验证效果
完成以上修改后:
UserResolver无需手动实例化依赖,代码冗余消除UserModule中无需再添加Object即可正常编译- 运行时依赖注入正常,不会再出现
Cannot read properties of undefined的报错
内容的提问来源于stack exchange,提问作者Bohdan Zdorovets
相关产品推荐
相关产品推荐

