I've created following abstraction for scheduling jobs:
public interface IJobData
{ }
public interface IJob<in TJobData> where TJobData : IJobData
{
Task ExecuteAsync(TJobData jobData);
}
I use this in the application layer to create jobs. E.g.
public record ForgotPasswordJobData() : IJobData;
public class ForgotPasswordJob : IJob<ForgotPasswordJobData>
{
public Task ExecuteAsync(ForgotPasswordJobData jobData)
{
// Do some work
return Task.CompletedTask;
}
}
Now I want to decorate the ExecuteAsync method with
[AutomaticRetry(Attempts = 5)]
However I dont want to put it in the application layer, because this will create a dependency on the infrastructure layer. AutomaticRetry is a feature of the hangfire library, which sits in the infrastructure layer.
Is there a way to abstract [AutomaticRetry(Attempts = 5)]
in the application layer?
This comment got me on the right track:
I would go for JobFilter with passive attribute. See stackoverflow.com/a/57396553/1236044 In the ServerFilterProvider.GetFilters method, you should "just" need to search the job.Method.GetCustomAttributes for your own passive MyAutomaticRetry attribute. Depending if you find one or not, you would return a JobFilter holding a Hangfire.AutomaticRetryAttribute. Sorry miss time to set up a correct answer
Define custom attribute in application layer:
[AttributeUsage(AttributeTargets.Method)]
public class JobRetryAttribute : Attribute
{
public int Attempts;
public bool DeleteOnAttemptsExceeded;
public JobRetryAttribute(int Attempts, bool DeleteOnAttemptsExceeded)
{
this.Attempts = Attempts;
this.DeleteOnAttemptsExceeded = DeleteOnAttemptsExceeded;
}
}
Create hangfire job filter provider:
public class ServerFilterProvider : IJobFilterProvider
{
public IEnumerable<JobFilter> GetFilters(Job job)
{
var attribute = job.Method
.GetCustomAttributesData()
.Where(a => a.AttributeType == typeof(JobRetryAttribute))
.Select(a => new AutomaticRetryAttribute
{
Attempts = Convert.ToInt32(a.ConstructorArguments.First().Value),
OnAttemptsExceeded = Convert.ToBoolean(a.ConstructorArguments.Skip(1).First().Value) ? AttemptsExceededAction.Delete : AttemptsExceededAction.Fail
})
.First();
if (attribute != null)
{
return new JobFilter[] { new JobFilter(attribute, JobFilterScope.Method, null) };
}
return Enumerable.Empty<JobFilter>();
}
}
Use it:
JobFilterProviders.Providers.Add(new ServerFilterProvider());