dotnet-sdk icon indicating copy to clipboard operation
dotnet-sdk copied to clipboard

Improve type safety on Workflows and Activities

Open WhitWaldo opened this issue 1 year ago • 1 comments

Describe the feature

I'd like to explore an improvement to the .NET SDK that makes it easier to build out workflows and their activities in a way that ensures that the inputs and outputs align with expectations.

I can see two ways of going about this:

  1. In the SDK, add an overload that supports generic typing. If developers wish to invoke workflows or activities created in a different language, they're welcome to do so using the method already provided, but this overload would provide the developer some piece of mind that if a refactoring changes the expected input or output type of a workflow or activity, that the issue will be caught at development/compile time instead of runtime when the object fails to map properly.

  2. Offer a source generator that introduces a static method for each workflow and activity that's appropriately typed. For example, rather than having context.CallActivityAsync<int>(nameof(MyActivity), "test value"); a static extension method would be introduced that provides static Task<int> CallMyActivityAsync(string value, DaprActivityOptions? options = null);

Release Note

RELEASE NOTE: ADD Stronger type safety validation in workflows and workflow activities

WhitWaldo avatar Nov 01 '24 18:11 WhitWaldo

It's unfortunate that there's no way to refer to a generic type's generic types, so we can't generate something along the lines of TActivity.TOutput WorkflowContext.CallActivity<TActivity>(TActivity.TInput input, /* ... */) where TActivity: WorkflowActivity<TInput, TOutput>.

And, even if we could, we've already defined TOutput WorkflowContext.CallActivity<TOutput>(string name, /* ... */) so we can't create extension methods that add activity-specific overloads as the existing method will always be chosen by the compiler. I wish the original API was to specify both input/output types, which would have allowed for generating activity-specific overloads, though I understand why the approach was taken.

We could add TOutput WorkflowContext.CallActivity<TActivity, TInput, TOutput>(TInput input, /* ... */) where TActivity: WorkflowActivity<TInput, TOutput> which at least enforces typing but at the cost of having to always specify the input/output types which feels redundant and tedious.

As suggested, we could certainly generate differently named methods (e.g. CallMyActivity()), but I worry about how discoverable that might be as well as has refactoring concerns (e.g. change the type name of the activity and the generated method will be different, which will break existing usages).

An option could be to "deprecate" CallActivity() in favor of an equivalent (e.g. InvokeActivity) that has a definition that better supports both manual and generated cases.

philliphoff avatar Nov 02 '24 20:11 philliphoff