Azure Service Fabric is not for the faint of heart. It is extremely cool but it’s a bit different than the traditional way of doing things in .NET projects. Thanks to a particularly persuasive client of mine, I’ve decided to dissect some of the samples available from Microsoft so that someone new to Service Fabric can get a bit more familiar with the anatomy from a more practical perspective.
I hope you find this helpful and a bit more in depth then the typical hand-waving you read in “microservices” articles (not that I don’t do my fair share of hand waving 😊). Because my post will target a specific code project, I would highly recommend having that project open while you read this article. My approach is to break down the project bit by bit from the high-level chunks down to the minutia.
You can find the code at GitHub: here. There’s a tangential walk through on upgrading an application using this sample that’s also pretty interesting if you want to check it out after you have a firm grasp of the anatomy of the solution.
First, let’s take a look at the Solution Explorer and get our bearings.
You’ll notice three projects:
- VisualObjects.ActorService (i.e. ActorService)
- VisualObjects.Common (i.e. Common)
- VisualObjects.WebService (i.e. WebService)
From here on out, I’ll be referring to them by their short names.
Service Fabric Application Project
Visual Objects is the main Service Fabric project. It contains important meta-data and launch scripts to configure the cluster for our application.
As the name implies, this is a project that contains the definition for one type of actor in our application. The actor defined in this project will be spawned zero to n times.
This project houses special classes called Reliable Services. These services provide a point of invocation to activate Actors within our application and communicate over the network.
This project houses some common plain-old-C#-objects (POCOs) that provide state for our Actors to be uniformly manipulated and accessed. It also provides a place for interfaces that we used for communication between our Services and Actors.
In the Beginning, We had Services and Actors.
In order to understand what’s inside of our Service Fabric application and how initial execution is transferred from the Service Fabric runtime to our code, we must first look inside the ApplicationManifest.xml file in the Service Fabric Application Project (e.g. ‘VisualObjects’).
We can see that there are two magic strings in this file that point to two of the projects we highlighted earlier. ActorService and WebService. Note that we are using the project’s exact name as the magic string. This is how the Service Fabric Runtime locates the package to load. Each package contains a Program that contains a Main method in it. Both ActorService and WebService have a Main method but they do very different things.
WebService’s main method is registering a service using the same magic string referenced in its corresponding StatelessService XML node.
This is essentially registering a start point of execution for the Service. Whenever Service Fabric initializes an application its main objective is to run all its application’s services. The services are already specified in the ApplicationManifest XML file but there is no link to what code to execute. This is creating a link between the service registered in the XML file and the actual code to invoke. All thanks to that magic string “VisualObjects.WebServiceType”. You’ll notice this guy shows up in both the ApplicationManifest XML file and in the C# code as a string constant. There’s no magic folks, we’re just connecting the dots for the runtime and its following our trail of bread crumbs.
In contrast, ActorService is simply registering an actor for the type VisualObjectActor (the only Actor type defined in the project).
This is essentially loading the Actor into the runtime so that instances of that Actor may be initialized when requested.
Services Kick Off the Party
Now that the Services and Actors are registered, the runtime will initialize and invoke our services. The actor(s) will await their activation. This is usually done by code invoked by a service.
We only have one service registered (the one in the WebService project). Services do two main things, they can run some code and they can register listeners that enable network communication. We can hook into these two pipelines by overriding two methods: RunAsync (execute some code) and CreateServiceInstanceListeners (register listeners that enable network communication).
Run Some Code
The main job of RunAsync is to update the state of the VisualObjectBox by polling all of the VisualObjectActors for their state and aggregating that state into VisualObjectBox. In this sample, we are creating n number of VisualObjectActors using the CreateVisualObjectActorIds. This is probably the code that is most ‘demoware’ of the whole lot. In production, actors will likely be initialized based on some application event, not a mindless for-each loop.
But once we have a collection of the actors, we can iterate through the actors and essentially create a threat that will poll the actor for its state. There are plenty of more efficient ways of doing this in an event-driven way but, again, demoware. This is a simple, albeit, inefficient way of aggregating state from all of our actors.
The important things to note are the following:
- This service is not changing the state of the actors. It is merely observing the state of the actors. We’ll see later that each of the actors is responsible for maintaining (and changing) their own state. This is one of the core principals of the Actor pattern, in which, each actor is an autonomous agent.
Enable Network Communication
It’s a relatively simple two step process. The chain of events are as follows:
1. Like all Communication Listeners, we handle OpenAsync. We establish the endpoint we want to host on and then we start a Web Application using the Startup class.
2. Startup specifies the physical location of the files and some settings for the web server.
Note the physical path is relative to the WebService’s project folder ‘wwwroot’.
1. Like all Communication Listeners, we handle OpenAsync, configure our endpoint and start listening.
2. This is way different than WebCommunicationListener because we are doing more than just using Web Application framework to do the listening and serving up requests to static content.
First, we need to continuously listen for new clients. When we find one we continuously send the aggregated state (as currently stored in our VisualObjectBox) down to the client.
Below is a whiteboard I made of the end to end solution so far. Pretty much everything we described has been in the WebService project. This entire project is dedicated to the following things:
- Aggregating the state of all independently executing actors.
- Synchronizing state between all connected clients and the server.
All of this stuff will produce a Web Site that displays this:
Non-moving, triangles. Well, I lied, this is a screenshot of the actors after they’ve been moving for a while (notice the historical positions displayed in the wake of each triangle). In reality, without the Actors doing anything you would only see static, non-moving triangles.
What’s a Party without Dancing?
In order to get these things to move around on the screen, the-no-each Actor needs to take things into their own hands.
The Actor begins its execution upon Activation. We hook into this activation using the OnActivateAsync override.
Upon activation we initialize a new model object that will be used to execute our business logic (i.e. moving). In case, this object is being resurrected from a failed Service Fabric node, we check for existing state and load that if it exists. Then we start a timer that will repeat logic every 10 milliseconds. The timer that we setup in this actor will be responsible for updating this singe instance of the VisualObject.
Inside the repeat logic is pretty simple.
- Get current state
- Change state (move)
- Save state
That’s it. I think it’s important to note that the logic inside the actual Actor is extremely simple. That’s because the code is written within the context of a single object. We aren’t writing a master control program here. When we write code for the Actor we only have to think about one object, the Actor. Sure, this is a simple example but even in more complicated real-world scenarios taking a more narrow context always simplifies logic.