icon

모듈, 컨트롤러, 서비스의 기본 개념


 NestJS는 모듈, 컨트롤러, 서비스라는 세 가지 핵심 요소를 통해 애플리케이션을 구조화합니다.

 이들의 적절한 활용은 확장 가능하고 유지보수가 용이한 애플리케이션 개발의 기반이 됩니다.

모듈 (Modules)

 모듈은 NestJS 애플리케이션의 기본 구성 단위입니다.

 관련된 기능을 그룹화하고 구조화하는 데 사용됩니다.

 @Module 데코레이터

 모듈은 @Module 데코레이터를 사용하여 정의되며 다음과 같은 속성을 가집니다.

  • imports : 이 모듈에서 사용할 다른 모듈들을 지정
  • controllers : 이 모듈에 속한 컨트롤러들을 정의
  • providers : 이 모듈에서 사용할 서비스나 다른 프로바이더들을 정의
  • exports : 이 모듈에서 다른 모듈로 내보낼 프로바이더들을 지정

 예시

@Module({
  imports: [DatabaseModule],
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService]
})
export class UserModule {}

컨트롤러 (Controllers)

 컨트롤러는 들어오는 요청을 처리하고 클라이언트에 응답을 반환하는 역할을 합니다.

 기본 구조와 라우팅

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}
 
  @Get()
  findAll(): Promise<User[]> {
    return this.userService.findAll();
  }
 
  @Post()
  create(@Body() createUserDto: CreateUserDto): Promise<User> {
    return this.userService.create(createUserDto);
  }
}

 이 예시에서 @Controller('users') 데코레이터는 '/users' 경로에 대한 라우팅을 정의합니다.

 @Get()@Post() 데코레이터는 각각 GET 및 POST 요청을 처리하는 메소드를 지정합니다.

서비스 (Services)

 서비스는 비즈니스 로직을 포함하며 컨트롤러와 데이터 계층 사이의 중간자 역할을 합니다.

 서비스 구현 예시

@Injectable()
export class UserService {
  constructor(@InjectRepository(User) private userRepository: Repository<User>) {}
 
  async findAll(): Promise<User[]> {
    return this.userRepository.find();
  }
 
  async create(createUserDto: CreateUserDto): Promise<User> {
    const user = this.userRepository.create(createUserDto);
    return this.userRepository.save(user);
  }
}

 여기서 @Injectable() 데코레이터는 이 클래스가 NestJS의 의존성 주입 시스템에 의해 관리됨을 나타냅니다.

의존성 주입 (Dependency Injection)

 NestJS는 의존성 주입을 통해 컴포넌트 간의 결합도를 낮추고 테스트 용이성을 높입니다.

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}
  // ...
}

 이 방식으로 UserServiceUserController에 자동으로 주입됩니다.

모듈 간 의존성 관리

 모듈 간 의존성은 imports 배열을 통해 관리됩니다.

 순환 종속성 문제를 피하기 위해 다음과 같은 전략을 사용할 수 있습니다.

  1. 공통 모듈 생성
  2. forwardRef() 함수 사용
  3. 이벤트 기반 통신 활용

글로벌 모듈과 동적 모듈

 글로벌 모듈

 전체 애플리케이션에서 공유되어야 하는 모듈은 글로벌 모듈로 선언할 수 있습니다.

@Global()
@Module({
  providers: [CommonService],
  exports: [CommonService],
})
export class CommonModule {}

 동적 모듈

 동적 모듈은 실행 시점에 동적으로 모듈을 구성할 수 있게 해줍니다.

@Module({})
export class ConfigModule {
  static forRoot(options: ConfigOptions): DynamicModule {
    return {
      module: ConfigModule,
      providers: [
        {
          provide: 'CONFIG_OPTIONS',
          useValue: options,
        },
        ConfigService,
      ],
      exports: [ConfigService],
    };
  }
}

Best Practices와 주의사항

  1. 단일 책임 원칙 준수 : 각 모듈, 컨트롤러, 서비스는 명확히 정의된 하나의 책임을 가져야 합니다.
  2. 모듈의 적절한 분리 : 기능 단위로 모듈을 분리하여 코드의 재사용성과 유지보수성을 높입니다.
  3. 의존성 주입 활용 : 직접적인 인스턴스 생성 대신 의존성 주입을 사용하여 결합도를 낮춥니다.
  4. 비즈니스 로직은 서비스에 : 컨트롤러는 요청 처리와 응답 반환에 집중하고, 복잡한 비즈니스 로직은 서비스에 구현합니다.
  5. 적절한 예외 처리 : 각 계층에서 발생할 수 있는 예외를 적절히 처리하고 전파합니다.
  6. 테스트 용이성 고려 : 각 컴포넌트를 설계할 때 단위 테스트의 용이성을 고려합니다.
  7. 순환 종속성 주의 : 모듈 간 순환 종속성을 피하고, 필요한 경우 적절한 해결 전략을 사용합니다.
  8. 글로벌 모듈 남용 주의 : 글로벌 모듈은 필요한 경우에만 제한적으로 사용합니다.
  9. 동적 모듈의 적절한 활용 : 구성의 유연성이 필요한 경우에 동적 모듈을 활용합니다.
  10. 명확한 네이밍 규칙 : 모듈, 컨트롤러, 서비스의 이름은 그 역할을 명확히 나타내야 합니다.

 NestJS의 모듈, 컨트롤러, 서비스는 애플리케이션의 구조와 로직을 체계적으로 조직하는 데 핵심적인 역할을 합니다.

 이들을 효과적으로 활용함으로써 확장 가능하고 유지보수가 용이한 애플리케이션을 개발할 수 있습니다.

 모듈은 관련 기능을 그룹화하고, 컨트롤러는 HTTP 요청을 처리하며, 서비스는 비즈니스 로직을 캡슐화합니다.

 의존성 주입 시스템은 이들 컴포넌트 간의 결합도를 낮추고 테스트 용이성을 높입니다.