-
Notifications
You must be signed in to change notification settings - Fork 302
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
Generic extensions to create orchestrations in a strongly-typed manner #714
base: main
Are you sure you want to change the base?
Conversation
Looks nice! Can you share some examples of what it will look like to use the |
Nearly the same, except for the fact that we need to include both input and result types in type parameters: |
Would you consider creating similar extensions for the other orchestration context APIs, like ScheduleTaskWithRetry and the sub-orchestration APIs? |
…lient functionality with generic implementations
Sure, done. |
@cgillum do you want anything else included into this PR? |
I am not against these extensions on their own, but I would recommend taking a look at the pattern MediatR uses. This is what we have adopted in our usage of DTFx. We defined some contracts which acts as both the input of the orchestration or activity and the description of what orchestration or activity is to be ran. In the end all the APIs get unified to a Some examples: public static class OrchestrationContextExtensions
{
public static Task<TResult> SendAsync(this OrchestrationContext context, IOrchestrationRequest<TResult> request)
{
return context.CreateSubOrchestrationInstance<TResult>(request.Descriptor.Name, request.Descriptor.Version, request);
}
public static Task<TResult> SendAsync(this OrchestrationContext context, IActivityRequest<TResult> request)
{
return context.ScheduleTask<TResult>(request.Descriptor.Name, request.Descriptor.Version, request);
}
}
public class MyOrchestration : IOrchestrationRequest<MyResult>
{
// this object is the input itself.
// This is part of the request contract. It will not be serialized and is used in the `SendAsync` implementation to get the Name and Version of the orcherstration to enqueue.
public TaskDescriptor Descriptor => new(typeof(Handler));
public class Handler : OrchestrationBase<MyOrchestration, MyResult>
{
public override async Task<MyResult> RunAsync(MyOrchestration input)
{
// Context is now a property. Invoke any sub orchestration or activity via 'SendAsync()'
MyActivityResult activityResult = await Context.SendAsync(new MyActivity(/* whatever input this needs */));
MyOtherOrchestrationResult orchestrationResult = await Context.SendAsync(new MyOtherOrchestration(/* ctor params */));
return new MyResult();
}
}
}
public class MyActivity : IActivityRequest<MyActivityResult>
{
// this object is the input itself.
// This is part of the request contract. It will not be serialized and is used in the `SendAsync` implementation to get the Name and Version of the activity to enqueue.
public TaskDescriptor Descriptor => new(typeof(Handler));
public class Handler : ActivityBase<MyActivity, MyActivityResult>
{
public override Task<MyActivityResult> RunAsync(MyActivity input)
{
return Task.FromResult(new MyActivityResult());
}
}
}
// also TaskHubClient works with this as well:
OrchestrationInstance instance = await client.StartOrchestrationAsync(new MyOrchestration(/* ctor params */)); This pattern works very well for us for a few reasons:
|
Adding some extensions to allow creation of Orchestrations/Activities in a type-safe manner.
Basically, a bit of modernizing API without breaking the backwards compatibility + checking in compile time that the parameter passed matches the orchestration/activity contracts.
Samples below based on Tests in project.
Before #1
OrchestrationInstance id = await this.client.CreateOrchestrationInstanceAsync(typeof(AsyncGreetingsOrchestration), null);
After #1
OrchestrationInstance id = await this.client.CreateOrchestrationInstanceAsync<AsyncGreetingsOrchestration>(null);
Before #2 (OK)
OrchestrationInstance id = await this.client.CreateOrchestrationInstanceAsync(typeof (TypeMissingOrchestration), "test");
Before #2 (Fails in Runtime)
OrchestrationInstance id = await this.client.CreateOrchestrationInstanceAsync(typeof (TypeMissingOrchestration), 1);
After #2 (Fails to Compile)
OrchestrationInstance id = await this.client.CreateOrchestrationInstanceAsync<TypeMissingOrchestration>(1);