Skip to content

Dapr Actors (Next) SDK - #1865

Merged
WhitWaldo merged 51 commits into
feature-actors-nextfrom
actors-next
Jul 10, 2026
Merged

Dapr Actors (Next) SDK#1865
WhitWaldo merged 51 commits into
feature-actors-nextfrom
actors-next

Conversation

@WhitWaldo

Copy link
Copy Markdown
Contributor

Description

This PR introduces a re-imagined implementation of the Dapr Actors SDK. In the longer term, it is intended to replace the current Dapr.Actors project, but that won't happen for quite some time. This is not a mere rewrite of the original SDK but rather an overhaul of the Dapr Actor SDK to support functionality that I've wanted to see in an actor framework in my years developing against actors. With the introduction of bidirectional gRPC stream support in v1.18 of the runtime, I thought now would be a good time to introduce this new project so I might take advantage of the opportunity to modernize the SDK and add this new functionality.

At a high level, the actor model itself is unchanged. Actors are still virtual, addressed by type and id, and support reentrancy, timers, reminders, and proxying. Dapr.Actors.Next is a separate package and is not API-compatible with Dapr.Actors, but it is fully wire-compatible with the Dapr runtime, so adopting it requires no runtime changes (except that prerequisite of v1.18). Adopting this package will require a rewrite of your actor implementation rather than being an in-place upgrade.

Two things drive the change. The first is the transport itself. Actor callbacks (invoke, reminder, timer, and deactivate) now arrive over a persistent, app-initiated bidirectional gRPC stream rather than per-call requests into hosted actor-handler endpoints. This is the same streaming pattern Dapr already uses for streaming pub/sub and workflow dispatch. It lowers per-call overhead and it means there are no actor handler endpoints to map. The second is the amount of work the SDK now does locally, on your side of the connection, to make actor code easier and safer to write, which is where most of the new functionality lives.

On the modernization front, the SDK is build for current .NET (supporting .NET 8, 9 and 10 and then 10 and 11 after the EOL of 8 and 9 later this year). Source generators replace the reflection-based proxy and dispatch machinery, which removes reflection from the hot path and makes the runtime packages trim- and Native-AOT-friendly. Nullable reference types are enabled across the public surface, registration and configuration follow the dependency injection and options patterns used elsewhere in the modern SDK, and serialization moves to the same pluggable IDaprSerializer abstraction that State Management and Workflows already use (System.Text.Json by default with no more DataContract or remoting/non-remoting proxy split). A set of Roslyn analyzers, several with code fixes, catch actor-specific mistakes at build time, from a state change that would break deserialization of existing data to blocking calls or wall-clock time reads inside a turn.

Beyond modernization, a package adds capabilities the original never had. State migration is handled through a versioned state envelope and typed upcaster chains, so older persisted state stays loadable under new code. A first-class state machine programming model lets actors that are genuinely state machines be authored as explicit transition tables, with a runtime-defined (interpreted) variant that can run a machine supplied as data and verify it before it goes live. Pub/sub-driven actors let an actor react to topic events through a single subscription attribute. A dynamic invocation surface allows calling actors by type, id, and method with no compile-time contract, which is also the natural path for cross-language callers.

The capability I'm most enthusiastic about, and the one that most justifies the effort, is testing. The hard parts of actors (reentrancy, a timer coming due mid-turn, duplicate or out-of-order delivery, a failure in the middle of a state write, etc.) are normally only reachable with a running sidecar and wall-clock timing, which makes them effectively untestable. This SDK ships an in-memory deterministic test runtime that isolates the sources of nondeterminism (transport, scheduling and time) behind abstractions, so the same actor code runs against either the real runtime or the test runtime. Concurrency races become plain, repeatable unit tests seeded for reproducibility, virtual time drives timers and reminders without real waits, faults can be injected deliberately, and state machines can be structurally verified, all with no sidecar, state store, or Docker. For deeper exploration, the runtime can optionally bridge to Microsoft Coyote which was the inspiration for this functionality. To be clear, this does not replace the end-to-end coverage afforded by Dapr.Testcontainers, which still proves the real protocol against a live sidecar; rather it supplements that with fast, deterministic testing of your actual application logic, letting you keep the bulk of your coverage in millisecond unit tests and reserve integration tests for what only a real runtime can prove.

A few design decisions are worth calling out explicitly because they shape how the SDK behaves. The host is build as a stream manager that opens one stream per hosted actor type, which gives per-type fault isolation, configuration and lifecycle. Consumption of the callback stream is non-blocking with the SDK owning backpressure. Delivery is treated as a first-class concept rather than as an afterthought: invokes are at-least-once, so turns must be safe to re-run, and reminders re-fire until acknowledged, so handlers must be idempotent. Reentrancy call-chain metadata is propagated automatically when reentrancy is enabled, so authors never have to manage headers by hand. A single [GenerateActorClient] interface can back multiple actor types.

In the dapr/docs repository, I'll be opening a separate documentation PR in the near future covering the concepts, a how-to guide, the state machine and dynamic invocation surfaces, testing and a full example suite with an accompanying tutorial, along with guidance for developers migrating from Dapr.Actors. My hope is that the tests, examples and inline structure make the intent legible in the meantime, and I welcome feedback on both the surface and the underlying design decisions.

Issue reference

We strive to have all PR being opened based on an issue, where the problem or feature have been discussed prior to implementation.

Please reference the issue this PR will close: #[issue number]

Checklist

Please make sure you've completed the relevant tasks for this PR, out of the following list:

  • Code compiles correctly
  • Created/updated tests
  • Extended the documentation

WhitWaldo added 29 commits June 30, 2026 07:04
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…irectly to UTF8 bytes

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…T 10 against the `Dapr.Actors` and `Dapr.Actors.Next` packages validating 1 and 8 actor types for registration and startup and then cold and warm starts and invocations. It also includes AOT testing for .NET 10, but this only validates startup. Filters available and documented in README for variations.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…sistent implementation of the Dapr.Actors package but from a complete redesign and is only compatible with Dapr runtime 1.18 and later. It supports nullable types, registration aliases, by default it offers automatic actor registration at startup via source generator and utilizes the `IDaprSerializer` from `Dapr.Common` to implement fully pluggable serialization (no remoting, non-remoting proxies).

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
… adding an attribute to a method in an actor, this will automatically set up the subscription at startup with the supplied filter and will invoke the method on that routed actor instance when a matching CloudEvent message is received to faciliate stream-driven actors.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…fe patterns in actor logic (e.g. don't block calls within actor turns and don't attempt to escape the scheduler from within the actor method)

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…revalence of sealed and non-abstracted types in the SDK. This release instead embodies many of the key ideas from the Coyote actor framework to make testing a first-class concept not only of the SDK types, but also to faciliate various common failure scenarios that simply are not testable typically. The three non-deterministic variables are time, transport and scheduling and the deterministic engine (available for testing with journaling as `ActorTestRuntime`) allows all three to be deterministically controlled to far more richly validate trigger and failure conditions. This doesn't replace E2E testing - it complements it to make app testing dramatically more valuable and applicable (and fast - takes milliseconds to run since it doesn't rely on `Dapr.Testcontainers`)

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…sometimes the problem calls for a state machine actor. The state lives indefinitely, short executions act against it and it can answer to asynchronous requests immediately or react to streams of events over its lifetime. Workflows aren't well suited to this, so I introduce a state machine implementation to the Dapr Actors SDK to represent distinct states and rules for what the engine does based on this state. It's more versatile than a collection of scattered `if` statements and allows the test runtime to validate the structure automatically for you. Docs and examples to follow.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…thoring and hosting functionality that doesn't exist in the other Dapr Actor SDks to my knowledge. I'll get more into what it does, why and how in the forthcoming documentation.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…embly like the other Dapr projects so source generators, analyzers and runtime libraries needn't be installed separately.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…ration regarding source generator and analyzer bundling accuracy) for all Dapr.Actors.Next projects - as of last check, this averages 94% test coverage.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…tion and how similar it is to Dapr.Actors + adding test project to demonstrate how trivially it's tested and instructions in README for running both.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…ks along with .http file and test project to demonstrate how readily it's tested in this SDK.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…scription to a streaming pubsub endpoint. This registers and uses the DaprClient to push data into that Pubsub instance (also to demonstrate registration and showing injection).

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…figure/Reset in favor of the DI approach.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…e headers if reentrancy is enabled in the options.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…lustrating an auction in which there are multiple bidders and rules cross timer guards.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…ogic, this example instead demonstrates an interpreted state machine actor in which all the components of the state machine are defined, but the actual logic is provided as a series of definitions and interpretted into a operable and verifiable state machine.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
… they have to differentiate themselves by name. Added an analyzer to show a warning if there are duplicate effective actor type names as that would result in a runtime startup conflict.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
@WhitWaldo WhitWaldo added this to the v1.19 milestone Jul 3, 2026
WhitWaldo added 12 commits July 5, 2026 20:09
…ites an older family member while a later family member is available and the migration graph can get there.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…rActor]` if it doesn't implement an interface extending `IActor` or said interface isn't itself decorated with `[GenerateActorClient]` as both attributes are required for this library to work.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…sting as they won't build how they're stored

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…ctually testing against

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…from scratch

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…e machines to also pair the idea with workflows

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…rfaces decorated with `[GenerateActorClient]` that extend `IActor` instead of being limited to just one interface

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
@WhitWaldo
WhitWaldo changed the base branch from master to feature/actors-next July 8, 2026 20:48
…pport more state shapes

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…r parity with original Dapr.Actors SDK for saving state before end of turn.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
…eminder registration exists in the app on an existing actor interface attached to the named actor type.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
@WhitWaldo
WhitWaldo changed the base branch from feature/actors-next to feature-actors-next July 10, 2026 20:21
Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant