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#01
GET /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 규칙에서 loginrecent 역시 그 자체로 구분이 되니 순서는 상관없다.

하지만 :userId는 반드시 loginrecent 뒤에 위치시켜야 한다.

그렇지 않으면 :userId 규칙이 loginrecent 규칙을 모두 가로챈다.

이것은 알면서도 자꾸 당하게 된다.

Decorator VS Annotation

이것은 프레임워크의 차이가 아니라 언어의 차이이다.

Java의 @Annotation 과 유사한 기능을 하는 함수 구현체를 typescript에서는 @Decorator()라고 부른다.

AOP의 구현 수단이라고 이해하면 된다.

Java의 @annotaion와 달리 항상 함수처럼 호출하는 식이다. like @Injectable()

Dependency Injection

Springboot에서는 @Autowired를 사용하거나 생성자에 필요한 컴포넌트를 인자로 정의하면 자동으로 의존성 주입이 완성된다.

CustomerService.java
public 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 도구까지 제공하고 있다.

https://docs.nestjs.com/cli/overview

일일이 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을 간략히 표현한 것.

자세한 사용법은 아래 링크를 참조