개발/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 설정

크게 아래와 같은 순서로 진행된다.

  1. proto file 등록
  2. 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
반응형