Spring boot와 NestJS의 차이점
NestJs는 Spring boot와 많은 면에서 유사하다. 하지만 자세히 알아두지 않으면 고생을 해야 하는 몇 가지 차이점에 대해서 설명한다.
차이점
handler 메소드의 순서
Spring boot와 달리 Controller 내에 선언된 handler 메소드의 순서가 중요하다.
아래의 NestJs의 Controller 구현을 살펴보자.
TS@Controller('customers')
export class CustomerController {
/**
* get document for the given region
*/
@Get(':docId/:region')
documentByRegion(
@Param('docId') documentId: string,
@Param('region') region: string) {}
/**
* return statistics data
*/
@Get('dashboard/stats')
findStats() {}
}
다음과 같이 통계 데이터에 대한 요청을 보내면 findStats
메소드는 호출되지 않는다.
REQ#01GET /customers/dashboard/stats HTTP/1.1
- 이 요청은
documentByRegion
에서 가로챈다.
Routing 규칙을 보면 /customers:docId/:region
규칙이 /customers/dashboard/stats
규칙보다 더 넓다.
Spring boot에서는 best match 로 요청 [REQ#01]을 dashboard/stats
으로 연결하지만, Nestjs에서는 Controller에 선언된 메소드의 순서대로 routing 규칙을 평가한다.
위에서부터 하나씩 검사하면서 /customer/:docId/:region
규칙이 요청 [REQ#01]에 해당하므로 뒤에 존재하는 더 구체적인 routing 규칙까지 평가하지 않는다.
다음과 같이 인자에 값이 연결된다.
- documentId === 'dashboard'
- region === 'stats'
handler 의 순서
더 구체적인 routing 규칙을 먼저 위치시킨다.
다음과 같은 routing 규칙들이 있을 때 어떻게 순서를 정해야 할까?
Routing rules@Post('login')
@Get('login')
@Get(':usderId')
@Get('recent')
@Post
와 @Get
은 method가 다르니 순서는 상관없다.
@Get routing 규칙에서 login
과 recent
역시 그 자체로 구분이 되니 순서는 상관없다.
하지만 :userId
는 반드시 login
과 recent
뒤에 위치시켜야 한다.
그렇지 않으면 :userId
규칙이 login
과 recent
규칙을 모두 가로챈다.
이것은 알면서도 자꾸 당하게 된다.
Decorator VS Annotation
이것은 프레임워크의 차이가 아니라 언어의 차이이다.
Java의 @Annotation
과 유사한 기능을 하는 함수 구현체를 typescript에서는 @Decorator()
라고 부른다.
AOP의 구현 수단이라고 이해하면 된다.
Java의 @annotaion
와 달리 항상 함수처럼 호출하는 식이다. like @Injectable()
Dependency Injection
Springboot에서는 @Autowired
를 사용하거나 생성자에 필요한 컴포넌트를 인자로 정의하면 자동으로 의존성 주입이 완성된다.
CustomerService.javapublic class CustomerService {
public CustomerService(KeyService keyService) {}
}
- 타입
KeyService
의 인스턴스가 생성자로 전달된다. - 지금은
@Autowired
조차 안쓴다.
NestJS에서는 controller, service, repository 이외에 module
이라는 구성 요소가 하나 더 존재한다.
[CustomerService.java]를 NestJS로 옮기면 다음과 같은 구조를 관행적으로 유지한다.
Module structure - customer
+- customer.controller.ts
+- customer.service.ts
+- customer.repository.ts
+- customer.module.ts
그리고 모듈은 다음과 같이 Dependency Injection될 클래스들을 등록해야 한다.
customer.module.ts@Module({
imports: [KeyModule],
controllers: [CustomerController],
providers: [CustomerService],
// exports: [],
})
export class SubjectsModule {}
- imports: [KeyModule] - 다른 모듈의 컴포넌트를 참조하려면 이와 같이 임포트에 입력해야 한다.
customer.module.ts
에서 KeyModule
을 import하고 있는데, Spring boot와 달리 다른 모듈의 service나 repository를 주입하려면 위와 같이 해당 moudule 을 import해야 한다.
CustomerService.java
를 NestJS로 바꾸면 아래와 같을 것이다.
customer.service.ts@Injectable()
export class CustomerService {
constructor(
readonly keyService: KeyService
) {}
@Injectable()
은 반드시 선언해주는게 좋다. Object graph를 추적해서 의존성 주입을 작동하게 하려면 클래스 앞에 입력해야 한다.- readonly - 자바의
final
키워드. 언어 차원에서 제공한다.
customer.service.ts
에서 KeySerivce를 사용하려면 key.module.ts
에서는 참조를 허용하도록 export
에 Service를 노출시켜야 한다.
key.module.ts@Module({
controllers: [KeyController],
providers: [KeyService],
exports: [KeyService], // 중요!
})
export class SubjectsModule {}
KeyService
를 노출시켜야 CustomerService 에서 참조할 수 있다.
그리고 다시 customer.module.ts
코드를 보면 참조할 Service를 임포트하는게 아니라 Service를 정의한 모듈을 임포트한다.
이게 상당히 귀찮은 작업이기 때문에 NestJS에서는 따로 CLI
도구까지 제공하고 있다.
일일이 controller, service, module을 코딩해서 만드는게 아니라 다음과 같이 입력해서 파일들을 생성하고 자동으로 모듈 그래프까지 업데이트 한다.
Example of CLI$ nest g s translation
=> creates translation.service.ts
$ nest g co translation
=> creates translation.controller.ts
nest generate service translation
을 간략히 표현한 것.
자세한 사용법은 아래 링크를 참조