Skip to content

Commit 915260b

Browse files
Merge pull request #252 from shubham1172/shubham1172/actors-docs
Fix actors documentation and examples
2 parents eb6ee2e + 1ca2ca9 commit 915260b

33 files changed

+988
-349
lines changed

daprdocs/content/en/js-sdk-docs/js-actors/_index.md

Lines changed: 103 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,28 @@ For a more in-depth overview of Dapr actors, visit the [actors overview page]({{
1717
- [JavaScript NPM package installed](https://www.npmjs.com/package/dapr-client)
1818

1919
## Scenario
20-
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).
20+
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.
2121

22-
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.
22+
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.
2323

2424
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).
2525

2626
## Actor Interface
2727
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:
2828

29-
```javascript
29+
```ts
3030
export default interface ParkingSensorInterface {
3131
carEnter(): Promise<void>;
3232
carLeave(): Promise<void>;
3333
}
3434
```
3535

3636
## Actor Implementation
37-
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:
37+
An actor implementation defines a class by extending the base type `AbstractActor` and implementing the actor interface (`ParkingSensorInterface` in this case).
3838

39-
```javascript
39+
The following code describes an actor implementation along with a few helper methods.
40+
41+
```ts
4042
import { AbstractActor } from "dapr-client";
4143
import ParkingSensorInterface from "./ParkingSensorInterface";
4244

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

52-
async getParkingSpaceUpdate(): Promise<object> {
54+
private async getInfo(): Promise<object> {
5355
// Implementation of requesting an update from the parking space sensor.
5456
}
5557

58+
/**
59+
* @override
60+
*/
5661
async onActivate(): Promise<void> {
5762
// Initialization logic called by AbstractActor.
5863
}
5964
}
6065
```
6166

6267
## Registering Actors
63-
Initialize and register your actors by using the DaprServer package:
68+
Initialize and register your actors by using the `DaprServer` package:
6469

6570
```javascript
66-
import { DaprServer } from "dapr-server";
71+
import { DaprServer } from "dapr-client";
6772
import ParkingSensorImpl from "./ParkingSensorImpl";
6873

69-
async function start() {
70-
const server = new DaprServer(`server-host`, `server-port`, `dapr-host`, `dapr-port`);
74+
const daprHost = "127.0.0.1";
75+
const daprPort = "50000";
76+
const serverHost = "127.0.0.1";
77+
const serverPort = "50001";
7178

72-
await server.actor.init(); // Let the server know we need actors
73-
server.actor.registerActor(ParkingSensorImpl); // Register the actor
74-
await server.start(); // Start the server
75-
}
79+
const server = new DaprServer(serverHost, serverPort, daprHost, daprPort);
80+
81+
await server.actor.init(); // Let the server know we need actors
82+
server.actor.registerActor(ParkingSensorImpl); // Register the actor
83+
await server.start(); // Start the server
84+
85+
// To get the registered actors, you can invoke `getRegisteredActors`:
86+
const resRegisteredActors = await server.actor.getRegisteredActors();
87+
console.log(`Registered Actors: ${JSON.stringify(resRegisteredActors)}`);
7688
```
7789

78-
## Invoking Actors
79-
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.
90+
## Invoking Actor Methods
91+
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.
8092

8193
```javascript
82-
import { DaprClient, DaprServer } from "dapr-client";
94+
import { DaprClient, ActorId } from "dapr-client";
8395
import ParkingSensorImpl from "./ParkingSensorImpl";
96+
import ParkingSensorInterface from "./ParkingSensorInterface";
8497

85-
async function start() {
86-
const server = new DaprServer(`server-host`, `server-port`, `dapr-host`, `dapr-port`);
87-
const client = new DaprClient(`dapr-host`, `dapr-port`);
98+
const daprHost = "127.0.0.1";
99+
const daprPort = "50000";
88100

89-
await server.actor.init();
90-
server.actor.registerActor(ParkingSensorImpl);
91-
await server.start();
101+
const client = new DaprClient(daprHost, daprPort);
92102

103+
// Create a new actor builder. It can be used to create multiple actors of a type.
104+
const builder = new ActorProxyBuilder<ParkingSensorInterface>(ParkingSensorImpl, client);
93105

94-
await client.actor.invoke("PUT", ParkingSensorImpl.name, `actor-id`, "carEnter"); // Invoke the ParkingSensor Actor by calling the carEnter function
95-
}
106+
// Create a new actor instance.
107+
const actor = builder.build(new ActorId("my-actor"));
108+
// Or alternatively, use a random ID
109+
// const actor = builder.build(ActorId.createRandomId());
110+
111+
// Invoke the method.
112+
await actor.carEnter();
96113
```
97114

98-
## Saving and Getting State
115+
## Using states with Actor
99116

100-
```javascript
101-
import { DaprClient, DaprServer } from "dapr-client";
102-
import ParkingSensorImpl from "./ParkingSensorImpl";
117+
```ts
118+
// ...
119+
120+
const PARKING_SENSOR_PARKED_STATE_NAME = "parking-sensor-parked"
121+
122+
const actor = builder.build(new ActorId("my-actor"))
103123

104-
async function start() {
105-
const server = new DaprServer(`server-host`, `server-port`, `dapr-host`, `dapr-port`);
106-
const client = new DaprClient(`dapr-host`, `dapr-port`);
107-
108-
await server.actor.init();
109-
server.actor.registerActor(ParkingSensorImpl);
110-
await server.start();
111-
112-
// Perform state transaction
113-
await client.actor.stateTransaction("ParkingSensorImpl", `actor-id`, [
114-
{
115-
operation: "upsert",
116-
request: {
117-
key: "parking-sensor-location-lat",
118-
value: "location-x"
119-
}
120-
},
121-
{
122-
operation: "upsert",
123-
request: {
124-
key: "parking-sensor-location-lang",
125-
value: "location-y"
126-
}
127-
}
128-
]);
129-
130-
// GET state from an actor
131-
await client.actor.stateGet("ParkingSensorImpl", `actor-id`, `parking-sensor-location-lat`)
132-
await client.actor.stateGet("ParkingSensorImpl", `actor-id`, `parking-sensor-location-lang`)
124+
// SET state
125+
await actor.getStateManager().setState(PARKING_SENSOR_PARKED_STATE_NAME, true);
126+
127+
// GET state
128+
const value = await actor.getStateManager().getState(PARKING_SENSOR_PARKED_STATE_NAME);
129+
if (!value) {
130+
console.log(`Received: ${value}!`);
133131
}
132+
133+
// DELETE state
134+
await actor.removeState(PARKING_SENSOR_PARKED_STATE_NAME);
134135
...
135136
```
136137

137138
## Actor Timers and Reminders
138-
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.
139+
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.
139140

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

142143
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" >}}).
143144

144145
### Actor Timers
145146
```javascript
146-
import { DaprClient, DaprServer } from "dapr-client";
147-
import ParkingSensorImpl from "./ParkingSensorImpl";
148-
149-
async function start()
150-
const server = new DaprServer(`server-host`, `server-port`, `dapr-host`, `dapr-port`);
151-
const client = new DaprClient(`dapr-host`, `dapr-port`);
152-
153-
await server.actor.init();
154-
server.actor.registerActor(ParkingSensorImpl);
155-
await server.start();
156-
157-
// Register a timer
158-
await client.actor.timerCreate(ParkingSensorImpl.name, `actor-id`, `timer-id`, {
159-
callback: "method-to-excute-on-actor",
160-
dueTime: Temporal.Duration.from({ seconds: 2 }),
161-
period: Temporal.Duration.from({ seconds: 1 }),
162-
ttl: Temporal.Duration.from({ seconds: 1 }),
163-
});
164-
165-
// Delete the timer
166-
await client.actor.timerDelete(ParkingSensorImpl.name, `actor-id`, `timer-id`);
167-
}
147+
// ...
148+
149+
const actor = builder.build(new ActorId("my-actor"));
150+
151+
// Register a timer
152+
await actor.registerActorTimer(
153+
"timer-id", // Unique name of the timer.
154+
"cb-method", // Callback method to execute when timer is fired.
155+
Temporal.Duration.from({ seconds: 2 }), // DueTime
156+
Temporal.Duration.from({ seconds: 1 }), // Period
157+
Temporal.Duration.from({ seconds: 1 }), // TTL
158+
50 // State to be sent to timer callback.
159+
);
160+
161+
// Delete the timer
162+
await actor.unregisterActorTimer("timer-id");
168163
```
169164

170165
### Actor Reminders
171166
```javascript
172-
import { DaprClient, DaprServer } from "dapr-client";
173-
import ParkingSensorImpl from "./ParkingSensorImpl";
167+
// ...
168+
169+
const actor = builder.build(new ActorId("my-actor"));
174170

175-
async function start()
176-
const server = new DaprServer(`server-host`, `server-port`, `dapr-host`, `dapr-port`);
177-
const client = new DaprClient(`dapr-host`, `dapr-port`);
171+
// Register a reminder, it has a default callback: `receiveReminder`
172+
await actor.registerActorReminder(
173+
"reminder-id", // Unique name of the reminder.
174+
Temporal.Duration.from({ seconds: 2 }), // DueTime
175+
Temporal.Duration.from({ seconds: 1 }), // Period
176+
Temporal.Duration.from({ seconds: 1 }), // TTL
177+
100 // State to be sent to reminder callback.
178+
);
178179

179-
await server.actor.init();
180-
server.actor.registerActor(ParkingSensorImpl);
181-
await server.start();
180+
// Delete the reminder
181+
await actor.unregisterActorReminder("reminder-id");
182+
```
182183

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

184-
// Register a reminder, it has a default callback
185-
await client.actor.reminderCreate(DemoActorImpl.name, `actor-id`, `timer-id`, {
186-
dueTime: Temporal.Duration.from({ seconds: 2 }),
187-
period: Temporal.Duration.from({ seconds: 1 }),
188-
ttl: Temporal.Duration.from({ seconds: 1 }),
189-
data: 100
190-
});
189+
/**
190+
* @override
191+
*/
192+
async receiveReminder(state: any): Promise<void> {
193+
// handle stuff here
194+
}
191195

192-
// Delete the reminder
193-
await client.actor.reminderDelete(DemoActorImpl.name, `actor-id`, `timer-id`);
196+
// ...
194197
}
195198
```
196199

197-
- For a full guide on actors visit [How-To: Use virtual actors in Dapr]({{< ref howto-actors.md >}}).
200+
For a full guide on actors, visit [How-To: Use virtual actors in Dapr]({{< ref howto-actors.md >}}).
Lines changed: 19 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,13 @@
1-
# Examples - Hello World
1+
# Actor Parking Sensor
22

3-
## TODO
3+
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.
4+
5+
## Prerequisites
6+
7+
Start influxdb, telegraf and grafana using docker-compose.
48

59
```bash
6-
# Run InfluxDB
7-
# Note: it auto removes after shutdown
8-
# Note: non-persistent volume, add "-v influxdb2:/var/lib/influxdb2" to make it persistent
9-
docker run --rm -it -d \
10-
-e DOCKER_INFLUXDB_INIT_MODE=setup \
11-
-e DOCKER_INFLUXDB_INIT_USERNAME=admin \
12-
-e DOCKER_INFLUXDB_INIT_PASSWORD=MyAdmin@123! \
13-
-e DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-token \
14-
-e DOCKER_INFLUXDB_INIT_ORG=my-parking-garage \
15-
-e DOCKER_INFLUXDB_INIT_BUCKET=my-sensors \
16-
--net=host \
17-
--name influxdb \
18-
influxdb:2.0
19-
20-
# Run Telegraf
21-
docker run --rm -it -d \
22-
--net=host \
23-
--name=telegraf \
24-
telegraf
25-
26-
# Run Grafana
27-
# Note: non-persistent volume, add "-v influxdb2:/var/lib/influxdb2" to make it persistent
28-
docker run --rm -it -d \
29-
--name=grafana \
30-
--net=host \
31-
grafana/grafana
10+
docker-compose up
3211
```
3312

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

43-
# Start a RabbitMQ Container (for the binding example part)
44-
# note: mgmt interface at http://localhost:15672
45-
docker run -d --rm --hostname my-rabbitmq --name my-rabbitmq \
46-
-e RABBITMQ_DEFAULT_USER=test-user -e RABBITMQ_DEFAULT_PASS=test-password \
47-
-p 0.0.0.0:5672:5672 -p 0.0.0.0:15672:15672 \
48-
rabbitmq:3-management
49-
50-
# Run the example
22+
# Build and run the example
23+
npm run build
5124
npm run start:dapr
5225
```
5326

54-
```
55-
from(bucket: "my-sensors")
56-
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
57-
|> filter(fn: (r) => r["_measurement"] == "sensor-states")
58-
|> pivot(columnKey: ["_field"], rowKey: ["_time"], valueColumn: "_value")
59-
|> group()
60-
|> yield(name: "last")
61-
```
27+
## Visualize
28+
29+
Start Grafana on [localhost:3001](localhost:3001) and use the credentials `admin`:`password` to login and visualize the dashboard!
30+
31+
Sample output:
32+
![Sample Output](./output.png)
33+
34+
## References
35+
1. Original blog post: https://xaviergeerinck.com/post/2021/10/09/parking-garage
36+
1. Influx/Telgraf/Grafana setup inspired from: https://github.com/bcremer/docker-telegraf-influx-grafana-stack
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Grafana options
2+
GF_SECURITY_ADMIN_USER=admin
3+
GF_SECURITY_ADMIN_PASSWORD=password
4+
GF_INSTALL_PLUGINS=
5+
6+
7+
# InfluxDB options
8+
DOCKER_INFLUXDB_INIT_MODE=setup
9+
DOCKER_INFLUXDB_INIT_USERNAME=admin
10+
DOCKER_INFLUXDB_INIT_PASSWORD=MyAdmin@123!
11+
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-token
12+
DOCKER_INFLUXDB_INIT_ORG=my-parking-garage
13+
DOCKER_INFLUXDB_INIT_BUCKET=my-sensors

0 commit comments

Comments
 (0)