Skip to content

Add support for entities to OrchestrationServiceClientShim package #228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,20 @@ namespace Microsoft.DurableTask.Client;
/// <summary>
/// Default builder for <see cref="IDurableTaskClientBuilder" />.
/// </summary>
public class DefaultDurableTaskClientBuilder : IDurableTaskClientBuilder
/// <remarks>
/// Initializes a new instance of the <see cref="DefaultDurableTaskClientBuilder"/> class.
/// </remarks>
/// <param name="name">The name of the builder.</param>
/// <param name="services">The service collection.</param>
public class DefaultDurableTaskClientBuilder(string? name, IServiceCollection services) : IDurableTaskClientBuilder
{
Type? buildTarget;

/// <summary>
/// Initializes a new instance of the <see cref="DefaultDurableTaskClientBuilder"/> class.
/// </summary>
/// <param name="name">The name of the builder.</param>
/// <param name="services">The service collection.</param>
public DefaultDurableTaskClientBuilder(string? name, IServiceCollection services)
{
this.Name = name ?? Options.DefaultName;
this.Services = services;
}

/// <inheritdoc/>
public string Name { get; }
public string Name { get; } = name ?? Options.DefaultName;

/// <inheritdoc/>
public IServiceCollection Services { get; }
public IServiceCollection Services { get; } = services;

/// <inheritdoc/>
public Type? BuildTarget
Expand Down
27 changes: 24 additions & 3 deletions src/Client/Core/DurableTaskClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Microsoft.DurableTask.Client;
public class DurableTaskClientOptions
{
DataConverter dataConverter = JsonDataConverter.Default;
bool enableEntitySupport;

/// <summary>
/// Gets or sets the data converter. Default value is <see cref="JsonDataConverter.Default" />.
Expand Down Expand Up @@ -50,7 +51,15 @@ public DataConverter DataConverter
/// Gets or sets a value indicating whether this client should support entities. If true, all instance ids starting with '@' are reserved for entities,
/// and validation checks are performed where appropriate.
/// </summary>
public bool EnableEntitySupport { get; set; }
public bool EnableEntitySupport
{
get => this.enableEntitySupport;
set
{
this.enableEntitySupport = value;
this.EntitySupportExplicitlySet = true;
}
}

/// <summary>
/// Gets a value indicating whether <see cref="DataConverter" /> was explicitly set or not.
Expand All @@ -63,6 +72,11 @@ public DataConverter DataConverter
/// </remarks>
internal bool DataConverterExplicitlySet { get; private set; }

/// <summary>
/// Gets a value indicating whether <see cref="EnableEntitySupport" /> was explicitly set or not.
/// </summary>
internal bool EntitySupportExplicitlySet { get; private set; }

/// <summary>
/// Applies these option values to another.
/// </summary>
Expand All @@ -72,8 +86,15 @@ internal void ApplyTo(DurableTaskClientOptions other)
if (other is not null)
{
// Make sure to keep this up to date as values are added.
other.DataConverter = this.DataConverter;
other.EnableEntitySupport = this.EnableEntitySupport;
if (!other.DataConverterExplicitlySet)
{
other.DataConverter = this.DataConverter;
}

if (!other.EntitySupportExplicitlySet)
{
other.EnableEntitySupport = this.EnableEntitySupport;
}
}
}
}
41 changes: 16 additions & 25 deletions src/Client/Core/Entities/EntityMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,15 @@ namespace Microsoft.DurableTask.Client.Entities;
/// Represents entity metadata.
/// </summary>
/// <typeparam name="TState">The type of state held by the metadata.</typeparam>
/// <remarks>
/// Initializes a new instance of the <see cref="EntityMetadata{TState}"/> class.
/// </remarks>
/// <param name="id">The ID of the entity.</param>
[JsonConverter(typeof(EntityMetadataConverter))]
public class EntityMetadata<TState>
public class EntityMetadata<TState>(EntityInstanceId id)
{
readonly TState? state;

/// <summary>
/// Initializes a new instance of the <see cref="EntityMetadata{TState}"/> class.
/// </summary>
/// <param name="id">The ID of the entity.</param>
public EntityMetadata(EntityInstanceId id)
{
this.Id = Check.NotDefault(id);
this.IncludesState = false;
}

/// <summary>
/// Initializes a new instance of the <see cref="EntityMetadata{TState}"/> class.
/// </summary>
Expand All @@ -41,7 +35,7 @@ public EntityMetadata(EntityInstanceId id, TState? state)
/// <summary>
/// Gets the ID for this entity.
/// </summary>
public EntityInstanceId Id { get; }
public EntityInstanceId Id { get; } = Check.NotDefault(id);

/// <summary>
/// Gets the time the entity was last modified.
Expand All @@ -64,9 +58,9 @@ public EntityMetadata(EntityInstanceId id, TState? state)
/// <remarks>
/// Queries can exclude the state of the entity from the metadata that is retrieved.
/// </remarks>
[MemberNotNullWhen(true, "State")]
[MemberNotNullWhen(true, "state")]
public bool IncludesState { get; }
[MemberNotNullWhen(true, nameof(State))]
[MemberNotNullWhen(true, nameof(state))]
public bool IncludesState { get; } = false;

/// <summary>
/// Gets the state for this entity.
Expand Down Expand Up @@ -96,16 +90,13 @@ public TState State
/// <summary>
/// Represents the metadata for a durable entity instance.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="EntityMetadata"/> class.
/// </remarks>
/// <param name="id">The ID of the entity.</param>
/// <param name="state">The state of this entity.</param>
[JsonConverter(typeof(EntityMetadataConverter))]
public sealed class EntityMetadata : EntityMetadata<SerializedData>
public sealed class EntityMetadata(EntityInstanceId id, SerializedData? state = null)
: EntityMetadata<SerializedData>(id, state)
{
/// <summary>
/// Initializes a new instance of the <see cref="EntityMetadata"/> class.
/// </summary>
/// <param name="id">The ID of the entity.</param>
/// <param name="state">The state of this entity.</param>
public EntityMetadata(EntityInstanceId id, SerializedData? state = null)
: base(id, state)
{
}
}
22 changes: 8 additions & 14 deletions src/Client/Core/SerializedData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,22 @@ namespace Microsoft.DurableTask.Client;
/// <summary>
/// Gets a type representing serialized data.
/// </summary>
public sealed class SerializedData
/// <remarks>
/// Initializes a new instance of the <see cref="SerializedData"/> class.
/// </remarks>
/// <param name="data">The serialized data.</param>
/// <param name="converter">The data converter.</param>
public sealed class SerializedData(string data, DataConverter? converter = null)
{
/// <summary>
/// Initializes a new instance of the <see cref="SerializedData"/> class.
/// </summary>
/// <param name="data">The serialized data.</param>
/// <param name="converter">The data converter.</param>
public SerializedData(string data, DataConverter? converter = null)
{
this.Value = Check.NotNull(data);
this.Converter = converter ?? JsonDataConverter.Default;
}

/// <summary>
/// Gets the serialized value.
/// </summary>
public string Value { get; }
public string Value { get; } = Check.NotNull(data);

/// <summary>
/// Gets the data converter.
/// </summary>
public DataConverter Converter { get; }
public DataConverter Converter { get; } = converter ?? JsonDataConverter.Default;

/// <summary>
/// Deserializes the data into <typeparamref name="T"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@ The client is responsible for interacting with orchestrations from outside the w
<EnableStyleCop>true</EnableStyleCop>
</PropertyGroup>

<PropertyGroup>
<!-- We are still working on this package for entities preview. -->
<VersionPrefix>1.0.5</VersionPrefix>
<VersionSuffix></VersionSuffix> <!-- Need this here to set it back to empty. -->
</PropertyGroup>


<ItemGroup>
<ProjectReference Include="../Core/Client.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
// Licensed under the MIT License.

using DurableTask.Core;
using DurableTask.Core.Entities;
using Microsoft.DurableTask.Client.OrchestrationServiceClientShim;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

namespace Microsoft.DurableTask.Client;

Expand Down Expand Up @@ -54,21 +57,74 @@ public static IDurableTaskClientBuilder UseOrchestrationService(
{
Check.NotNull(builder);
Check.NotNull(configure);
builder.Services.Configure(builder.Name, configure);
builder.Services.AddOptions<ShimDurableTaskClientOptions>(builder.Name)
.PostConfigure<IServiceProvider>((opt, sp) =>
builder.Services.AddOptions<ShimDurableTaskClientOptions>(builder.Name).Configure(configure);
builder.Services.TryAddSingleton<IPostConfigureOptions<ShimDurableTaskClientOptions>, OptionsConfigure>();
builder.Services.TryAddSingleton<IValidateOptions<ShimDurableTaskClientOptions>, OptionsValidator>();
return builder.UseBuildTarget<ShimDurableTaskClient, ShimDurableTaskClientOptions>();
}

static IEntityOrchestrationService? GetEntityService(
IServiceProvider services, ShimDurableTaskClientOptions options)
{
return options.Client as IEntityOrchestrationService
?? services.GetService<IEntityOrchestrationService>()
?? services.GetService<IOrchestrationServiceClient>() as IEntityOrchestrationService
?? services.GetService<IOrchestrationService>() as IEntityOrchestrationService;
}

class OptionsConfigure(IServiceProvider services) : IPostConfigureOptions<ShimDurableTaskClientOptions>
{
public void PostConfigure(string name, ShimDurableTaskClientOptions options)
{
ConfigureClient(services, options);
ConfigureEntities(name, services, options); // Must be called after ConfigureClient.
}

static void ConfigureClient(IServiceProvider services, ShimDurableTaskClientOptions options)
{
if (options.Client is not null)
{
if (opt.Client is not null)
{
return;
}
return;
}

// Try to resolve client from service container.
opt.Client = sp.GetService<IOrchestrationServiceClient>()
?? sp.GetService<IOrchestrationService>() as IOrchestrationServiceClient;
})
.Validate(x => x.Client is not null, "ShimDurableTaskClientOptions.Client must not be null.");
// Try to resolve client from service container.
options.Client = services.GetService<IOrchestrationServiceClient>()
?? services.GetService<IOrchestrationService>() as IOrchestrationServiceClient;
}

return builder.UseBuildTarget<ShimDurableTaskClient, ShimDurableTaskClientOptions>();
static void ConfigureEntities(string name, IServiceProvider services, ShimDurableTaskClientOptions options)
{
if (options.Entities.Queries is null)
{
options.Entities.Queries = services.GetService<EntityBackendQueries>()
?? GetEntityService(services, options)?.EntityBackendQueries;
}

if (options.Entities.MaxSignalDelayTime is null)
{
EntityBackendProperties? properties = services.GetService<IOptionsMonitor<EntityBackendProperties>>()?.Get(name)
?? GetEntityService(services, options)?.EntityBackendProperties;
options.Entities.MaxSignalDelayTime = properties?.MaximumSignalDelayTime;
}
}
}

class OptionsValidator : IValidateOptions<ShimDurableTaskClientOptions>
{
public ValidateOptionsResult Validate(string name, ShimDurableTaskClientOptions options)
{
if (options.Client is null)
{
return ValidateOptionsResult.Fail("ShimDurableTaskClientOptions.Client must not be null.");
}

if (options.EnableEntitySupport && options.Entities.Queries is null)
{
return ValidateOptionsResult.Fail(
"ShimDurableTaskClientOptions.Entities.Queries must not be null when entity support is enabled.");
}

return ValidateOptionsResult.Success;
}
}
}
Loading
Loading