Microsoft Orleans — Reminders and grains calling grains
cover image by Linda Perez Johannessen on Unsplash
Orleans is an actor model framework — a framework used for easily creating distributed systems across a cluster of machines. In this post we’ll explore the “Reminders” feature of Orleans.
First off, the docs:
Timers and Reminders | Microsoft Orleans Documentation
Reminders can be used in Orleans to perform tasks on a “schedule” more or less. I’m having trouble thinking of a simple example that actually makes sense, so, we’re going to go with an everything’s ok alarm:
To build the everything’s ok alarm, we’ll need to do a few things:
- Enable a reminder service within the
ISiloHostBuilder
— we’ll use the in memory one just for simplicity's sake, and to not have to rely on additional infrastructure - A new reminder grain
- Something for the reminder grain to do
I figured we could use the FakeEmailSender
introduced in:
Microsoft Orleans — Dependency Injection
in order to send “Fake email” everything’s ok notifications.
Enable the Reminder service
Starting from https://github.com/Kritner-Blogs/OrleansGettingStarted/releases/tag/v0.40, we’ll enable the in-memory reminder service by adding the following line to our ISiloHostBuilder.
.UseInMemoryReminderService()
The full method is:
private static async Task<ISiloHost> StartSilo()
{
// define the cluster configuration
var builder = new SiloHostBuilder()
.UseLocalhostClustering()
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "dev";
options.ServiceId = "HelloWorldApp";
})
.Configure<EndpointOptions>(options => options.AdvertisedIPAddress = IPAddress.Loopback)
.AddMemoryGrainStorage(Constants.OrleansMemoryProvider)
.ConfigureApplicationParts(parts =>
{
parts.AddApplicationPart(typeof(IGrainMarker).Assembly).WithReferences();
})
.ConfigureServices(DependencyInjectionHelper.IocContainerRegistration)
.UseDashboard(options => { })
.UseInMemoryReminderService()
.ConfigureLogging(logging => logging.AddConsole());
var host = builder.Build();
await host.StartAsync();
return host;
}
Some other possible reminder services to use include AzureTableReminderService
and AdoNetReminderService
.
Reminder Grain
Let’s create our everything’s ok alarm grain! I discovered in writing this, that there is a 1 minute minimum on the amount of time between “reminds”, so unfortunately, we’ll not be going with the originally planned 3 seconds :(
Anyway… our grain interface:
public interface IEverythingIsOkGrain : IGrainWithStringKey, IRemindable
{
Task Start();
Task Stop();
}
In the above, we’re doing a pretty standard grain interface, with the additional (to be) implemented IRemindable
. Two methods are attached to the interface, one to start the reminder, one to stop it. Note that the IRemindable
interface requires the implementing class to implement:
Task ReceiveReminder(string reminderName, TickStatus status);
The Grain Implementation — also Grainception!
As I mentioned previously, we’ll be using the FakeEmailSender
created from a previous post, as well as having our to be created grain utilize other grains (grainception)!
That could look like:
[StorageProvider(ProviderName = Constants.OrleansMemoryProvider)]
public class EverythingIsOkGrain : Grain, IEverythingIsOkGrain
{
IGrainReminder _reminder = null;
public async Task ReceiveReminder(string reminderName, TickStatus status)
{
// Grain-ception!
var emailSenderGrain = GrainFactory
.GetGrain<IEmailSenderGrain>(Guid.Empty);
await emailSenderGrain.SendEmail(
"homer@anykey.com",
new[]
{
"marge@anykey.com",
"bart@anykey.com",
"lisa@anykey.com",
"maggie@anykey.com"
},
"Everything's ok!",
"This alarm will sound every 1 minute, as long as everything is ok!"
);
}
public async Task Start()
{
if (_reminder != null)
{
return;
}
_reminder = await RegisterOrUpdateReminder(
this.GetPrimaryKeyString(),
TimeSpan.FromSeconds(3),
TimeSpan.FromMinutes(1) // apparently the minimum
);
}
public async Task Stop()
{
if (_reminder == null)
{
return;
}
await UnregisterReminder(_reminder);
_reminder = null;
}
}
A few things of note from the above:
-
[StorageProvider(ProviderName = Constants.OrleansMemoryProvider)]
— we’re making the grain stateful so (theoretically) the reminder will persist on shutdown. Note, it will not in our case because of using in memory storage, I think it would otherwise. -
IGrainReminder _reminder = null;
— holds reference to our started reminder, used for stopping the reminder. -
Task ReceiveReminder(string reminderName, TickStatus status)
— this is the method where we actually define what happens when the reminder occurs. -
var emailSenderGrain = GrainFactory.GetGrain<IEmailSenderGrain>(Guid.Empty);
— here we’re using a slightly different means of retrieving a grain, since we’re actually doing it from theSiloHost
, rather thanClient
. Note that this grain being pulled also makes use of dependency injection, but its dependency is only injected into the grain that actually needs it, not this reminder grain.
New grain menu option
as per usual, we’re going to create a new IOrleansFunction
concretion for use in our menu system; that new grain will also be added to be returned from our IOrleansFunctionProvider
.
public class EverythingIsOkReminder : IOrleansFunction
{
public string Description => "Demonstrates a reminder service, notifying the user that everything is ok... every three seconds...";
public async Task PerformFunction(IClusterClient clusterClient)
{
var grain = clusterClient.GetGrain<IEverythingIsOkGrain>(
$"{nameof(IEverythingIsOkGrain)}-{Guid.NewGuid()}"
);
Console.WriteLine("Starting everything's ok alerm after key press.");
Console.ReadKey();
Console.WriteLine("Starting everything's ok reminder...");
await grain.Start();
Console.WriteLine("Reminder started. Press any key to stop reminder.");
Console.ReadKey();
await grain.Stop();
ConsoleHelpers.ReturnToMenu();
}
}
Trying it out
As per the norm, we’ll be starting the SiloHost
, the Client
, and trying the new grain out.
In the above, you can see that our “FakeEmail” went out to the Orleans log, stating that everything’s ok.
One other cool thing we can see due to adding the Orleans Dashboard in a previous post is:
Neat!
In this post we learned a little bit about another Orleans feature — Reminders! You can find the code as of this post at:
Kritner-Blogs/OrleansGettingStarted
Related:
- Getting Started with Microsoft Orleans
- Microsoft Orleans — Reusing Grains and Grain State
- Microsoft Orleans — Reporting Dashboard
- Using polymorphism to update Orleans Project to be ready for new Orleans Examples!
- Microsoft Orleans — Dependency Injection
- Code as of post — v0.45
- Microsoft Orleans Docs — Reminders and Timers
Top comments (0)