Monday, May 5, 2014

Fine-graining your Orleans Grains inside the IoT universe

My previous was about Internet of Things and how MSR Orleans can play an important role in that scenario. Keeping state and additional complex computing that is executed by individual Grains. Thus, offloading your core backend services and move the load to the Grains. For completeness, see the figure below to get an overview of the architecture.



Every provisioned device causes a Device Grain to be activated in the Orleans Universe. To create some statistical functionality, one Universe Grain is also activated. This Grain keeps a list of Device Grains and is able to explose some statistical information like number of devices, number of messages exchanged but also some methods to remove or disable devices. A Device Grain has the following interface:

    [ExtendedPrimaryKey]
    public interface IDeviceGrain : Orleans.IGrain
    {
        Task SetLatitude(string latitude);
        Task SetLongitude(string longitude);

        Task GetLatitude();
        Task GetLongitude();
        Task GetDeviceID();
        Task RegisterDevice();
    }

To take the primary key inside your grain code use the following snippet. Override the ActivateAsync method and use the GetPrimaryKey method. The returned "out deviceID" is then stored inside the private string deviceID.

        private string deviceID;

        public override async Task ActivateAsync()
        {
            //store the deviceID
            this.GetPrimaryKey(out deviceID);
            this.State.DeviceID = deviceID;
         await base.ActivateAsync();
        }

To activate a device grain from your code have a look at the following:

            var device = DeviceGrainFactory.GetGrain(0, deviceID);
            await device.SetLatitude(latitude);
            await device.SetLongitude(longitude);

            await device.RegisterDevice();

As you can see there is no new keyword or some factory there to "create" grains but you only "activate" them. The deviceID is passed as an additional parameter to the GetGrain method. This deviceID is then grabbed inside the ActivateAsync method of the Device Grain (GetPrimaryKey).

Some async methods and an extended primary key since I want to use my own private key mechanisme. In this scenario, a device is uniquely identified with a guid.

So, the Grains are interacting with their Universe Grain (that keeps track of every grain and some statistical information like number of devices

I also have one super Grain that takes care off all the SignalR functionality. Once a device is created:
- a Grain gets activated with the DeviceID Guid (returned from the provision backend service)
- this Grain also notifies the SignalR Grain that the device is registered
- The SignalR Grain maintains a SignalR Hub and call methods on the clients to notify them of a new registration and draw a pushpin on the Bing Map.

The SignalR Grain is decorated with the [Reentrant] attribute to enable interleaving. By default, an activation of a grain doesn't receive a new requests untill the current request is being handled and all the promises created during the processing of the request are resolved. Marking a class [Reentrant] overrides this behavior and enables multiple calls to a grain, causing multiple activations of the grain (if needed).

See the code snippet on the SignalR Grain. This one is also used in the GPSTracker sample.

[Reentrant]
    public class PushNotifierGrain : Orleans.GrainBase, IPushNotifierGrain
    {
//implementation
}

The ActivateAsync method is overriden and opens a hub connection, whereas the Hub is hosted inside the MVC app called IoTHub. To make sure the code is working in both Azure and locally without Azure you need to check the RoleEnvironment.IsAvailable.

if (RoleEnvironment.IsAvailable)
            {
                 var hubConnection = new HubConnection("http://127.0.0.1:81");
            var hub = hubConnection.CreateHubProxy("IoTHub");
            await hubConnection.Start();

            }
            else
            {
                 var hubConnection = new HubConnection("http://localhost:48777");
            var hub = hubConnection.CreateHubProxy("IoTHub");
            await hubConnection.Start();
            }
Notice the await keyword since we are heavily into asynchronous modus!

The Universe Grain keeps a list of all the devices that are available in your ecosystem. Once a new client (a webbrowser) opens the application containing the bing map. This MVC application contains some WebAPI that interact with Orleans. One of the WebAPI methods is to get a list of all the current devices (a snapshot) and draws them on the bing map. From that point on, new status updates and so on are notified by the SignalR infrastructure.

The device registers itself at the SignalR Grain.

public async Task RegisterDevice()
        {
            var notifier = PushNotifierGrainFactory.GetGrain(0);
            await notifier.RegisterDevice(this);
        }

Inside the RegisterDevice method of the SignalR Grain:

public Task RegisterDevice(IDeviceGrain device)
        {
            foreach (var hub in hubs.Values)
            {
                try
                {
                    if (hub.State == ConnectionState.Connected)
                    {
                        //notify every attached hubclient (web, .NET client, Windows Store or others)
                        //the hub method Register is called with the specific device information (id, latitude, longitude)
                        hub.Invoke("Register", device.GetDeviceID(), device.GetLatitude(), device.GetLongitude());
                    }
                    else
                    {
                        hub.Start();
                    }
                }
                catch (Exception ex)
                {
                    Trace.TraceInformation("Something went wrong..." + ex.Message);
                }
            }
            return TaskDone.Done;
        }

Next time, more on the Universe Grain!


No comments:

Post a Comment