Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / Azure

Microsoft Orleans - A Worked Example

5.00/5 (11 votes)
19 Jul 2016CPOL3 min read 36.2K   762  
A quick example of how to use the Microsoft Orleans library to develop a distributed, fault tolerant fund accounting system

Introduction

Microsoft Orleans is a framework that claims massively to simplify the creation of fault tolerant, asynchronous distributed systems by abstracting away the complexities of persistence and thread synchronization that arise in such systems. It does this by using the actor model (albeit calling the actors "Grains") and by restricting the operations you can perform on them so as to make them thread safe.

Prerequisites

  • The code in this article and the Microsoft Orleans framework require the .NET Framework v4.5 (or higher when released) to run
  • The Orleans Tools for Visual Studio plug in was used to create the project
  • Visual Studio 2015 (professional or higher if you want to use the plug-in)
  • The Orleans Templates plug in simplifies

Background

In order to demonstrate this technology, I have put together an outline of a financial services application that is used to track funds (mutual funds, hedge funds, etc.). This is (of course) a hello-world level application but I think sufficient to show the promise this technology holds.

In our simple system, we will have these entities that we are going to model:

  • Investors being the people who put money into the fund
  • Fund being a type of trading account the investor can invest in
  • Assets being the things bought and sold by the fund
  • Brokers being the companies through which we trade the assets

Getting Started

The first step is to create a new project that defines the grain interfaces (the ways that the different grain "entities" can communicate with each other).

Image 1

For each of our entities, we need to decide how we are going to uniquely identify it. Out of the box, the options are by unique string, integer or by globally unique id (GUID). For this hello-world level demo, I will be using strings but if your actual entities don't have any intrinsic unique identifiers, then either GUID or incremental integer can be used.

The interface defines what can happen to an instance of the grain (entity) - for example, we might say that an Investor can subscribe to or redeem from a fund:

C#
namespace FundGrainInterfaces
{
    /// <summary>
    /// Grain interface for an investor
    /// </summary>
    /// <remarks>
    /// For this example, each investor gets an unique string identifier to identify them
    /// </remarks>
	public interface IInvestorGrain : IGrainWithStringKey
    {
        /// <summary>
        /// The investor subscribes into the fund - paying money in
        /// </summary>
        /// <param name="fundToSubscribe">
        /// The fund that the investor is subscribing to
        /// </param>
        /// <param name="amountToSubscribe">
        /// The amount of money being subscribed
        /// </param>
        Task Subscribe(IFundGrain fundToSubscribe, decimal amountToSubscribe);

        /// <summary>
        /// The investor takes money out of the fund
        /// </summary>
        /// <param name="fundToRedeem">
        /// The fund that the investor has previously subscribed to
        /// </param>
        /// <param name="redemptionAmount">
        /// The amount redeemed
        /// </param>
        Task<decimal> Redeem(IFundGrain fundToRedeem, decimal redemptionAmount);
    }
}

Then we need to create a project to implement these interfaces in concrete classes. Each concrete grain class must inherit from the abstract class "Grain" as well as implement the above declared interface.

To illustrate the communication between grains, when an investor subscribes to or redeems from a fund that information is passed on to the Fund grain:

C#
/// <summary>
/// Grain implementation class for investor.
/// </summary>
public class InvestorGrain : Grain, IInvestorGrain
{
    // The investors own holdings in the funds
    private Dictionary<string, decimal> _holdings = new Dictionary<string, decimal>();

    public Task<decimal> Redeem(IFundGrain fundToRedeem, decimal redemptionAmount)
    {
        if (_holdings.ContainsKey(fundToRedeem.GetPrimaryKeyString()))
        {
            if (redemptionAmount <= _holdings[fundToRedeem.GetPrimaryKeyString()])
            {
                // cannot redeem what you do not hold
                redemptionAmount = _holdings[fundToRedeem.GetPrimaryKeyString()];
            }
            return fundToRedeem.Redemption(redemptionAmount);
        }
        else
        {
            // No holding therefore cannot redeem
            return Task.FromResult(decimal.Zero);
        }
    }

    public Task Subscribe(IFundGrain fundToSubscribe, decimal amountToSubscribe)
    {
        fundToSubscribe.Subscription(amountToSubscribe);

        if (_holdings.ContainsKey(fundToSubscribe.GetPrimaryKeyString()))
        {
            _holdings[fundToSubscribe.GetPrimaryKeyString()] += amountToSubscribe;
        }
        else
        {
            _holdings.Add(fundToSubscribe.GetPrimaryKeyString(), amountToSubscribe);
        }
        // Indicate that all went well
        return TaskDone.Done;
    }
}

Where the corresponding Fund grain can deal with subscriptions and redemptions form the funds own available liquid cash:

C#
public class FundGrain : Grain, IFundGrain
{
    private decimal _liquidCash ; // Liquid cash available in the fund...

    public Task<decimal> Redemption(decimal redemptionAmount)
    {
        if (_liquidCash >= redemptionAmount)
        {
            _liquidCash -= redemptionAmount;
        }
        else
        {
            // Redeem as much cash as is available
            redemptionAmount = _liquidCash;
            _liquidCash = 0;
        }
        return Task.FromResult(redemptionAmount);
    }

    public Task Subscription(decimal subscriptionAmount)
    {
        _liquidCash += subscriptionAmount;
        return TaskDone.Done;
    }
}

Creating a silo

Instances of these grains (entities) need to be hosted by a silo, which is effectively a virtual machine environment for that grain. In order to test these, we need to create a silo test project:

Image 2

In this host, you instantiate instances of your grains (entities) and you can then interact with them via their defined interface:

C#
// - - - 8< - - - - - - - - - - - - - - - - - - - - - - -
            IFundGrain myTestFund = GrainClient.GrainFactory.GetGrain

Now, if you run multiple instances of this silo, they will allow run off the same underlying fund grain without the developer having to implement (or care about) any concurrency checking at all.

Image 3

Points of Interest

  • There are a number of storage providers you can use to persist the grains between uses including the various Azure cloud storage options.

History

  • 19th July, 2016: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)