Skip to content

Commit

Permalink
Add entities samples (#206)
Browse files Browse the repository at this point in the history
  • Loading branch information
jviau authored Oct 6, 2023
1 parent b9f01b9 commit 1a44381
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 3 deletions.
2 changes: 2 additions & 0 deletions nuget.config
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<configuration>
<packageSources>
<clear/>
<add key="durabletask" value="https://pkgs.dev.azure.com/durabletaskframework/734e7913-2fab-4624-a174-bc57fe96f95d/_packaging/durabletask/nuget/v3/index.json" />
<add key="AzureFunctionsTempStaging" value="https://pkgs.dev.azure.com/azfunc/e6a70c92-4128-439f-8012-382fe78d6396/_packaging/AzureFunctionsTempStaging/nuget/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="durabletask" value="https://pkgs.dev.azure.com/durabletaskframework/734e7913-2fab-4624-a174-bc57fe96f95d/_packaging/durabletask/nuget/v3/index.json" />
</packageSources>
Expand Down
6 changes: 3 additions & 3 deletions samples/AzureFunctionsApp/AzureFunctionsApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.18.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.0.2" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.19.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.1.0-entities-preview.2" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.13.0" OutputItemType="Analyzer" />
<PackageReference Include="Microsoft.DurableTask.Generators" Version="1.0.0-preview.1" OutputItemType="Analyzer" />
Expand Down
89 changes: 89 additions & 0 deletions samples/AzureFunctionsApp/Entities/Counter.Entity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

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

namespace AzureFunctionsApp.Entities.Entity;

/// <summary>
/// Example on how to dispatch to an entity which directly implements TaskEntity<TState>. Using TaskEntity<TState> gives
/// the added benefit of being able to use DI. When using TaskEntity<TState>, state is deserialized to the "State"
/// property. No other properties on this type will be serialized/deserialized.
/// </summary>
public class Counter : TaskEntity<int>
{
readonly ILogger logger;

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

public int Add(int input)
{
this.logger.LogInformation("Adding {Input} to {State}", input, this.State);
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.StartOrchestration("SomeOrchestration", "SomeInput");

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

public int Get() => this.State;

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

[Function("Counter3")]
public static Task RunEntityStaticAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
{
// Can also dispatch to a TaskEntity<TState> by using a static method.
return dispatcher.DispatchAsync<Counter>();
}

[Function("StartCounter2")]
public static async Task<HttpResponseData> StartAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData request,
[DurableClient] DurableTaskClient client)
{
Payload? payload = await request.ReadFromJsonAsync<Payload>();
string id = await client.ScheduleNewOrchestrationInstanceAsync("CounterOrchestration2", payload);
return client.CreateCheckStatusResponse(request, id);
}

[Function("CounterOrchestration2")]
public static async Task<int> RunOrchestrationAsync(
[OrchestrationTrigger] TaskOrchestrationContext context, Payload input)
{
ILogger logger = context.CreateReplaySafeLogger<Counter>();
int result = await context.Entities.CallEntityAsync<int>(
new EntityInstanceId("Counter2", input.Key), "add", input.Add);

logger.LogInformation("Counter value: {Value}", result);
return result;
}

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

public record Payload(string Key, int Add);
}
65 changes: 65 additions & 0 deletions samples/AzureFunctionsApp/Entities/Counter.State.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

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

namespace AzureFunctionsApp.Entities.State;

/// <summary>
/// Example on how to dispatch to a POCO as the entity implementation. When using POCO, the entire object is serialized
/// and deserialized.
/// </summary>
public class Counter
{
public int Value { get; set; }

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.StartOrchestration("SomeOrchestration", "SomeInput");
return this.Add(input);
}

public int Get() => this.Value;

[Function("Counter1")]
public static Task RunEntityAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
{
// Using the dispatch to a state object will deserialize the state directly to that instance and dispatch to an
// appropriate method.
// Can only dispatch to a state object via generic argument.
return dispatcher.DispatchAsync<Counter>();
}

[Function("StartCounter1")]
public static async Task<HttpResponseData> StartAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData request,
[DurableClient] DurableTaskClient client)
{
Payload? payload = await request.ReadFromJsonAsync<Payload>();
string id = await client.ScheduleNewOrchestrationInstanceAsync("CounterOrchestration1", payload);
return client.CreateCheckStatusResponse(request, id);
}

[Function("CounterOrchestration1")]
public static async Task<int> RunOrchestrationAsync(
[OrchestrationTrigger] TaskOrchestrationContext context, Payload input)
{
ILogger logger = context.CreateReplaySafeLogger<Counter>();
int result = await context.Entities.CallEntityAsync<int>(
new EntityInstanceId("Counter1", input.Key), "add", input.Add);

logger.LogInformation("Counter value: {Value}", result);
return result;
}

public record Payload(string Key, int Add);
}
54 changes: 54 additions & 0 deletions samples/AzureFunctionsApp/Entities/StateManagement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.DurableTask.Entities;
using Microsoft.Extensions.Logging;

namespace AzureFunctionsApp.Entities;

public class StateManagement : TaskEntity<MyState>
{
readonly ILogger logger;

public StateManagement(ILogger<StateManagement> 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;

public MyState Get() => this.State;

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 (this will still delete it). To 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()
{
// This method allows for customizing the default state value for a new entity.
return new("Default", 10);
}
}


public record MyState(string PropA, int PropB);

0 comments on commit 1a44381

Please sign in to comment.