Dapr 與 NestJs ,實戰編寫一個 Pub & Sub 裝飾器
- 2022 年 8 月 1 日
- 筆記

Dapr 是一個可移植的、事件驅動的運行時,它使任何開發人員能夠輕鬆構建出彈性的、無狀態和有狀態的應用程式,並可運行在雲平台或邊緣計算中,它同時也支援多種程式語言和開發框架。Dapr 確保開發人員專註於編寫業務邏輯,不必分神解決分散式系統難題,從而顯著提高了生產力。Dapr 降低了構建微服務架構類現代雲原生應用的門檻。
系列
- 本地使用 Docker Compose 與 Nestjs 快速構建基於 Dapr 的 Redis 發布/訂閱分散式應用
- NodeJS 基於 Dapr 構建雲原生微服務應用,從 0 到 1 快速上手指南
Dapr JavaScript SDK
用於在 JavaScript 和 TypeScript 中構建 Dapr 應用程式的客戶端庫。 該客戶端抽象了公共 Dapr API,例如服務到服務調用、狀態管理、發布/訂閱、Secret 等,並為構建應用程式提供了一個簡單、直觀的 API。
安裝
要開始使用 Javascript SDK,請從 NPM 安裝 Dapr JavaScript SDK 包:
npm install --save @dapr/dapr
⚠️ dapr-client 現在已棄用。 請參閱#259 了解更多資訊。
結構
Dapr Javascript SDK 包含兩個主要組件:
- DaprServer: 管理所有 Dapr sidecar 到應用程式的通訊。
- DaprClient: 管理所有應用程式到 Dapr sidecar 的通訊。
上述通訊可以配置為使用 gRPC 或 HTTP 協議。
實戰
創建一個小應用程式來生成有關網站中用戶行為的統計資訊。
Demo 源碼
//github.com/Hacker-Linner/dapr-nestjs-jssdk-decorator
準備環境和項目結構
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 //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 '//localhost:7001/v1.0/invoke/api/method/statistics/add-page-view' \
--header 'Content-Type: application/json' \
--data-raw '{
"pageUrl" : "//test.com/some-page",
"userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"
}'
