Simplified IFlow Method Signature
DX is an extremely important focus for Didact. Recently, a code-smell occurred to me for the IFlow interface in Didact Core. I scored a nice DX win by simplifying the ConfigureAsync method signature.
Hard at work
I realize yet again that it's been far too long since my last blogpost on here! My marketing efforts for Didact over the past year have been a little topsy-turvy.
Previously, I would highly monitor Reddit and market Didact there as well as some other places. But over these past many months, I've been focusing on getting Didact complete as fast as I can, so I've lessoned my marketing efforts to massively increase my development and engineering throughput. These days, I am making fairly frequent posts on Didact's socials (you can find them in the Footer), particularly in my efforts to do frequent build in public updates showing my gradual progress building the platform up to v1.
But it was time that I make a small blogpost on here again, too! I'll keep this one pretty short.
Overcomplicated method signature
I recently realized - to my embarassement, I will admit - that one of the oldest Types that I made in the Didact Core NuGet package, specifically the IFlow interface, was needlessly complicated. Specifically, I am referring to the ConfigureAsync method where you define a flow's metadata and orchestration behaviors.
The ConfigureAsync method has gone through a few changes here and there, but for the most part it's been largely the same design: a configurator is injected into the method parameters within an IFlowConfigurationContext object that you define metadata against using fluent API methods, and then you return that context containing that configurator in the method signature. Pretty straightforward stuff, you just take the configurator, modify it, and then return it.
public class SomeFlow : IFlow
{
public Task<IFlowConfigurationContext> ConfigureAsync(IFlowConfigurationContext context)
{
context.Configurator
.WithName(nameof(SomeFlow))
.WithCronSchedule("weekly schedule", "0 0 * * 0");
return Task.FromResult(context);
}
public async Task ExecuteAsync(IFlowExecutionContext context)
{
context.Logger.LogInformation("Simulating work...");
await Task.Delay(1000);
}
}What struck me as painfully obvious a few weeks ago was the unnecessity to return the configurator in the method signature. I finally asked myself the following question: do users really need to return that object from the method? Doesn't the calling code in Didact Engine have access to this object already?
Do it like Program.cs
The answer was - as I facepalmed - obviously yes! If the intention behind ConfigureAsync is simply to modify the method-injected configurator, there is simply no need for users to have to return that configurator. The caller can grab it on its own at the completion of the method's execution inside Didact Engine.
Even worse, having users return the configurator might allow them to instantiate a brand new configurator, devoid of all metadata and necessary dependency-injections from within Didact Engine. It was simply a footgun that didn't need to exist, and a code smell on my end as the architect of Didact.
One helpful thought experiment that helped me realized this was thinking on how modern Program.cs files are written when someone builds a .NET web API or a generic host for some kind of console app. Throughout my personal career, and also from reading many examples online, most modern Program.cs files are chains of fluent API calls against various objects or properties.
For example, you'll probably see something like
builder.Services.AddScoped<IMyService, MyService>();if someone wants to register a scoped service to the dependency injection container in a modern .NET app; property accessors and fluent API calls, nice and clean. I personally enjoy these sort of fluent API's in modern .NET versions: the method calls are legible and read like English sentences.
Configure and return
So, I'm pleased to say that the ConfigureAsync method has been simplified on the IFlow interface which, again, is the main type that you need to define your background jobs and workflows in Didact.
Now, the method simply returns a Task. I imagine most people don't need any actual async behavior within their ConfigureAsync definitions, so if that's your case, you can omit the async keyword on the method signature and simply end your method definiton with return Task.CompletedTask.
Here's the modified example below:
public class SomeFlow : IFlow
{
public Task ConfigureAsync(IFlowConfigurationContext context)
{
context.Configurator
.WithName(nameof(SomeFlow))
.WithCronSchedule("weekly schedule", "0 0 * * 0");
return Task.CompletedTask;
}
public async Task ExecuteAsync(IFlowExecutionContext context)
{
context.Logger.LogInformation("Simulating work...");
await Task.Delay(1000);
}
}Simple, clean, straightforward, just the way I want to design Didact's APIs for you!
Any day that I can score a win for Didact's DX is a very good day for me.
