NestJS에서 Order 생성하기
1. OrderController
@Controller('order')
export class OrderController {
  constructor(private readonly orderService: OrderService) {}
  @Post()
  @UsePipes(ValidationPipe)
  async createOrder(token: string, @Body() createOrderDto: CreateOrderDto) {
    return this.orderService.createOrder(createOrderDto, token);
  }
}
/order POST 요청으로 주문 생성
ValidationPipe를 적용해 DTO 유효성 검사 수행
토큰과 주문 정보를 OrderService에 전달
⚠️ 여기서 token을 단순 매개변수로 받는 것은 한계가 있습니다. 일반적으로 NestJS에서는 @Headers('Authorization') 데코레이터를 사용하거나 커스텀 데코레이터(@Authorization())를 만들어 토큰을 주입하는 것이 더 안전하고 일관적입니다.
2. OrderModule
@Module({
  imports: [
    MongooseModule.forFeature([{ name: 'Order', schema: OrderSchema }]),
  ],
  controllers: [OrderController],
  providers: [OrderService],
})
export class OrderModule {}
MongoDB에 Order 스키마를 등록
컨트롤러와 서비스를 모듈에 연결해 DI(의존성 주입) 가능
3. OrderService 핵심 로직
주문 생성 로직은 OrderService에 구현됩니다.
@Injectable()
export class OrderService {
  constructor(
    @Inject(USER_SERVICE) private readonly userService: ClientProxy,
    @Inject(PRODUCT_SERVICE) private readonly productService: ClientProxy,
    @InjectModel(Order.name) private readonly orderModel: Model<Order>,
  ) {}
  async createOrder(createOrderDto: CreateOrderDto, token: string) {
    const { productIds, address, payment } = createOrderDto;
    // 1) 사용자 정보 가져오기
    const user = await this.getUserFromToken(token);
    // 2) 상품 정보 가져오기
    const products = await this.getProductsByIds(productIds);
    // 3) 총 금액 계산하기
    const totalAmount = this.calculateTotalAmount(products);
    // 4) 금액 검증하기
    this.validatePaymentAmount(totalAmount, payment.amount);
    // 5) 주문 생성하기 (DB 저장)
    const customer = this.createCustomer(user);
    const order = await this.createNewOrder(customer, products, address, payment);
    // TODO: 6) 결제 시도, 7) 주문 상태 업데이트, 8) 최종 결과 반환
    return order;
  }
사용자 정보 가져오기
private async getUserFromToken(token: string) {
  const resp = await lastValueFrom(
    this.userService.send({ cmd: 'parse_bearer_token' }, { token }),
  );
  if (resp.status === 'error') {
    throw new PaymentCancelledException(resp);
  }
  const userId = resp.data.sub;
  const uResp = await lastValueFrom(
    this.userService.send({ cmd: 'get_user_info' }, { userId }),
  );
  if (uResp.status === 'error') {
    throw new PaymentCancelledException(uResp);
  }
  return uResp.data;
}
User MS에 parse_bearer_token 메시지를 보내 토큰 유효성 검증
검증 후 get_user_info 메시지로 사용자 세부 정보를 획득
잘못된 토큰이거나 유저가 없을 경우 PaymentCancelledException 발생
상품 정보 가져오기
private async getProductsByIds(productIds: string[]): Promise<Product[]> {
  const resp = await lastValueFrom(
    this.productService.send({ cmd: 'get_products_info' }, { productIds }),
  );
  if (resp.status === 'error') {
    throw new PaymentCancelledException('상품 정보가 잘못됐습니다!');
  }
  return resp.data.map((product) => ({
    productId: product.id,
    name: product.name,
    price: product.price,
  }));
}
Product MS에 get_products_info 메시지를 보내 상품 정보 확인
응답이 유효하지 않으면 주문을 취소
받은 데이터를 Product 엔티티 형식으로 변환
총 금액 계산 및 검증
private calculateTotalAmount(products: Product[]) {
  return products.reduce((total, product) => total + product.price, 0);
}
private validatePaymentAmount(totalA: number, totalB: number) {
  if (totalA !== totalB) {
    throw new PaymentCancelledException('결제 금액이 일치하지 않습니다!');
  }
}
상품 가격 합산 후, 프론트에서 전달한 결제 금액과 비교 검증
불일치 시 예외 발생 → 보안 및 무결성 보장
주문 생성
private createCustomer(user: { id: string; email: string; name: string }) {
  return { userId: user.id, email: user.email, name: user.name };
}
private createNewOrder(
  customer: Customer,
  products: Product[],
  deliveryAddress: AddressDto,
  payment: PaymentDto,
) {
  return this.orderModel.create({ customer, products, deliveryAddress, payment });
}
사용자 정보를 Customer 객체로 변환
MongoDB에 주문 데이터를 저장
4. 실무적 고려 사항
토큰 주입 방식 : 현재는 단순 token: string 인자로 받지만, 실무에서는 @Headers('Authorization') 또는 커스텀 데코레이터를 이용하는 것이 더 안전합니다.
트랜잭션 처리 : 주문 생성과 결제는 반드시 원자성을 보장해야 하므로, 이후 단계에서는 Saga 패턴 또는 이벤트 기반 아키텍처를 적용하는 것이 권장됩니다.
에러 핸들링 : 단순히 PaymentCancelledException만 던지는 대신, 에러 코드와 메시지를 세분화해 클라이언트에서 구체적인 대응을 할 수 있도록 해야 합니다.
성능 최적화 : Product 서비스에 다수의 상품 ID를 전달할 경우, batch 쿼리나 캐싱을 고려할 수 있습니다.
5. 결론
핵심 흐름:
User 서비스로 토큰 검증 및 사용자 정보 획득
Product 서비스로 상품 정보 획득
금액 계산 및 검증
MongoDB에 주문 생성













댓글 ( 0)  
댓글 남기기