Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 103 additions & 100 deletions daprdocs/content/en/js-sdk-docs/js-actors/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,28 @@ For a more in-depth overview of Dapr actors, visit the [actors overview page]({{
- [JavaScript NPM package installed](https://www.npmjs.com/package/dapr-client)

## Scenario
The below code examples loosely describe the scenario of a Parking Garage Spot Monitoring System, which can be seen in this [video] by Mark Russinovich(https://www.youtube.com/watch?v=eJCu6a-x9uo&t=3785).
The below code examples loosely describe the scenario of a Parking Garage Spot Monitoring System, which can be seen in this [video](https://www.youtube.com/watch?v=eJCu6a-x9uo&t=3785) by Mark Russinovich.

A parking garage consists of hundreds of parking spaces, where each parking space includes a sensor that provides updates to a centralized monitoring system. The parking space sensors (our actors) detect if a parking space is occupied, or available.
A parking garage consists of hundreds of parking spaces, where each parking space includes a sensor that provides updates to a centralized monitoring system. The parking space sensors (our actors) detect if a parking space is occupied or available.

To jump in and run this example yourself, clone the source code, which can be found in the [JavaScript SDK examples directory](https://github.com/dapr/js-sdk/tree/master/examples/http/actor-parking-sensor).

## Actor Interface
The actor interface defines the contract that is shared between the actor implementation and the clients calling the actor. In the example below, we have created an interace for a parking garage sensor. Each sensor has 2 methods: `carEnter` and `carLeave`, which defines the state of the parking space:

```javascript
```ts
export default interface ParkingSensorInterface {
carEnter(): Promise<void>;
carLeave(): Promise<void>;
}
```

## Actor Implementation
An actor implementation defines a class by extending the base type `AbstractActor` and implements the actor interface. The following code describes what an actor implmentation consists of by implementing the methods defined in the `ParkingSensorInterface`. It also defines a few extra helper methods:
An actor implementation defines a class by extending the base type `AbstractActor` and implementing the actor interface (`ParkingSensorInterface` in this case).

```javascript
The following code describes an actor implementation along with a few helper methods.

```ts
import { AbstractActor } from "dapr-client";
import ParkingSensorInterface from "./ParkingSensorInterface";

Expand All @@ -49,149 +51,150 @@ export default class ParkingSensorImpl extends AbstractActor implements ParkingS
// Implementation that updates state that this parking spaces is available.
}

async getParkingSpaceUpdate(): Promise<object> {
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.
}
}
```

## Registering Actors
Initialize and register your actors by using the DaprServer package:
Initialize and register your actors by using the `DaprServer` package:

```javascript
import { DaprServer } from "dapr-server";
import { DaprServer } from "dapr-client";
import ParkingSensorImpl from "./ParkingSensorImpl";

async function start() {
const server = new DaprServer(`server-host`, `server-port`, `dapr-host`, `dapr-port`);
const daprHost = "127.0.0.1";
const daprPort = "50000";
const serverHost = "127.0.0.1";
const serverPort = "50001";

await server.actor.init(); // Let the server know we need actors
server.actor.registerActor(ParkingSensorImpl); // Register the actor
await server.start(); // Start the server
}
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)}`);
```

## Invoking Actors
After Actors are registered, use the DaprClient to invoke methods on an actor. The client will call the actor methods defined in the actor interface.
## Invoking Actor Methods
After Actors are registered, create a Proxy object that implements `ParkingSensorInterface` using the `ActorProxyBuilder`. You can invoke the actor methods by directly calling methods on the Proxy object. Internally, it translates to making a network call to the Actor API and fetches the result back.

```javascript
import { DaprClient, DaprServer } from "dapr-client";
import { DaprClient, ActorId } from "dapr-client";
import ParkingSensorImpl from "./ParkingSensorImpl";
import ParkingSensorInterface from "./ParkingSensorInterface";

async function start() {
const server = new DaprServer(`server-host`, `server-port`, `dapr-host`, `dapr-port`);
const client = new DaprClient(`dapr-host`, `dapr-port`);
const daprHost = "127.0.0.1";
const daprPort = "50000";

await server.actor.init();
server.actor.registerActor(ParkingSensorImpl);
await server.start();
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);

await client.actor.invoke("PUT", ParkingSensorImpl.name, `actor-id`, "carEnter"); // Invoke the ParkingSensor Actor by calling the carEnter function
}
// 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();
```

## Saving and Getting State
## Using states with Actor

```javascript
import { DaprClient, DaprServer } from "dapr-client";
import ParkingSensorImpl from "./ParkingSensorImpl";
```ts
// ...

const PARKING_SENSOR_PARKED_STATE_NAME = "parking-sensor-parked"

const actor = builder.build(new ActorId("my-actor"))

async function start() {
const server = new DaprServer(`server-host`, `server-port`, `dapr-host`, `dapr-port`);
const client = new DaprClient(`dapr-host`, `dapr-port`);

await server.actor.init();
server.actor.registerActor(ParkingSensorImpl);
await server.start();

// Perform state transaction
await client.actor.stateTransaction("ParkingSensorImpl", `actor-id`, [
{
operation: "upsert",
request: {
key: "parking-sensor-location-lat",
value: "location-x"
}
},
{
operation: "upsert",
request: {
key: "parking-sensor-location-lang",
value: "location-y"
}
}
]);

// GET state from an actor
await client.actor.stateGet("ParkingSensorImpl", `actor-id`, `parking-sensor-location-lat`)
await client.actor.stateGet("ParkingSensorImpl", `actor-id`, `parking-sensor-location-lang`)
// 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);
...
```

## Actor Timers and Reminders
The JS SDK supports actors that can schedule periodic work on themselves by registering either timers or reminders. The main difference between timers and reminders is that the Dapr actor runtime is not retaining any information about timers after deactivation, while persisting the information about reminders using the Dapr actor state provider.
The JS SDK supports actors that can schedule periodic work on themselves by registering either timers or reminders. The main difference between timers and reminders is that the Dapr actor runtime does not retain any information about timers after deactivation, but persists reminders information using the Dapr actor state provider.

This distintcion allows users to trade off between light-weight but stateless timers vs. more resource-demanding but stateful reminders.
This distinction allows users to trade off between light-weight but stateless timers versus more resource-demanding but stateful reminders.

The scheduling interface of timers and reminders is identical. For an more in-depth look at the scheduling configurations see the [actors timers and reminders docs]({{< ref "howto-actors.md#actor-timers-and-reminders" >}}).

### Actor Timers
```javascript
import { DaprClient, DaprServer } from "dapr-client";
import ParkingSensorImpl from "./ParkingSensorImpl";

async function start()
const server = new DaprServer(`server-host`, `server-port`, `dapr-host`, `dapr-port`);
const client = new DaprClient(`dapr-host`, `dapr-port`);

await server.actor.init();
server.actor.registerActor(ParkingSensorImpl);
await server.start();

// Register a timer
await client.actor.timerCreate(ParkingSensorImpl.name, `actor-id`, `timer-id`, {
callback: "method-to-excute-on-actor",
dueTime: Temporal.Duration.from({ seconds: 2 }),
period: Temporal.Duration.from({ seconds: 1 }),
ttl: Temporal.Duration.from({ seconds: 1 }),
});

// Delete the timer
await client.actor.timerDelete(ParkingSensorImpl.name, `actor-id`, `timer-id`);
}
// ...

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");
```

### Actor Reminders
```javascript
import { DaprClient, DaprServer } from "dapr-client";
import ParkingSensorImpl from "./ParkingSensorImpl";
// ...

const actor = builder.build(new ActorId("my-actor"));

async function start()
const server = new DaprServer(`server-host`, `server-port`, `dapr-host`, `dapr-port`);
const client = new DaprClient(`dapr-host`, `dapr-port`);
// 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.
);

await server.actor.init();
server.actor.registerActor(ParkingSensorImpl);
await server.start();
// Delete the reminder
await actor.unregisterActorReminder("reminder-id");
```

To handle the callback, you need to override the default `receiveReminder` implementation in your actor. For example, from our original actor implementation:
```ts
export default class ParkingSensorImpl extends AbstractActor implements ParkingSensorInterface {
// ...

// Register a reminder, it has a default callback
await client.actor.reminderCreate(DemoActorImpl.name, `actor-id`, `timer-id`, {
dueTime: Temporal.Duration.from({ seconds: 2 }),
period: Temporal.Duration.from({ seconds: 1 }),
ttl: Temporal.Duration.from({ seconds: 1 }),
data: 100
});
/**
* @override
*/
async receiveReminder(state: any): Promise<void> {
// handle stuff here
}

// Delete the reminder
await client.actor.reminderDelete(DemoActorImpl.name, `actor-id`, `timer-id`);
// ...
}
```

- For a full guide on actors visit [How-To: Use virtual actors in Dapr]({{< ref howto-actors.md >}}).
For a full guide on actors, visit [How-To: Use virtual actors in Dapr]({{< ref howto-actors.md >}}).
63 changes: 19 additions & 44 deletions examples/http/actor-parking-sensor/README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,13 @@
# Examples - Hello World
# Actor Parking Sensor

## TODO
This example shows a car parking area where cars (virtual actors) enter and leave randomly. The car sensor data is sent via Dapr actors to InfluxDB and displayed on Grafana.

## Prerequisites

Start influxdb, telegraf and grafana using docker-compose.

```bash
# Run InfluxDB
# Note: it auto removes after shutdown
# Note: non-persistent volume, add "-v influxdb2:/var/lib/influxdb2" to make it persistent
docker run --rm -it -d \
-e DOCKER_INFLUXDB_INIT_MODE=setup \
-e DOCKER_INFLUXDB_INIT_USERNAME=admin \
-e DOCKER_INFLUXDB_INIT_PASSWORD=MyAdmin@123! \
-e DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-token \
-e DOCKER_INFLUXDB_INIT_ORG=my-parking-garage \
-e DOCKER_INFLUXDB_INIT_BUCKET=my-sensors \
--net=host \
--name influxdb \
influxdb:2.0

# Run Telegraf
docker run --rm -it -d \
--net=host \
--name=telegraf \
telegraf

# Run Grafana
# Note: non-persistent volume, add "-v influxdb2:/var/lib/influxdb2" to make it persistent
docker run --rm -it -d \
--name=grafana \
--net=host \
grafana/grafana
docker-compose up
```

## Running
Expand All @@ -40,22 +19,18 @@ docker run --rm -it -d \
# Install (from the example directory)
npm install

# Start a RabbitMQ Container (for the binding example part)
# note: mgmt interface at http://localhost:15672
docker run -d --rm --hostname my-rabbitmq --name my-rabbitmq \
-e RABBITMQ_DEFAULT_USER=test-user -e RABBITMQ_DEFAULT_PASS=test-password \
-p 0.0.0.0:5672:5672 -p 0.0.0.0:15672:15672 \
rabbitmq:3-management

# Run the example
# Build and run the example
npm run build
npm run start:dapr
```

```
from(bucket: "my-sensors")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "sensor-states")
|> pivot(columnKey: ["_field"], rowKey: ["_time"], valueColumn: "_value")
|> group()
|> yield(name: "last")
```
## Visualize

Start Grafana on [localhost:3001](localhost:3001) and use the credentials `admin`:`password` to login and visualize the dashboard!

Sample output:
![Sample Output](./output.png)

## References
1. Original blog post: https://xaviergeerinck.com/post/2021/10/09/parking-garage
1. Influx/Telgraf/Grafana setup inspired from: https://github.com/bcremer/docker-telegraf-influx-grafana-stack
13 changes: 13 additions & 0 deletions examples/http/actor-parking-sensor/configuration.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Grafana options
GF_SECURITY_ADMIN_USER=admin
GF_SECURITY_ADMIN_PASSWORD=password
GF_INSTALL_PLUGINS=


# InfluxDB options
DOCKER_INFLUXDB_INIT_MODE=setup
DOCKER_INFLUXDB_INIT_USERNAME=admin
DOCKER_INFLUXDB_INIT_PASSWORD=MyAdmin@123!
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-token
DOCKER_INFLUXDB_INIT_ORG=my-parking-garage
DOCKER_INFLUXDB_INIT_BUCKET=my-sensors
Loading