This article is part of #25DaysOfServerless. New challenges will be published every day from Microsoft Cloud Advocates throughout the month of December. Find out more about how Microsoft Azure enables your Serverless functions.
Have an idea or a solution? Share your thoughts on Twitter!
Let's head over to South of France where Julie and her colleagues have decided to prepare a special gift for all patients
where she is working: custom-shaped inflated balloons! To make them, she'll need to inflate a LOT of balloons
with the help of an air compressor and one 12 liters air tank. Will they have enough air to do it?
We have put some IoT devices that tell us when the air compressor is starting and stopping to fill the tanks.
With the help of Azure IoT Hub and some serverless magic, your mission is to compute the amount of air available and
make this information available.
Create our Project
After having created a new Azure Functions project, we will need to install the Azure Functions Durable extension, by adding the corresponding NuGet package.
func extensions install -p Microsoft.Azure.WebJobs.Extensions.DurableTask -v 2.0.0
You'll also need to create several Azure Resources, including:
- A Storage Account. Your entity state will be serialized into it,
- An Event Hub, to reroute messages from the IoT Hub to the Azure Function
- An IoT Hub and a device that sending some messages :) (you can fake it if needed.
Coding our Durable Entity
That's probably the easiest part. You simply have to write a class, with some assumptions and todos :
- Any property will be only read-only. If you need to change the state of an object, you will need to create a method for it. Methods will be called, but not directly,
- We will only serialize what's needed. So you need to put some attributes for this,
- Any public method can be called by other functions
- You'll need a special
Run
method that will dispatch the actual work to the appropriate method. Remember, methods are not called directly on your object.
Here is a simple example of a class representing a Tank, with different properties.
[JsonObject(MemberSerialization.OptIn)]
public class Tank
{
[JsonProperty("pressure")]
public int CurrentPressure {get; private set;} = 0;
[JsonProperty("isFilling")]
public bool IsFilling {get;private set;}
[JsonProperty("fillingStartTime")]
public DateTime FillingStartTime {get; private set;}
[FunctionName(nameof(Tank))]
public static Task Run([EntityTrigger] IDurableEntityContext ctx)
=> ctx.DispatchAsync<Tank>();
public int GetActualPressure() => this.CurrentPressure;
public void StartFilling()
{
IsFilling = true;
FillingStartTime = DateTime.Now;
}
public void Stop()
{
IsFilling = false;
var fillingStopTime = DateTime.Now;
CurrentPressure += ComputeFillingPressure(FillingStartTime, fillingStopTime);
}
}
Calling a method on our durable entity from regular functions
At the previous step, we've created some methods like StartFilling
and Stop
. We now need to call it when we receive an event from our IoT device.
If you try to instantiate an entity directly, you'll have the feeling that's everything is working. However, nothing will work :). You'll need to use an IDurableEntityClient
to communicate with a durable entity. This client will handle all the work behind the scenes to scale, serialize and restore your entities. You get this client via method parameters injection.
In a case of just calling a method in an entity without expecting any return value, you'll be simply signaling it. That means that your Azure function will almost always return before any code within the entity gets executed. You just create an EntityId
and call the method SignalEntityAsync()
to send a request to execute a specific method.
[FunctionName("processCompressorEvent")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
ILogger log,
[DurableClient]IDurableEntityClient client)
{
log.LogInformation("New event from IoT device.");
string compressorId = req.Query["id"];
string queryType = req.Query["eventType"];
var entityId = new EntityId(nameof(Tank), compressorId);
switch (queryType)
{
case "start":
await client.SignalEntityAsync(entityId, "StartFilling");
break;
case "stop":
await client.SignalEntityAsync(entityId, "Stop");
break;
}
return (ActionResult)new OkObjectResult($"Event processed.");
}
Accessing values of our durable entity from regular functions
If you need to access the value of an entity, it's almost the same than calling a function:
- Use an
IDurableEntityClient
, - Create an
EntityId
object to _locate _ your entity, - Call
durableEntityClient.ReadEntityStateAsync()
There is only one thing you need to know: if you try to get an entity state for an entity that does not exists yet - because nobody called a method on it for example - You'll get a "value that is null". That's why the return value of ReadEntityStateAsync
is not your object, but a wrapper object that exposes an EntityExists
property.
[FunctionName("GetCompressorState")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log,
[DurableClient]IDurableEntityClient client)
{
log.LogInformation("New request to get compressor state.");
string compressorId = req.Query["id"];
var entityId = new EntityId(nameof(Tank), compressorId);
var currentValue = await client.ReadEntityStateAsync<Tank>(entityId);
if (currentValue.EntityExists)
return (ActionResult)new OkObjectResult( new { value = currentValue.EntityState.CurrentPressure});
else
return (ActionResult)new OkObjectResult( new { value = 0, state = "no-data"});
}
Voilà ! With very few lines of code, you're now able to store a concrete state of an IoT object just from events data, and you're ready to scale it to millions of balloons :).
Want to submit your solution to this challenge? Build a solution locally and then submit an issue. If your solution doesn't involve code you can record a short video and submit it as a link in the issue description. Make sure to tell us which challenge the solution is for. We're excited to see what you build! Do you have comments or questions? Add them to the comments area below.
Watch for surprises all during December as we celebrate 25 Days of Serverless. Stay tuned here on dev.to as we feature challenges and solutions! Sign up for a free account on Azure to get ready for the challenges!
Enjoying this initiative by Azure Advocates? Stay in touch via our newsletter.
Top comments (0)