This document describes how to create an Actor(DemoActor) and invoke its methods on the client application.
- The actor interface(demo_actor_interface.py). This contains the interface definition for the actor. Actor interfaces can be defined with any name. The interface defines the actor contract that is shared by the actor implementation and the clients calling the actor. Because client may depend on it, it typically makes sense to define it in an assembly that is separate from the actor implementation.
- The actor service(demo_actor_service.py). This implements FastAPI service that is going to host the actor. It contains the implementation of the actor,
demo_actor.py. An actor implementation is a class that derives from the base typeActorand implements the interfaces defined indemo_actor_interface.py. - The actor service for flask(demo_actor_flask.py). This implements Flask web service that is going to host the actor.
- The actor service for gRPC(demo_actor_grpc_service.py). This hosts the actor over the app-initiated gRPC actor stream (alpha) — no HTTP callback endpoints at all.
- The actor client(demo_actor_client.py) This contains the implementation of the actor client which calls DemoActor's method defined in Actor Interfaces.
- Actor tests(test_demo_actor.py) This contains actor unit tests using mock actor testing functionality.
You can install dapr SDK package using pip command:
pip3 install -r demo_actor/requirements.txt-
Run Demo Actor service in new terminal window
cd demo_actor dapr run --app-id demo-actor --app-port 3000 -- uvicorn --port 3000 demo_actor_service:appExpected output:
... Activate DemoActor actor! has_value: False INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/GetMyData HTTP/1.1" 200 OK has_value: False INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/GetMyData HTTP/1.1" 200 OK set_my_data: {'data': 'new_data'} INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/SetMyData HTTP/1.1" 200 OK has_value: True INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/GetMyData HTTP/1.1" 200 OK set reminder to True set reminder is done INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/SetReminder HTTP/1.1" 200 OK set_timer to True set_timer is done INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/SetTimer HTTP/1.1" 200 OK receive_reminder is called - demo_reminder reminder - b'reminder_state' clear_my_data ...
-
Run Demo client in new terminal window
# Run actor client cd demo_actor dapr run --app-id demo-client -- python3 demo_actor_client.py
Expected output:
... call actor method via proxy.invoke_method() b'null' call actor method using rpc style None Actor reentrancy enabled: True call SetMyData actor method to save the state call GetMyData actor method to get the state {'data': 'new_data', 'ts': datetime.datetime(2020, 11, 13, 0, 38, 36, 163000, tzinfo=tzutc())} Register reminder Register timer waiting for 12 seconds stop reminder stop timer clear actor state
Instead of exposing HTTP callback endpoints, the actor host can receive all
actor callbacks (method invocations, reminders, timers, deactivations) over a
single app-initiated gRPC stream (SubscribeActorEventsAlpha1). The same
DemoActor implementation runs unchanged; only the hosting service differs.
This feature is alpha and requires Dapr runtime v1.18.0 or later (the first release with the
SubscribeActorEventsAlpha1RPC). daprd needs a gRPC app channel, so run with--app-protocol grpc --app-port;ActorGrpcHostmanages the minimal listener on that port for you (no server boilerplate in your code), and every actor callback arrives over the host's outbound stream while outbound actor operations (state, reminders, timers) use daprd's gRPC API.
-
Run the gRPC Demo Actor service in a new terminal window
cd demo_actor dapr run --app-id demo-actor --app-port 3000 --app-protocol grpc -- uv run demo_actor_grpc_service.pyExpected output:
... DemoActor is hosted over the Dapr gRPC actor stream Activate DemoActor actor! has_value: False set_my_data: {'data': 'new_data'} has_value: True set reminder to True set reminder is done set_timer to True set_timer is done receive_reminder is called - demo_reminder reminder - b'reminder_state' time_callback is called - timer_state clear_my_data ... -
Run the Demo client in a new terminal window (same client as the HTTP example)
cd demo_actor dapr run --app-id demo-client -- uv run demo_actor_client.py
-
Build and push docker image
$ cd examples/demo_actor/demo_actor $ docker build -t [docker registry]/demo_actor:latest . $ docker push [docker registry]/demo_actor:latest $ cd ..
For example, [docker registry] is docker hub account.
-
Follow these steps to create a Redis store.
-
Once your store is created, confirm validate
redis.ymlfile in thedeploydirectory.Note: the
redis.ymluses the secret created bybitmany/redisHelm chat to securely inject the password. -
Apply the
redis.ymlfile:kubectl apply -f ./deploy/redis.ymland observe that your state store was successfully configured!component.dapr.io/statestore configured
-
Update docker image location in
./deploy/demo_actor_client.ymland./deploy/demo_actor_service.yml -
Deploy actor service and clients
kubectl apply -f ./deploy/demo_actor_service.yml kubectl apply -f ./deploy/demo_actor_client.yml -
See logs for actor service and client
Logs for actor service sidecar:
dapr logs -a demoactor -kLogs for actor service app:
kubectl logs -l app="demoactor" -c demoactorLogs for actor client sidecar:
dapr logs -a demoactor-client -kLogs for actor service app:
kubectl logs -l app="demoactor-client" -c demoactor-client
-
Run Tests
cd demo_actor python -m unittest test_demo_actor.pyExpected output (note that the unit test print outputs might not necessarily be in this order - what really matters is that all tests pass anyway):
set_my_data: {'state': 5} has_value: True clear_my_data has_value: False has_value: False set reminder to True set reminder is done set reminder to False set reminder is done set_my_data: {'state': 5} has_value: True ---------------------------------------------------------------------- Ran 5 tests in 0.052s OK