Dapr 是一个可移植的、事件驱动的运行时,它使任何开发人员能够轻松构建出弹性的、无状态和有状态的应用程序,并可运行在云平台或边缘计算中,它同时也支持多种编程语言和开发框架。Dapr 确保开发人员专注于编写业务逻辑,不必分神解决分布式系统难题,从而显著提高了生产力。Dapr 降低了构建微服务架构类现代云原生应用的门槛。
用于在 JavaScript 和 TypeScript 中构建 Dapr 应用程序的客户端库。该客户端抽象了公共 Dapr API,例如服务到服务调用、状态管理、发布/订阅、机密等,并为构建应用程序提供了一个简单、直观的 API。
要开始使用 Javascript SDK,请从 NPM 安装 Dapr JavaScript SDK 包:
npm install --save @dapr/dapr
⚠️ dapr-client 现在已弃用。请参阅#259 了解更多信息。
Dapr Javascript SDK 包含两个主要组件:
上述通信可以配置为使用 gRPC 或 HTTP 协议。
Dapr Client 允许您与 Dapr Sidecar 通信并访问其面向客户端的功能,例如发布事件、调用输出绑定、状态管理、Secret 管理等等。
npm i @dapr/dapr --save
import { DaprClient, DaprServer, HttpMethod, CommunicationProtocolEnum } from "@dapr/dapr";
const daprHost = "127.0.0.1"; // Dapr Sidecar Host
const daprPort = "3500"; // Dapr Sidecar Port of this Example Server
const serverHost = "127.0.0.1"; // App Host of this Example Server
const serverPort = "50051"; // App Port of this Example Server
// HTTP Example
const client = new DaprClient(daprHost, daprPort);
// GRPC Example
const client = new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);vvvvvvvvv
要运行示例,您可以使用两种不同的协议与 Dapr sidecar 交互:HTTP(默认)或 gRPC。
import { DaprClient } from "@dapr/dapr";
const client = new DaprClient(daprHost, daprPort);
# Using dapr run
dapr run --app-id example-sdk --app-protocol http -- npm run start
# or, using npm script
npm run start:dapr-http
由于 HTTP 是默认设置,因此您必须调整通信协议以使用 gRPC。您可以通过向客户端或服务器构造函数传递一个额外的参数来做到这一点。
import { DaprClient, CommunicationProtocol } from "@dapr/dapr";
const client = new DaprClient(daprHost, daprPort, CommunicationProtocol.GRPC);
# Using dapr run
dapr run --app-id example-sdk --app-protocol grpc -- npm run start
# or, using npm script
npm run start:dapr-grpc
通过代理请求,我们可以利用 Dapr 通过其 sidecar 架构带来的独特功能,例如服务发现、日志记录等,使我们能够立即“升级”我们的 gRPC 服务。gRPC 代理的这一特性在community call 41 中得到了展示。
community call 41https://www.youtube.com/watch?v=B_vkXqptpXY&t=71s
要执行 gRPC 代理,只需调用 client.proxy.create() 方法创建一个代理:
// As always, create a client to our dapr sidecar
// this client takes care of making sure the sidecar is started, that we can communicate, ...
const clientSidecar = new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);
// Create a Proxy that allows us to use our gRPC code
const clientProxy = await clientSidecar.proxy.create<GreeterClient>(GreeterClient);
我们现在可以调用 GreeterClient 接口中定义的方法(在本例中来自 Hello World 示例)
--app-port
告诉 Dapr 这个 gRPC 服务器在哪个端口上运行,并通过 --app-id <APP_ID_HERE>
给它一个唯一的 Dapr 应用 IDdapr-app-id
的元数据键,其中包含在 Dapr 中启动的 gRPC 服务器的值(例如,我们示例中的 server
)JavaScript 客户端 SDK 允许您与专注于 Client to Sidecar 功能的所有 Dapr 构建块进行交互。
调用一个服务
import { DaprClient, HttpMethod } from "@dapr/dapr";
const daprHost = "127.0.0.1";
const daprPort = "3500";
async function start() {
const client = new DaprClient(daprHost, daprPort);
const serviceAppId = "my-app-id";
const serviceMethod = "say-hello";
// POST Request
const response = await client.invoker.invoke(serviceAppId , serviceMethod , HttpMethod.POST, { hello: "world" });
// GET Request
const response = await client.invoker.invoke(serviceAppId , serviceMethod , HttpMethod.GET);
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
有关服务调用的完整指南,请访问 How-To: Invoke a service。https://docs.dapr.io/developing-applications/building-blocks/service-invocation/howto-invoke-discover-services/
保存、获取和删除应用程序状态
import { DaprClient } from "@dapr/dapr";
const daprHost = "127.0.0.1";
const daprPort = "3500";
async function start() {
const client = new DaprClient(daprHost, daprPort);
const serviceStoreName = "my-state-store-name";
// Save State
const response = await client.state.save(serviceStoreName, [
{
key: "first-key-name",
value: "hello"
},
{
key: "second-key-name",
value: "world"
}
]);
// Get State
const response = await client.state.get(serviceStoreName, "first-key-name");
// Get Bulk State
const response = await client.state.getBulk(serviceStoreName, ["first-key-name", "second-key-name"]);
// State Transactions
await client.state.transaction(serviceStoreName, [
{
operation: "upsert",
request: {
key: "first-key-name",
value: "new-data"
}
},
{
operation: "delete",
request: {
key: "second-key-name"
}
}
]);
// Delete State
const response = await client.state.delete(serviceStoreName, "first-key-name");
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
有关状态操作的完整列表,请访问 How-To: Get & save state。https://docs.dapr.io/developing-applications/building-blocks/state-management/howto-get-save-state/
import { DaprClient } from "@dapr/dapr";
async function start() {
const client = new DaprClient(daprHost, daprPort);
const res = await client.state.query("state-mongodb", {
filter: {
OR: [
{
EQ: { "person.org": "Dev Ops" }
},
{
"AND": [
{
"EQ": { "person.org": "Finance" }
},
{
"IN": { "state": ["CA", "WA"] }
}
]
}
]
},
sort: [
{
key: "state",
order: "DESC"
}
],
page: {
limit: 10
}
});
console.log(res);
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
发布消息
import { DaprClient } from "@dapr/dapr";
const daprHost = "127.0.0.1";
const daprPort = "3500";
async function start() {
const client = new DaprClient(daprHost, daprPort);
const pubSubName = "my-pubsub-name";
const topic = "topic-a";
const message = { hello: "world" }
// Publish Message to Topic
const response = await client.pubsub.publish(pubSubName, topic, message);
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
订阅消息
import { DaprServer } from "@dapr/dapr";
const daprHost = "127.0.0.1"; // Dapr Sidecar Host
const daprPort = "3500"; // Dapr Sidecar Port of this Example Server
const serverHost = "127.0.0.1"; // App Host of this Example Server
const serverPort = "50051"; // App Port of this Example Server "
async function start() {
const server = new DaprServer(serverHost, serverPort, daprHost, daprPort);
const pubSubName = "my-pubsub-name";
const topic = "topic-a";
// Configure Subscriber for a Topic
await server.pubsub.subscribe(pubSubName, topic, async (data: any) => console.log(`Got Data: ${JSON.stringify(data)}`));
await server.start();
}
有关状态操作的完整列表,请访问 How-To: Publish and subscribe。https://docs.dapr.io/developing-applications/building-blocks/pubsub/howto-publish-subscribe/
调用输出绑定
import { DaprClient } from "@dapr/dapr";
const daprHost = "127.0.0.1";
const daprPort = "3500";
async function start() {
const client = new DaprClient(daprHost, daprPort);
const bindingName = "my-binding-name";
const bindingOperation = "create";
const message = { hello: "world" };
const response = await client.binding.send(bindingName, bindingOperation, message);
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
有关输出绑定的完整指南,请访问 How-To: Use bindings。https://docs.dapr.io/developing-applications/building-blocks/bindings/howto-bindings/
检索 secret
import { DaprClient } from "@dapr/dapr";
const daprHost = "127.0.0.1";
const daprPort = "3500";
async function start() {
const client = new DaprClient(daprHost, daprPort);
const secretStoreName = "my-secret-store";
const secretKey = "secret-key";
// Retrieve a single secret from secret store
const response = await client.secret.get(secretStoreName, secretKey);
// Retrieve all secrets from secret store
const response = await client.secret.getBulk(secretStoreName);
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
有关 secrets 的完整指南,请访问 How-To: Retrieve Secrets。https://docs.dapr.io/developing-applications/building-blocks/secrets/howto-secrets/
获取配置 key
import { DaprClient } from "@dapr/dapr";
const daprHost = "127.0.0.1";
const daprAppId = "example-config";
async function start() {
const client = new DaprClient(daprHost, process.env.DAPR_HTTP_PORT);
const config = await client.configuration.get('config-store', ['key1', 'key2']);
console.log(config);
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
Dapr Server 将允许您接收来自 Dapr Sidecar 的通信并访问其面向服务器的功能,例如:订阅事件、接收输入绑定等等。
npm
安装 SDK:npm i @dapr/dapr --save
import { DaprClient, DaprServer, HttpMethod, CommunicationProtocolEnum } from "@dapr/dapr";
const daprHost = "127.0.0.1"; // Dapr Sidecar Host
const daprPort = "3500"; // Dapr Sidecar Port of this Example Server
const serverHost = "127.0.0.1"; // App Host of this Example Server
const serverPort = "50051"; // App Port of this Example Server
// HTTP Example
const client = new DaprClient(daprHost, daprPort);
// GRPC Example
const client = new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);
要运行示例,您可以使用两种不同的协议与 Dapr sidecar 交互:HTTP(默认)或 gRPC。
import { DaprServer } from "@dapr/dapr";
const server= new DaprServer(appHost, appPort, daprHost, daprPort);
// initialize subscribtions, ... before server start
// the dapr sidecar relies on these
await server.start();
# Using dapr run
dapr run --app-id example-sdk --app-port 50051 --app-protocol http -- npm run start
# or, using npm script
npm run start:dapr-http
ℹ️ Note:这里需要 app-port,因为这是我们的服务器需要绑定的地方。Dapr 将在完成启动之前检查应用程序是否绑定到此端口。
由于 HTTP 是默认设置,因此您必须调整通信协议以使用 gRPC。您可以通过向客户端或服务器构造函数传递一个额外的参数来做到这一点。
import { DaprServer, CommunicationProtocol } from "@dapr/dapr";
const server = new DaprServer(appHost, appPort, daprHost, daprPort, CommunicationProtocol.GRPC);
// initialize subscribtions, ... before server start
// the dapr sidecar relies on these
await server.start();
# Using dapr run
dapr run --app-id example-sdk --app-port 50051 --app-protocol grpc -- npm run start
# or, using npm script
npm run start:dapr-grpc
ℹ️ Note:这里需要 app-port,因为这是我们的服务器需要绑定的地方。Dapr 将在完成启动之前检查应用程序是否绑定到此端口。
JavaScript Server SDK 允许您与专注于 Sidecar 到 App 功能的所有 Dapr 构建块进行交互。
监听调用
import { DaprServer } from "@dapr/dapr";
const daprHost = "127.0.0.1"; // Dapr Sidecar Host
const daprPort = "3500"; // Dapr Sidecar Port of this Example Server
const serverHost = "127.0.0.1"; // App Host of this Example Server
const serverPort = "50051"; // App Port of this Example Server "
async function start() {
const server = new DaprServer(serverHost, serverPort, daprHost, daprPort);
await server.invoker.listen('hello-world', mock, { method: HttpMethod.GET });
// You can now invoke the service with your app id and method "hello-world"
await server.start();
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
有关服务调用的完整指南,请访问 How-To: Invoke a service。https://docs.dapr.io/developing-applications/building-blocks/service-invocation/howto-invoke-discover-services/
订阅消息
import { DaprServer } from "@dapr/dapr";
const daprHost = "127.0.0.1"; // Dapr Sidecar Host
const daprPort = "3500"; // Dapr Sidecar Port of this Example Server
const serverHost = "127.0.0.1"; // App Host of this Example Server
const serverPort = "50051"; // App Port of this Example Server "
async function start() {
const server = new DaprServer(serverHost, serverPort, daprHost, daprPort);
const pubSubName = "my-pubsub-name";
const topic = "topic-a";
// Configure Subscriber for a Topic
await server.pubsub.subscribe(pubSubName, topic, async (data: any) => console.log(`Got Data: ${JSON.stringify(data)}`));
await server.start();
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
有关状态操作的完整列表,请访问 How-To: Publish and subscribe。https://docs.dapr.io/developing-applications/building-blocks/pubsub/howto-publish-subscribe/
接收一个输入绑定
import { DaprServer } from "@dapr/dapr";
const daprHost = "127.0.0.1";
const daprPort = "3500";
const serverHost = "127.0.0.1";
const serverPort = "5051";
async function start() {
const server = new DaprServer(serverHost, serverPort, daprHost, daprPort);
const bindingName = "my-binding-name";
const response = await server.binding.receive(bindingName, async (data: any) => console.log(`Got Data: ${JSON.stringify(data)}`));
await server.start();
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
有关输出绑定的完整指南,请访问 How-To: Use bindings。https://docs.dapr.io/developing-applications/building-blocks/bindings/howto-bindings/
💡 配置 API 目前只能通过 gRPC 使用
获取配置值
import { DaprServer } from "dapr-client";
const daprHost = "127.0.0.1";
const daprPort = "3500";
const serverHost = "127.0.0.1";
const serverPort = "5051";
async function start() {
const client = new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);
const config = await client.configuration.get("config-redis", ["myconfigkey1", "myconfigkey2"]);
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
订阅 key 更改
import { DaprServer } from "dapr-client";
const daprHost = "127.0.0.1";
const daprPort = "3500";
const serverHost = "127.0.0.1";
const serverPort = "5051";
async function start() {
const client = new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);
const stream = await client.configuration.subscribeWithKeys("config-redis", ["myconfigkey1", "myconfigkey2"], () => {
// Received a key update
});
// When you are ready to stop listening, call the following
await stream.close();
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
Dapr actors 包允许您从 JavaScript 应用程序与 Dapr virtual actors 进行交互。下面的示例演示了如何使用 JavaScript SDK 与 virtual actors 进行交互。
如需更深入地了解 Dapr actors,请访问 actors 概览页面。
下面的代码示例粗略地描述了停车场现场监控系统的场景,可以在 Mark Russinovich 的这段视频中看到。
一个停车场由数百个停车位组成,每个停车位都包含一个传感器,可为中央监控系统提供更新。停车位传感器(我们的 actors)检测停车位是否被占用或可用。
要自己跳入并运行此示例,请克隆源代码,该源代码可在 JavaScript SDK 示例目录中找到。
Actor 接口定义了在 Actor 实现和调用 Actor 的客户端之间共享的合约。在下面的示例中,我们为停车场传感器创建了一个接口。每个传感器有 2 个方法:carEnter
和 carLeave
,它们定义了停车位的状态:
export default interface ParkingSensorInterface {
carEnter(): Promise<void>;
carLeave(): Promise<void>;
}
Actor 实现通过扩展基本类型 AbstractActor
并实现 Actor
接口(在本例中为 ParkingSensorInterface
)来定义一个类。
下面的代码描述了一个 actor 实现以及一些辅助方法。
import { AbstractActor } from "@dapr/dapr";
import ParkingSensorInterface from "./ParkingSensorInterface";
export default class ParkingSensorImpl extends AbstractActor implements ParkingSensorInterface {
async carEnter(): Promise<void> {
// Implementation that updates state that this parking spaces is occupied.
}
async carLeave(): Promise<void> {
// Implementation that updates state that this parking spaces is available.
}
private async getInfo(): Promise<object> {
// Implementation of requesting an update from the parking space sensor.
}
/**
* @override
*/
async onActivate(): Promise<void> {
// Initialization logic called by AbstractActor.
}
}
使用 DaprServer 包初始化和注册你的 actors:
import { DaprServer } from "@dapr/dapr";
import ParkingSensorImpl from "./ParkingSensorImpl";
const daprHost = "127.0.0.1";
const daprPort = "50000";
const serverHost = "127.0.0.1";
const serverPort = "50001";
const server = new DaprServer(serverHost, serverPort, daprHost, daprPort);
await server.actor.init(); // Let the server know we need actors
server.actor.registerActor(ParkingSensorImpl); // Register the actor
await server.start(); // Start the server
// To get the registered actors, you can invoke `getRegisteredActors`:
const resRegisteredActors = await server.actor.getRegisteredActors();
console.log(`Registered Actors: ${JSON.stringify(resRegisteredActors)}`);
注册 Actor 后,使用 ActorProxyBuilder
创建一个实现 ParkingSensorInterface
的代理对象。您可以通过直接调用 Proxy 对象上的方法来调用 actor 方法。在内部,它转换为对 Actor API 进行网络调用并取回结果。
import { DaprClient, ActorId } from "@dapr/dapr";
import ParkingSensorImpl from "./ParkingSensorImpl";
import ParkingSensorInterface from "./ParkingSensorInterface";
const daprHost = "127.0.0.1";
const daprPort = "50000";
const client = new DaprClient(daprHost, daprPort);
// Create a new actor builder. It can be used to create multiple actors of a type.
const builder = new ActorProxyBuilder<ParkingSensorInterface>(ParkingSensorImpl, client);
// Create a new actor instance.
const actor = builder.build(new ActorId("my-actor"));
// Or alternatively, use a random ID
// const actor = builder.build(ActorId.createRandomId());
// Invoke the method.
await actor.carEnter();
// ...
const PARKING_SENSOR_PARKED_STATE_NAME = "parking-sensor-parked"
const actor = builder.build(new ActorId("my-actor"))
// SET state
await actor.getStateManager().setState(PARKING_SENSOR_PARKED_STATE_NAME, true);
// GET state
const value = await actor.getStateManager().getState(PARKING_SENSOR_PARKED_STATE_NAME);
if (!value) {
console.log(`Received: ${value}!`);
}
// DELETE state
await actor.removeState(PARKING_SENSOR_PARKED_STATE_NAME);
...
JS SDK 支持 actors 可以通过注册 timers 或 reminders 来为自己安排定期工作。timers 和 reminders 之间的主要区别在于,Dapr actor runtime 在停用后不保留有关 timers 的任何信息,而是使用 Dapr actor state provider 保留 reminders 信息。
这种区别允许用户在轻量级但无状态的 timers 和更需要资源但有状态的 reminders 之间进行权衡。
Timers 和 reminders 的调度界面是相同的。要更深入地了解调度配置,请参阅 actors timers 和 reminders 文档。
// ...
const actor = builder.build(new ActorId("my-actor"));
// Register a timer
await actor.registerActorTimer(
"timer-id", // Unique name of the timer.
"cb-method", // Callback method to execute when timer is fired.
Temporal.Duration.from({ seconds: 2 }), // DueTime
Temporal.Duration.from({ seconds: 1 }), // Period
Temporal.Duration.from({ seconds: 1 }), // TTL
50 // State to be sent to timer callback.
);
// Delete the timer
await actor.unregisterActorTimer("timer-id");
// ...
const actor = builder.build(new ActorId("my-actor"));
// Register a reminder, it has a default callback: `receiveReminder`
await actor.registerActorReminder(
"reminder-id", // Unique name of the reminder.
Temporal.Duration.from({ seconds: 2 }), // DueTime
Temporal.Duration.from({ seconds: 1 }), // Period
Temporal.Duration.from({ seconds: 1 }), // TTL
100 // State to be sent to reminder callback.
);
// Delete the reminder
await actor.unregisterActorReminder("reminder-id");
要处理回调,您需要覆盖 actor 中的默认 receiveReminder
实现。例如,从我们最初的 actor 实现中:
export default class ParkingSensorImpl extends AbstractActor implements ParkingSensorInterface {
// ...
/**
* @override
*/
async receiveReminder(state: any): Promise<void> {
// handle stuff here
}
// ...
}
有关 actors 的完整指南,请访问 How-To: Use virtual actors in Dapr。
JavaScript SDK 带有一个开箱即用的基于 Console
的 logger。SDK 发出各种内部日志,以帮助用户了解事件链并解决问题。此 SDK 的使用者可以自定义日志的详细程度,并为 logger 提供自己的实现。
有五个级别的日志记录,按重要性降序排列 - error
、warn
、info
、verbose
和 debug
。将日志设置为一个级别意味着 logger 将发出至少与上述级别一样重要的所有日志。例如,设置为 verbose
日志意味着 SDK 不会发出 debug
级别的日志。默认日志级别是 info
。
import { CommunicationProtocolEnum, DaprClient, LogLevel } from "@dapr/dapr";
// create a client instance with log level set to verbose.
const client = new DaprClient(
daprHost,
daprPort,
CommunicationProtocolEnum.HTTP,
{ logger: { level: LogLevel.Verbose } });
有关如何使用 Client 的更多详细信息,请参阅 JavaScript Client。https://docs.dapr.io/developing-applications/sdks/js/js-client/
import { CommunicationProtocolEnum, DaprServer, LogLevel } from "@dapr/dapr";
// create a server instance with log level set to error.
const server = new DaprServer(
serverHost,
serverPort,
daprHost,
daprPort,
CommunicationProtocolEnum.HTTP,
{ logger: { level: LogLevel.Error } });
有关如何使用 Server 的更多详细信息,请参阅 JavaScript Server。https://docs.dapr.io/developing-applications/sdks/js/js-server/
JavaScript SDK 使用内置 Console
进行日志记录。要使用 Winston 或 Pino 等自定义 logger,您可以实现 LoggerService
接口。
基于 Winston 的日志记录:
创建 LoggerService
的新实现。
import { LoggerService } from "@dapr/dapr";
import * as winston from 'winston';
export class WinstonLoggerService implements LoggerService {
private logger;
constructor() {
this.logger = winston.createLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'combined.log' })
]
});
}
error(message: any, ...optionalParams: any[]): void {
this.logger.error(message, ...optionalParams)
}
warn(message: any, ...optionalParams: any[]): void {
this.logger.warn(message, ...optionalParams)
}
info(message: any, ...optionalParams: any[]): void {
this.logger.info(message, ...optionalParams)
}
verbose(message: any, ...optionalParams: any[]): void {
this.logger.verbose(message, ...optionalParams)
}
debug(message: any, ...optionalParams: any[]): void {
this.logger.debug(message, ...optionalParams)
}
}
将新实现传递给 SDK。
import { CommunicationProtocolEnum, DaprClient, LogLevel } from "@dapr/dapr";
import { WinstonLoggerService } from "./WinstonLoggerService";
const winstonLoggerService = new WinstonLoggerService();
// create a client instance with log level set to verbose and logger service as winston.
const client = new DaprClient(
daprHost,
daprPort,
CommunicationProtocolEnum.HTTP,
{ logger: { level: LogLevel.Verbose, service: winstonLoggerService } });