November 1, 2017 // By Jason Bock
In this blog post, I’ll kick off a series of articles on actor systems and frameworks, specifically focusing on .NET. I’ll discuss the essentials of actor systems, how they work and what their advantages are. I’ll also introduce the Orleans framework and how that will be used in these articles.
Creating Reliable Distributed Systems
Writing modern applications is hard. We’re deluged by a plethora of front-end frameworks and client application choices (native, or web, or mobile?). Our applications are expected to scale on the back-end, both out and in, depending on the loads generated by users. Furthermore, all of this needs to happen such that our applications are resilient to unexpected failures and use system resources effectively.
Let’s be honest: it’s difficult getting things to work right these days! Sure, we hear terms like “microservices”, “serverless”, “containerization”, and “orchestration”, among others, but what do these terms really mean? How do we develop and maintain systems that are well-organized, decoupled correctly, testable, and highly scalable? Just throwing a buzzword into the mix isn’t going to help. It’s important to write code based on effective patterns and frameworks that will lead one down a path of success.
Moreover, it doesn’t take long as a developer to start running into problems that have plagued code for decades. Deadlocks, network faults and/or overloading, I/O contention, shared state – the list goes on and on. These problems are not insurmountable, but they should not be ignored either.
What are Actor Systems?
The concept of an actor system has been around for quite some time. Carl Hewitt is considered to be the person who constructed the idea of an actor system, and his work started in the early 1970s. Here’s a description of actor systems from a paper authored by a graduate student of his:
(The actor model is) motivated by the prospect of highly parallel computing machines consisting of dozens, hundreds or even thousands of independent microprocessors, each with its own local memory and communications processor, communicating via a high-performance communications network.
There’s a rich body of information on actor systems, so I don’t want to delve too deeply into the theory of actors. Fortunately, actors can be described in fairly simple terms: actors are the foundational computational units of an application that process (and potentially create more) messages and actors. For example, here’s a diagram of an actor system where actors process messages and create more actors:
In this example, Actor A receives a message from an outside source (step 1), and proceeds to send a message to Actor B, C, and D (step 2). Actor B decides to send a message in response to the message received from A to Actor E (step 3). In turn, Actor E sends a message to Actor A (step 4), which doesn’t cause any other messages to be created.
It should be noted that actors are designed to pass messages asynchronously, and actors can be working concurrently. However, an actor processes received messages synchronously. Furthermore, actor systems are resilient to failure. That is, if an actor fails for some reason, the actor system is responsible for creating a new instance of that actor as well as keeping the system as a whole up and running.
Typically, actor systems are hosted in a distributed fashion. This isn’t a requirement – developers can create local actor systems in an application and gain all the benefits of asynchronous processing and message passing. However, actor systems are well-suited by their nature to empower massively distributed applications.
One of the first well-known implementations of actors in a programming language was Erlang. Erlang was primarily used in the telecom industry, which required massive, reliable distributed processing. While Erlang isn’t a widely-used language in this day and age, it’s worth taking some time reading up on the language to see how actors were implemented – I highly encourage you to do this when you can. I’d also recommend investigating Elixir, as it was inspired by elements of Erlang.
What is Orleans?
Let’s say my very brief introduction intrigued you and you decided to dive into actor systems. After a while, you’re very intrigued and you want to start making everything an actor in your .NET program. OK, maybe not make everything an actor, but you want to at least experiment with it. Where do you begin?
.NET at its core is an object-oriented system, where the fundamental until of abstraction is an object. There is no first class notion of an “actor” in .NET, so you won’t see an “actor” keyword in a language like C#. To use actors in C#, you have to use a framework and follow its conventions, and that’s what I’m going to do in all of the articles in this series.
The framework I’ve chosen is Orleans. It originally came out of Microsoft Research, and its first claim to fame was powering the cloud services backing Halo 4 and 5. Given the immense data size that is generated by distributed, online games, it’s a testament to the reliability of a framework like Orleans. For example, take a look at this picture of the statistics released by 343 Industries on their Halo 5 multiplayer run:
That’s a lot of data to keep track of! Knowing that Orleans is a key part of managing statistics like users, games, and total gameplay hours to this volume gives me confidence in the strength and stability of the framework.
It should be noted that Orleans is not strictly an actor system as it is defined by traditional actor system descriptions and definitions. For example, actors in Orleans do not have the concept of changing their behavior. However, that does not diminish the power and simplicity that Orleans brings to the table. Also, there are other .NET-based actor frameworks that I encourage you to take a look. Just because I chose one for my series doesn’t mean it’s the only choice other there. See what these have to offer – you may end up picking one of them as your go-to actor framework:
Repeating the Documentation
As I work through this series, you may notice that there’s a fair amount of reflection from the articles and examples that exist on the Orleans site. My intent is not to copy or duplicate the information there, even though I’m sure there will be some overlap. There’s two over-arching goals that I have in doing these articles.
First, I want to give .NET developers a clear understanding of how they can use Orleans in their applications effectively. It’s simply not enough to tell developers to use a framework and expect them to take advantage of all the gains they provide. I strongly believe good frameworks should have good documentation and self-evident features, but at the same time, there is some measureable learning curve to a framework. Just telling developers “make everything a grain!” will not make a program scalable. There has to be more pragmatic experiences helping developers understand how Orleans works and how they can make it work “right”.
Also, I personally want to spend time diving into some aspects of Orleans for my own self edification and share whatever I find that I think developers may want to see. The first example in this series is going to look pretty simply, but there’s a lot more that Orleans has to offer in terms of configuration, persistence and hosting, just to name a few topics I’ll be tackling. It’s important to know that these pieces exist and how they work.
I strongly encourage you to read the documentation on Orleans’s site. In general, it’s good and gives decent explanations of what Orleans does. My hope is provide another view of Orleans that complements that documentation.
Conclusion
In this article, I describes the essentials of an actor system and the features it provides for a developer. I also discussed what Orleans was and how it can be used to build a fault-tolerant, service-based distributed application. In the next article, you’ll see how you can create a simple actor in Orleans, host it, and call it from another application. Until next time, happy coding!