microservice가 필요한 service들에 다음의 패키지를 설치하도록 하겠습니다.
npm i --save @nestjs/microservices
npm i --save amqplib amqp-connection-manager
그리고 다음과 같이 microservice를 사용하기 위한 설정들을 하도록 하겠습니다. 현재 진행할 microservice는 rabbitmq를 기반으로 할 것이기 때문에 Transport.RMQ를 전송방법으로 하고 options값들을 주도록 하겠습니다. 우선 auth-service에서 catalog-service의 값을 받아오는 예제를 만들어 보도록 하겠습니다.
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를 다음과 같이 수정해주도록 하겠습니다.
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();
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 {}
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에 대한 대략적인 설정을 마쳤으니 메시지를 통해 서비스 간 통신하는 방법을 알아보도록 하겠습니다.
우선 예제로 catalog-service에서의 값을 auth-service에서 볼 수 있게 코드를 작성하겠습니다.
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의 코드를 살펴 보겠습니다.
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)이 매핑이 되는 것이죠. 그러면 예제를 만들어 봤으니 결과를 확인해보도록 하겠습니다.
auth-service에서 catalog-serivce의 상태 값을 확인해보는 예제의 결과가 잘 나오고 있음을 확인할 수 있습니다. 그러면 이 예제를 바탕으로 order-service를 만들어 auth-service에서 주문 정보도 확인할 수 있는 microservice를 만들어 보겠습니다.