Skip to content

Commit

Permalink
feat: NestJS 의존성 주입에서 Symbol 활용하기 글 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
yujiniii committed Sep 2, 2024
1 parent 1e31d9a commit dfec097
Showing 1 changed file with 163 additions and 0 deletions.
163 changes: 163 additions & 0 deletions _posts/2024-08-31-nestjs-di.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
---
title: "NestJS 의존성 주입에서 Symbol 활용하기"
author: yujiniii
date: 2024-08-31 20:00:00 +0900
categories: [Backend, NestJS]
tags: ["backend", "node"]
img_path: "/assets/img/posts/"
pin: false
---
> 1. NestJS 의존성 주입(DI)에 대해 알아본다.
> 2. NestJS DI 방식과 `Symbol` 을 사용해야 하는 이유에 대해 알아본다.
# 의존성 주입이란?
의존성 주입은 클래스나 모듈이 필요한 의존성을 직접 생성하지 않고 외부에서 주입받는 디자인 패턴입니다.
이를 통해 코드의 결합도를 낮추고, 테스트와 유지보수를 쉽게 할 수 있습니다.

## injection scope
[NestJS::injection-scopes](https://docs.nestjs.com/fundamentals/injection-scopes)
> **Using singleton scope is recommended for most use cases.**
> Sharing providers across consumers and across requests means that an instance can be cached and its initialization occurs only once, during application startup.
| Scope | Description |
|-------------|-------------------------------------------------------------------------------------------------------------------------------------------|
| `DEFAULT` | 제공자의 단일 인스턴스는 전체 애플리케이션에 걸쳐 공유됩니다.(Singleton 스코프) <br> 인스턴스 수명은 애플리케이션 lifecycle 직접 연결됩니다. <br>애플리케이션이 부트스트랩되면 모든 싱글톤 provider가 인스턴스화됩니다. |
| `REQUEST` | 공급자의 새 인스턴스는 request 마다 전용으로 생성되며, request 처리를 완료한 후에는 인스턴스가 garbage-collected 됩니다. |
| `TRANSIENT` | Transient(임시) provider 는 consumer 간에 공유되지 않습니다. Transient provider를 주입하는 각각의 consumer는 새로운 전용 인스턴스를 받게 됩니다. |



# 종속성 주입하기

## **@Injectable()** 을 이용한 종속성 주입 예시

```ts
// user.repository.ts
@Injectable()
export class UserRepository extends Repository<User> {}
```

```ts
// user.service.ts
import {Injectable} from '@nestjs/common';

@Injectable()
export class UserService {
constructor(
private readonly UserRepository: UserRepository
) {}
}
```
이때 @Injectable() 키워드를 사용할 경우 NestJS의 IoC 컨테이너에 의해 의존성이 주입/관리됩니다.
> The @Injectable() decorator attaches metadata, which declares that Service is a class that can be managed by the Nest IoC container.
> [NestJS::providers#services](https://docs.nestjs.com/providers#services)
```ts
// user.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';

@Module({
imports: [],
controllers: [],
providers: [UserService, UserRepository],
})
export class UserModule {}
```
`UserService` 에서 `UserRepository` 를 사용하고 있기 때문에, provider 에 `UserRepository` 를 불러오지 않으면 에러가 발생합니다.




## **Symbol** 을 통해 의존성 주입 예시

> Symbol 은 고유한 값을 정의합니다.
> [geeksforgeeks::explain-the-symbol-type-in-typescript](https://www.geeksforgeeks.org/explain-the-symbol-type-in-typescript/)
```ts
// user.repository.ts

/*
* The Symbol.for() static method searches for existing symbols in a runtime-wide symbol registry with the given key and returns it if found.
* Otherwise a new symbol gets created in the global symbol registry with this key.
*/
export const USER_REPOSITORY = Symbol.for('USER_REPOSITORY');

@Injectable()
export class UserRepository extends Repository<User> {}
```

```ts
// user-repository.module.ts
import { Module } from '@nestjs/common';
import { USER_REPOSITORY, UserRepository } from './user.repository';

@Module({
providers: [
{
provide: USER_REPOSITORY,
useClass: UserRepository,
},
],
imports: [],
exports: [USER_REPOSITORY],
})
export class UserRepositoryModule {}

```

`USER_REPOSITORY` 키워드를 `Symbol` 로 선언하고 프로바이더로서 등록합니다.
> 사실 굳이 `UserRepositoryModule` 모듈로 분리할 필요는 없고, `UserModule` 에서 바로 프로바이더로 등록해도 무방합니다.([](#injectable-을-이용한-종속성-주입-예시)의 예시처럼..)
> 하지만 나중에 의존성이 꼬이는 것을 방지하려면 모듈을 세세하게 나누는게 좋습니다.

```ts
// user.module.ts

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

```ts
// user.service.ts
@Injectable()
export class UserService {
constructor(
@Inject(USER_REPOSITORY)
private readonly UserRepository: UserRepository
){}
}
```

서비스에서 `@Inject(USER_REPOSITORY)` 데코레이터를 사용하여 `UserRepository`를 주입받습니다.
이때 심볼이 가리키는 값의 타입과, 데코레이터 이하의 변수의 타입이 일치해야 합니다.

### @Inject() 을 사용해야 하는 이유
`UserRepositoryModule` 에서 프로바이더가 클래스 타입이 아닌 커스텀 토큰(심볼)으로 등록되었기 때문입니다.
[github::nest/inject.decorator.ts](https://github.com/nestjs/nest/blob/78408352d51098ff60782accc5b19a12e9fa40ae/packages/common/decorators/core/inject.decorator.ts#L28)
```ts
/**
* @param token lookup key for the provider to be injected (assigned to the constructor parameter).
*/
export declare function Inject<T = any>(token?: T): PropertyDecorator & ParameterDecorator;
```


### 왜 Symbol을 사용해야 하는가?
1. 네임스페이스 충돌 방지:
여러 모듈에서 동일한 이름의 프로바이더를 사용할 때 심볼을 사용하면 충돌을 방지할 수 있습니다.
또, 라이브러리 내의 예약어와 겹치는 경우에 발생하는 에러를 방지할 수 있습니다.
2. 유연성:
동일한 인터페이스나 타입을 구현하는 여러 프로바이더를 필요에 따라 교체하거나 다르게 설정할 수 있습니다.
단순히 클래스 이름 변경에도 유연하게 대응할 수 있습니다.



# 마무리
진짜 오랫동안 묵혀둔 글인데 마무리를 늦게 하게 되었다.. 정신차리자!ㅠㅠ

0 comments on commit dfec097

Please sign in to comment.