1. Interceptor란?
Interceptor는 gRPC 호출 전/후에 특정 로직을 삽입할 수 있는 기능입니다. 예를 들어:
요청마다 공통으로 필요한 Metadata를 추가
호출 추적(trace-id) 삽입
클라이언트 서비스 이름 전달
이런 기능들을 Interceptor에 정의해두면, 모든 서비스 호출에서 일관되게 적용할 수 있습니다.
2. traceInterceptor 구현
공통 모듈(@app/common/grpc/interceptor)에 traceInterceptor를 구현합니다. 이 Interceptor는 호출 시 client-service라는 Metadata를 자동으로 추가합니다.
import { InterceptingCall } from '@grpc/grpc-js';
export const traceInterceptor = (service: string) => (options, nextCall) => {
return new InterceptingCall(nextCall(options), {
start: function (metadata, listener, next) {
metadata.set('client-service', service);
next(metadata, listener);
},
});
};
이제 모든 gRPC 호출마다 서비스 이름이 자동으로 붙어 추적과 로깅에 활용할 수 있습니다.
3. constructMetadata 유틸리티
각 메서드 호출 시 trace-id, 호출 클래스, 호출 메서드 이름 등을 Metadata에 담아주는 유틸리티입니다.
import { Metadata } from '@grpc/grpc-js';
import { v4 } from 'uuid';
export const constructMetadata = (
callerClass: string,
callerMethod: string,
prevMetadata?: Metadata,
) => {
const metadata = prevMetadata ?? new Metadata();
// trace-id 없으면 새로 생성
const traceId = metadata.getMap()['trace-id'] ?? v4();
metadata.set('trace-id', traceId.toString());
metadata.set('client-class', callerClass);
metadata.set('client-method', callerMethod);
return metadata;
};
이 방식으로 각 요청마다 trace-id가 유지되고, 호출 경로를 쉽게 추적할 수 있습니다.
4. AppModule에서 Interceptor 적용
NestJS ClientsModule 설정에서 gRPC channelOptions에 Interceptor를 추가합니다.
ClientsModule.registerAsync({
clients: [
{
name: USER_SERVICE,
useFactory: (configService: ConfigService) => ({
transport: Transport.GRPC,
options: {
channelOptions: {
interceptors: [traceInterceptor('Gateway')],
},
package: UserMicroservice.protobufPackage,
protoPath: join(process.cwd(), 'proto', 'user.proto'),
url: configService.getOrThrow('USER_GRPC_URL'),
},
}),
inject: [ConfigService],
},
// Product, Order 서비스에도 동일 적용
],
isGlobal: true,
}),
Gateway에서 User, Product, Order 서비스로 호출할 때 자동으로 Interceptor가 적용됩니다.
5. AuthService와 Metadata 활용
AuthService는 constructMetadata를 활용하여 호출 시 필요한 Metadata를 구성합니다.
@Injectable()
export class AuthService implements OnModuleInit {
authService: UserMicroservice.AuthServiceClient;
constructor(
@Inject(USER_SERVICE)
private readonly userMicroservice: ClientGrpc,
) {}
onModuleInit() {
this.authService =
this.userMicroservice.getService<UserMicroservice.AuthServiceClient>(
'AuthService',
);
}
register(token: string, registerDto: RegisterDto) {
return lastValueFrom(
this.authService.registerUser(
{ ...registerDto, token },
constructMetadata(AuthService.name, 'register'),
),
);
}
login(token: string) {
return lastValueFrom(
this.authService.loginUser(
{ token },
constructMetadata(AuthService.name, 'login'),
),
);
}
}
이제 register와 login 호출마다 trace-id와 호출 정보가 Metadata에 자동으로 포함됩니다.
6. AuthController에서 Metadata 확인
Controller에서는 gRPC 요청이 들어올 때 전달된 Metadata를 확인할 수 있습니다.
@Controller('auth')
@UserMicroservice.AuthServiceControllerMethods()
export class AuthController implements UserMicroservice.AuthServiceController {
constructor(private readonly authService: AuthService) {}
registerUser(request: UserMicroservice.RegisterUserRequest) {
console.log(' * microService user registerUser token:', request.token);
if (request.token === null) {
throw new UnauthorizedException('토큰을 입력해주세요!!!');
}
return this.authService.register(request.token, request);
}
loginUser(request: UserMicroservice.LoginUserRequest, metadata: Metadata) {
console.log('로그인 시도 !!:', request);
console.log('✅ Metadata !!:', metadata);
if (request.token === null) {
throw new UnauthorizedException('토큰을 입력해주세요!');
}
return this.authService.login(request.token);
}
}
로그인 시도 시 Metadata에 포함된 client-service, trace-id 등을 확인할 수 있습니다.
7. 정리
gRPC Interceptor를 구현해 client-service Metadata를 자동 삽입하는 방법
constructMetadata 유틸리티로 trace-id 및 호출 정보 관리하기
AppModule에서 Interceptor를 적용하는 방법
AuthService와 Controller에서 Metadata 활용하기













댓글 ( 0)
댓글 남기기