Contents

Azure Durable Entities Revisited

Hello to C# Advent 2023 readers
This post is part of C# Advent 2023 series, there’s loads of great content that’s worth checking out from a wide range of authors

Azure Durable Entities have been around for a while, initially there was some good fan fare but it’s probably fair to say that there hasn’t been wide spread adoption of the technology. This could be because the virtual actor concept is unfamiliar to the traditional C# / Dotnet approach to solving software problems, or it could be that we don’t often find problem spaces that are best solved in this particular way.

In this post I’m going to give a brief overview of what Azure Durable Entities are and provide an example of how they can be used.

What are Durable Entities and when are they useful?

Durable Entities are part of the durable task framework that ships as part of Azure Functions, using the framework you can define classes that represent stateful objects, and in particular serverless entities that will run in the context of an Azure Function. Because they are stateful they can be useful for managing and persisting state across long-running processes - something that a typical Azure Function doesn’t do (since they aren’t stateful, they just run and then finish).

At DrDoctor most of our communication happens on Slack, years ago our main engineering channel was littered with messages from devs either letting everyone know they were using a cloud dev-test environment or asking what was free to be used and when they were done they would send another message to say they were finished.

Dev1: @nerds I’m using UAT 1

Dev2: @nerds are any UATs free?

Dev3: I’m still using UAT 2

Dev1: Finished using UAT 1

All these messages were really distracting.

Introducing /uat - Slack and Durable Entities

I spent an evening learning how to build a custom Slash command for Slack, then a couple more evenings throwing together a simple Azure Function which used Durable Entities to represent our dev-test environments and our developers.

After a few evenings of tinkering with durable entities I started to get the hang of how they worked. My goal was to build a custom slash command for Slack that would call out to an Azure Function, the Azure Function would use Durable Entities to represent 1) our dev-test environments and 2) the developers.

Developers would be able to do the following from the custom slash command:

  1. Be able to claim an environment
  2. Be able to release an environment (if they are using it)
  3. Be able to see who is using what

To claim an environment developers can send /uat claim uat[n] or release an environment by sending /uat release uat[n]

/azure-durable-entities-revisited/images/claimrelease.gif

A developer can also see a list of all the environments by sending /uat list

/azure-durable-entities-revisited/images/image-1.png

A look at the DevTestResourceEntity

The full feature set of what our /uat tool does now goes beyond the above simple requirements so instead of going into all the details I’m going to provide a simple overview of the entities and how they work.

The following snippet shows the DevTestResourceEntity

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public interface IDevTestResourceEntity
{
  public Task Claim(string userName);
  public Task Release();
  public Task<bool> IsAvailable();
}

[JsonObject(MemberSerialization.OptIn)]
public class DevTestResourceEntity : IDevTestResourceEntity
{
  [JsonProperty("isAvailable")]
  public bool IsAvailable { get; set; }

  [JsonProperty("inUseBy")]
  public string InUseBy { get; set; }

  [JsonProperty("claimedAtUtc")]
  public DateTime? ClaimedAtUtc { get; set; }

  public Task Claim(string userName)
  {
    IsAvailable = false;
    InUseBy = userName;
    ClaimedAtUtc = DateTime.UtcNow;

    return Task.CompletedTask;
  }

  public Task Release()
  {
    IsAvailable = true;
    InUseBy = string.Empty;
    ClaimedAtUtc = null;

    return Task.CompletedTask;
  }

  public Task<bool> IsAvailable()
  {
    return Task.FromResult(IsAvailable);
  }

  [FunctionName(nameof(DevTestResourceEntity))]
  public static Task Run([EntityTrigger] IDurableEntityContext ctx => ctx.DispatchAsync<DevTestResourceEntity>());
}

The first thing you’ll probably notice is the interface IDevTestResourceEntity - this has very deliberately been added, you will see shortly the benefit we gain from this interface. The other thing to note is the Run method, this is the boiler plate code that indicates to the Function runtime that this is an Entity, and is also the code that is executed each time an entity is signalled.

Working with Entities

Entities communicate via messages or signals - the Functions framework/runtime provides a few different interfaces which can be injected into your normal functions which provide a mechanism to send a message to an entity or read the current state. Entities can also signal other entities.

Claiming a resource

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[FunctionName("claim")]
public static async Task<HttpResponseMessage> ClaimResource(
    [HttpTrigger(AuthorizationLevel.Function)] HttpRequestMessage req,
    [DurableClient] IDurableEntityClient client)
{
    var resource = "uat1";
    var userName = "bob";
    var entityId = new EntityId(nameof(DevTestResourceEntity), resource);

    var current = await client.ReadEntityStateAsync<ClaimableResourceEntity>(entityId);

    if (!current.EntityExists)
    {
        return new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent($"❗ Sorry, {resource} doesn't exist")
        };
    }

    if (!current.EntityState.IsAvailable)
    {
        return new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent($"❗ Sorry, {resource} is currently being used by <@{current.EntityState.InUseBy}>")
        };
    }

    await client.SignalEntityAsync<IDevTestResourceEntity>(entityId, proxy => proxy.Claim(userName));

    return new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StringContent($"✔️ {resource} is now claimed by {userName}")
    };
}

The ClaimResource function does a couple of things, the first is that it reads the current state of the entity (using the ReadEntityStateAsync method), to do this an EntityId is passed in. A user can only claim a resource that exists or is unavailable, so if either of these conditions are met then an appropriate error message is sent back, otherwise IDurableEntityClient interface is used to send a signal to the entity that it should execute the Claim method. You can see this on line 28 above - and this is where having the strongly typed interface comes in handy, having the interface means strongly typed execution and reduces the likelihood of runtime errors since we’ll get compiler errors if a parameter of type is incorrect. This is also the recommended approach.

When working with Durable Entities, there are two methods of communication: one-way and two-way communication. In the above example, when using SignalEntityAsync this is an example of one-way communication, the Claim method return type Task but even if I attempted to return a Task<string> or something else the result will always be null.

Two-way communication is possible using IDurableOrchestrationContext, in this case it would look something like this

1
2
3
4
5
6
7
IDurableOrchestrationContext context; //this is injected into your Azure Function

var entityId = new EntityId(nameof(DevTestResourceEntity), "uat1");
var proxy = context.CreateEntityProxy<IDevTestResourceEntity>(entityId);

//assuming `WhoIsUsingResource` is defined in the IDevTestResourceEntity interface and returns `Task<string>
var result = await proxy.WhoIsUsingResource();`

More details on how this works and other access patterns can be found on Microsoft Learn - Developer’s guide to durable entities in .NET .

Releasing a resource

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[FunctionName("claim")]
public static async Task<HttpResponseMessage> ReleaseResource(
    [HttpTrigger(AuthorizationLevel.Function)] HttpRequestMessage req,
    [DurableClient] IDurableEntityClient client)
{
    var resource = "uat1";
    var userName = "bob";
    var entityId = new EntityId(nameof(DevTestResourceEntity), resource);

    var current = await client.ReadEntityStateAsync<ClaimableResourceEntity>(entityId);

    if (!current.EntityExists)
    {
        return $"❗ Umm, {resource} doesn't exist";
    }
    
    if (current.EntityState.IsAvailable)
    {
        return $"{resource} is already available";
    }
    if (current.EntityState.InUseBy.Equals(userName))
    {
        //signal entity
        await client.SignalEntityAsync<IClaimableResourceOperations>(entityId, proxy => proxy.Release());
        return $"{current.EntityState.Emoji} {resource} is being released";
    }
    else
    {
        return $"❗ It looks like {resource} is being used by <@{current.EntityState.InUseBy}>";
    }
}

Much like the above ClaimResource function the ReleaseResource function works in much the same way, it checks a few things (whether the resource is being used and who it’s being used by, after all it wouldn’t be fair to release a resource being used by someone else 😀) and then signals the entity to execute the Release method.

Listing all resources

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  IDurableEntityClient client = ...;
  
  using CancellationTokenSource source = new CancellationTokenSource();

  var result = await client.ListEntitiesAsync(new EntityQuery()
  {
      EntityName = nameof(DevTestResourceEntity),
      PageSize = 100,
      FetchState = true
  }, source.Token);

Listing all resources is also relatively straightforward using the ListEntitiesAsync method on IDurableEntityClient, you will need to supply a PageSize and then implement a looping mechanism if the number of entities is going to exceed the page size.

Making the most of Azure Serverless

The above two sections show the basic interactions with Durable Entities but there are some other really nifty things that can be done with Durable Entities and other Serverless offerings from Azure.

SignalR for real-time updates

SignalR is great for building web apps that need real-time updating capabilities, and with the serverless offering from Azure it’s really easy to integrate with Azure Functions. One of the things I put together was a real-time dashboard which shows who is using what resources.

/azure-durable-entities-revisited/images/image-2.png

My design skills clearly leave something to be desired.

Integrating this into the Durable Entities is surprisingly easy. As mentioned above the Run method defined in the DevTestResourceEntity serves as the entry point for the entity when executing. When this method is invoked the Azure framework injects the IDurableEntityContext, but it can also be used to inject anything that is registered with the dependency injection framework.

We could change the above definition to the following

1
2
3
4
5
[FunctionName(nameof(DevTestResourceEntity))]
  public static Task Run(
      [EntityTrigger] IDurableEntityContext ctx,
      [SignalR(HubName = "resourcestatus")] IAsyncCollector<SignalRMessage> signalRMessages
   => ctx.DispatchAsync<DevTestResourceEntity>(signalRMessages, ctx));

This will result in an IAsyncCollector being injected during execution, the constructor will also need to be updated

1
2
3
4
5
public DevTestResourceEntity(IAsyncCollector<SignalRMessage> signalRMessages, IDurableEntityContext context)
{
  _signalrMessages = signalRMessages;
  _context = context;
}

This will then allow our entity to broadcast status updates to SignalR during execution, so for example the Claim and Release method could be updated with the following

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public async Task Claim(string userName)
{
  IsAvailable = false;
  InUseBy = userName;
  ClaimedAtUtc = DateTime.UtcNow;

  await _signalRMessages.AddAsync(new SignalRMessage
  {
      Target = "statusChanged",
      Arguments = new[] { new { resource = Entity.Current.EntityKey, state = "claimed", inUseBy = InUseByUserName } }
  });
}

public async Task Release()
{
  IsAvailable = true;
  InUseBy = string.Empty;
  ClaimedAtUtc = null;

  await _signalRMessages.AddAsync(new SignalRMessage
  {
      Target = "statusChanged",
      Arguments = new[] { new { resource = Entity.Current.EntityKey, state = "released" } }
  });
}

Now whenever a resource is claimed or released the entity will broadcast an update to the resourcestatus hub, which the web app subscribes to and when updates are broadcast the webpage will update the appropriate element to indicate the resource is either in use or available.

Azure Storage Queues

One of the other features that I implemented soon after getting the Slack app live was a reminder feature - after a resource had been claimed for 2 hours I wanted a message to go to the developer in Slack and ask if they were still using the resource (so they could release it if they no longer needed it). To do this I made use of Azure Storage Queues and schedule messages.

Publishing to a Storage queue from a Durable Entity is as simple as using SignalR, first the Run method is updated

1
2
3
4
5
[FunctionName(nameof(DevTestResourceEntity))]
  public static Task Run(
      [EntityTrigger] IDurableEntityContext ctx,
      [Queue("reminders"), StorageAccount("AzureWebJobsStorage")] CloudQueue reminderQueue
   => ctx.DispatchAsync<DevTestResourceEntity>(reminderQueue, ctx));

and the constructor

1
2
3
4
5
public DevTestResourceEntity(CloudQueue reminderQueue, IDurableEntityContext context)
{
  _reminderQueue = reminderQueue;
  _context = context;
}

The Claim method can then be updated to make use of the _reminderQueue

1
2
3
4
5
6
7
8
public async Task Claim(string userName)
{
  IsAvailable = false;
  InUseBy = userName;
  ClaimedAtUtc = DateTime.UtcNow;

  await _reminderQueue.AddMessageAsync(new CloudQueueMessage($"{Entity.Current.EntityKey}|{InUseBy}"), null, TimeSpan.FromHours(2), null, null);
}

This will put a message into the reminders queue, scheduled for 2 hours time. I then implemented an Azure Function with a Storage Queue trigger which did the following:

  1. Looked up the entity
  2. Checked it was still claimed by that user
  3. If it was claimed by the same user it would send them a message on Slack asking if they were still using it

Wrapping up 🎁

I hope you’ve enjoyed this article on Azure Durable Entities, if you’ve not had a chance to play with them before I would encourage you to start looking out for places where this pattern could be useful and give them a try.

There’s lots of content I’ve not covered here, the Microsoft Learn docs are an absolute gold mine, and go into a lot of the details I’ve brushed over or completely skipped in this article:

🍪 I use Disqus for comments

Because Disqus requires cookies this site doesn't automatically load comments.

I don't mind about cookies - Show me the comments from now on (and set a cookie to remember my preference)