-
Notifications
You must be signed in to change notification settings - Fork 12
Teach JobOptionBuilder to only accept a parameter #241
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
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
🚀 New features to boost your workflow:
|
[Fact] | ||
public void ShouldCreateMultipleParameterizedJobsWithAnd() | ||
{ | ||
var builder = new JobOptionBuilder(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand the problems behind WithParameter().WithParameter
- but when would a user anyway use WithParameter().And.WithParameter
?
How would he trigger each of them without a cron expression nor a name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right!
Indeed, the main use case for this is the one described in the doco
It's also possible to configure a job with only a parameter and no cron expression.
Services.AddNCronJob(options =>
{
options.AddJob<MaintenanceJob>(j =>
{
j.WithParameter("lightMode");
});
});
This can later be triggered through the `IInstantJobRegistry` in a preconfigured
mode (See [_"Instant jobs"_](./instant-jobs.md)).
app.MapPost("/maintenance/light", (IInstantJobRegistry jobRegistry) =>
{
// "lightMode" will be passed as a parameter to the job
jobRegistry.RunInstantJob<MaintenanceJob>();
return Results.Ok();
});
Of course, the preconfigured parameter can also be overriden when triggering the job.
app.MapPost("/maintenance/thorough", (IInstantJobRegistry jobRegistry) =>
{
jobRegistry.RunInstantJob<MaintenanceJob>("thoroughMode");
return Results.Ok();
});
Indeed, the And.
brings up some weird possible usages.
But I don't think that should be solved at the Fluent approach level, as It's also possible to trigger this behavior without the And
.
builder
.WithParameter("foo");
builder
.WithParameter("bar");
Let me see if I can protect the user from this interesting weird case....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically we'd need these tests to pass
[Fact]
public void CanRegisterAJobWithAPreconfiguredParameter()
{
Action act = () =>
ServiceCollection.AddNCronJob(n => n.AddJob<DummyJob>(p => p.WithParameter("one")));
act.ShouldNotThrow();
}
[Fact]
public void RegisteringAmbiguousJobConfigurationsLeadToAnExceptionWhenRegistration()
{
Action act = () =>
ServiceCollection.AddNCronJob(
n => n.AddJob<DummyJob>(p => p
.WithParameter("one")
.And
.WithParameter("two")));
act.ShouldThrow<InvalidOperationException>();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great news! The first one already passes. That's 50% of the problem solved! 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gave this a little bit of thought and added some more tests to better describe the behavioral contract.
Found one where the And.
doesn't seem too "stupid"
[Fact]
public void CanRegisterAJobWithMultipleDifferentlyParameterizedJobsAsStartupJobs()
{
Action act = () =>
ServiceCollection.AddNCronJob(
n => n.AddJob<DummyJob>(p => p
.WithParameter("one")
.And
.WithParameter("two")).RunAtStartup());
act.ShouldNotThrow();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lol :D I am confused what I should expect here. Do we now have two startup jobs?
Yep. That's how And
and RunAtStartup
combine together.
FWIW, I'm not a big fan of RunAtStartup()
, I'd rather see it as part of the JobOptionBuilder
type
eg.
n => n.AddJob<DummyJob>(p => p
.WithParameter("one")
.RunAtStartup()
.And
.WithParameter("two")
.RunAtStartup());
Proposal: Breaking change for V5?
I am really not sure if this is too implicit.
So, that we configure a job and trigger an instant job. How could the user override it? What if the user wants an explicit >null? How can we distinguish this behavior?
We currently can't because of how IJobExecutionContext
is designed.
public interface IJobExecutionContext
{
[...]
/// <summary>The passed in parameters to a job.</summary>
object? Parameter { get; }
[...]
}
Would this interface be turned into something like this
public interface IJobExecutionContext
{
[...]
/// <summary>The passed in parameters to a job.</summary>
Parameter? AnotherNameThanParameterToEaseTheUserMigrationFromV4ToV5 { get; }
[...]
}
public record Parameter(object? Value) { }
It would be possible for jobs to detect if a parameter has been explicitly passed.
Proposal: Breaking change for V5?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't look very intuitive (looks clunky)
n => n.AddJob<DummyJob>(p => p
.WithParameter("one")
.RunAtStartup()
.And
.WithParameter("two")
.RunAtStartup());
I mean if we're introducing breaking changes anyways then just make it clear, like:
n => n.AddStartupJob<DummyJob>(p => p
.WithParameters("one", "two",...));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or we can have a separate "triggers" mechanic (like quartz, hangfire, temporal, elsa etc.) and designate the specific job to trigger on application "startup". A separate "triggers" mechanic would also allow future functionality necessary to trigger jobs based on external events like Webhooks etc. This is a separate and much bigger conversation if you guys are interested I have ideas on how this can be achieved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean if we're introducing breaking changes anyways then just make it clear, like:
That wouldn't be a breaking change from an API point of view :D
But before adding a new API, it might be worth taking a step back and having an idea of what people really need and have a "better" version for v5
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential move of RunAtStartup()
into a JobOption is tracked through #250
48f3448
to
a8d502e
Compare
f136a87
to
78c520c
Compare
115d25b
to
4402ea0
Compare
s => s.AddJob<DummyJob>(p => p | ||
.WithParameter("one")) | ||
}, | ||
// TODO: Re-implement this test in a UseNCronJob context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@linkdotnet There might be a way to work around this, but it would trigger a some rewrite of the fluent interface. Basically, not storing the jobdefinitions during the AddJob() call, but collect them, have RunAtStartup() execute against those and then add them to the jobregistry.
Not impossible, but not trivial either.
I'd rather postpone until V5. Besides, with #219 we'll have to revisit this whole fluent thingie
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically as discussed in #221
If we have the "new" semantics we can do everything inside AddNCronJob
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not impossible, but not trivial either.
Done with #255
4402ea0
to
3074ce4
Compare
@@ -43,7 +43,41 @@ public class ReportJob : IJob | |||
} | |||
``` | |||
|
|||
It's also possible to configure a job with only a parameter and no cron expression. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am really not sure if this is too implicit.
So, that we configure a job and trigger an instant job. How could the user override it? What if the user wants an explicit null
? How can we distinguish this behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Configuring a Job with a parameter and allowing its override at runtime is already supported.
See
NCronJob/tests/NCronJob.Tests/IntegrationTests.cs
Lines 109 to 125 in ed0f4ce
[Fact] | |
public async Task InstantJobCanOverrideInitiallyDefinedParameter() | |
{ | |
ServiceCollection.AddNCronJob( | |
n => n.AddJob<DummyJob>(o => o.WithCronExpression(Cron.Never).WithParameter("Hello from AddNCronJob"))); | |
await StartNCronJob(startMonitoringEvents: true); | |
var orchestrationId = ServiceProvider.GetRequiredService<IInstantJobRegistry>().RunInstantJob<DummyJob>("Hello from InstantJob", CancellationToken); | |
await WaitForOrchestrationCompletion(orchestrationId, stopMonitoringEvents: true); | |
var filteredOrchestrationEvents = Events.FilterByOrchestrationId(orchestrationId); | |
filteredOrchestrationEvents.ShouldBeInstantThenCompleted<DummyJob>(); | |
Storage.Entries[0].ShouldBe("DummyJob - Parameter: Hello from InstantJob"); | |
Storage.Entries.Count.ShouldBe(1); |
However, handling null
explicitly as a parameter value looks like a new requirement.
I have no idea how to weigh how useful that could be (I've haven't designed any job in such a way).
If you believe this should be handled, let's create an issue and try and make it happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
app.MapPost("/maintenance/light", (IInstantJobRegistry jobRegistry) => | ||
{ | ||
// "lightMode" will be passed as a parameter to the job | ||
jobRegistry.RunInstantJob<MaintenanceJob>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we don't have a way of having "nothing" as parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}, | ||
{ | ||
// Pure duplicate registration | ||
s => s.AddJob<DummyJob>(p => p |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With #255 merged, this test now pass. As such, I've resurrected this PR.
@linkdotnet Thoughts? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small change for the boolean "tree" :D will approve, but that change would be nice.
@nulltoken much appreciate your work and I am really sorry that I currently can't spare much time and you have to wait very long for a PR review.
I am a bit "overwhelmed" with stuff (more in a positive way). Currently, there is a lot of stuff on my table and I don't have too much time and energy left to take that hour and go through stuff. I know that is by far not the answer you are looking for, but I just wanted to be absolutely transparent.
Unfortunately, sometimes Open Source has to take a back seat in priorities and I hope that I can find some more time in the near future. There are some stuffs upcoming like conferences and so on - afterwards it should look a bit better.
Long story short: Thanks @nulltoken for offering all your valuable time to the project! Really appreaciate that and if there is something on how I can compensate you, let me know.
@@ -155,6 +156,29 @@ Job registration conflict detected. A job has already been registered with the n | |||
"""); | |||
} | |||
|
|||
private void AssertOnlyOneUnnamedUnscheduledParameterizedTypedJob(JobDefinition jobDefinition) | |||
{ | |||
if (jobDefinition.CustomName is not null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we put this onto the JobDefintion type
? That would be then also super trivial to unit test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will. But this will be one hell of a property: IsUnnamedOrUnscheduledOrParameterlessTypedJob
😉
Pull request description
Follow up to #215 and a minor step toward #217
PR meta checklist
main
branch for codeCode PR specific checklist