What would be the downside of writing it such as:
`ExecuteActivityAsync<OneClickBuyWorkflow>(e => e.DoPurchaseAsync, etc...)`?
What would be the downside of writing it such as:
`ExecuteActivityAsync<OneClickBuyWorkflow>(e => e.DoPurchaseAsync, etc...)`?
'We solve this problem by allowing users to create instances of the class/interface without invoking anything on it. For classes this is done via FormatterServices.GetUninitializedObject and for interfaces this is done via Castle Dynamic Proxy. This lets us "reference" (hence the name Ref) methods on the objects without actually instantiating them with side effects. Method calls should never be made on these objects (and most wouldn't work anyways).'
Which sounds like a lot of heavy lifting. It seems something like
public class WorkflowBuilder<T> where T : class
{
// Somehow workflow gets the real instance
private T Instance;
public async Task<TResult> ExecuteActivityAsync<TResult>(Func<T, Task<TResult>> func) => await func(Instance);
public async Task<TResult> ExecuteActivityAsync<TResult>(Func<Task<TResult>> func) => await func();
public async Task ExecuteActivityAsync(Func<Task> func) => await func();
public async Task ExecuteActivityAsync(Func<T, Task> func) => await func(Instance);
}
public record Purchase(string ItemID, string UserID);
public class PurchaseActivities
{
public static WorkflowBuilder<PurchaseActivities> OneClickBuyWorkflow => new WorkflowBuilder<PurchaseActivities>();
public async Task DoPurchaseAsync(Purchase purchase)
{
await OneClickBuyWorkflow.ExecuteActivityAsync(e => e.DoPurchaseAsync(purchase));
}
public static async Task DoPurchaseAsyncStatic(Purchase purchase)
{
await OneClickBuyWorkflow.ExecuteActivityAsync(() => DoPurchaseAsyncStatic(purchase));
}
}
Would pretty much achieve the same thing.How is `e` created for the `e => e.DoPurchaseAsync(purchase)` lambda? You're going to have to do that lifting anyways to create an instance for `e` that isn't really a usable instance. Unless you use source generators which we plan on doing.
I think what you have there is a lot more heavy lifting. Also note that workflows and activities are unrelated to each other. Workflow can invoke any activities. The code you have is a bit confusing because `PurchaseActivities` should be completely unrelated to workflows.
But this should never happen, these is no reason to create an unusable instance. The real instance should be resolved in the same way however the current workflow resolves it.
You can jump through a bunch of hoops like requiring interfaces which is what some frameworks do. But in our case, we just decided to make it easy to reference the method without invoking it or its instance.
You can parse an expression to serialize it and run it on a different server etc
See e.g. https://github.com/6bee/Remote.Linq
Of course the "Ref" pattern is user-choice/suggested-pattern, it's not a requirement in any of our calls that just take simple delegates however you can create those delegates. So I may be able to work it in there.
E.g. https://docs.hangfire.io/en/latest/background-methods/passin...
BackgroundJob.Enqueue<EmailSender>(x => x.Send(13, "Hello!"));