Skip to content

Commit

Permalink
Add TaskOrchestrationContext entity abstractions (#173)
Browse files Browse the repository at this point in the history
  • Loading branch information
jviau authored Aug 14, 2023
1 parent 571c2ce commit 046e600
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 15 deletions.
17 changes: 2 additions & 15 deletions src/Abstractions/Entities/CallEntityOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,8 @@ namespace Microsoft.DurableTask.Entities;
/// </summary>
public record CallEntityOptions
{
/// <summary>
/// Gets options indicating whether to signal the entity or not.
/// </summary>
/// <remarks>
/// Setting this to non-<c>null</c> will signal the entity without waiting for a response.
/// </remarks>
/// <example>
/// Signal without start time:
/// <code>new CallEntityOptions { Signal = true };</code>
/// </example>
/// <example>
/// Signal with start time:
/// <code>new CallEntityOptions { Signal = DateTimeOffset };</code>
/// </example>
public SignalEntityOptions? Signal { get; init; }
// No call options at the moment. Keeping this class so we can ship with options in the API. This will
// allow us to easily add them later without adjusting API surface.
}

/// <summary>
Expand Down
114 changes: 114 additions & 0 deletions src/Abstractions/Entities/TaskOrchestrationEntityFeature.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.Diagnostics.CodeAnalysis;

namespace Microsoft.DurableTask.Entities;

/// <summary>
/// Feature for interacting with durable entities from an orchestration.
/// </summary>
public abstract class TaskOrchestrationEntityFeature
{
/// <summary>
/// Calls an operation on an entity and waits for it to complete.
/// </summary>
/// <typeparam name="TResult">The result type of the entity operation.</typeparam>
/// <param name="id">The target entity.</param>
/// <param name="operationName">The name of the operation.</param>
/// <param name="input">The operation input.</param>
/// <param name="options">The call options.</param>
/// <returns>The result of the entity operation.</returns>
public abstract Task<TResult> CallEntityAsync<TResult>(
EntityInstanceId id, string operationName, object? input = null, CallEntityOptions? options = null);

/// <summary>
/// Calls an operation on an entity and waits for it to complete.
/// </summary>
/// <typeparam name="TResult">The result type of the entity operation.</typeparam>
/// <param name="id">The target entity.</param>
/// <param name="operationName">The name of the operation.</param>
/// <param name="options">The call options.</param>
/// <returns>The result of the entity operation.</returns>
public virtual Task<TResult> CallEntityAsync<TResult>(
EntityInstanceId id, string operationName, CallEntityOptions? options)
=> this.CallEntityAsync<TResult>(id, operationName, null, options);

/// <summary>
/// Calls an operation on an entity and waits for it to complete.
/// </summary>
/// <param name="id">The target entity.</param>
/// <param name="operationName">The name of the operation.</param>
/// <param name="input">The operation input.</param>
/// <param name="options">The call options.</param>
/// <returns>A task that completes when the operation has been completed.</returns>
public abstract Task CallEntityAsync(
EntityInstanceId id, string operationName, object? input = null, CallEntityOptions? options = null);

/// <summary>
/// Calls an operation on an entity and waits for it to complete.
/// </summary>
/// <param name="id">The target entity.</param>
/// <param name="operationName">The name of the operation.</param>
/// <param name="options">The call options.</param>
/// <returns>A task that completes when the operation has been completed.</returns>
public virtual Task CallEntityAsync(EntityInstanceId id, string operationName, CallEntityOptions? options)
=> this.CallEntityAsync(id, operationName, null, options);

/// <summary>
/// Calls an operation on an entity, but does not wait for completion.
/// </summary>
/// <param name="id">The target entity.</param>
/// <param name="operationName">The name of the operation.</param>
/// <param name="input">The operation input.</param>
/// <param name="options">The signal options.</param>
/// <returns>
/// A task that represents scheduling of the signal operation. Dependening on implementation, this may complete
/// either when the operation has been signalled, or when the signal action has been enqueued by the context.
/// </returns>
public abstract Task SignalEntityAsync(
EntityInstanceId id, string operationName, object? input = null, SignalEntityOptions? options = null);

/// <summary>
/// Calls an operation on an entity, but does not wait for completion.
/// </summary>
/// <param name="id">The target entity.</param>
/// <param name="operationName">The name of the operation.</param>
/// <param name="options">The signal options.</param>
/// <returns>
/// A task that represents scheduling of the signal operation. Dependening on implementation, this may complete
/// either when the operation has been signalled, or when the signal action has been enqueued by the context.
/// </returns>
public virtual Task SignalEntityAsync(
EntityInstanceId id, string operationName, SignalEntityOptions? options)
=> this.SignalEntityAsync(id, operationName, null, options);

/// <summary>
/// Acquires one or more entity locks.
/// </summary>
/// <param name="entityIds">The entity IDs to lock.</param>
/// <returns>An async-disposable which can be disposed to release the lock.</returns>
public abstract Task<IAsyncDisposable> LockEntitiesAsync(IEnumerable<EntityInstanceId> entityIds);

/// <summary>
/// Acquires one or more entity locks.
/// </summary>
/// <param name="entityIds">The entity IDs to lock.</param>
/// <returns>An async-disposable which can be disposed to release the lock.</returns>
public virtual Task<IAsyncDisposable> LockEntitiesAsync(params EntityInstanceId[] entityIds)
=> this.LockEntitiesAsync((IEnumerable<EntityInstanceId>)entityIds); // let the implementation decide how to handle nulls.

/// <summary>
/// Gets a value indicating whether this orchestration is in a critical section, and if true, any entity locks are
/// owned by this instance.
/// </summary>
/// <param name="entityIds">The list of locked entities.</param>
/// <returns>True if any locks are owned, false otherwise.</returns>
public abstract bool InCriticalSection([NotNullWhen(true)] out IReadOnlyList<EntityInstanceId>? entityIds);

/// <summary>
/// Gets a value indicating whether this orchestration is in a critical section.
/// </summary>
/// <returns>True if any locks are owned, false otherwise.</returns>
public virtual bool InCriticalSection() => this.InCriticalSection(out _);
}
7 changes: 7 additions & 0 deletions src/Abstractions/TaskOrchestrationContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

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

namespace Microsoft.DurableTask;
Expand Down Expand Up @@ -58,6 +59,12 @@ public abstract class TaskOrchestrationContext
/// </value>
public abstract bool IsReplaying { get; }

/// <summary>
/// Gets the entity feature, for interacting with entities.
/// </summary>
public virtual TaskOrchestrationEntityFeature Entities =>
throw new NotSupportedException($"Durable entities are not supported by {this.GetType()}.");

/// <summary>
/// Gets the logger factory for this context.
/// </summary>
Expand Down

0 comments on commit 046e600

Please sign in to comment.