Over the past week, I've ventured into software development by researching the concept of virtual actors. I ended up looking into two different frameworks: Dapr and Orleans.
Both are very concise projects with tons of interesting use cases. Both use the idea of "virtual" actors. A virtual actor is a state and logic unit that:
- It can be uniquely identified by ID
- It is single-threaded
- Can be in-memory or persistent - its lifecycle is managed by the framework
I really like the idea of virtual actors and find them very helpful in my exploration of building scalable and reliable tools to handle complex task workflows. If each task is a single-threaded virtual participant, the race condition problem disappears.
Because Orleans and Dapr are both Microsoft projects, I envision a day in a Western Story style showdown at the Microsoft cafeteria.
Orleans
I started with Orleans because it has been on my radar for a while after seeing some videos about it on YouTube. It started really badly because I thought I'd be using the 4.x version of all their NuGet packages. However, absolutely none of their documentation works with the 4.x package. I ended up using version 3.6.2.
Grains / State / Timers
Creating a grain that tracks its own state and performs actions is very simple. I was even able to follow the documentation for grain persistence and create my own CosmosDB (SQL API) implementation of IGrainStorage.
Reminders
Reminders are also easy to set. Until I tried to configure real-world persistence for them. At this point in my research, I'm trying to keep everything tidy and store everything in ComsosDB. Unfortunately, I can't get Orleans' reminder persistence package to work at all. I ended up having to switch to the AzureStorage package. So now my data is half in the SQL API account and half in the table API account.
Streams
That's where I didn't go well. In Orleans, flows are identified by a GUID and an optional namespace. I'm sure there's a good reason why streams have to be identified by a GUID, but wow, that's impractical.
I'm very frustrated with streams because I was able to create them easily, but once I stop and restart my project and trigger a new event, everything crashes.
This is followed by a very valuable piece of information, as it took me 8 hours to reverse engineer the Orleans code to figure it out:
When a grain is a stream subscriber, the grain must call ResumeAsync on the subscription handle in its OnActivateAsync method, or you will crash with an unrecognized error.
I also had the issue of the same subscription being duplicated, so I used the code to delete all the grain's subscriptions and then recreated it:
Other Orleans Gotchas / Tips
Streams works well with Azure Event Hubs (via AddEventHubStreams).
Don't use / or other special characters in the Grain name of the CosmosDB SQL API!
Orleans conclusion
I like Orleans and I think it has potential. However, it has a very steep learning curve. Due to my long struggle with streaming, I don't have time to study how clusters/deployments work.
Dapr
I found Dapr by looking for alternatives to Orleans. It's a bit strange that it's also a Microsoft-sponsored project. Perhaps they are here to take a survival of the fittest approach. If yes, I think Dapr will be a survivor.
First, Dapr's REST/gRPC-based design allows actors to be implemented using any programming language. I also found it trivial to run everything (participants, statuses, timers, reminders, events) on a single Redis instance. On top of that, it only took me about a third of the time to start using Dapr. Such a fast startup time is due to Dapr's excellent documentation.
Actors / Timers / Reminders
Did I just say that Dapr's documentation is great? Well, it's everywhere, except for JavaScript examples. I spend most of my time on Dapr, trying to figure out how to call methods on actors. The code for the Dapr Javascript sample is as follows:
This is clearly outdated. I had to spend a lot of time coaxing these three lines through Dapr's test/sample code exploration
The code examples for getting/setting state have similar issues, so I created a GitHub issue for them.
Except for those minor problems, setting up actors is a piece of cake.
Setting timers and reminders for my cast is also very easy.
State
I was able to configure Dapr to persist with Postgres very easily.
One thing I've noticed is that there may be scalability issues with how reminders are stored. Dapr stores all alerts for a specific participant type in a single JSON array. What happens if someone has a ton of reminders?
Other Dapr Gotchas / Tips
One thing I noticed when browsing the code for the JavaScript SDK is that there aren't many comments in the codebase at all. This makes it almost impossible to figure out something. For example, in the state manager's addOrUpdateState method, there is a third parameter called updateValueFactory. If there are no comments in the code, it is almost impossible to tell what the callback is for.
I'm also not sure how much I like the "dapr init" command trying to set up and run a redis container for me. What if I already have a redis container? What if I want to use postgres instead? I can't find documentation explaining how to change the dapr init feature.
A note to anyone who is having trouble using pubsub. You must use "dapr run" to run both your publisher and subscriber:
For actors and pubsub, note that it's important to use the --app-port parameter to let dapr know which port your service is running on. pubsub events and actor calls are sent to your service from the Dapr sidecar via http calls, so it needs to know where to send them:
I tested a small Dapr self-hosted "cluster" by launching my pubsub subscriber instance on two different machines on my home network. It just worked!
Dapr conclusion
If you want to know more ideas about distributed applications or virtual actors, I recommend you start with Dapr. Orleans was the original trailblazer, while Dapr was a reboot that took things to the next level.
Original link:The hyperlink login is visible.
|