18. Execution context

Nest는 여러 애플리케이션 컨텍스트(예: Nest HTTP 서버 기반, 마이크로서비스 및 WebSockets 애플리케이션 컨텍스트)에서 작동하는 애플리케이션을 쉽게 작성하는 데 도움이 되는 여러 유틸리티 클래스를 제공한다. 이러한 유틸리티는 광범위한 컨트롤러 , 메서드  실행 컨텍스트 집합에서 작동할 수 있는 일반 가드, 필터 및 인터셉터를 구축하는 데 사용할 수 있는 현재 실행 컨텍스트에 대한 정보를 제공한다 .

 

이 장에서는 이러한 두 가지 클래스인 ArgumentsHostExecutionContext 다룹니다.

 

ArgumentsHost class

클래스 ArgumentsHost는 핸들러에 전달되는 인수를 검색하기 위한 메서드를 제공한다. 이를 통해 인수를 검색할 적절한 컨텍스트(예: HTTP, RPC(마이크로서비스) 또는 WebSocket)를 선택할 수 있다. 프레임워크는 일반적으로 host 매개변수로 참조되는 ArgumentsHost 인스턴스를 당신이 액세스하려는 위치에 제공한다. 예를 들어 예외 필터 catch()  메서드는 ArgumentsHost 인스턴스와 함께 호출된다 .

 

ArgumentsHost 는 단순히 핸들러의 인수에 대한 추상화 역할을 한다. 예를 들어, HTTP 서버 애플리케이션( @nestjs/platform-express사용 중인 경우) 의 경우 host개체는 Express의 [request, response, next]배열을 캡슐화 한다. 여기서 요청 개체는 request 이면 response는 응답 개체이며 next 는 애플리케이션의 요청-응답 주기를 제어하는 ​​함수다. 반면 GraphQL 애플리케이션 의 경우 host객체에는 [root, args, context, info] 배열이 포함된다.

 

현재 애플리케이션 컨텍스트

여러 애플리케이션 컨텍스트에서 실행되는 일반 가드 , 필터  인터셉터를 구축할 때 현재 메소드가 실행 중인 애플리케이션 유형을 결정하는 방법이 필요다. 다음 ArgumentsHostgetType() 메소드를 사용하여 이를 수행 한다.

if (host.getType() === 'http') {
  // do something that is only important in the context of regular HTTP requests (REST)
} else if (host.getType() === 'rpc') {
  // do something that is only important in the context of Microservice requests
} else if (host.getType<GqlContextType>() === 'graphql') {
  // do something that is only important in the context of GraphQL requests
}

힌트) GqpContextType 은 @nestjs/graphql 패키지에서 가져온다.

 

사용 가능한 애플리케이션 유형을 사용하면 아래와 같이 보다 일반적인 구성 요소를 작성할 수 있다.

 

호스트 핸들러 인수

핸들러에 전달되는 인수 배열을 검색하기 위한 한 가지 접근 방식은 host 개체의 getArgs()메서드를 사용하는 것이다.

const [req, res, next] = host.getArgs();

다음  getArgByIndex()  메소드를 사용하여 인덱스별로 특정 인수를 추출할 수 있다.

const request = host.getArgByIndex(0);
const response = host.getArgByIndex(1);

이 예제에서는 인덱스별로 요청 및 응답 개체를 검색했는데, 이는 애플리케이션을 특정 실행 컨텍스트에 연결하므로 일반적으로 권장되지 않는다. 대신, host 객체의 유틸리티 메서드 중 하나를 사용하여 애플리케이션에 적합한 애플리케이션 컨텍스트로 전환함으로써 코드를 더욱 강력하고 재사용 가능하게 만들 수 있다 . 컨텍스트 전환 유틸리티 메서드는 다음과 같다.

/**
 * Switch context to RPC.
 */
switchToRpc(): RpcArgumentsHost;
/**
 * Switch context to HTTP.
 */
switchToHttp(): HttpArgumentsHost;
/**
 * Switch context to WebSockets.
 */
switchToWs(): WsArgumentsHost;

이 메서드를 사용하여 이전 예제를 다시 작성해 보자. switchToHttp(). 헬퍼 host.switchToHttp()  호출은 HTTP 애플리케이션 컨텍스트에 적합한 HttpArgumentsHost  개체를 반환한다. 객체 HttpArgumentsHost에는 원하는 객체를 추출하는 데 사용할 수 있는 두 가지 유용한 방법이 있다. 또한 이 경우 Express 유형 assertion을 사용하여 기본 Express 유형 개체를 반환한다.

const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();
const response = ctx.getResponse<Response>();

마찬가지로 마이크로서비스 및 WebSocket 컨텍스트에서 적절한 개체를 반환하는 메서드 WsArgumentsHost 와 RpcArgumentsHost 도 있다.

export interface WsArgumentsHost {
  /**
   * Returns the data object.
   */
  getData<T>(): T;
  /**
   * Returns the client object.
   */
  getClient<T>(): T;
}

RpcArgumentsHost 를 사용하는 방법

export interface RpcArgumentsHost {
  /**
   * Returns the data object.
   */
  getData<T>(): T;

  /**
   * Returns the context object.
   */
  getContext<T>(): T;
}

 

ExecutionContext 클래스

ArgumentsHost  상속한 ExecutionContext 는 현재 실행 프로세스에 대한 추가 세부 정보를 제공한다 . 마찬가지로 Nest는 가드canActivate()  메서드  인터셉터의  intercept() 메서드와 같이 필요할 수 있는 위치에 ExecutionContext  인스턴스를 제공한다.

export interface ExecutionContext extends ArgumentsHost {
  /**
   * Returns the type of the controller class which the current handler belongs to.
   */
  getClass<T>(): Type<T>;
  /**
   * Returns a reference to the handler (method) that will be invoked next in the
   * request pipeline.
   */
  getHandler(): Function;
}

 getHandler()메서드는 호출될 핸들러에 대한 참조를 반환한다. 이 getClass() 메소드는 이 특정 핸들러가 속한 Controller 클래스의 유형을 반환한다. 예를 들어, HTTP 컨텍스트에서 현재 처리된 요청이 메서드 POST에 바인딩된 CatsController 의 create() 요청인 경우 getHandler() 메서드는 메서드에 대한 참조를 반환하고 getClass() 메서드는 CatsController 유형 (인스턴스 아님) 을 반환한다.

const methodKey = ctx.getHandler().name; // "create"
const className = ctx.getClass().name; // "CatsController"

현재 클래스와 핸들러 메서드 모두에 대한 참조에 액세스하는 기능은 뛰어난 유연성을 제공한다. 가장 중요한 것은 가드나 인터셉터 내에서 Reflector#createDecorator 의 생성된 데코레이터나, 내장 @SetMetadata() 데코레이터를 통해 생성된  메타데이터 세트에 액세스할 수 있는 기회를 제공한다는 것이다 . 아래에서 이 사용 사례를 다룬다.

 

리플렉션 및 메타데이터

Nest는 Reflector#createDecorator 메서드를 통해 생성된 데코레이터와 내장 @SetMetadata() 데코레이터를 통해 경로 핸들러에 맞춤 메타데이터를 첨부하는 기능을 제공한다. 이 섹션에서는 두 가지 접근 방식을 비교하고 가드 또는 인터셉터 내에서 메타데이터에 액세스하는 방법을 살펴보자.

 

Reflector#createDecorator 를 사용하여 강력한 형식의 데코레이터를 만들려면 Type 인수를 지정해야 한다. Roles 데코레이터, 예를 들어 문자열 배열을 인수로 사용하는 데코레이터를 만들어 보겠다.

// roles.decorator.ts

import { Reflector } from '@nestjs/core';

export const Roles = Reflector.createDecorator<string[]>();

여기서 데코레이터 Roles는 string[] 유형의 단일 인수를 취하는 함수다 .

이제 이 데코레이터를 사용하려면 핸들러에 주석을 달기만 하면 된다.

// cats.controller.ts

@Post()
@Roles(['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

여기에서는 해당 admin 역할을 가진 사용자만 이 경로에 액세스할 수 있음을 나타내는 Roles데코레이터 메타데이터를 create() 메서드에 연결했다.

경로의 역할(사용자 정의 메타데이터)에 액세스하기 위해 Reflector 헬퍼 클래스를 다시 사용합니다. Reflector 는 일반적인 방법으로 클래스에 주입할 수 있다.

// roles.guards.ts

@Injectable()
export class RolesGuard {
  constructor(private reflector: Reflector) {}
}

힌트) Reflector 클래스는 @nestjs/core 패키지에서 가져옴.

 

이제 핸들러 메타데이터를 읽으려면 get() 메소드를 사용면 된다.

const roles = this.reflector.get(Roles, context.getHandler());

Reflector#get 메서드를 사용하면 데코레이터 참조와 메타데이터를 검색할 컨텍스트 (데코레이터 대상) 라는 두 가지 인수를 전달하여 메타데이터에 쉽게 액세스할 수 있다. 이 예에서 지정된 데코레이터 Roles 는 다음과 같다 (위 roles.decorator.ts 파일 참조 ). 컨텍스트는 context.getHandler() 호출을 통해 제공되며 , 이로 인해 현재 처리된 경로 핸들러에 대한 메타데이터가 추출된다. 기억해야 할 것은 getHandler() 는 라우트 핸들러 함수에 대한 참조를 제공한다는 것이다.

 

또는 컨트롤러 수준에서 메타데이터를 적용하고 컨트롤러 클래스의 모든 경로에 적용하여 컨트롤러를 구성할 수 있다.

// cats.controller.ts

@Roles(['admin'])
@Controller('cats')
export class CatsController {}

이 경우 컨트롤러 메타데이터를 추출하기 위해 context.getClass()를 context.getHandler() 대신 Reflext#ge 메서드의 두 번째 인수(메타데이터 추출을 위한 컨텍스트로 컨트롤러 클래스를 제공하기 위해)로 전달한다.

// roles.guard.ts

const roles = this.reflector.get(Roles, context.getClass());

 

여러 수준에서 메타데이터를 제공할 수 있는 기능이 주어지면 여러 컨텍스트에서 메타데이터를 추출하고 병합해야 할 수도 있다. 클래스 Reflector는 이를 지원하는 데 사용되는 두 가지 유틸리티 메서드를 제공한다. 이러한 메서드는 컨트롤러와 메서드 메타데이터를 동시에 추출하고 다양한 방식으로 결합한다.

 

두 수준 모두에서 Roles 메타데이터를 제공한 다음 시나리오를 고려해보자.

// cats.controller.ts

@Roles(['user'])
@Controller('cats')
export class CatsController {
  @Post()
  @Roles(['admin'])
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }
}

기본 'user' 역할로 지정하고 특정 메서드에 대해 선택적으로 이를 재정의하려는 경우 아마도 해당 getAllAndOverride()메서드를 사용할 것이다.

const roles = this.reflector.getAllAndOverride(Roles, [context.getHandler(), context.getClass()]);

위의 메타데이터와 함께 create() 메소드의 컨텍스트에서 실행되는 이 코드가 있는 가드 roles는 ['admin'] 을 갖고 있을 것이다.

둘 다에 대한 메타데이터를 가져오고 병합하려면(이 방법은 배열과 객체를 모두 병합한다) getAllAndMerge()  메서드를 사용한다.

const roles = this.reflector.getAllAndMerge(Roles, [context.getHandler(), context.getClass()]);

 

낮은 수준의 접근 방식

앞서 언급했듯이 Reflector#createDecorator 를 사용하는 대신 내장 @SetMetadata()데코레이터를 사용하여 메타데이터를 핸들러에 연결할 수도 있다.

// cats.controller.ts

@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

힌트) @SetMetaData() 데코레이터는 @nest/common 패키지에서 가져온다.

 

위의 구성을 통해 roles 메타데이터( roles는 메타데이터 키이고 ['admin']는 관련 값)를 create()메서드에 연결했다. @SetMetadata()를 직접 사용할 수 도 있기는 하지만 경로에서 직접 사용하는 것은 좋지 않다 . 대신 아래와 같이 자신만의 데코레이터를 만들 수 있다.

// Roles.Decorator.ts

import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

이 접근 방식은 훨씬 더 깔끔하고 읽기 쉬우며 Reflector#createDecorator접근 방식과 다소 유사하다. 차이점은 @SetMetadata 메타데이터의 키와 값을 더 많이 제어할 수 있고 둘 이상의 인수를 사용하는 데코레이터를 만들 수도 있다는 것이다.

 

이제 사용자 정의 @Roles() 데코레이터가 있으므로 이를 사용하여 create() 메서드를 꾸밀 수 있다.

// cats.controller.ts

@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

경로의 역할(사용자 정의 메타데이터)에 액세스하기 위해 Reflector 헬퍼 클래스를 다시 사용한다.

// Roles.guard.ts

@Injectable()
export class RolesGuard {
  constructor(private reflector: Reflector) {}
}

이제 핸들러 메타데이터를 읽으려면 get() 메소드를 사용하면 된다.

const roles = this.reflector.get<string[]>('roles', context.getHandler());

 

'Backend(Framework) > NestJS 개요(공식문서 번역)' 카테고리의 다른 글

20. 테스트  (1) 2023.12.10
19. 수명 주기 이벤트  (0) 2023.12.03
17. Lazy-loading modules  (1) 2023.12.02
16. Module reference  (1) 2023.12.02
15. Circular Dependency  (1) 2023.12.02
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유