Microsoft Orleans — Reusing Grains and Grain State
We’ve explored Orleans for distributing application logic across a cluster. Next, we’ll be looking at grain reuse and grain state…
Recap
Note the starting point of this code can be found here. As described previously, grains are the “primitives” that are created for use with Orleans code. You invoke grains in a very similar manner to your “normal” code to make it as simple as possible. In the previous example we simply called the grain a single time; it takes in a value, and spits it back:
1 |
|
Returns:
Microsoft Orleans — Reusing Grains and Grain State
Grain Reuse
Grains don’t have to be used only once, in most situations I would wager they’re used in the hundreds and thousands of times. Though the current grain we’re working with doesn’t have much use to be invoked multiple times, it can still make for a (good?) example.
Let’s change our grain implementation a bit from:
1 |
|
To:
1 |
|
In the above, we’re now printing out the grain’s uniqueId/primary key. The primary key isn’t super important in the current state of the grain implementation, but patience you must have, my young padawan.
Next, let’s change our Client to call a single grain, several times. The original:
1 |
|
Should change to:
1 |
|
Running the above code will present us with:
Above, we can see that the same grain (as indicated by *grn/CD25ADD4/ba676182
) was used for all three invokes of grain.SayHello.
Now you may be wondering to yourself:
Can we have multiple instantiations of the same grain, with separate primary keys?
We sure can! Let’s take a look:
1 |
|
output:
What does the above all mean? In part, it means that grains can be instantiated one or multiple times depending on need. What kind of need would we have in multiple instantiations? Well in this grain’s case, none that I can think of since the grain always returns what it receives. Where multiple instantiations can really shine is when it comes to grains containing state and/or contextual data.
Stateful Grains
Grains can have a notion of “state”, or instance variables pertaining to some internals of the grain, or its context. Orleans has two methods of tracking grain state:
- Extend
Grain<T>
rather thanGrain
- Do it yo’self
Generally, I’d prefer to not write code that’s already been written, so I’ll be sticking with the first option.
Grain Persistence
In order to track grain state, our grains state needs to be persisted somewhere. Orleans offers several methods of grain state persistence (doc). For demonstration purposes, I’ll be using the MemoryGrainStorage. Be advised this this method of persistence is destroyed when the silo goes down, so is probably not especially useful in production like scenarios.
To utilize stateful grains you need a few things:
- configured persistence store(s)
- stateful grain(s)
Grain Persistence how-to
We need to configure our storage provider — some of the providers can be “more involved” in that you need some sort of backing infrastructure and/or cloud capabilities/money (:D). That’s a big reason I’m going for the memory provider!
To register the memory provider, let’s update our original SiloHost’s builder from:
1 |
|
to:
1 |
|
Note that since the above is using a Builder Pattern, you could just add:
1 |
|
as a separate line in between the instantiation of the builder, and the var host = builder.Build();.
That’s it!
A persistent grain
Next, let’s slap together a grain with some state.
We’ll create a grain that can track the number of times a user visits our “site” (pretend it’s a website). The first thing is to define the interface:
1 |
|
Properties of the above:
String key
— since we’re using it to track visits to our site, using the account email seems to make sense as a unique keyTask<int> GetNumberOfVisits()
— this method will be used to retrieve the number of times a user has visited.Task Visit()
— this method will be invoked when a user visits the site.
The grain implementation:
1 |
|
A few new things going on above:
- Specifying a storage provider as a class level attribute — this is the storage provider we defined in the changes to the SiloHost earlier.
- Extending
Grain<T>
instead of Grain where<T>
is a state class. - Manipulation of this.State, in order to keep track of the specific instantiation “state”.
- A state class VisitTrackerState
Seeing Grain State in action
Finally, let’s see what sort of things we can do in our Client app using our new stateful grain.
1 |
|
Nothing in the above that we haven’t really done before, getting instances of the same type of grain using two separate “users”, invoking grain methods on them several times, and printing the results.
When running the app, we are presented with:
You can see in the above that our visit counter is incrementing with each visit, and kritner is visiting a lot more than notKritner.
What happens if we run this same app again?
You can see that our visit counter left off from the first run — but of course it did; we’re using stateful grains! Just as a note again, because we’re using the memory provider, once the SiloHost is brought down, the grain’s state will not be kept. This state would not be destroyed on silo shutdown when using other grain storage providers.
Hopefully this helps others start to see the powerful possibilities that Orleans offers — Actors, grains, and grain state are barely scratching the surface of what Orleans can do. Hopefully I’ll have more to write about regarding Orleans sometime soon!
Full code at this point can be found at https://github.com/Kritner-Blogs/OrleansGettingStarted/releases/tag/v0.11
Related:
- Blog post — Getting Started with Microsoft Orleans
- GitHub repo as of the start of this post — v0.1
- GitHub repo at the end of this post — v0.11
- Orleans Documentation — Grain Persistence
Microsoft Orleans — Reusing Grains and Grain State
https://blog.kritner.com/2018/10/17/Microsoft-Orleans-Reusing-Grains-and-Grain-State/