You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS依赖注入优化:移除Resolver构造器冗余代码遇阻

问题:NestJS+DDD架构下依赖注入优化与报错解决

开发基于DDD架构的NestJS+GraphQL+PostgreSQL应用,已实现用户入库功能。当前存在两个问题:

  1. 表现层UserResolver构造器需手动实例化UserService和UserApplication,代码冗余
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 18:07:13