Here are the interfaces of my three types of Grains. The Orleans SDK generates code during compile-time and creates factories for every single interface that has the Orleans.IGrain marker interface.
The UniverseGrain
public interface IUniverseGrain : Orleans.IGrain { Task<int> NumberOfDevices(); Task<double> AverageTemperature(); Task RegisterDevice(IDeviceGrain grain); Task> GetDevices(); }
1. NumberOfDevices() : returns the total number of devices that are registered to the universe
2. AverageTemperature(): takes the average temperature of every single device, sums it up and averages again.
3. RegisterDevice(): a DeviceGrain registers itself at the universe
4. GetDevices() : returns a list of registered DeviceGrains. This comes in handy to get a 'snapshot' of the current device universe. This method is called by the SignalR hub in the OnConnected override. Every time a new browser clients connects, the hub call this method on the UniverseGrain to get all the devices and sends this information to the browser where pushpins are drawn and temperatures are shown.
Once a device registers itself at the UniverseGrain, the UniverseGrain also calls the PushNotifierGrain to tell the world of this registration. Showing a toast on a webpage for example.
public async Task RegisterDevice(IDeviceGrain grain) { //also register at SignalR grain grains.Add(grain); var notifier = PushNotifierGrainFactory.GetGrain(0); await notifier.RegisterDevice(grain); }
The DeviceGrain
The DeviceGrain implements several methods, some of them are obvious but notice the attribute that decorates the interfaces.
[ExtendedPrimaryKey] public interface IDeviceGrain : Orleans.IGrain { Task SetLatitude(string latitude); Task SetLongitude(string longitude); Task<string> GetLatitude(); Task<string> GetLongitude(); Task<string> GetDeviceID(); Task<double> GetAverageTemperature(); Task<double> GetTemperature(); Task SetTemperature(double temperature); Task RegisterDevice(); }
By setting this attribute you can use your own propriarty "primary key" mechanism.
var deviceID = Guid.NewGuid().ToString("N");
var device = DeviceGrainFactory.GetGrain(0, deviceID);
The DeviceGrain holds this deviceID primary key by overriding the ActivateAsync method. Later on, this deviceID is also persisted in "State".
public override async Task ActivateAsync() { //store the deviceID this.GetPrimaryKey(out deviceID); this.State.DeviceID = deviceID; await base.ActivateAsync(); }
Registration of a device is simple. Just call RegisterDevice on the UniverseGrain and the device is added to a list in the UniverseGrain.
async Task IDeviceGrain.RegisterDevice() { var universe = UniverseGrainFactory.GetGrain(0); await universe.RegisterDevice(this); }
The PushNotifierGrain(SignalR) grain is decorated with the StatelessWorker attribute. This means that Orleans can create multiple activations of this Grain for throughput reasons. This can only be done for grains with no state or only immutable state. Since the PushNotifierGrain only holds a "IHubProxy" that is immutable this can be done.
[StatelessWorker]
public interface IPushNotifierGrain : Orleans.IGrain { Task SendMessage(string deviceid, string temperature); Task SendGeneralMessage(string message); Task RegisterDevice(IDeviceGrain device); Task UpdateTemperature(IDeviceGrain device); Task SendMessage(string message); }
When the temperature is set on a device grain. For example, when a true device somewhere in the world reports back his temperature on a Service Bus Topic and one of the Subscriptions is maintained by a workerrole. A role that maintains the Orleans Universe) in my scenario. Very scalable since I can scale up this workerrole to infinity to handle more and more report backs from devices.
So, the temperature is set and then the PushNotifierGrain kicks in. The DeviceGrain sets its own current temperature and add the temperature to its history of temperatures which is held in its state (to make it able to persist). Finally, it tells the notifiergrain to send this updated temperature to everybody that is involved (in this case, browsers). In my scenario, this results in a change of the text of a pushpin on Bing Maps. The text displays the temperature.
var notifier = PushNotifierGrainFactory.GetGrain(0); this.State.Temperature = temperature; temperatures.Add(temperature); await notifier.UpdateTemperature(this);
Persistency
I want my universe of Grains to be persistent. If my silo goes down for any reason (locally running a Silo in a Workerrole means that it goes down every time I stop debugging...) the state of grains needs to be persisted. What data needs to be persistet then? First of all, the current temperature of each and every devicegrain but also the history of temperatures of that particular devices. The UniverseGrain also needs some persistence because the next time the silo comes up I want the deviceGrains to be there and populated but also the UniverseGrain needs to get it's list of DeviceGrains again. So how can I assure this persistency? I will also explain this in some of the next posts.
Observable?
Are Grains Observable? Yes they are but more on that topic in the next post! Enjoy Orleans :-) Thanks to http://hilite.me/ to nicely format C# code.
And thanks @Sergey Bykov from the Orleans team for providing some nice insights!
No comments:
Post a Comment