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.)