Skip to content

Commit f8aeb69

Browse files
authored
Add support for entities to OrchestrationServiceClientShim package (#228)
* Add support for entities from client shim * Add support for entities from client shim * Update OrchestrationServiceClientShim to v1.1.0-preview.1 * Fix EnableEntitySupport overwriting * Add unit tests for shim entity client support * Multi-target test helpers
1 parent 5676d0c commit f8aeb69

17 files changed

+996
-93
lines changed

src/Client/Core/DependencyInjection/DefaultDurableTaskClientBuilder.cs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,20 @@ namespace Microsoft.DurableTask.Client;
99
/// <summary>
1010
/// Default builder for <see cref="IDurableTaskClientBuilder" />.
1111
/// </summary>
12-
public class DefaultDurableTaskClientBuilder : IDurableTaskClientBuilder
12+
/// <remarks>
13+
/// Initializes a new instance of the <see cref="DefaultDurableTaskClientBuilder"/> class.
14+
/// </remarks>
15+
/// <param name="name">The name of the builder.</param>
16+
/// <param name="services">The service collection.</param>
17+
public class DefaultDurableTaskClientBuilder(string? name, IServiceCollection services) : IDurableTaskClientBuilder
1318
{
1419
Type? buildTarget;
1520

16-
/// <summary>
17-
/// Initializes a new instance of the <see cref="DefaultDurableTaskClientBuilder"/> class.
18-
/// </summary>
19-
/// <param name="name">The name of the builder.</param>
20-
/// <param name="services">The service collection.</param>
21-
public DefaultDurableTaskClientBuilder(string? name, IServiceCollection services)
22-
{
23-
this.Name = name ?? Options.DefaultName;
24-
this.Services = services;
25-
}
26-
2721
/// <inheritdoc/>
28-
public string Name { get; }
22+
public string Name { get; } = name ?? Options.DefaultName;
2923

3024
/// <inheritdoc/>
31-
public IServiceCollection Services { get; }
25+
public IServiceCollection Services { get; } = services;
3226

3327
/// <inheritdoc/>
3428
public Type? BuildTarget

src/Client/Core/DurableTaskClientOptions.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace Microsoft.DurableTask.Client;
1111
public class DurableTaskClientOptions
1212
{
1313
DataConverter dataConverter = JsonDataConverter.Default;
14+
bool enableEntitySupport;
1415

1516
/// <summary>
1617
/// Gets or sets the data converter. Default value is <see cref="JsonDataConverter.Default" />.
@@ -50,7 +51,15 @@ public DataConverter DataConverter
5051
/// Gets or sets a value indicating whether this client should support entities. If true, all instance ids starting with '@' are reserved for entities,
5152
/// and validation checks are performed where appropriate.
5253
/// </summary>
53-
public bool EnableEntitySupport { get; set; }
54+
public bool EnableEntitySupport
55+
{
56+
get => this.enableEntitySupport;
57+
set
58+
{
59+
this.enableEntitySupport = value;
60+
this.EntitySupportExplicitlySet = true;
61+
}
62+
}
5463

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

75+
/// <summary>
76+
/// Gets a value indicating whether <see cref="EnableEntitySupport" /> was explicitly set or not.
77+
/// </summary>
78+
internal bool EntitySupportExplicitlySet { get; private set; }
79+
6680
/// <summary>
6781
/// Applies these option values to another.
6882
/// </summary>
@@ -72,8 +86,15 @@ internal void ApplyTo(DurableTaskClientOptions other)
7286
if (other is not null)
7387
{
7488
// Make sure to keep this up to date as values are added.
75-
other.DataConverter = this.DataConverter;
76-
other.EnableEntitySupport = this.EnableEntitySupport;
89+
if (!other.DataConverterExplicitlySet)
90+
{
91+
other.DataConverter = this.DataConverter;
92+
}
93+
94+
if (!other.EntitySupportExplicitlySet)
95+
{
96+
other.EnableEntitySupport = this.EnableEntitySupport;
97+
}
7798
}
7899
}
79100
}

src/Client/Core/Entities/EntityMetadata.cs

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,15 @@ namespace Microsoft.DurableTask.Client.Entities;
1111
/// Represents entity metadata.
1212
/// </summary>
1313
/// <typeparam name="TState">The type of state held by the metadata.</typeparam>
14+
/// <remarks>
15+
/// Initializes a new instance of the <see cref="EntityMetadata{TState}"/> class.
16+
/// </remarks>
17+
/// <param name="id">The ID of the entity.</param>
1418
[JsonConverter(typeof(EntityMetadataConverter))]
15-
public class EntityMetadata<TState>
19+
public class EntityMetadata<TState>(EntityInstanceId id)
1620
{
1721
readonly TState? state;
1822

19-
/// <summary>
20-
/// Initializes a new instance of the <see cref="EntityMetadata{TState}"/> class.
21-
/// </summary>
22-
/// <param name="id">The ID of the entity.</param>
23-
public EntityMetadata(EntityInstanceId id)
24-
{
25-
this.Id = Check.NotDefault(id);
26-
this.IncludesState = false;
27-
}
28-
2923
/// <summary>
3024
/// Initializes a new instance of the <see cref="EntityMetadata{TState}"/> class.
3125
/// </summary>
@@ -41,7 +35,7 @@ public EntityMetadata(EntityInstanceId id, TState? state)
4135
/// <summary>
4236
/// Gets the ID for this entity.
4337
/// </summary>
44-
public EntityInstanceId Id { get; }
38+
public EntityInstanceId Id { get; } = Check.NotDefault(id);
4539

4640
/// <summary>
4741
/// Gets the time the entity was last modified.
@@ -64,9 +58,9 @@ public EntityMetadata(EntityInstanceId id, TState? state)
6458
/// <remarks>
6559
/// Queries can exclude the state of the entity from the metadata that is retrieved.
6660
/// </remarks>
67-
[MemberNotNullWhen(true, "State")]
68-
[MemberNotNullWhen(true, "state")]
69-
public bool IncludesState { get; }
61+
[MemberNotNullWhen(true, nameof(State))]
62+
[MemberNotNullWhen(true, nameof(state))]
63+
public bool IncludesState { get; } = false;
7064

7165
/// <summary>
7266
/// Gets the state for this entity.
@@ -96,16 +90,13 @@ public TState State
9690
/// <summary>
9791
/// Represents the metadata for a durable entity instance.
9892
/// </summary>
93+
/// <remarks>
94+
/// Initializes a new instance of the <see cref="EntityMetadata"/> class.
95+
/// </remarks>
96+
/// <param name="id">The ID of the entity.</param>
97+
/// <param name="state">The state of this entity.</param>
9998
[JsonConverter(typeof(EntityMetadataConverter))]
100-
public sealed class EntityMetadata : EntityMetadata<SerializedData>
99+
public sealed class EntityMetadata(EntityInstanceId id, SerializedData? state = null)
100+
: EntityMetadata<SerializedData>(id, state)
101101
{
102-
/// <summary>
103-
/// Initializes a new instance of the <see cref="EntityMetadata"/> class.
104-
/// </summary>
105-
/// <param name="id">The ID of the entity.</param>
106-
/// <param name="state">The state of this entity.</param>
107-
public EntityMetadata(EntityInstanceId id, SerializedData? state = null)
108-
: base(id, state)
109-
{
110-
}
111102
}

src/Client/Core/SerializedData.cs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,22 @@ namespace Microsoft.DurableTask.Client;
88
/// <summary>
99
/// Gets a type representing serialized data.
1010
/// </summary>
11-
public sealed class SerializedData
11+
/// <remarks>
12+
/// Initializes a new instance of the <see cref="SerializedData"/> class.
13+
/// </remarks>
14+
/// <param name="data">The serialized data.</param>
15+
/// <param name="converter">The data converter.</param>
16+
public sealed class SerializedData(string data, DataConverter? converter = null)
1217
{
13-
/// <summary>
14-
/// Initializes a new instance of the <see cref="SerializedData"/> class.
15-
/// </summary>
16-
/// <param name="data">The serialized data.</param>
17-
/// <param name="converter">The data converter.</param>
18-
public SerializedData(string data, DataConverter? converter = null)
19-
{
20-
this.Value = Check.NotNull(data);
21-
this.Converter = converter ?? JsonDataConverter.Default;
22-
}
23-
2418
/// <summary>
2519
/// Gets the serialized value.
2620
/// </summary>
27-
public string Value { get; }
21+
public string Value { get; } = Check.NotNull(data);
2822

2923
/// <summary>
3024
/// Gets the data converter.
3125
/// </summary>
32-
public DataConverter Converter { get; }
26+
public DataConverter Converter { get; } = converter ?? JsonDataConverter.Default;
3327

3428
/// <summary>
3529
/// Deserializes the data into <typeparamref name="T"/>.

src/Client/OrchestrationServiceClientShim/Client.OrchestrationServiceClientShim.csproj

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@ The client is responsible for interacting with orchestrations from outside the w
88
<EnableStyleCop>true</EnableStyleCop>
99
</PropertyGroup>
1010

11-
<PropertyGroup>
12-
<!-- We are still working on this package for entities preview. -->
13-
<VersionPrefix>1.0.5</VersionPrefix>
14-
<VersionSuffix></VersionSuffix> <!-- Need this here to set it back to empty. -->
15-
</PropertyGroup>
16-
17-
1811
<ItemGroup>
1912
<ProjectReference Include="../Core/Client.csproj" />
2013
</ItemGroup>

src/Client/OrchestrationServiceClientShim/DependencyInjection/DurableTaskClientBuilderExtensions.cs

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
// Licensed under the MIT License.
33

44
using DurableTask.Core;
5+
using DurableTask.Core.Entities;
56
using Microsoft.DurableTask.Client.OrchestrationServiceClientShim;
67
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.DependencyInjection.Extensions;
9+
using Microsoft.Extensions.Options;
710

811
namespace Microsoft.DurableTask.Client;
912

@@ -54,21 +57,74 @@ public static IDurableTaskClientBuilder UseOrchestrationService(
5457
{
5558
Check.NotNull(builder);
5659
Check.NotNull(configure);
57-
builder.Services.Configure(builder.Name, configure);
58-
builder.Services.AddOptions<ShimDurableTaskClientOptions>(builder.Name)
59-
.PostConfigure<IServiceProvider>((opt, sp) =>
60+
builder.Services.AddOptions<ShimDurableTaskClientOptions>(builder.Name).Configure(configure);
61+
builder.Services.TryAddSingleton<IPostConfigureOptions<ShimDurableTaskClientOptions>, OptionsConfigure>();
62+
builder.Services.TryAddSingleton<IValidateOptions<ShimDurableTaskClientOptions>, OptionsValidator>();
63+
return builder.UseBuildTarget<ShimDurableTaskClient, ShimDurableTaskClientOptions>();
64+
}
65+
66+
static IEntityOrchestrationService? GetEntityService(
67+
IServiceProvider services, ShimDurableTaskClientOptions options)
68+
{
69+
return options.Client as IEntityOrchestrationService
70+
?? services.GetService<IEntityOrchestrationService>()
71+
?? services.GetService<IOrchestrationServiceClient>() as IEntityOrchestrationService
72+
?? services.GetService<IOrchestrationService>() as IEntityOrchestrationService;
73+
}
74+
75+
class OptionsConfigure(IServiceProvider services) : IPostConfigureOptions<ShimDurableTaskClientOptions>
76+
{
77+
public void PostConfigure(string name, ShimDurableTaskClientOptions options)
78+
{
79+
ConfigureClient(services, options);
80+
ConfigureEntities(name, services, options); // Must be called after ConfigureClient.
81+
}
82+
83+
static void ConfigureClient(IServiceProvider services, ShimDurableTaskClientOptions options)
84+
{
85+
if (options.Client is not null)
6086
{
61-
if (opt.Client is not null)
62-
{
63-
return;
64-
}
87+
return;
88+
}
6589

66-
// Try to resolve client from service container.
67-
opt.Client = sp.GetService<IOrchestrationServiceClient>()
68-
?? sp.GetService<IOrchestrationService>() as IOrchestrationServiceClient;
69-
})
70-
.Validate(x => x.Client is not null, "ShimDurableTaskClientOptions.Client must not be null.");
90+
// Try to resolve client from service container.
91+
options.Client = services.GetService<IOrchestrationServiceClient>()
92+
?? services.GetService<IOrchestrationService>() as IOrchestrationServiceClient;
93+
}
7194

72-
return builder.UseBuildTarget<ShimDurableTaskClient, ShimDurableTaskClientOptions>();
95+
static void ConfigureEntities(string name, IServiceProvider services, ShimDurableTaskClientOptions options)
96+
{
97+
if (options.Entities.Queries is null)
98+
{
99+
options.Entities.Queries = services.GetService<EntityBackendQueries>()
100+
?? GetEntityService(services, options)?.EntityBackendQueries;
101+
}
102+
103+
if (options.Entities.MaxSignalDelayTime is null)
104+
{
105+
EntityBackendProperties? properties = services.GetService<IOptionsMonitor<EntityBackendProperties>>()?.Get(name)
106+
?? GetEntityService(services, options)?.EntityBackendProperties;
107+
options.Entities.MaxSignalDelayTime = properties?.MaximumSignalDelayTime;
108+
}
109+
}
110+
}
111+
112+
class OptionsValidator : IValidateOptions<ShimDurableTaskClientOptions>
113+
{
114+
public ValidateOptionsResult Validate(string name, ShimDurableTaskClientOptions options)
115+
{
116+
if (options.Client is null)
117+
{
118+
return ValidateOptionsResult.Fail("ShimDurableTaskClientOptions.Client must not be null.");
119+
}
120+
121+
if (options.EnableEntitySupport && options.Entities.Queries is null)
122+
{
123+
return ValidateOptionsResult.Fail(
124+
"ShimDurableTaskClientOptions.Entities.Queries must not be null when entity support is enabled.");
125+
}
126+
127+
return ValidateOptionsResult.Success;
128+
}
73129
}
74130
}

0 commit comments

Comments
 (0)