Note this is not an Orleans post, not exactly — it’s just something I wanted to enhance on my Orleans Project, prior to moving on to demonstrating even more Orleans features! (Also, I need to learn more Orleans features to demonstrate!)
Prior to continuing with my Microsoft Orleans series, I wanted to make the Console app calling my Orleans Grains a bit simpler to use under the various examples. How can I do that? Polymorphism to the rescue!
In the previous few Orleans posts (links at bottom) I was swapping in/out calls to various Orleans methods in my Console app’s Program.Main.
That looked something like this:
using (var client = await StartClientWithRetries())
{
//await DoClientWork(client);
await DoStatefulWork(client);
Console.ReadKey();
}
In the above, as I added new Orleans functionality, I would simply tack on additional calls and/or comment out previously used functions. I’m not sure how far I will go with these Orleans examples, but since this is going on my third or fourth sample, I wanted a better way to manage these calls. In addition, give the user of the console application the option of choosing which function to play with, without having to require them to change code in between runs.
How can we do this? With a few simple steps:
- Add a “menu” system to the console application
- Have the menu system content consist of Orleans functionality that’s been implemented (The different Orleans features I’ve demonstrated thus far).
Polymorphism
We’ve touched on polymorphism before, though perhaps not called it by its name. From Wikipedia:
In programming languages and type theory, polymorphism is the provision of a single interface to entities of different types[1] or the use of a single symbol to represent multiple different types.
What does this mean? Basically — use interfaces and/or other abstractions (like abstract classes). How does it fit in with what I’m trying to accomplish? Well, I’m implementing a bunch of different examples that call into a Orleans cluster to demonstrate a feature of Orleans. So what kind of interface definition could I use? Let’s start with:
public interface IOrleansFunctions
{
Task PerformFunction();
}
In the above we have a method that does nothing but PerformFunction
(whatever that ends up meaning), and returns a task.
Hmm, one other thing that’s going to be needed is to use a IClusterClient
. There are a few ways I could accomplish this, keep it separate from the abstraction, and make it available to the implementing class, or just make it a parameter of the interface method. In the way I’m going to use the implemented classes later, I’m opting for making the IClusterClient
a parameter on the interface method. Updated to look like:
public interface IOrleansFunctions
{
Task PerformFunction(IClusterClient clusterClient);
}
One final thing we’ll need (at least for now), is some method of describing the IOrleansFunction
. We can do that with this simple addition:
public interface IOrleansFunction
{
string Description { get; }
Task PerformFunction(IClusterClient clusterClient);
}
Now, we can provide a description for each implementation — something we’ll be using as the “choices” within the console app menu.
So why go through all this trouble? Prior to going this route, I had a harder time implementing new functionality, without having to “move around a bunch of stuff” and continual editing of already existing classes. How can I avoid this now? Well, the only thing I need to do now (ideally, if I did this right), would be add a new implementation of IOrleansFunction
and the system would pick it up without any fuss.
Why is polymorphism the neat? Because you can interact with interface methods without caring about the actual implementation. This makes your code more loosely coupled, and things like unit testing, and code maintainability are simpler; this is touched on a few posts elsewhere I’ve done (links at bottom).
The Implementations
I’ve done a few separate Orleans examples:
- Hello World
- Multiple Instantiations
- Stateful Grains
Those seem perfect for new implementations IOrleansExamples
!
This is going to mostly be copy/pasting from previous posts, just into their new abstraction, and providing a description.
HelloWorld:
public class HelloWorld : IOrleansFunction
{
public string Description => "Demonstrates the most basic Orleans function of 'Hello World'.";
public async Task PerformFunction(IClusterClient clusterClient)
{
var grain = clusterClient.GetGrain<IHelloWorld>(Guid.NewGuid());
Console.WriteLine("Hello! What should I call you?");
var name = Console.ReadLine();
Console.WriteLine(await grain.SayHello(name));
ConsoleHelpers.ReturnToMenu();
}
}
MultipleInstantiations:
public class MultipleInstantiations : IOrleansFunction
{
public string Description => "Demonstrates multiple instances of the same grain.";
public async Task PerformFunction(IClusterClient clusterClient)
{
var grain = clusterClient.GetGrain<IHelloWorld>(Guid.NewGuid());
var grain2 = clusterClient.GetGrain<IHelloWorld>(Guid.NewGuid());
Console.WriteLine($"{await grain.SayHello("1")}");
Console.WriteLine($"{await grain2.SayHello("2")}");
Console.WriteLine($"{await grain.SayHello("3")}");
ConsoleHelpers.ReturnToMenu();
}
}
StatefulWork:
public class StatefulWork : IOrleansFunction
{
public string Description => "Demonstrates using stateful grains with numerous instantiations.";
public async Task PerformFunction(IClusterClient clusterClient)
{
var kritnerGrain = clusterClient.GetGrain<IVisitTracker>("[kritner@gmail.com](mailto:kritner@gmail.com)");
var notKritnerGrain = clusterClient.GetGrain<IVisitTracker>("[notKritner@gmail.com](mailto:notKritner@gmail.com)");
await PrettyPrintGrainVisits(kritnerGrain);
await PrettyPrintGrainVisits(notKritnerGrain);
ConsoleHelpers.LineSeparator();
Console.WriteLine("Ayyy some people are visiting!");
await kritnerGrain.Visit();
await kritnerGrain.Visit();
await notKritnerGrain.Visit();
ConsoleHelpers.LineSeparator();
await PrettyPrintGrainVisits(kritnerGrain);
await PrettyPrintGrainVisits(notKritnerGrain);
ConsoleHelpers.LineSeparator();
Console.Write("ayyy kritner's visiting even more!");
for (int i = 0; i < 5; i++)
{
await kritnerGrain.Visit();
}
ConsoleHelpers.LineSeparator();
await PrettyPrintGrainVisits(kritnerGrain);
await PrettyPrintGrainVisits(notKritnerGrain);
ConsoleHelpers.ReturnToMenu();
}
private static async Task PrettyPrintGrainVisits(IVisitTracker grain)
{
Console.WriteLine($"{grain.GetPrimaryKeyString()} has visited {await grain.GetNumberOfVisits()} times");
}
}
The Menu
Now that our implementations of IOrleansFunction
are complete, we simply need to plug them into our to be created menu system!
With the menu system, the user should be able to enter a number that corresponds to a specific Orleans feature, then that feature should execute.
A few things we’ll need for the menu:
- A collection of Orleans Features to list
- A way to display the features
- A way to execute a feature
- A way for the user to try multiple features, and exit from the menu
A collection of Orleans Features to list
We’ll need to keep a collection of our OrleansFeatures
and what better way to do that than with another interface! I’ve defined a new interface IOrleansFunctionProvider
as:
public interface IOrleansFunctionProvider
{
IList<IOrleansFunction> GetOrleansFunctions();
}
The above interface does nothing but return a IList<IOrleansFunctions>
when invoked.
The concretion of said interface is simple enough as well:
public class OrleansFunctionProvider : IOrleansFunctionProvider
{
public IList<IOrleansFunction> GetOrleansFunctions()
{
return new List<IOrleansFunction>()
{
new HelloWorld(),
new MultipleInstantiations(),
new StatefulWork()
};
}
}
In the above, we’re just newing up and returning a list of each one of our current OrleansFunctions
that we created earlier.
“A way to display the features” and “a way to execute a feature”
Now we need a way to display features on our menu — luckily for us we thought ahead, and created a Description on our IOrleansFeature
interface.
That coupled along with our IOrleansFeatureProvider
, means we can enumerate what the provider returns us, printing out each description. We’ll be using a [slightly hacky(?)] method of assigning a feature to a number via the collection’s index, but /shrug, that’s ok right?
Let’s start a new class OrleansExamples
:
public class OrleansExamples
{
private readonly IOrleansFunctionProvider _orleansFunctionProvider;
public OrleansExamples(IOrleansFunctionProvider orleansFunctionProvider)
{
_orleansFunctionProvider = orleansFunctionProvider;
}
public async Task ChooseFunction(IClusterClient clusterClient)
{
var orleansFunctions = _orleansFunctionProvider.GetOrleansFunctions();
var input = string.Empty;
while (true)
{
Console.WriteLine("Pick a function to use for Orleans demonstration:");
ConsoleHelpers.LineSeparator();
for (int i = 0; i < orleansFunctions.Count; i++)
{
Console.WriteLine($" {i} - {orleansFunctions[i].Description}");
}
ConsoleHelpers.LineSeparator();
input = Console.ReadLine();
if (!int.TryParse(input, out var inputResult))
{
Console.WriteLine("Invalid Input. Please input a number.");
continue;
}
try
{
await orleansFunctions[inputResult].PerformFunction(clusterClient);
ConsoleHelpers.LineSeparator();
}
catch(ArgumentOutOfRangeException)
{
Console.WriteLine("Invalid Input. Please ensure you pick a number/function from the provided list.");
ConsoleHelpers.LineSeparator();
}
}
return;
}
}
In the above, we’re using the results provided by our IOrleansFunctionProvider
, enumerating them, printing out the IList<IOrleansFunction>
index along with the IOrleansFunction.Description
, parsing the user input, and attempting to invoke the appropriate method on the collection as per the index. Notice how we are only working with interfaces here as it pertains to IOrleansFunctionProvider
and IOrleansFunction
. This class has no idea what the implementors are, because it doesn’t really matter as to the scope of this class (loose coupling).
A way for the user to try multiple features, and exit from the menu
It’s a simple matter of adding an if conditional to check for a specific entry to exit the menu, as currently, our menu will loop indefinitely.
Add a new const to the class:
private const string ESCAPE_STRING = "-1";
and a new conditional within the while loop to check for that escape string, prior to executing the grain sample:
if (input == ESCAPE_STRING)
{
Console.WriteLine("Exiting...");
return;
}
Putting it all together
The final piece for completing our menu, is to plug the new bits into the Program.Main of the application.
That’s simple enough to do, because it’s mostly deleting of code! I love deleting code!
our original:
private static async Task<int> RunMainAsync()
{
try
{
using (var client = await StartClientWithRetries())
{
//await DoClientWork(client);
await DoStatefulWork(client);
Console.ReadKey();
}
return 0;
}
catch (Exception e)
{
Console.WriteLine(e);
Console.ReadKey();
return 1;
}
}
private static async Task DoClientWork(IClusterClient client)
{
// example of calling grains from the initialized client
var grain = client.GetGrain<IHelloWorld>(Guid.NewGuid());
var grain2 = client.GetGrain<IHelloWorld>(Guid.NewGuid());
Console.WriteLine($"{await grain.SayHello("1")}");
Console.WriteLine($"{await grain2.SayHello("2")}");
Console.WriteLine($"{await grain.SayHello("3")}");
PrintSeparatorThing();
}
private static async Task DoStatefulWork(IClusterClient client)
{
var kritnerGrain = client.GetGrain<IVisitTracker>("[kritner@gmail.com](mailto:kritner@gmail.com)");
var notKritnerGrain = client.GetGrain<IVisitTracker>("[notKritner@gmail.com](mailto:notKritner@gmail.com)");
await PrettyPrintGrainVisits(kritnerGrain);
await PrettyPrintGrainVisits(notKritnerGrain);
PrintSeparatorThing();
Console.WriteLine("Ayyy some people are visiting!");
await kritnerGrain.Visit();
await kritnerGrain.Visit();
await notKritnerGrain.Visit();
PrintSeparatorThing();
await PrettyPrintGrainVisits(kritnerGrain);
await PrettyPrintGrainVisits(notKritnerGrain);
PrintSeparatorThing();
Console.Write("ayyy kritner's visiting even more!");
for (int i = 0; i < 5; i++)
{
await kritnerGrain.Visit();
}
PrintSeparatorThing();
await PrettyPrintGrainVisits(kritnerGrain);
await PrettyPrintGrainVisits(notKritnerGrain);
}
private static async Task PrettyPrintGrainVisits(IVisitTracker grain)
{
Console.WriteLine($"{grain.GetPrimaryKeyString()} has visited {await grain.GetNumberOfVisits()} times");
}
private static void PrintSeparatorThing()
{
Console.WriteLine($"{Environment.NewLine}-----{Environment.NewLine}");
}
Damn… that’s a lot. But all that code now becomes…
private static async Task<int> RunMainAsync()
{
try
{
using (var client = await StartClientWithRetries())
{
await new OrleansExamples(new OrleansFunctionProvider())
.ChooseFunction(client);
}
return 0;
}
catch (Exception e)
{
Console.WriteLine(e);
Console.ReadKey();
return 1;
}
}
That’s a lot of removed code! (Granted, a lot of that code was refactored into the individual IOrleansFunctions
. But we never have to look at all that code again!
So what does it all look like?
Application Start:
Choosing the “Hello World” example (choice 0):
Choosing the stateful grains example (choice 2):
Exiting (choice -1):
This ended up being a longer post than I intended, but hopefully it will help convey how working with interfaces can help better abstract and break down the work you need to do. That coupled with other “features” of abstraction such as unit testing, loose coupling, and an easier “high level view” of an applications architecture, are why I enjoy working on abstractions so much.
Code for this post can be found https://github.com/Kritner-Blogs/OrleansGettingStarted/releases/tag/v0.30
Related:
- Getting Started with Microsoft Orleans
- Microsoft Orleans — Reusing Grains and Grain State
- GitHub Code as of post — https://github.com/Kritner-Blogs/OrleansGettingStarted/releases/tag/v0.30
- It’s all about the abstractions baby
- What is the Business Value of Unit Testing?
- Getting Started with Unit Testing and Moq
Top comments (0)