Skip to content

Commit

Permalink
Update entity samples (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
jviau authored Oct 12, 2023
1 parent ac5eb32 commit 8a0c761
Show file tree
Hide file tree
Showing 5 changed files with 370 additions and 91 deletions.
61 changes: 24 additions & 37 deletions samples/AzureFunctionsApp/Entities/Counter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,38 +30,23 @@ public int Add(int input)
return this.State += input;
}

public int OperationWithContext(int input, TaskEntityContext context)
{
// Get access to TaskEntityContext by adding it as a parameter. Can be with or without an input parameter.
// Order does not matter.
context.ScheduleNewOrchestration("SomeOrchestration", "SomeInput");

// When using TaskEntity<TState>, the TaskEntityContext can also be accessed via this.Context.
this.Context.ScheduleNewOrchestration("SomeOrchestration", "SomeInput");
return this.Add(input);
}

public int Get() => this.State;

[Function("Counter")]
public Task RunEntityAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
public Task DispatchAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
{
// Can dispatch to a TaskEntity<TState> by passing a instance.
return dispatcher.DispatchAsync(this);
}

[Function("Counter_Alt")]
public static Task RunEntityStaticAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
public static Task DispatchStaticAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
{
// Can also dispatch to a TaskEntity<TState> by using a static method.
// However, this is a different entity ID - "counter_alt" and not "counter". Even though it uses the same
// entity implementation, the function attribute has a different name, which determines the entity ID.
return dispatcher.DispatchAsync<Counter>();
}

protected override int InitializeState(TaskEntityOperation operation)
{
// Optional method to override to customize initialization of state for a new instance.
return base.InitializeState(operation);
}
}

/// <summary>
Expand All @@ -80,18 +65,10 @@ public class StateCounter

public int Add(int input) => this.Value += input;

public int OperationWithContext(int input, TaskEntityContext context)
{
// Get access to TaskEntityContext by adding it as a parameter. Can be with or without an input parameter.
// Order does not matter.
context.ScheduleNewOrchestration("SomeOrchestration", "SomeInput");
return this.Add(input);
}

public int Get() => this.Value;

[Function("StateCounter")]
public static Task RunEntityAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
[Function("Counter_State")]
public static Task DispatchAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
{
// Using the dispatch to a state object will deserialize the state directly to that instance and dispatch to an
// appropriate method.
Expand All @@ -100,7 +77,7 @@ public static Task RunEntityAsync([EntityTrigger] TaskEntityDispatcher dispatche
}
}

public static class CounterHelpers
public static class CounterApis
{
/// <summary>
/// Usage:
Expand All @@ -125,17 +102,27 @@ public static async Task<HttpResponseData> StartAsync(
{
_ = int.TryParse(request.Query["value"], out int value);

string name = "counter";

// switch to StateCounter if ?mode=1 or ?mode=state is supplied.
// switch to Counter_State if ?mode=1 or ?mode=state is supplied.
// or to Counter_Alt if ?mode=2 or ?mode=static is supplied.
string name;
string? mode = request.Query["mode"];
if (int.TryParse(mode, out int m) && m == 1)
if (int.TryParse(mode, out int m))
{
name = "state";
name = m switch
{
1 => "counter_state",
2 => "counter_alt",
_ => "counter",
};
}
else if (mode == "state")
else
{
name = "statecounter";
name = mode switch
{
"state" => "counter_state",
"static" => "counter_alt",
_ => "counter",
};
}

string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
Expand Down
114 changes: 114 additions & 0 deletions samples/AzureFunctionsApp/Entities/Lifetime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.Entities;
using Microsoft.DurableTask.Entities;
using Microsoft.Extensions.Logging;

namespace AzureFunctionsApp.Entities;

/// <summary>
/// Example showing the lifetime of an entity. An entity is initialized on the first operation it receives and then
/// is considered deleted when <see cref="TaskEntity{TState}.State"/> is <c>null</c> at the end of an operation.
/// </summary>
public class Lifetime : TaskEntity<MyState>
{
readonly ILogger logger;

public Lifetime(ILogger<Lifetime> logger)
{
this.logger = logger;
}

/// <summary>
/// Optional property to override. When 'true', this will allow dispatching of operations to the TState object if
/// there is no matching method on the entity. Default is 'false'.
/// </summary>
protected override bool AllowStateDispatch => base.AllowStateDispatch;

// NOTE: when using TaskEntity<TState>, you cannot use "RunAsync" as the entity trigger name as this conflicts
// with the base class method 'RunAsync'.
[Function(nameof(Lifetime))]
public Task DispatchAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
{
this.logger.LogInformation("Dispatching entity");
return dispatcher.DispatchAsync(this);
}

public void Init() { } // no op just to init this entity.

public void CustomDelete()
{
// Deleting an entity is done by null-ing out the state.
// The '!' in `null!;` is only needed because we are using C# explicit nullability.
// This can be avoided by either:
// 1) Declare TaskEntity<MyState?> instead.
// 2) Disable explicit nullability.
this.State = null!;
}

public void Delete()
{
// Entities have an implicit 'delete' operation when there is no matching 'delete' method. By explicitly adding
// a 'Delete' method, it will override the implicit 'delete' operation.
// Since state deletion is determined by nulling out `this.State`, it means that value-types cannot be deleted
// except by the implicit delete (which will still delete it). To manually delete a value-type, you can declare
// it as nullable. Such as TaskEntity<int?> instead of TaskEntity<int>.
this.State = null!;
}

protected override MyState InitializeState(TaskEntityOperation operation)
{
// This method allows for customizing the default state value for a new entity.
return new("Default", 10);
}
}

public static class LifetimeApis
{
[Function("GetLifetime")]
public static async Task<HttpResponseData> GetAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "state/{id}")] HttpRequestData request,
[DurableClient] DurableTaskClient client,
string id)
{
EntityMetadata<MyState>? entity = await client.Entities.GetEntityAsync<MyState>(
new EntityInstanceId(nameof(Lifetime), id));

if (entity is null)
{
return request.CreateResponse(HttpStatusCode.NotFound);
}

HttpResponseData response = request.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(entity);
return response;
}

[Function("InitLifetime")]
public static async Task<HttpResponseData> InitAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "state/{id}")] HttpRequestData request,
[DurableClient] DurableTaskClient client,
string id)
{
await client.Entities.SignalEntityAsync(new EntityInstanceId(nameof(Lifetime), id), "init");
return request.CreateResponse(HttpStatusCode.Accepted);
}

[Function("DeleteLifetime")]
public static async Task<HttpResponseData> DeleteAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "state/{id}")] HttpRequestData request,
[DurableClient] DurableTaskClient client,
string id)
{
string operation = bool.TryParse(request.Query["custom"], out bool b) && b ? "customDelete" : "delete";
await client.Entities.SignalEntityAsync(new EntityInstanceId(nameof(Lifetime), id), operation);
return request.CreateResponse(HttpStatusCode.Accepted);
}
}

public record MyState(string PropA, int PropB);
54 changes: 0 additions & 54 deletions samples/AzureFunctionsApp/Entities/StateManagement.cs

This file was deleted.

Loading

0 comments on commit 8a0c761

Please sign in to comment.