E-commerce Application(Nest js Microservice) - 5. Catalog-Service(1)

yellow_note·2021년 8월 20일
0
post-thumbnail

#1 Catalog-Service

카탈로그 서비스는 상품의 재고 파악에 대한 서비스입니다. 시나리오를 살펴 보겠습니다.

1) 유저는 a라는 상품 재고를 파악하고 싶습니다.
2) 상품 재고 파악을 위해 catalog-service에 요청을 합니다.
3) catalog-service는 a라는 상품의 재고 상태를 파악하여 유저에게 보여줍니다.

간단하게 시나리오를 살펴보면 우선 auth-service와 catalog-service가 서로 통신을 해야될 필요가 있을 것 같습니다. 왜냐하면 이전 auth-service에서는 catalog에 관한 어떠한 것도 만들지 않았기 때문이죠. 그렇게 auth-service에서 catalog-service에게 정보를 요청하고 정보를 받아오게끔 만들면 될 것 같습니다.

Microservice에서는 이렇게 각각의 서비스를 나누어 다른 서비스의 정보가 필요할 경우 통신을 이용해 정보를 얻어옵니다. Nest.js에서는 이런 서비스의 통신이 TCP Layer에서 이루어지며 메시지 기반의 이벤트 발행기로 통신을 합니다. rabbitMQ, Kafka같은 메시지 기반 서비스를 이용하면 될 것 같습니다.

우선 Catalog-Service를 만들고 후에 Auth-Service와 메시지 기반 통신을 진행해보도록 하겠습니다.

#2 프로젝트 설치

다음의 명령어들을 이용해 catalog-service를 만들고 모듈, 컨트롤러, 서비스를 만들도록 하겠습니다.

cd ../
nest new catalog-service
cd catalog-service
nest generate module catalog
nest generate controller catalog
nest generate service catalog

우선 지금 구현하는 Catalog-Service는 7100포트에서 동작하기 때문에 포트번호를 다음과 같이 변경하도록 하겠습니다.

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(7100);
}
bootstrap();

그리고 컨트롤러에 필요한 메서드들을 구현해보도록 하겠습니다.

import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
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 {
    constructor(private readonly catalogService: CatalogService) {}

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

    @Post('create')
    public async create(@Body requestCreate: RequestCreate) : Promise<ResponseCatalog> {
        const catalogDto = new CatalogDto();

        catalogDto.productId = requestCreate.productId;
        catalogDto.productName = requestCreate.productName;
        catalogDto.stock = requestCreate.stock;
        catalogDto.unitPrice = requestCreate.unitPrice;
        
        return await this.catalogService.create(catalogDto);
    }

    @Get(':productId')
    public async getCatalog(@Param('productId') productId: string): Promise<ResponseCatalog> {
        return await this.catalogService.getCatalog(productId);
    }

    @Get('')
    public async getCatalogs(): Promise<ResponseCatalog[]> {
        return await this.catalogService.getCatalogs();
    }

    @Patch(':productId')
    public async updateCatalog(
        @Param('productId') productId: string, 
        @Body() requestUpdate: RequestUpdate
    ): Promise<ResponseCatalog> {
        const catalogDto = new CatalogDto();

        catalogDto.productId = productId;
        catalogDto.stock = requestUpdate.stock;
        catalogDto.unitPrice = requestUpdate.unitPrice;

        return await this.catalogService.updateCatalog(catalogDto);
    }
}

대략적으로 메서드들을 작성했고, test-driven방식으로 오류 해결을 위한 코드를 작성하면서 구현을 하겠습니다.

vo폴더, dto폴더, entity폴더를 만들어 request, response관련 객체, dto객체, entity객체들을 만들어 주도록 하겠습니다. 그리고 검증을 위한 auth-service 폴더에서도 설치했던 패키지들을 설치하도록 하겠습니다.

그리고 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 { CatalogModule } from './catalog/catalog.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'password',
      database: 'catalog',
      autoLoadEntities: true,
      synchronize: true,
    }),
    CatalogModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
  • catalog.module.ts
import { Module } from '@nestjs/common';
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])],
  controllers: [CatalogController],
  providers: [CatalogService]
})
export class CatalogModule {}
  • 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;
};
  • request.update.ts
import { IsNotEmpty, IsNumber } from "class-validator";

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

    @IsNumber()
    @IsNotEmpty()
    readonly unitPrice: number;
}
  • response.catalog.ts
export class ResponseCatalog {
    productId: string;
    productName: string;
    stock: number;
    unitPrice: number;
    createdAt: Date;
}
  • catalog.dto.ts
import { IsNumber, IsString } from "class-validator";

export class CatalogDto {
    @IsString()
    productId: string;

    @IsString()
    productName: string;

    @IsNumber()
    stock: number;

    @IsNumber()
    unitPrice: number;
}
  • catalog.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

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

    @Column()
    productId: string;
    
    @Column()
    productName: string;
    
    @Column()
    stock: number;
    
    @Column()
    unitPrice: number;
    
    @Column()
    createdAt: Date;
}

CatalogService클래스에 Contoller에 필요한 메서드들을 구현하도록 하겠습니다.

import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { response } from 'express';
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>) {}

    public async create(catalogDto: CatalogDto): Promise<ResponseCatalog> {
        try {
            const catalogEntity = new CatalogEntity();

            catalogEntity.productId = catalogDto.productId;
            catalogEntity.productName = catalogDto.productName;
            catalogEntity.stock = catalogDto.stock;
            catalogEntity.unitPrice = catalogDto.unitPrice;
            
            return await this.catalogRepository.save(catalogEntity);
        } catch(err) {
            throw new HttpException(err, HttpStatus.BAD_REQUEST);
        }
    }
    
    public async getCatalog(productId: string): Promise<ResponseCatalog> {
        try {
            return await this.catalogRepository.findOne({ where: { productId: productId }});
        } catch(err) {
            throw new HttpException(err, HttpStatus.BAD_REQUEST);
        } 
    }

    public async getCatalogs(): Promise<ResponseCatalog[]> {
        try {
            return await this.catalogRepository.find();
        } catch(err) {
            throw new HttpException(err, HttpStatus.BAD_REQUEST);
        } 
    }

    public async updateCatalog(catalogDto: CatalogDto): Promise<ResponseCatalog> {
        try {
            const catalogEntity = await this.catalogRepository.findOne({ where: { productId: catalogDto.productId }});
            const responseCatalog = new ResponseCatalog();

            catalogEntity.stock = catalogDto.stock;
            catalogEntity.unitPrice = catalogDto.unitPrice;

            await this.catalogRepository.save(catalogEntity);
            
            responseCatalog.productId = catalogEntity.productId;
            responseCatalog.stock = catalogEntity.stock;
            responseCatalog.unitPrice = catalogEntity.unitPrice;

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

이전 Auth-Service에서 많은 설명을 했으므로 이번 Catalog-Service의 rest를 위한 모듈 구현은 설명을 따로 적지 않겠습니다. 지금까지 구현한 모듈들을 바탕으로 rest 요청을 해보겠습니다.

  • POST /catalogs/create

  • GET /catalogs/product-001

  • GET /catalogs/

  • PATCH /catalogs/Product-001

예상했던 결과값들이 잘 요청이 되는 모습들을 살펴볼 수 있습니다. 우선 값들을 확인하고 저장할 때 서비스가 잘 동작하는지를 확인하기 위해 rest로 구현을 진행했습니다. 하지만 nestjs의 microservice는 메시지 기반으로 통신을 진행하기 때문에 메시지 기반으로 바꾸어야 할 필요성이 있습니다.

다음 포스트에서는 microservice 라이브러리를 설치하고 메시지기반으로 요청을 진행해보도록 하겠습니다.

0개의 댓글

관련 채용 정보