1. 전체 흐름
클라이언트 → Gateway (HTTP 요청)
Gateway Controller → AuthService 호출
AuthService → user 서비스로 TCP 요청 전송
User 서비스 → 실제 회원가입/로그인 로직 처리
응답 → Gateway → 클라이언트 반환
2. 코드 흐름과 설명
(1) Gateway app.module.ts
환경변수를 ConfigModule로 관리합니다.
ClientsModule.registerAsync로 user 서비스와 TCP 연결을 설정합니다.
Joi 스키마를 이용해 필수 환경변수를 검증합니다.
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
HTTP_PORT: Joi.number().required(),
USER_HOST: Joi.string().required(),
USER_TCP_PORT: Joi.number().required(),
}),
}),
ClientsModule.registerAsync({
clients: [
{
name: USER_SERVICE,
useFactory: (cs: ConfigService) => ({
transport: Transport.TCP,
options: {
host: cs.getOrThrow('USER_HOST'),
port: cs.getOrThrow('USER_TCP_PORT'),
},
}),
inject: [ConfigService],
},
],
isGlobal: true,
}),
AuthModule,
],
})
export class AppModule {}
???? 보완 설명: isGlobal: true를 지정하면 ConfigModule과 ClientModule을 앱 전체에서 재사용할 수 있습니다. 따라서 각 모듈마다 중복 선언할 필요가 없습니다.
(2) main.ts
전역 파이프 ValidationPipe를 사용하여 DTO 유효성 검사를 자동으로 처리합니다.
환경 변수 HTTP_PORT로 실행 포트를 제어합니다.
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(process.env.HTTP_PORT ?? 3000);
}
bootstrap();
???? 보완 설명: ValidationPipe는 class-validator와 함께 동작하여 DTO에 지정된 검증 규칙(@IsString(), @IsNotEmpty() 등)을 자동 적용합니다.
(3) AuthController
@Authorization() 데코레이터를 통해 요청 헤더에서 토큰을 가져옵니다.
토큰이 없으면 UnauthorizedException을 발생시킵니다.
실제 로직은 Gateway가 아니라 User 서비스가 담당합니다.
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('register')
registerUser(@Authorization() token: string, @Body() dto: RegisterDto) {
if (!token) throw new UnauthorizedException('토큰이 필요합니다');
return this.authService.register(token, dto);
}
@Post('login')
loginUser(@Authorization() token: string) {
if (!token) throw new UnauthorizedException('토큰이 필요합니다');
return this.authService.login(token);
}
}
???? 보완 설명: Controller는 단순히 요청을 검증하고 Service에 전달하는 역할만 맡습니다. 비즈니스 로직은 없습니다.
(4) AuthService
User 서비스와의 TCP 통신을 담당합니다.
ClientProxy.send()로 메시지를 보내고 lastValueFrom()으로 응답을 Promise로 변환합니다.
@Injectable()
export class AuthService {
constructor(@Inject(USER_SERVICE) private readonly userMicroservice: ClientProxy) {}
register(token: string, dto: RegisterDto) {
return lastValueFrom(
this.userMicroservice.send({ cmd: 'register' }, { ...dto, token }),
);
}
login(token: string) {
return lastValueFrom(
this.userMicroservice.send({ cmd: 'login' }, { token }),
);
}
}
???? 보완 설명: 실제 운영 환경에서는 timeout() 연산자를 추가해 무한 대기 상황을 방지하는 것이 좋습니다. 또한 에러가 발생했을 때 Gateway에서 클라이언트에게 적절한 예외를 반환해야 합니다.
(5) Authorization 데코레이터
HTTP 요청의 authorization 헤더 값을 추출합니다.
export const Authorization = createParamDecorator((data: any, ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest();
return req.headers['authorization'];
});
???? 보완 설명: 일반적으로 Bearer <token> 형태로 전달되므로, Bearer 접두어를 잘라내고 순수 토큰만 전달하도록 개선하는 것이 바람직합니다.
(6) DTO
Gateway용 DTO
클라이언트로부터 입력값을 검증합니다.
export class RegisterDto {
@IsString() @IsNotEmpty() name: string;
@IsNumber() @IsNotEmpty() age: number;
@IsString() @IsNotEmpty() profile: string;
}
User 서비스용 DTO
Gateway에서 합쳐진 payload(token 포함)를 검증합니다.
export class RegisterDto {
@IsString() token: string;
@IsString() name: string;
@IsNumber() age: number;
@IsString() profile: string;
}
???? 보완 설명: Gateway와 User 서비스에서 사용하는 DTO는 목적이 다르므로 분리하는 것이 바람직합니다.
3. 최종 정리
Gateway: 요청 수신 → 토큰 검증 → user 서비스로 메시지 전달.
User 서비스: 실제 회원가입/로그인 로직 수행.
이점: 관심사 분리, 서비스 확장성, 보안/로깅 일원화.
보완점: 토큰 파싱, 예외 처리, 통신 타임아웃.
4. 체크리스트
Gateway에 AuthController 추가
Authorization 데코레이터 구현
AuthService에서 user 서비스와 TCP 통신 구현
Gateway/User 서비스 DTO 분리
ConfigModule로 환경 변수 관리
ValidationPipe로 입력 검증
Bearer 토큰 파싱 및 예외 처리 고려
타임아웃/에러 처리 보완













댓글 ( 0)
댓글 남기기