Ryan Brooks

Using Microsoft Orleans: Why?

Introduction

The explosion of Software-as-a-Service (SaaS) over the last two decades created new challenges for the industry professionals tasked with creating and maintaining those systems. When building online applications, large or small, teams must face several decisions that will have long term impacts on the design. Data storage, user patterns, and other “big decisions” will follow (or haunt) the team through the life of the application.

For businesses that were early to the internet, many of these decisions have already been made for them - for better or for worse – in their legacy codebases. For new applications, developers can choose other paths, but the number of options for foundational components has exploded just as the SaaS scene has. Most businesses recognize that the cloud is the future and are already working to progress along any of the common “cloud maturity models” that exist today, but all of those models end in one place: “cloud-native” greenfield builds (or rebuilds) of microservice architectures.

Microservices architectures, themselves, introduce new challenges. Data distribution, concurrency, eventual consistency, and other “hard” patterns point to the need for teams that are highly trained and skilled. Hiring new developers with microservices design experience or creating internal training programs for existing developers can help teams move forward, but both options can be expensive and slow.

Expensive, slow, and hard are not words that excite a business; especially one that’s growing quickly. How, then, do we make that last step to cloud-native software without these downsides?

Microsoft’s Orleans framework is an excellent answer to this complicated question. Suitable for both small and large applications, Orleans solves many of these hard problems in a single, unified framework that allows teams to concentrate on delivering business value over boilerplate or foundational code.

History of Orleans

Orleans was originally built and by Microsoft Research in the late 2000s in response to the demand for hyperscale services to handle the then-new wave of multiplayer Xbox games. Initially deployed as the back-end for Halo 4, Microsoft now uses Orleans in Azure, Xbox, Skype, Halo, PlayFab, Gears of War, and many other internal services. Visa and Honeywell also write applications based on Orleans.

Orleans release timeline. 1.0 in 2011: internal Microsoft use only, built for the now-legacy Azure Service Fabric. 2.0 in 2017: first publicly available version, .NET Framework, added most features Orleans has today. 3.0 in 2019: simplified configuration, .NET Standard 2.0, distributed ACID transactions. 7.0 in 2022: versions synced to .NET releases. 8.0 in 2024: .NET 8 and live migration of grains between silos.
Microsoft Orleans, first used in 2011, has been under development for over a decade.

The first publicly available version of Orleans was 2.0, released in 2011 as Open Source software (using the MIT license). Since that time, Orleans has added new functionality, gotten easier to use, and received significant performance updates.

How does it work?

An early decision facing teams is the adoption of an application framework, and most of them can be placed into 2 groups: low-code or high-code. Low-code systems provide huge productivity gains, because you don’t have to write as much code, but are generally less flexible in terms of app capabilities. High-code systems provide the opposite characteristics: more code & more flexibility.

How much code you write plotted against framework flexibility. Low-code systems - Power Apps, OutSystems, Mendix, Retool - cluster at write-less-code and less-flexibility. High-code and DIY stacks - Go, ASP.NET, Spring - cluster at write-more-code and more-flexibility. Orleans sits on its own between the two.
“Low-Code” systems are an extremely powerful automation tool to write specific types of applications extremely fast, but they do impose a range of limitations on how you build your applications. “High-Code”, on the other hand, requires a lot more work, but how the apps operate is entirely up to you.

Orleans sits in a sweet spot between the two larger groups because it provides a “strongly opinionated” framework that eases sophisticated development while preserving your team’s existing skill investments in .NET. Additionally, because the team already understands the underlying technologies, it’s possible to color outside the lines if the need arises. That said, you probably won’t need to, since Orleans is a very complete offering.

What does Orleans do for you?

Orleans is a deep framework that provides several major integrated components that dev teams would normally need to figure out for themselves. The vocabulary of Orleans centers around “Grains” and “Silos”. Grains are the encapsulated “virtual actors” that represent business logic, state persistence, and more. And silos are the clustered hosts that serve grain data to the grain client. Together, these represent the core functional structures within Orleans.

Data Storage

Orleans contains a flexible state persistence engine that can transparently save and retrieve grain state using a variety of backing data stores. This includes official support for:

It is also possible to do heterogenous storage, putting some grain data in a document Db while other data lives in a relational store or as a blob. Lastly, if you have other needs, Orleans gives you the ability to implement custom providers via the IStorageProvider interface.

Remote Processing

At their heart, even if an app is doing Event Sourcing, CQRS, or other performance optimization patterns, most SaaS apps are still N-Tier Architectures that have a user client, a front end, a logic/processing layer, and a data store. Commonly, the front end (the REST or GraphQL API) and logic/processing layer, while logically separated in code, are still executing on the same compute resources. Orleans changes that paradigm.

Traditional architecture beside Orleans architecture. In the traditional one, the front-end API and business logic sit inside a single shared-resources box between the user client and the database. In the Orleans one, the front-end API and the Orleans silo holding business logic are separate boxes, so each scales independently.
Orleans gives you an opportunity to scale your front-end APIs separately from the scaling needs of the business logic, giving you finer control over how resources are allocated to your application.

While you can co-host the Orleans silo in the same application as the front end, it is also possible to split them into 2 separate applications running in separate compute pools. This gives several advantages like increased isolation of logic processes, ability to scale domain execution without scaling the front end apps, and additional resilience; when running separately, Orleans can create a fault tolerant cluster of silos to ensure your app stays up.

Caching

Keeping frequently accessed data in a fast cache is an important part of ensuring that your front end supplies data for the best user experience possible. With Orleans, distributed in-memory caching is a core feature that you don’t have to worry about. The automatic grain activation and deactivation cycle ensures that frequently accessed data remains in the silo’s memory for efficient use. Additionally, this memory cache is distributed for resilience and performance. Each grain only resides in only one silo at any given time and the silos communicate with each other to find requested grains. This extremely sophisticated feature is “for free” in Orleans. There is no configuration, development, or other work necessary to take advantage of it. It is simply the way Orleans works.

Command Queueing

Another “hard problem” to solve in SaaS applications is the handling of simultaneous access. What do you do if two users want to manipulate the same data at the same time? There are different ways to deal with this issue, but one of the most common in use today is utilizing Kafka/EventHub command queueing to ensure single-threaded execution against individual domain entities. This can be a powerful architecture for creating apps with lots of simultaneous data access.

Orleans, again, gives you this “for free”. Every grain is fed by a command queue that receives, caches, and executes grain commands in order, providing you thread-safe state changes to your grains’ data. Like caching, there is no configuration, development, or other work necessary to take advantage of this extremely sophisticated pattern. It is, again, simply the way Orleans works.

How does Orleans compare with other similar offerings?

Being a very complete framework, there are plenty of frameworks that do parts of what Orleans does, but only a small few that are true competitors. Two other frameworks that perform similar functions are Akka and Dapr.

Table comparing Microsoft Orleans, Akka and Dapr. Orleans: distributed applications and microservices, actor model, .NET, automatic actor state recovery, horizontal scaling with stateless actors, virtual actors and distributed task scheduling. Akka: distributed systems and concurrency, actor model, Java and Scala with Akka.NET for C#, supervision and monitoring, cluster sharding. Dapr: portable event-driven microservices, sidecar architecture, language agnostic over HTTP and gRPC, built-in resilience and circuit breakers, scaling via Kubernetes.

Comparing them, we see a lot of similarities in core functionality, but there are several critical differences that would suggest that Orleans is a better choice for many solutions.

Dapr, for example, isn’t exactly a development framework. Instead, it is a run-time that you host in a separate container from your core application. You then integrate with Dapr to provide the required functionality. The big advantage here, however, is that you can use any language to create an app on top of it.

Akka, which is much more like Orleans, features both Java, Scala, and .NET versions, but has fewer “automatic” features (like actor state recovery) than Orleans. While you’d be able to receive many of the same benefits as Orleans, you’d likely end up writing more code to do so. However, with the primary version written in Java, you have access to a rich set of other tools that also run in the JVM.

How will Orleans scale with my business?

Interestingly, Orleans isn’t a technology that is suitable for only the very small or very large business needs. It easily scales from POC to early product iterations through growth phase and can support hyperscale apps, too.

Four growth stages. POC: co-host your silo in a single app, use common conventions for separating grains. Early Product: continue single-app co-hosting, increase use of asynchronous patterns such as EventGrid or SNS. Growth: separate your silo app into containers, begin splitting front-ends into discrete microservices. Hyperscale: full microservice separation, analyze grain use patterns and examine advanced hosting options such as grain affinity.

The implications of this kind of scalability are huge. You will, ultimately, throw away less code on your way up the ladder. Less code thrown away means less new code to write. Less new code to write means increased delivery of business value and higher ROI on your development efforts.

Is Orleans ready for primetime?

I’ve implemented these “hard patterns”, like command queueing and caching, in the past using bespoke code. I almost certainly would have used Orleans for those projects if it had been in the state it’s in today. In the last few years, I’ve put Orleans to use in several domains. In FinTech, I used Orleans as the core of a high-throughput transaction switch. In a Social Media app, Orleans represented more than 70% of domain concepts within the application. In a backend for an iPhone game, Orleans was used for the whole implementation. It’s flexible, easy to use, and powerful.

Whether you’re building a new application in .NET or updating an existing one, Orleans is an excellent choice. It gracefully solves many of the hard problems in modern SaaS development and can be a powerful, flexible tool in your portfolio.