E-commerce Application(Nest js Microservice) - 9. order-service와 catalog-service 통신(2)
#1 시나리오
이전 포스트에서는 order-service -> catalog-service의 데이터 동기화를 다뤄 봤습니다. 우선 데이터 동기화 부분에선 잘 이루어지는 모습이 보였지만 catalog-service에서의 에러 케이스와 주문 취소의 경우를 다루지 못했습니다. 이번 포스트에선 에러 케이스 이후 status의 ERROR_ORDER로의 상태 변화, 주문 취소 이후 status의 CANCEL_ORDER로의 상태 변화 이 두 가지를 다뤄 보도록 하겠습니다.
이런 식으로 에러 케이스가 발생할 경우 ERROR_ORDER라는 메시지를 큐로 보내고 status의 값 또한 ERROR_ORDER로 변화시킵니다. order-service는 ERROR_ORDER라는 메시지를 기다리고 있으니 이 메시지를 받게 되면 사용자에게 error의 메시지를 반환시켜주도록 하겠습니다.
그리고 주문 취소의 경우는 이전에 작성해둔 흐름도를 따라가도록 하고 재주문의 경우 CREATE_ORDER와 비슷한 로직이니 그대로 따라가도록 하겠습니다.
#2 ERROR_ORDER 구현
우선 저는 이번 메시지 패턴을 구현하면서 rabbitmq의 queue를 2개를 썼습니다. 1개로 양방향 통신을 해보려고 여러 시도를 해봤지만 module import에러가 계속해서 발생해서 차라리 2개를 order -> catalog, catalog -> order 따로 사용해봤습니다.
그러면 catalog -> order로 향하는 메시지 큐를 연동하도록 하겠습니다. 메시지 큐는 이전과 같은 방식으로 만들었고, catalog_queue라는 이름으로 만들었습니다. 메시지 큐 연동은 이전 포스트와는 반대 방향으로 코드를 작성했습니다.
- catalog-service - catalog.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CatalogEntity } from 'src/entity/catalog.entity';
import { CatalogController } from './catalog.controller';
import { CatalogService } from './catalog.service';
@Module({
  imports: [
    TypeOrmModule.forFeature([CatalogEntity]),
    ClientsModule.register([{
      name: 'catalog-service',
      transport: Transport.RMQ,
      options: {
        urls: ['CATALOG_QUEUE_URL'],
        queue: 'catalog_queue',
        queueOptions: {
          durable: false,
        },
      },
    }]),
  ],
  controllers: [CatalogController],
  providers: [CatalogService]
})
export class CatalogModule {}- order-service - app.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { OrderModule } from './order/order.module';
@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: '10a10a',
      database: 'orders',
      autoLoadEntities: true,
      synchronize: true,
    }),
    ClientsModule.register([{
      name: 'catalog-service',
      transport: Transport.RMQ,
      options: {
        urls: ['CATALOG_QUEUE_URL'],
        queue: 'catalog_queue',
        queueOptions: {
          durable: false,
        },
      },
    }]),
    OrderModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}- order-service - main.ts
import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const microservice = app.connectMicroservice({
    transport: Transport.RMQ,
    options: {
      urls: ['CATALOG_QUEUE_URL'],
      queue: 'catalog_queue',
      queueOptions: {
        durable: false,
      },
    },
  });
  await app.startAllMicroservices();
  await app.listen(7200);
}
bootstrap();여기까지 연동을 완료했고, 이제 에러 케이스인 1-4-1) ~ 1-4-2)를 구현하도록 하겠습니다.
1-4-1) 만약 qty > stock 즉, 주문량이 재고량보다 많다면 에러메시지를 반환합니다.
- catalog.service.ts
import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { InjectRepository } from '@nestjs/typeorm';
import { CatalogDto } from 'src/dto/catalog.dto';
import { CatalogEntity } from 'src/entity/catalog.entity';
import { ResponseCatalog } from 'src/vo/response.catalog';
import { Repository } from 'typeorm';
@Injectable()
export class CatalogService {
    constructor(
        @InjectRepository(CatalogEntity) private catalogRepository: Repository<CatalogEntity>,
        @Inject('catalog-service') private readonly client: ClientProxy    
    ) {}
    ...
    public async createOrderAndDecreaseStock(data: any): Promise<any> {
        const catalogEntity = await this.catalogRepository.findOne({ where: { productId: data.productId }});
        catalogEntity.stock -= data.qty;
        if(catalogEntity.stock < 0) {
            data.status = 'ERROR_ORDER';
            this.client.emit('ERROR_ORDER', data);
            
            catalogEntity.stock += data.qty;
            await this.catalogRepository.save(catalogEntity);
            
            return data.status + " : Product stock is less than 0";
        } else {
            await this.catalogRepository.save(catalogEntity);
            return catalogEntity;
        }
    }  
}1-4-2) 에러메시지(ERROR_ORDER)를 order-service에서 받는다면 status의 값을 ERROR_ORDER로 변경합니다.
- order.controller.ts
import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
import { EventPattern } from '@nestjs/microservices';
import { OrderDto } from 'src/dto/order.dto';
import { RequestCreate } from 'src/vo/request.create';
import { RequestUpdate } from 'src/vo/request.update';
import { ResponseOrder } from 'src/vo/response.order';
import { OrderService } from './order.service';
@Controller('orders')
export class OrderController {
    constructor(private readonly orderService: OrderService,) {}
   ...
    @EventPattern('ERROR_ORDER')
    public async errorOrder(data: any) {
        return await this.orderService.errorOrder(data);
    }
}- order.service.ts
import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { InjectRepository } from '@nestjs/typeorm';
import { OrderDto } from 'src/dto/order.dto';
import { OrderEntity } from 'src/entity/order.entity';
import { ResponseOrder } from 'src/vo/response.order';
import { Repository } from 'typeorm';
import { v4 as uuid } from 'uuid';
@Injectable()
export class OrderService {
    ...
    public async errorOrder(data: any): Promise<any> {
        const orderEntity = await this.orderRepository.findOne({ where: { orderId: data.orderId }});
        orderEntity.status = "ERROR_ORDER";
        await this.orderRepository.save(orderEntity);
        return "Product stock is less than 0 so please modifying qty, reorder this product";
    }
}이런 흐름대로 orderEntity의 값을 ERROR_ORDER로 바꾸어 주고, 다시 수정하게끔 구현을 마쳤습니다. 결과를 한번 보도록 하겠습니다.
product-001의 재고량은 100입니다. 에러 케이스를 테스트해보기 위해 qty를 110으로 맞추고 주문을 진행했습니다. 예상되는 결과값은 재고량의 변동이 없고, 주문상태가 ERROR_ORDER이겠죠. 데이터베이스를 살펴 본 결과 재고량 변동 X, 주문 상태는ERROR_ORDER임을 알 수 있습니다.
- 에러케이스인 경우 에러메시지를 postman에서 반환받아야 하는데 이 부분은 다음 포스트에서 고치도록 하겠습니다.
#3 주문 취소 구현
주문 취소를 구현하도록 하겠습니다. 다시 시나리오를 살펴보겠습니다.
2-1) order.status의 값이 CANCEL로 변경됩니다.
2-2) 메시지 큐로 order.status의 값이 CANCEL이 됐음을 알립니다.
2-3) catalog-service에서는 해당 메시지를 받고 stock의 값을 Rollback시킵니다.
일전에 OrderController에서 REST로 cancel메서드를 구현했고, 비즈니스 로직을 구현하는 OrderService부터 이 방식대로 구현을 진행하겠습니다.
2-1) order.status의 값이 CANCEL로 변경됩니다.
- order.service.ts
import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { InjectRepository } from '@nestjs/typeorm';
import { OrderDto } from 'src/dto/order.dto';
import { OrderEntity } from 'src/entity/order.entity';
import { ResponseOrder } from 'src/vo/response.order';
import { Repository } from 'typeorm';
import { v4 as uuid } from 'uuid';
@Injectable()
export class OrderService {
    constructor(
        @InjectRepository(OrderEntity) private orderRepository: Repository<OrderEntity>,
        @Inject('order-service') private readonly client: ClientProxy    
    ) {}
    ...
    public async cancelOrder(orderId: string): Promise<ResponseOrder> {
        try {
            const orderEntity = await this.orderRepository.findOne({ where: { orderId: orderId }});
            const responseOrder = new ResponseOrder();
            
            orderEntity.status = 'CANCEL_ORDER';
            await this.orderRepository.save(orderEntity);
            
            this.client.emit('CANCEL_ORDER', orderEntity);
            
            responseOrder.orderId = orderEntity.orderId;
            responseOrder.status = orderEntity.status;
            return responseOrder;
        } catch(err) {
            throw new HttpException(err, HttpStatus.BAD_REQUEST);
        }
    }
    ...2-2) 메시지 큐로 CANCEL_ORDER 메시지를 알립니다.
- catalog.controller.ts
import { Body, Controller, Get, Inject, Logger, Param, Patch, Post } from '@nestjs/common';
import { ClientProxy, EventPattern } from '@nestjs/microservices';
import { CatalogDto } from 'src/dto/catalog.dto';
import { RequestCreate } from 'src/vo/request.create';
import { RequestUpdate } from 'src/vo/request.update';
import { ResponseCatalog } from 'src/vo/response.catalog';
import { CatalogService } from './catalog.service';
@Controller('catalogs')
export class CatalogController {
    ...
    @EventPattern('CANCEL_ORDER')
    public async cancelOrderAndRollbackStock(data: any): Promise<any> {
        return await this.catalogService.cancelOrderAndRollbackStock(data);
    }
}2-3) catalog-service에서는 해당 메시지를 받고 stock의 값을 Rollback시킵니다.
- catalog.service.ts
import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { InjectRepository } from '@nestjs/typeorm';
import { CatalogDto } from 'src/dto/catalog.dto';
import { CatalogEntity } from 'src/entity/catalog.entity';
import { ResponseCatalog } from 'src/vo/response.catalog';
import { Repository } from 'typeorm';
@Injectable()
export class CatalogService {
    constructor(
        @InjectRepository(CatalogEntity) private catalogRepository: Repository<CatalogEntity>,
        @Inject('catalog-service') private readonly client: ClientProxy    
    ) {}
    ...
    public async cancelOrderAndRollbackStock(data: any): Promise<any> {
        const catalogEntity = await this.catalogRepository.findOne({ where: { productId: data.productId }});
        catalogEntity.stock += data.qty;
        await this.catalogRepository.save(catalogEntity);
        return "Successfully Rollback stock";
    }
}흐름도대로 잘 구현이 되었는지 결과를 한번 살펴보겠습니다.
product-002를 주문하였고, 재고량 파악 결과 stock의 갯수가 10개가 차감되었음을 볼 수 있습니다. 그리고 해당 주문 번호로 주문을 취소하였고, 다시 stock의 갯수가 10개가 증감이 되었음을 볼 수 있습니다.
#3 RE_ORDER 구현
RE_ORDER는 CREATE_ORDER랑 같은 흐름이므로 이를 바탕으로 구현을 하도록 하겠습니다.
- order.service.ts
import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { InjectRepository } from '@nestjs/typeorm';
import { OrderDto } from 'src/dto/order.dto';
import { OrderEntity } from 'src/entity/order.entity';
import { ResponseOrder } from 'src/vo/response.order';
import { Repository } from 'typeorm';
import { v4 as uuid } from 'uuid';
@Injectable()
export class OrderService {
    ...
    public async reOrder(orderId: string): Promise<ResponseOrder> {
        try {
            const orderEntity = await this.orderRepository.findOne({ where: { orderId: orderId }});
            const responseOrder = new ResponseOrder();
            
            orderEntity.status = 'RE_ORDER';
            this.client.emit("RE_ORDER", orderEntity);
            
            await this.orderRepository.save(orderEntity);
            responseOrder.orderId = orderEntity.orderId;
            responseOrder.status = orderEntity.status;
            return responseOrder;
        } catch(err) {
            throw new HttpException(err, HttpStatus.BAD_REQUEST);
        }
    }
    ...- catalog.controller.ts
import { Body, Controller, Get, Inject, Logger, Param, Patch, Post } from '@nestjs/common';
import { ClientProxy, EventPattern } from '@nestjs/microservices';
import { CatalogDto } from 'src/dto/catalog.dto';
import { RequestCreate } from 'src/vo/request.create';
import { RequestUpdate } from 'src/vo/request.update';
import { ResponseCatalog } from 'src/vo/response.catalog';
import { CatalogService } from './catalog.service';
@Controller('catalogs')
export class CatalogController {
    ...
    @EventPattern('RE_ORDER')
    public async reOrderAndDecreaseStock(data: any): Promise<any> {
        return await this.catalogService.reOrderAndDecreaseStock(data);
    }
}- catalog.service.ts
import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { InjectRepository } from '@nestjs/typeorm';
import { CatalogDto } from 'src/dto/catalog.dto';
import { CatalogEntity } from 'src/entity/catalog.entity';
import { ResponseCatalog } from 'src/vo/response.catalog';
import { Repository } from 'typeorm';
@Injectable()
export class CatalogService {
    ...
    public async reOrderAndDecreaseStock(data: any): Promise<any> {
        const catalogEntity = await this.catalogRepository.findOne({ where: { productId: data.productId }});
        catalogEntity.stock -= data.qty;
        await this.catalogRepository.save(catalogEntity);
        return "Complete Reorder";
    }
}
CANCEL_ORDER상태인 주문번호 69adadf6-c099-4d0b-a448-6c4a0aabbd97를 reorder를 진행하였고, postman에서는 결과값이 잘 반환되었습니다. 그리고 데이터베이스도 확인한 결과 RE_ORDER로 변경되었고, stock도 10이 차감이 된 상태입니다.
이렇게 총 3가지 케이스에 대한 메시지 기반 데이터 동기화를 구현해보았습니다. 우선 오늘 포스트를 작성하면서 수정하거나 구현해야할 상황은 ERROR_ORDER 대한 재주문 케이스입니다.
다음 포스트에서는 재주문 케이스에 관해 다뤄보도록 하겠습니다.
Author And Source
이 문제에 관하여(E-commerce Application(Nest js Microservice) - 9. order-service와 catalog-service 통신(2)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@biuea/E-commerce-ApplicationNest-js-Microservice-9.-order-service와-catalog-service-통신2저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
                                
                                
                                
                                
                                
                                우수한 개발자 콘텐츠 발견에 전념
                                (Collection and Share based on the CC Protocol.)