Nodejs

 

 

사용자 정보 가져오기 로직은 Order 서비스, User 서비스, Auth 서비스를 오가며 작동합니다. 핵심 구조는 아래와 같습니다.

apps/
 ├── order/
 │   ├── order.service.ts
 │   └── exception/
 │       ├── payment-cancelled.exception.ts
 │       └── payment-failed.exception.ts
 ├── user/
 │   ├── user.controller.ts
 │   └── dto/
 │       └── get-user-info.dto.ts
 └── auth/
     ├── auth.controller.ts
     └── dto/
         └── parse-bearer-token.dto.ts
libs/
 └── common/
     └── interceptor/
         ├── rpc.interceptor.ts
         └── index.ts

 

 사용자 정보 가져오기 로직

사용자 정보는 주문(Order) 서비스에서 결제를 진행할 때 반드시 필요합니다. OrderService에서는 전달받은 토큰을 기반으로 사용자 정보를 가져옵니다. 이 과정은 다음 단계로 이뤄집니다.

 

1. 토큰 검증 요청 (Auth 서비스)

  • parse_bearer_token 메시지를 UserServiceClient를 통해 전송.

  • Auth 서비스는 JWT 토큰을 파싱하고 payload 반환.

  • 파싱 실패 시 PaymentCancelledException 발생.

const resp = await lastValueFrom(
  this.userServiceClient.send({ cmd: 'parse_bearer_token' }, { token }),
);

if (resp.status === 'error') {
  throw new PaymentCancelledException(resp);
}

 

 

2. 사용자 정보 요청 (User 서비스)

  • 토큰에서 추출한 sub 값을 userId로 활용.

  • get_user_info 메시지를 User 서비스로 전송.

  • 사용자 정보를 반환받고, 실패 시 PaymentCancelledException 발생.

const userId = resp.data.sub;
const uResp = await lastValueFrom(
  this.userServiceClient.send({ cmd: 'get_user_info' }, { userId }),
);

if (uResp.status === 'error') {
  throw new PaymentCancelledException(uResp);
}

return uResp.data;

 

 

3. RpcInterceptor의 역할

모든 마이크로서비스 응답을 성공/실패 응답 객체로 표준화합니다.

  • 성공 시 { status: 'success', data }

  • 실패 시 { status: 'error', error }

return next.handle().pipe(
  map((data) => ({ status: 'success', data })),
  catchError((err) => throwError(() => new RpcException(err))),
);

 

 

4. 예외 처리 클래스

  • PaymentCancelledException: 결제 취소 시 발생.

  • PaymentFailedException: 결제 실패 시 발생.

export class PaymentCancelledException extends HttpException {
  constructor(message: any) {
    super(message, HttpStatus.BAD_REQUEST);
  }
}

코드 흐름 정리

  1. Order 서비스 → createOrder 호출.

  2. 내부에서 getUserFromToken 실행.

  3. Auth 서비스에 토큰 검증 요청.

  4. User 서비스에 사용자 정보 요청.

  5. Order 서비스에서 사용자 정보 반환받아 주문 생성 및 결제 로직 진행.

시퀀스 다이어그램 (Order → Auth → User)

sequenceDiagram
    participant Order
    participant Auth
    participant User

    Order->>Auth: parse_bearer_token (token)
    Auth-->>Order: { status: 'success', data: { sub: userId } }

    Order->>User: get_user_info (userId)
    User-->>Order: { status: 'success', data: UserInfo }

    Order-->>Order: createOrder 진행

이 다이어그램은 Order 서비스가 Auth 서비스를 통해 토큰을 검증한 뒤, User 서비스에서 실제 사용자 정보를 가져오는 전체 흐름을 시각적으로 보여줍니다.

참고용 전체 코드 + 해설

 

 

1. Insomnia 테스트 스크립트

insomnia.test("Status code is 201", function (){
    insomnia.response.to.have.status(201)

    const accessToken=insomnia.environment.get("host");
    console.log(":::::::::::::: :" ,accessToken);
    const body = insomnia.response.json();
    
    insomnia.environment.set("accessToken", body.accessToken);
    insomnia.environment.set("refreshToken", body.refreshToken);
})

 API 호출 후 반환된 accessToken, refreshToken을 환경 변수에 저장.

 

{
	"productIds": ["8cc28226-076e-4472-8b08-4e5c2b51ebc5", "39a9fe98-091a-4dba-a314-195fea19f48c"],
	"address":{
		"name" : "홍길동",
		"street" :"도산데로 14",
		"city" : "서울",
		"postalCode": "123123",
		"country":"대한민국"
	},
	"payment":{
		"paymentMethod": "CreditCard",
		"paymentName":"법인카드",
		"cardNumber":"123123123123",
		"expiryYear":"26",
		"expiryMonth":"12",
		"birthOrRegistration" :"9",
		"passwordTwoDigits":"12",
		"amount":30000
		
		
	}
	
	
}

 

 

 

 

 

2. PaymentDto

import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { PaymentMethod } from '../entity/payment.entity';

export class PaymentDto {
  @IsString()
  @IsNotEmpty()
  paymentMethod: PaymentMethod;

  @IsString()
  @IsNotEmpty()
  paymentName: string;

  @IsString()
  @IsNotEmpty()
  cardNumber: string;

  @IsString()
  @IsNotEmpty()
  expiryYear: string;

  @IsString()
  @IsNotEmpty()
  expiryMonth: string;

  @IsString()
  @IsNotEmpty()
  birthOrRegistration: string;

  @IsString()
  @IsNotEmpty()
  passwordTwoDigits: string;

  @IsNumber()
  @IsNotEmpty()
  amount: number;
}

???? 결제 요청 시 필요한 필드들을 정의하고 class-validator로 검증.

 

 

3. OrderService (핵심)

@Injectable()
export class OrderService {
  constructor(
    @Inject(USER_SERVICE) private readonly userServiceClient: ClientProxy,
  ) {}

  async createOrder(createOrderDto: CreateOrderDto, token: string) {
    const user = await this.getUserFromToken(token);
    // 이후 주문 생성 및 결제 로직...
  }

  async getUserFromToken(token: string) {
    const resp = await lastValueFrom(
      this.userServiceClient.send({ cmd: 'parse_bearer_token' }, { token }),
    );

    if (resp.status === 'error') {
      throw new PaymentCancelledException(resp);
    }

    const userId = resp.data.sub;
    const uResp = await lastValueFrom(
      this.userServiceClient.send({ cmd: 'get_user_info' }, { userId }),
    );

    if (uResp.status === 'error') {
      throw new PaymentCancelledException(uResp);
    }

    return uResp.data;
  }
}

???? Order 서비스가 Auth → User 서비스와 통신하여 사용자 정보 확보.

 

 

4. 예외 처리

export class PaymentCancelledException extends HttpException {
  constructor(message: any) {
    super(message, HttpStatus.BAD_REQUEST);
  }
}

export class PaymentFailedException extends HttpException {
  constructor(message: any) {
    super(message, HttpStatus.BAD_REQUEST);
  }
}

???? 결제 실패 및 취소 상황에서 호출.

 

 

5. AuthController

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('register')
  registerUser(
    @Authorization() token: string,
    @Body() registerDto: RegisterDto,
  ) {
    if (token === null) {
      throw new UnauthorizedException('토큰을 입력해주세요!!!');
    }
    return this.authService.register(token, registerDto);
  }

  @Post('login')
  loginUser(@Authorization() token: string) {
    if (token === null) {
      throw new UnauthorizedException('토큰을 입력해주세요!');
    }
    return this.authService.login(token);
  }

  @MessagePattern({ cmd: 'parse_bearer_token' })
  @UsePipes(ValidationPipe)
  @UseInterceptors(RpcInterceptor)
  parseBearerToken(@Payload() payload: ParseBearerTokenDto) {
    return this.authService.parseBearerToken(payload.token, false);
  }
}

???? Auth 서비스가 JWT 파싱을 담당.

 

 

6. UserController

@Controller()
export class UserController {
  constructor(private readonly userService: UserService) {}

  @MessagePattern({ cmd: 'get_user_info' })
  @UsePipes(ValidationPipe)
  @UseInterceptors(RpcInterceptor)
  getUserInfo(@Payload() data: GetUserInfoDto) {
    return this.userService.getUserById(data.userId);
  }
}

???? User 서비스가 userId를 받아 사용자 정보 반환.

 

 

7. GetUserInfoDto

export class GetUserInfoDto {
  @IsString()
  @IsNotEmpty()
  userId: string;
}

???? userId 필수값 검증.

 

 

8. RpcInterceptor

@Injectable()
export class RpcInterceptor implements NestInterceptor{
    intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> {
        return next.handle()
            .pipe(
                map((data) => {
                    const resp = {
                        status: 'success',
                        data,
                    };
                    return resp;
                }),
                catchError((err) => {
                    const resp = {
                        status: 'error',
                        error: err,
                    };
                    return throwError(()=> new RpcException(err))
                })
            )
    }
}

✅ 모든 RPC 응답을 일관된 구조로 변환.

추가 설명 (지식 보완)

  • 마이크로서비스 구조: NestJS 마이크로서비스는 MessagePattern 기반으로 RPC 통신을 수행합니다.

  • 에러 처리: RpcInterceptor를 통해 모든 응답을 status 기반으로 표준화.

  • 실무 팁: timeout, retry 연산자를 활용하여 네트워크 장애에도 안정성 확보.

 

 

이번 주제의 내용은 사용자 정보 가져오기 로직을 통해 마이크로서비스 간 데이터 흐름을 이해

핵심 포인트는:

  • Order 서비스가 Auth → User 서비스와 순차적으로 통신.

  • RpcInterceptor로 응답 구조 표준화.

  • 예외 처리를 통해 결제 안정성 확보.

이 과정을 통해, 주문 생성 시 사용자 인증 및 정보 확인 절차가 명확히 보장됩니다.

 

 

 

 

about author

PHRASE

Level 60  라이트

사람의 새끼는 서울로 보내고 마소 새끼는 시골로 보내라 , 사람은 대도시에 있어야 출세할 기회가 있다는 말.

댓글 ( 0)

댓글 남기기

작성