E-commerce Application(Nest js Microservice) - 6. RabbitMQ을 이용한 통신

yellow_note·2021년 8월 23일
0

#1 microservice 설치

microservice가 필요한 service들에 다음의 패키지를 설치하도록 하겠습니다.

npm i --save @nestjs/microservices
npm i --save amqplib amqp-connection-manager

그리고 다음과 같이 microservice를 사용하기 위한 설정들을 하도록 하겠습니다. 현재 진행할 microservice는 rabbitmq를 기반으로 할 것이기 때문에 Transport.RMQ를 전송방법으로 하고 options값들을 주도록 하겠습니다. 우선 auth-service에서 catalog-service의 값을 받아오는 예제를 만들어 보도록 하겠습니다.

  • auth-service - main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule, {
      transport: Transport.RMQ,
      options: {
        urls: ['http://localhost:5672'],
        queue: 'ecommerce_queue',
        queueOptions: {
          durable: false,
        }
      }
    }
  );
  
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      transform: true,
      forbidNonWhitelisted: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  );

  await app.listen();
}

bootstrap();

https://www.cloudamqp.com/ 로 접속해서 로그인을 한 후에 인스턴스를 생성하도록 하겠습니다.




이렇게 인스턴스가 생성되고 해당 인스턴스에 들어가면 보이는 AMQP URL을 복사해서 사용하도록 하겠습니다. auth-service, catalog-service를 다음과 같이 수정해주도록 하겠습니다.

  • auth-service - main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule, {
      transport: Transport.RMQ,
      options: {
        urls: ['AMQP_URL'],
        queue: 'ecommerce_queue',
        queueOptions: {
          durable: false,
        }
      }
    }
  );
  
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      transform: true,
      forbidNonWhitelisted: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  );

  await app.listen(); // on PORT 7000
}

bootstrap();
  • auths-service - app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from './auth/auth.module';
import { ClientsModule, Transport } from '@nestjs/microservices';

@Module({
  // set mysql
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: '10a10a',
      database: 'auth',
      autoLoadEntities: true,
      synchronize: true,
    }),
    ClientsModule.register([
    {
      name: 'catalog-service',
      transport: Transport.RMQ,
      options: {
        urls: ['AMQP_URL'],
        queue: 'ecommerce_queue',
        queueOptions: {
          durable: false
        },
      },
    }]),
    UserModule,
    AuthModule
  ],
  controllers: [AppController],
  providers: [AppService],
})

export class AppModule {}
  • 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: ['AMQP_URL'],
        queue: 'ecommerce_queue',
        queueOptions: {
          durable: false
        },
      },
    }])
  ],
  controllers: [CatalogController],
  providers: [CatalogService]
})
export class CatalogModule {}

이렇게 RabbitMQ기반의 Microservice에 대한 대략적인 설정을 마쳤으니 메시지를 통해 서비스 간 통신하는 방법을 알아보도록 하겠습니다.

#2 Message 발행

우선 예제로 catalog-service에서의 값을 auth-service에서 볼 수 있게 코드를 작성하겠습니다.

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

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

    @Post('create')
    public async create(@Body() requestCreate: RequestCreate) : Promise<ResponseCatalog> {
        Logger.debug(requestCreate);
        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);
    }
}

컨트롤러 전체 코드에서 해당 부분을 살펴 보도록 하겠습니다.

constructor(
    private readonly catalogService: CatalogService,
    @Inject('catalog-service') private readonly client: ClientProxy
) {}

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

우선 생성자에 catalog-service라는 이름으로 메시지 발행을 위한 client를 주입하겠습니다. 이렇게 생성한 객체를 가지고 메시지패턴 - 데이터 쌍으로 메시지를 발행할 수 있습니다. 코드의 this.client.emit('Message Pattern', 'Data') 이런 식으로 말이죠.

그리고 이 메시지를 받기 위해 auth-service의 AppController의 코드를 살펴 보겠습니다.

  • AppContoller
import { Body, Controller, Get, Param, Patch, Post, Request, UseGuards } from '@nestjs/common';
import { EventPattern, MessagePattern } from '@nestjs/microservices';
import { AuthService } from './auth/auth.service';
import { JwtAuthGuard } from './auth/guard/jwt-auth.guard';
import { LocalAuthGuard } from './auth/guard/local-auth.guard';
import { UserDto } from './dto/user.dto';
import { UserService } from './user/user.service';
import { RequestLogin } from './vo/request.login';
import { RequestRegister } from './vo/request.register';
import { RequestUpdate } from './vo/request.update';
import { ResponseUser } from './vo/response.user';

@Controller('users')
export class AppController {
    constructor(
        private readonly authService: AuthService,
        private readonly userService: UserService
    ) { }
    
    @UseGuards(LocalAuthGuard)
    @Post('login')
    public async login(@Body() requestLogin: RequestLogin): Promise<any> {
        const userDto = new UserDto();

        userDto.email = requestLogin.email;
        
        return await this.authService.login(userDto);
    }
    
    @UseGuards(JwtAuthGuard)
    @Get('status')
    public async getStatus() {
        return "auth-serivce is working successfully";
    }

    @Post('register')
    public async register(@Body() requestRegister: RequestRegister): Promise<ResponseUser> {
        const userDto = new UserDto();

        userDto.email = requestRegister.email;
        userDto.password = requestRegister.password;
        userDto.nickname = requestRegister.nickname;

        return await this.userService.register(userDto);
    }

    @UseGuards(JwtAuthGuard)
    @Get(':userId')
    public async getUser(@Param('userId') userId: string) {
        return this.userService.getUser(userId);
    }

    @UseGuards(JwtAuthGuard)
    @Patch(":userId")
    public async updateUser(
        @Param('userId') userId: string,
        @Body() requestUpdate: RequestUpdate
    ) {
        const userDto = new UserDto();

        userDto.userId = userId;
        userDto.nickname = requestUpdate.nickname;
        
        return this.userService.updateUser(userDto);
    }
    
    @EventPattern('health_check')
    public async getCatalogServiceStatus(data: any): Promise<any> {
        console.log(data);
        return data;
    }
}

마지막의 EventPattern이라는 데코레이터 코드를 보겠습니다. EventPattern은 EventPattern('Pattern')안의 인자값인 Pattern을 찾아서 메시지를 가져오겠다는 의미입니다. catalog-service의 Message Pattern(health_check)와 auth-service의 EventPattern(health_check)이 매핑이 되는 것이죠. 그러면 예제를 만들어 봤으니 결과를 확인해보도록 하겠습니다.

#3 결과 확인



auth-service에서 catalog-serivce의 상태 값을 확인해보는 예제의 결과가 잘 나오고 있음을 확인할 수 있습니다. 그러면 이 예제를 바탕으로 order-service를 만들어 auth-service에서 주문 정보도 확인할 수 있는 microservice를 만들어 보겠습니다.

참고

0개의 댓글

관련 채용 정보