Introduction to Microsoft Orleans
Transcript
Hi, my name is Stu. If you're new here, this channel is all about me breaking down different technologies into easily digestible chunks. In this video, I'm going to give you an overview of the Microsoft Orleans framework, including a demo of how to get started. So let's take a look at some of the questions that we're going to be answering today. Firstly, we're going to be taking a brief look at the virtual actor model so that we can better understand Microsoft Orleans. Next, we're going to take a look at the concept of a grain and some of its features, including the life cycle of a grain. Then we're going to take a look at defining a silo and some of its responsibilities before going into a demo and taking a look at this all in action.
To understand Microsoft Orleans as a framework, we must first understand the virtual actor model. Within the virtual actor model, actors are a unit of isolation in the context of a process or cluster. Typically, actors implement a queue-like system to process requests one at a time. This allows developers to maintain certain guarantees with their applications around application state. They can be implemented either as a unit of work pattern or based on something like a domain aggregate. The virtual actor model is most applicable for use cases where the clustering and logical sharding are primary concerns of the domain. An example of this could be maintaining information about the player in a large scale game.
Now we have a basic understanding of the virtual actor model, let's take a look at how it's implemented in Microsoft Orleans. Grains are Microsoft Orleans' version of actors. They typically have two parts associated with them: an interface and a corresponding implementation. The implementation must inherit from the type Grain and be registered in DI. The identity of the grain is deterministic in nature and can be either a number, a GUID, or a simple string depending on the application's requirements. We'll see how to manage this identity a bit later on. When it comes to the grain state, you can have a choice to have it as a volatile entity or have the state persisted into a data store of your choosing. And after a period of inactivity or under heavier load, the grain may be deactivated to free up machine resources.
Each grain has three distinct life cycle phases. The first phase is the activation phase. This is where the grain is requested by the application, instantiated from DI, and assigned its identity. The second phase is when the grain is actually running. The application can call the grain one or multiple times depending on the context of the application. After each call, the grain may also persist its state to the data store. Lastly, we have the deactivation phase. As mentioned previously, this may be due to application load or a period of inactivity, or could even be deactivated from within the grain itself. A grain may also persist state at this stage.
Within Microsoft Orleans, a grain is activated by a silo. A silo is a host of one or more grains, service workers, and other components in the Microsoft Orleans framework. A silo is responsible for the activation and deactivation of grains across a cluster, including which grains are activated on which silo. Typically, there is a one-to-one mapping between a silo and a container or compute instance, although there can be more depending on the infrastructure setup. Microsoft have designed silos that can be easily integrated alongside other components such as ASP.
NET Core through the use of the generic hosting model. By adding more silos to the system through clustering, we can achieve great levels of fault tolerance and scalability for our applications.
Now we've taken a look at what is the virtual actor model, a grain, and a silo, it is now time for us to dive into the code and take a look at how to set up Microsoft Orleans. Now that we've taken a look at some of the concepts behind Microsoft Orleans, let's go and take a look at how we can integrate those with an ASP.NET Core application. Here I have an empty solution with nothing in it so far, so I can show you everything from the start. So I'm going to right click on the solution inside the Solution Explorer and add a new project by going to Add, New Project. I'm going to ensure that I have ASP.NET Core Web Application selected, which can also be found in the menu on the right hand side, and select Next. In the project name, I'm going to give it a simple HelloOrleans name, then click Create. I'm going to ensure that this is an ASP.
NET Core 3.1 application, and I have the API selected in the main window. For the purposes of this demo, I'm going to turn off HTTPS because I'm not going to configure that here today. Lastly, I'm just going to click Create. Once this is done, you will see the default setup where we have a WeatherForecast controller, Program, and Startup. I'm going to right click on the project and manage its NuGet packages to install some stuff from Microsoft Orleans. In the Browse tab, I'm going to search for Microsoft Orleans. In here, there are three packages that I want to integrate. Firstly, I want the Microsoft.Orleans.Client. This makes sure that we have all the necessary bits and pieces for using a client in our application. I'm going to select Microsoft.Orleans.CodeGeneration.MSBuild, which basically allows us to have a proxy between a client and a server. And lastly, I'm going to install the Microsoft.Orleans.Server package. In a clustering scenario, it is very important that we have a transparent proxy which Microsoft Orleans provides between the client and the server, and that's what this Microsoft Orleans Code Generator package does for us. This is all the packages that we're going to need for now, so let's close it down. The first thing that I'm going to do is set up the server side by going to Program.
cs, finding the default host builder that it's created for me and appending .UseOrleans. I'm going to pass in a builder lambda function and inside I'm going to do builder.UseLocalhostClustering. This is the only bit that's required for our server in this demo. Occasionally you may not get the method available, so to fix this we're going to go to the top of the usings and add using Orleans.Hosting; which resolves the error.
Next, we're going to build our grain. So I'm going to add a new class to the project by right clicking on it, clicking Add, and then New Class. I'm going to call this HelloWorldGrain. Initially, I'm going to add a class to inherit from the type Grain, which is in the Orleans namespace. I also need a way to make sure that the grain can be communicated with across a cluster, even though the cluster is going to be local for now. To do this, I'm going to back the grain with an interface, which is a required step. So we're going to declare a public interface IHelloWorldGrain, and we also need to give this some form of identity, so we're going to inherit from IGrainWithStringKey. Here you'll notice you can have the other types of identity that I mentioned previously, such as an integer key, a GUID key, or some form of compound key. For today, I'm going to stick with a string key.
And lastly, write in the method that I wish to invoke inside of the cluster, and this is just going to be a simple SayHelloTo method that takes a string of name. Now the last bit of hookup for the interface is to say I'm also going to inherit from this over here, IHelloWorldGrain, and implement the interface. I'm just going to keep track of the number of times that we've had this invoked, so I'm going to create a new invocation count. And lastly, I'm going to return a string from here and say "Hello " which is the name we're going to get from our controller in a bit, "from ". And what this GetPrimaryKeyString is going to be is the identity of the grain that we have passed in. And because we know that IHelloWorldGrain inherits from IGrainWithStringKey, we know that it's going to be safe to get the primary key as a string. Then we're going to say "I've said hello" and then we're going to grab the invocation count, making sure it's incremented after we get it, "said hello times."
Now to make sure that Orleans knows about this, we need to make sure that this is in DI. So I'm now going to go to the Startup, have a look for the ConfigureServices method, and make sure that is bound in transient scope. So we're going to go services.
AddTransient, pass in the interface which is IHelloWorldGrain, and the concrete type of HelloWorldGrain. Service types such as singletons will not work because it is assumed that each grain can be instantiated more than once, so therefore it needs to be isolated. So please make sure that you're using the transient scope here.
The last bit for us to do is set up our controller method. So I'm going to create a new class, call it HelloWorldController, and make sure that this inherits from Controller so that we get some helper methods here. Now let's create a new constructor by typing the ctor keyword and pressing Tab, which expands this out, and taking in an IClusterClient. This lives inside the Orleans namespace, and this is our gateway to be able to access a cluster from our API. Next, we need to create a new method, so we're going to have an async Task of IActionResult and we're just going to call it Hello for now, and we're going to take a string name. Now we're going to have the result equal to the clusterClient.
GetGrain, and the grain type that we want. So I want the IHelloWorldGrain because we always reference the grains by the interface, not the implementation. And then we need to pass it an identity, and because we know that the identity is a string, I'm just going to pass in "stu". And then I'm going to add the SayHelloToAsync because this is the method that I want to invoke. The GetGrain returns me an instance of the IHelloWorldGrain, and here I'm just going to simply pass through the name that was passed to me. Just need to make sure that I await this. And the two more bits that we need to do is just really make sure that we return Ok, just pass in the result, and ensure that we have a route that's assigned to this. So I'm going to say the route is always going to be "hello" and then I'm just going to pass in the name as a template to make sure everything's bound up correctly.
All being well, now I'm going to switch this over to the HelloOrleans on my execution task. I'm going to hit Ctrl+F5 to run without debugging, and you should see a console that appears that gives you information about Orleans starting up. I've got a window open already which is the localhost:5000 which is the default, and I'm going to pass through the path /hello/john. And now we can see the result from our grain, which is "Hello John from Stu, I said hello 0 times." If I refresh this a few times, you can see that the invocation count is going to be increasing.
To learn more about Microsoft Orleans, including how to integrate diagnostic tooling, click on the playlist that's on screen now.
If you enjoyed this video, consider subscribing to the YouTube channel for more content like this.