Nodejs

 

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. 결론

 

핵심 흐름:

  1. User 서비스로 토큰 검증 및 사용자 정보 획득

  2. Product 서비스로 상품 정보 획득

  3. 금액 계산 및 검증

  4. MongoDB에 주문 생성

 

 

 

about author

PHRASE

Level 60  라이트

가르치는 데에도 역시 여러 가지 방법이 많다. 내가 탐탁하게 여기지 않아서 가르쳐 주지 않는다면 그것 역시 하나의 교육 방법일 따름이다. 그렇게 거절함으로써 당자를 격하게 하여 반성하게 하고, 또는 분발하도록 하는 것 역시 가르쳐 주는 방법의 하나가 될 것이라는 뜻. -맹자

댓글 ( 0)

댓글 남기기

작성