Dapr와 NestJS를 활용한 Pub/Sub 데코레이터 실전 구현

Dapr는 이식 가능한 이벤트 기반 런타임으로, 개발자가 탄력적인 상태 없는(stateless) 및 상태 있는(stateful) 애플리케이션을 쉽게 구축할 수 있게 해줍니다. 클라우드 플랫폼 또는 엣지 컴퓨팅 환경에서 실행될 수 있으며, 다양한 프로그래밍 언어와 개발 프레임워크를 지원합니다. Dapr은 개발자가 분산 시스템 문제에 신경 쓰지 않고 비즈니스 로직에만 집중할 수 있도록 하여 생산성을 크게 향상시킵니다. Dapr은 마이크로서비스 아키텍처 기반의 현대적인 클라우드 네이티브 애플리케이션 구축의 진입 장벽을 낮춥니다.

Dapr JavaScript SDK

JavaScript와 TypeScript에서 Dapr 애플리케이션을 구축하기 위한 클라이언트 라이브러리입니다. 이 클라이언트는 서비스 간 호출, 상태 관리, 발행/구독, 비밀 관리 등 공통 Dapr API를 추상화하며, 애플리케이션 구축을 위한 간단하고 직관적인 API를 제공합니다.

설치

Javascript SDK를 사용하려면 NPM에서 Dapr JavaScript SDK 패키지를 설치하세요:

npm install --save @dapr/dapr

️ dapr-client는 이제 더 이상 사용되지 않습니다. 자세한 내용은 #259를 참조하세요.

구조

Dapr Javascript SDK는 두 가지 주요 구성 요소로 이루어져 있습니다:

  • DaprServer: 모든 Dapr 사이드카와 애플리케이션 간 통신을 관리합니다.
  • DaprClient: 모든 애플리케이션과 Dapr 사이드카 간 통신을 관리합니다.

위 통신은 gRPC 또는 HTTP 프로토콜을 사용하도록 구성할 수 있습니다.

실전 예제

웹사이트 사용자 행동에 대한 통계 정보를 생성하는 작은 애플리케이션을 만들어 보겠습니다.

환경 및 프로젝트 구조 설정

npm install -g @nestjs/cli
nest new api
mv api nest-dapr
cd nest-dapr
nest generate app page-view
npm install dapr-client
wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash

decorators.ts 파일(apps/shared/decorators.ts)을 생성하여 모든 마이크로서비스가 우리가 작성할 인프라에서 혜택을 받을 수 있도록 합니다.

Dapr 의존성 주입

DaprClient와 DaprServer를 주입하려면 nest.js제공해야 합니다.

app.module.ts에서 DaprClient를 등록합니다:

providers: [
...
  {
    provide: DaprClient,
    useValue: new DaprClient()
  }
]

page-view.module.ts에 동일한 방식으로 DaprServer를 추가합니다:

providers: [
...
  {
    provide: DaprServer,
    useValue: new DaprServer()
  }
]

Dapr 구성 요소(RabbitMQ) 설정

docker compose를 사용하여 rabbitmq를 시작합니다:

version: '3.9'
services:
  pubsub:
    image: rabbitmq:3-management-alpine
    container_name: 'pubsub'
    ports:
      - 5674:5672
      - 15674:15672 #web port

Dapr 구성 요소를 구성해야 합니다. 루트 폴더에 component/pubsub.yml를 생성합니다:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: pubsub
  namespace: default
spec:
  type: pubsub.rabbitmq
  version: v1
  metadata:
    - name: host
      value: 'amqp://guest:guest@localhost:5674'

각 Dapr 마이크로서비스에는 자체 config가 필요합니다.

api/dapr/config.yml:

apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
  name: api
  namespace: default

page-view/dapr/config.yml:

apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
  name: page-view
  namespace: default

API/Gateway 서비스

app.controller.ts에서 간단한 API인 /add-page-view를 공개합니다:

@Post('/add-page-view')
async prderAdd(@Body() pageViewDto: PageViewDto): Promise<void> {
   try {
      console.log(pageViewDto);
      await this.daprClient.pubsub.publish('pubsub', 'page-view-add', pageViewDto);
    } catch (e) {
      console.log(e);
    }
}

내부 구독 마이크로서비스

데이터를 큐에 게시한 후, 이를 수신하고 호출해야 합니다:

page-view.controller.ts에 다음을 추가합니다:

@DaprPubSubSubscribe('pubsub', 'add')
addPageView(data: PageViewDto) {
    console.log(`addPageView executed with data: ${JSON.stringify(data)}`);
    this.data.push(data);
}

이제 새로 만들어야 하는 데코레이터: @DaprPubSubscribe에 주목하세요.

@DaprPubSubscribe 데코레이터

shared/decorators.ts에서:

import { INestApplication } from '@nestjs/common';
import { DaprServer } from 'dapr-client';

export type PubsubMap = {
  [pubSubName: string]: {
    topic: string;
    target: any;
    descriptor: PropertyDescriptor;
  };
};
export const DAPR_PUB_SUB_MAP: PubsubMap = {};
export const DaprPubSubSubscribe = (
  pubSubName: string,
  topic: string,
): MethodDecorator => {
  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    DAPR_PUB_SUB_MAP[pubSubName] = {
      topic,
      target,
      descriptor,
    };
    return descriptor;
  };
};

export const useDaprPubSubListener = async (app: INestApplication) => {
  const daprServer = app.get(DaprServer);

  for (const pubSubName in DAPR_PUB_SUB_MAP) {
    const item = DAPR_PUB_SUB_MAP[pubSubName];
    console.log(
      `Listening to the pubsub name - "${pubSubName}" on topic "${item.topic}"`,
    );
    await daprServer.pubsub.subscribe(
      pubSubName,
      item.topic,
      async (data: any) => {
        const targetClassImpl = app.get(item.target.constructor);
        await targetClassImpl[item.descriptor.value.name](data);
      },
    );
  }
};

애플리케이션 실행

실행:

docker-compose up -d # rabbitmq 시작
npm run dapr:api:dev # api/gateway 시작
npm run page-view:dev # 내부 마이크로서비스 dapr rabbitmq 큐 수신 시작

요청 실행:

curl --location --request POST 'http://localhost:7001/v1.0/invoke/api/method/statistics/add-page-view' \
--header 'Content-Type: application/json' \
--data-raw '{
    "pageUrl" : "https://test.com/some-page",
    "userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"
}'

태그: dapr nestjs Pub/Sub 데코레이터 마이크로서비스

7월 6일 04:19에 게시됨