Hi,
Currently in orchestrator function we need use syntax likecontext.CallActivityAsync<T>("activityName",parameter_object); to call activities. Sometimes we have activities with common domain, which are located in one class and essentially this class represent some service\manager.
I was really impressed when access through interface was provided by durable entities. It greatly improves type safety and code readability.
But there is no such option for classes that represent service\manager (without state and identity)
It would be great to add similar interface support for them. Syntax may look something like
var service=context.CreateServiceProxy<TInterface>("serviceName");
Thank you
Interesting idea. @cgillum and @sebastianburckhardt thoughts on the feasibility of this?
I would love to have something built in that moves away from the magic strings. The FunctionName, and the parameters if you are using the DurableActivityContext, hide errors.
I ended up building a extension class that has methods to call the activities for me, so that I can have strongly typed inputs and outputs, and don't have to pay attention to the magic strings.
public async Task<ResultModel> ActivityFunctionName(Guid FirstInput, int SecondInput)
{
return await context.CallActivityAsync<ResultModel>("ActivityFunctionName", (FirstInput, SecondInput));
}
@amadard I do this too. I hide a lot of stuff so that I don't the _pollute_ the business logic of the workflow in the orchestration.
Example
public static async Task DoSomething(this DurableOrchestrationContextBase context, State state, IAppInsightsLogger logger)
{
context.SetStatus($"Doing Something : {0}", state);
var retryOptions = new RetryOptions(TimeSpan.FromSeconds(5), 10);
retryOptions.BackoffCoefficient = 2;
retryOptions.MaxRetryInterval = TimeSpan.FromSeconds(60);
retryOptions.RetryTimeout = TimeSpan.FromMinutes(10);
context.RunWhenNotReplay(() => logger.TrackEvent("[2-0] [Starting] Do something"));
await context.CallActivityWithRetryAsync("do_something_activity", retryOptions, submission);
context.RunWhenNotReplay(() => logger.TrackEvent("[2-1] [Completed] Do something"));
}
I just had another thought about this that I wanted to throw out there. It could be a strongly typed IServiceCollection style interface. Typed AddHttpClient is what made me think of it. The primary benefit I see from this is having an explicit collection that we add our Activities and SubOrchestrators, and there can be a configuration delegate parameter to allow "pre-function" code like @olitomlinson described.
There is another idea that can address all issues mentioned above. It is inspired by MediatR project.
Functions can be resolved by type of argument(and return value) to avoid dealing with magic strings.
There are 3 types for functions
1 Queries(or mixed with commands)
SomeFunctionClass : IRequestHandler<TRequest, TResponse>
{
public Task<TResponse> HandleRequestAsync(TRequest request, CancellationToken)
}
2 Commands
SomeFunctionClass : IRequestHandler<TRequest>
{
public Task HandleRequestAsync(TRequest request, CancellationToken)
}
3 Notifications (fire and forget, also may be handled by several functions)
SomeFunctionClass : INotificationHandler<TNotification>
{
public Task HandleNotificationAsync(TNotification notification, CancellationToken)
}
this also solves context interface issue
Query:
var response=await context.SendRequestAsync<TRequest,TResponse>(TRequest) //stateless function
var response=await context.SendEntityRequestAsync<TRequest,TResponse>(TRequest, EntityKey) //statefull
Command:
await context.SendRequestAsync<TRequest>(TRequest) //stateless
await context.SendEntityRequestAsync<TRequest>(TRequest, EntityKey) //statefull
Notification:
await context.SendNotificationAsync<TNotification>(TNotification) //stateless
await context.SendEntityNotificationAsync<TNotification>(TNotification, EntityKey) //statefull
IDurableOrchestrationContext, in which you wouldn't need to pass in a type for GetInput.
Most helpful comment
I would love to have something built in that moves away from the magic strings. The FunctionName, and the parameters if you are using the DurableActivityContext, hide errors.
I ended up building a extension class that has methods to call the activities for me, so that I can have strongly typed inputs and outputs, and don't have to pay attention to the magic strings.
public async Task<ResultModel> ActivityFunctionName(Guid FirstInput, int SecondInput) { return await context.CallActivityAsync<ResultModel>("ActivityFunctionName", (FirstInput, SecondInput)); }