개발/Node & Javascript
Nest.js 탐험기 4 - microservice (grpc) 를 사용해보자 - 튜토리얼편
말고기
2021. 12. 28. 18:25
728x90
반응형
개요
- nest.js에서 제공하는 microservice module은 microservice들 간의 통신을 쉽게 구현하게 되어 있다.
- 기본적으로 http base를 통해서 web server를 구현하지만 여러가지 규격의 통신이 필요할 때가 있는데, nest.js에서는 이를 구현하기 편리하게 되어있다.
- grpc를 사용해서 통신하는 튜토리얼을 작성해보자.
설치
아래의 모듈을 추가해주도록 한다.
# microservice를 구현하기 위해서 사용한다.
yarn add @nestjs/microservices --save
# grpc 용
yarn add @grpc/grpc-js @grpc/proto-loader --save
예제
- 우선 nest.js guide 가이드에 나와 있는 예제를 찬찬히 따라해보자.
- 지금은 잘 모르지만 우선 작성해보자.
- 아래는 localhost:3000이라는 client가 localhost:5000으로 grpc를 통해서 요청을 보내는 예제이다.
서버 예제 작성
server - 1. src/hero/hero.proto
syntax = "proto3";
package hero;
service HeroesService {
rpc FindOne (HeroById) returns (Hero) {}
}
message HeroById {
int32 id = 1;
}
message Hero {
int32 id = 1;
string name = 2;
}
server - 2. src/hero/hero.controller.ts
export interface HeroById {
id: number;
}
export interface Hero {
id: number;
name: string;
}
@Controller()
export class HeroesController {
@GrpcMethod('HeroesService', 'FindOne')
findOne(data: HeroById, metadata: Metadata, call: ServerUnaryCall<any,any>): Hero {
const items = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Doe' },
];
return items.find(({ id }) => id === data.id);
}
}
server - 3. src/app.module.ts
@Module({
controllers: [HeroesController],
})
export class AppModule {}
server - 4. src/main.ts
// 기본적으로 port는 5000번이다.
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
transport: Transport.GRPC,
options: {
package: 'hero',
protoPath: join(__dirname, './hero/hero.proto'),
},
});
await app.listen();
server - 5. nest-cli.json 수정
- 아래와 같이 build시 proto asset이 복사될 수 있게 수정해준다.
{
"compilerOptions": {
"assets": ["**/*.proto"],
"watchAssets": true
}
}
클라이언트 예제 작성
- bloomrpc 같은 rpc client 툴에서 체크도 가능하지만, 예제에 충실하게 클라이언트도 작성해주도록 한다.
- 따로 서버를 띄워도 되지만 이번에는 port만 다르게 체크해서 사용해보도록 하자.
client - 1. src/hero/client/hero.client.service.ts
@Injectable()
export class HeroClientService implements OnModuleInit {
private heroesController: HeroesController;
constructor(@Inject('HERO_PACKAGE') private client: ClientGrpc) {}
onModuleInit(): any {
this.heroesController = this.client.getService<HeroesController>('HeroesService');
}
async findOne(): Promise<any> {
return this.heroesController.findOne({ id: 1 });
}
}
client - 2. src/hero/client/hero.client.controller.ts
@Controller('/hero-client/v1')
export class HeroClientController {
constructor(private readonly heroClientService: HeroClientService) {}
@Get('/heroes')
async test(): Promise<any> {
return this.heroClientService.findOne();
}
}
client - 3. src/hero/client/hero.client.module.ts
@Module({
imports: [
ClientsModule.register([
{
name: 'HERO_PACKAGE',
transport: Transport.GRPC,
options: {
package: 'hero',
protoPath: join(__dirname, '../hero.proto'),
},
},
]),
],
providers: [HeroClientService],
controllers: [HeroClientController],
})
export class HeroClientModule {}
client - 4. src/app.module.ts 수정
// 같은 곳에 두었다.
@Module({
imports: [HeroClientModule],
controllers: [HeroesController],
})
export class AppModule {}
client - 5. src/main.ts 수정
// localhost:3000 => localhost:5000으로 연결되게 된다.
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(...PIPES);
app.connectMicroservice({
transport: Transport.GRPC,
options: {
package: 'hero',
credentials: ServerCredentials.createInsecure(),
protoPath: join(__dirname, './hero/hero.proto'),
},
})
await app.startAllMicroservices();
await app.listen(3000);
테스트
아래와 같이 요청을 보내 보면 정상적으로 응답했음을 확인할 수 있다.
curl -v http://localhost:3000/hero-client/v1/heroes
실제로 통신단을 확인하고 싶다면 wireshark에 아래와 같이 셋팅할 수 있다.
wireshark 설정
크게 아래와 같은 순서로 진행된다.
- proto file 등록
- decoding 등록
1. proto file 등록
wireshark => preference => protocols => ProtoBuf에 해당 proto file의 path를 등록해준다.
2. decoding 등록
루프백 인터페이스 (알맞은 환경의 인터페이스를 사용해주면 된다.)에 들어가서 패킷을 우클릭 한 후 "Decoded as"로 들어가서 5000번의 port를 http2로 decode할 수 있도록 등록해준다.
3. 확인
리퀘스트를 보내보면 아래와 같이 파싱된 데이터를 볼 수 있다.
( tcp.dstport == 5000 ) 필터를 적용해서 보면 잘 보인다.
결론
- 위와 같은 예제를 통해서 어떻게 동작이 되는지 간단하게 맛보았다.
- 다음에는 grpc란 무엇인지에 대해서 좀 더 알아보려고 한다.
출처
728x90
반응형