E-commerce Application(Nest js Microservice) - 7. order-service

yellow_note·2021년 8월 24일
0

#1 order-service 생성


다음의 명령어로 order-service를 생성하도록 하겠습니다.

nest new order-service
nest generate module order
nest generate controller order
nest generate service order

E-commerce 어플리케이션에서 order-service가 담당하는 부분은 다음과 같습니다.

1) 유저가 order를 할 경우 catalog-service와 통신을 통해 order 생성
2) 유저가 order에 대한 정보를 알고 싶을 경우 order를 유저에게 반환
3) 유저가 order 정보를 수정할 경우 catalog-service와 통신을 통해 order 수정
4) 유저가 order를 취소할 경우 status값을 CANCEL_ORDER로 변경
5) 유저가 order를 재주문할 경우 status값을 RE_ORDER로 변경

그러면 상기 목적들을 중점적으로 order-service를 구현하도록 하겠습니다.

#2 order-service 구현

order-service도 마찬가지로 catalog-service와 비슷하므로 필요한 부분은 카피하면서 수정해나가는데 우선 rabbitmq에 관한 설정들은 하지않고 구현을 마친 후에 rabbitmq를 도입하도록 하겠습니다.

  • main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(7200);
}
bootstrap();
  • order.controller.ts
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
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,) {}

    @Get('health_check')
    public async status() {
        return await "It's working order-service";
    }

    @Post('create')
    public async create(@Body() requestCreate: RequestCreate) : Promise<ResponseOrder> {
        const orderDto = new OrderDto();

        orderDto.productId = requestCreate.productId;
        orderDto.productName = requestCreate.productName;
        orderDto.qty = requestCreate.qty;
        orderDto.unitPrice = requestCreate.unitPrice;
        orderDto.totalPrice = (Number(requestCreate.qty) * Number(requestCreate.unitPrice));
        orderDto.userId = requestCreate.userId;

        return await this.orderService.create(orderDto);
    }

    @Get(':orderId')
    public async getOrder(@Param('orderId') orderId: string): Promise<ResponseOrder> {
        return await this.orderService.getOrder(orderId);
    }

    @Get(':userId/getOrder')
    public async getOrdersByUserId(@Param('userId') userId: string): Promise<ResponseOrder[]> {
        return await this.orderService.getOrdersByUserId(userId);
    }

    @Patch(':orderId')
    public async updateOrder(
        @Param('orderId') orderId: string, 
        @Body() requestUpdate: RequestUpdate
    ): Promise<ResponseOrder> {
        const orderDto = new OrderDto();

        orderDto.orderId = orderId;
        orderDto.qty = requestUpdate.qty;

        return await this.orderService.updateOrder(orderDto);
    }

    @Post(':orderId/cancel')
    public async cancelOrder(@Param('orderId') orderId: string): Promise<ResponseOrder> {
        return await this.orderService.cancelOrder(orderId);
    }

    @Post(':orderId/reorder')
    public async reOrder(@Param('orderId') orderId: string) {
        return await this.orderService.reOrder(orderId);
    }
}

우선 컨트롤러에 대한 부분을 구현하였고, 이를 바탕으로 vo폴더, dto폴더, entity폴더를 만들어 request, response관련 객체, dto객체, entity객체들을 만들어 주고 mysql, 검증 관련 라이브러리들도 추가해주도록 하겠습니다.

npm i class-validator class-transformer
npm install --save @nestjs/typeorm typeorm mysql
  • app.module.ts
import { Module } from '@nestjs/common';
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: 'password',
      database: 'orders',
      autoLoadEntities: true,
      synchronize: true,
    }),
    OrderModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
  • order.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { OrderController } from './order.controller';
import { OrderService } from './order.service';

@Module({
  imports: [TypeOrmModule.forFeature([OrderEntity])],
  controllers: [OrderController],
  providers: [OrderService]
})
export class OrderModule {}
  • request.create.ts
import { IsNotEmpty, IsNumber, IsString } from "class-validator";

export class RequestCreate {
    @IsString()
    @IsNotEmpty()
    readonly productId: string;
    
    @IsString()
    @IsNotEmpty()
    readonly productName: string;
    
    @IsNumber()
    @IsNotEmpty()
    readonly stock: number;
    
    @IsNumber()
    @IsNotEmpty()
    readonly unitPrice: number;
    
    @IsNumber()
    @IsNotEmpty()
    readonly totalPrice: number;
    
    @IsString()
    @IsNotEmpty()
    readonly userId: string;
}
  • request.update.ts
import { IsNotEmpty, IsNumber } from "class-validator";

export class RequestUpdate {
    @IsNumber()
    @IsNotEmpty()
    readonly stock: number;

    @IsNumber()
    @IsNotEmpty()
    readonly totalPrice: number;
}
  • response.order.ts
import { IsNotEmpty, IsNumber, IsString } from "class-validator";

export class ResponseOrder {
    @IsString()
    orderId: string;
    
    @IsString()
    productId: string;
    
    @IsString()
    productName: string;
    
    @IsNumber()
    stock: number;
    
    @IsNumber()
    unitPrice: number;
    
    @IsNumber()
    totalPrice: number;
    
    @IsString()
    userId: string;
    
    @IsBoolean()
    status: string;
}
  • order.dto.ts
import { IsNumber, IsString } from "class-validator";

export class OrderDto {
    @IsString()
    orderId: string;
    
    @IsString()
    productId: string;
    
    @IsString()
    productName: string;
    
    @IsNumber()
    stock: number;
    
    @IsNumber()
    unitPrice: number;
    
    @IsNumber()
    totalPrice: number;
    
    @IsString()
    userId: string;
    
    @IsBoolean()
    status: string;
}
  • order.entity.ts
import { Column, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class OrderEntity {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    orderId: string;
    
    @Column()
    productId: string;
    
    @Column()
    productName: string;
    
    @Column()
    stock: number;
    
    @Column()
    unitPrice: number;
    
    @Column()
    totalPrice: number;
    
    @Column()
    userId: string;
    
    @Column()
    status: string;
}
  • order.service.ts
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
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>) {}

    public async create(orderDto: OrderDto): Promise<ResponseOrder> {
        try {
            const orderEntity = new OrderEntity();

            orderEntity.orderId = uuid();
            orderEntity.productId = orderDto.productId;
            orderEntity.productName = orderDto.productName;
            orderEntity.qty = orderDto.qty;
            orderEntity.unitPrice = orderDto.unitPrice;
            orderEntity.totalPrice = orderDto.totalPrice;
            orderEntity.userId = orderDto.userId;
            orderEntity.status = 'CREATE_ORDER';

            await this.orderRepository.save(orderEntity);
            
            const responseOrder = new ResponseOrder();

            responseOrder.orderId = orderEntity.orderId;
            responseOrder.productId = orderEntity.productId;
            responseOrder.productName = orderEntity.productName;
            responseOrder.qty = orderEntity.qty;
            responseOrder.unitPrice = orderEntity.unitPrice;
            responseOrder.totalPrice = orderEntity.totalPrice;
            responseOrder.userId = orderEntity.userId;
            responseOrder.status = orderEntity.status;

            return responseOrder;
        } catch(err) {
            throw new HttpException(err, HttpStatus.BAD_REQUEST);
        }
    }
    
    public async getOrder(orderId: string): Promise<ResponseOrder> {
        try {
            return await this.orderRepository.findOne({ where: { orderId: orderId }});
        } catch(err) {
            throw new HttpException(err, HttpStatus.BAD_REQUEST);
        } 
    }

    public async getOrdersByUserId(userId: string): Promise<ResponseOrder[]> {
        try {
            return await this.orderRepository.find({ where: { userId: userId }});
        } catch(err) {
            throw new HttpException(err, HttpStatus.BAD_REQUEST);
        } 
    }

    public async updateOrder(orderDto: OrderDto): Promise<ResponseOrder> {
        try {
            const orderEntity = await this.orderRepository.findOne({ where: { orderId: orderDto.orderId }});
            const responseOrder = new ResponseOrder();

            orderEntity.qty = orderDto.qty;
            orderEntity.totalPrice = (Number(orderDto.qty) * Number(orderEntity.unitPrice));
        
            await this.orderRepository.save(orderEntity);
            
            responseOrder.orderId = orderEntity.orderId;
            responseOrder.productName = orderEntity.productName;
            responseOrder.qty = orderEntity.qty;
            responseOrder.unitPrice = orderEntity.unitPrice;
            responseOrder.totalPrice = orderEntity.totalPrice;

            return responseOrder; 
        } catch(err) {
            throw new HttpException(err, HttpStatus.BAD_REQUEST);
        } 
    }

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

            responseOrder.orderId = orderEntity.orderId;
            responseOrder.status = orderEntity.status;

            return responseOrder;
        } catch(err) {
            throw new HttpException(err, HttpStatus.BAD_REQUEST);
        }
    }

    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';

            await this.orderRepository.save(orderEntity);

            responseOrder.orderId = orderEntity.orderId;
            responseOrder.status = orderEntity.status;

            return responseOrder;
        } catch(err) {
            throw new HttpException(err, HttpStatus.BAD_REQUEST);
        }
    }
}

이렇게 order-service의 crud를 위한 모듈들을 구현했고 결과를 확인해보겠습니다.

#3 order-service 결과

  • /orders/health_check
  • /orders/create
  • /orders/:orderId
  • /orders/:userId/getOrder
  • /orders/:orderId
  • /orders/:orderId/cancel
  • /orders/:orderId/reorder

order-serivce에 대한 결과값들이 잘 나옴을 확인할 수 있습니다. 이제 catalog-service와 message기반으로의 연동이 필요합니다. 고려해볼 사항은 다음과 같습니다.

1) order-service에서는 order를 생성 혹은 재주문할 경우 qty(주문할 제품의 갯수)라는 값이 필요합니다. 즉, order를 생성할 경우 catalog의 stock(제품 재고수)가 qty만큼 차감이 되어야겠죠.

2) order-service에서 order를 생성할 경우 qty가 catalog의 stock의 값보다 크다면 주문할 제품의 양이 없다는 것이므로 이에 대한 오류메시지가 필요합니다.

3) order-service에서 order를 취소할 경우 order가 없는 것으로 간주되므로 catalog의 stock수는 qty만큼 증감되어야 합니다.

우선 고려해볼 사항은 이 정도인 것 같습니다. 1, 2, 3번의 경우들은 전부 catalog-service와 order-service의 데이터 동기화에 대한 문제가 존재하는데요, 이 데이터 동기화에 대한 문제를 rabbitMQ의 메시지를 도입하여 해결해보도록 하겠습니다.

0개의 댓글

관련 채용 정보