Quartz.NET – remote scheduler without job assembly reference on the client side

I have seen few tutorials showing how to schedule Quartz.NET jobs on the remote process using scheduler proxy and all of them suffered th e same inconvenience – they assumed that jobs assembly is added as a reference to the both client and a server projects. In my opinion this is a serious architecture smell because jobs are most certainly part of your business logic (in the scenarios I can think of) and it can cause problems with deployment (imagine one server running Quartz.NET scheduler and few or dozens of clients) and could cause some security issues (disassembling etc.). I really think that jobs should be placed in the business logic, not visible to clients which should be able to schedule them using some kind of contract / interface.

Here is how to achieve this.

Job contract – referenced by client and server side. Exposes only information required to identify a job and prepare its parameters:

//contract for the job - simple class with constant unique job name and some helper method
//used to create parameters for the job instance
public class SomeJobContract
{
	public const string JobName = "SomeApi.SomeJobUniqueName";

	public const string SomeParameterName = "BusinessObjectId";

	public static IDictionary<string, object> BuildJobDetails(int businessObjectId)
	{
		return new Dictionary<string, object>()
		{
			{ SomeParameterName, businessObjectId }
		};
	}
}

Job implementation – its type can not be resolved on the client side because there is no reference to the implementation assembly:

//implementation of the job which contract is exposed
public class SomeJob : IJob
{
	public void Execute(IJobExecutionContext context)
	{
		//use data passed with trigger - context.Trigger instead of context.JobDetails.JobDataMap
		var businessObjectId = (int?)context.Trigger.JobDataMap[SomeApi.SomeJobContract.SomeParameterName];
		
		//... regular job code
	}
}

Server code used to register a specific job (using its type) with a unique identifier exposed in contract and hence known to the client:

//create job details - use unique job identifier
var preProcessingJob = JobBuilder.Create<SomeJob>()
                .StoreDurably(true)
                .RequestRecovery(true)
                .WithIdentity(SomeApi.SomeJobContract.JobName)
                .Build();

//add a durable job to scheduler without using a trigger
scheduler.AddJob(preProcessingJob, true, true);

Client side code used to schedule the job with appropriate parameters – uses only information exposed by the contract assembly:

//create a trigger for the job with specified identifier (we use contract, we have no reference to job implementation on the client side)
var trigger = TriggerBuilder
                .Create()
                .ForJob(SomeApi.SomeJobContract.JobName)
                .UsingJobData(new JobDataMap(SomeApi.SomeJobContract.BuildJobDetails(myBusinessObjectId)))
                .WithSimpleSchedule().StartNow()
                .Build();

schedulerProxy.ScheduleJob(trigger);

Fluent API – coding monotony remedy?

Do you find yourself repeating the same code fragments in your application layer over and over again? Get request, authorize user, validate request , maybe push to audit log, perform some operations, catch exceptions, log them, then commit/rollback transaction – all of these repeated in 90% of your application layer methods? Anyway, it is a case for me. Repeating such things is basically waste of time, money, energy and it is error/mistake prone, especially if performed by many developers (Copy-Paste-Refactor). One might use template pattern to avoid repetition. By using it we could pass delegate/action/lambda to a template method and everything would be better. Howevr it is not that simple – not every business logic operation needs to be a sequence of all the actions mentioned above. No problem! All we need to do is to override the template method. But there is a risk that our template will end up like this overloaded fellow (DAO template of Spring framework which kicks ass IMO [Spring – not template]):

namespace Spring.Data.Core
{
    public class AdoTemplate : AdoAccessor, IAdoOperations, ICommonAdoOperations
    {
       	//(...)

	public virtual int ExecuteNonQuery(CommandType cmdType, string cmdText);
        public virtual int ExecuteNonQuery(CommandType cmdType, string cmdText, ICommandSetter commandSetter);
        public virtual int ExecuteNonQuery(CommandType cmdType, string cmdText, IDbParameters parameters);
        public virtual int ExecuteNonQuery(CommandType cmdType, string cmdText, string parameterName, Enum dbType, int size, object parameterValue);
        public virtual IDictionary ExecuteScalar(IDbCommandCreator commandCreator);
        public virtual object ExecuteScalar(CommandType cmdType, string cmdText);
        public virtual object ExecuteScalar(CommandType cmdType, string cmdText, ICommandSetter commandSetter);
        public virtual object ExecuteScalar(CommandType cmdType, string cmdText, IDbParameters parameters);
        public virtual object ExecuteScalar(CommandType cmdType, string cmdText, string parameterName, Enum dbType, int size, object parameterValue);
        protected virtual void InitExceptionTranslator();
        public virtual object QueryForObject(CommandType cmdType, string cmdText, IRowMapper rowMapper);
        public virtual object QueryForObject(CommandType cmdType, string cmdText, IRowMapper rowMapper, ICommandSetter commandSetter);
        public virtual object QueryForObject(CommandType cmdType, string cmdText, IRowMapper rowMapper, IDbParameters parameters);
        public virtual object QueryForObject(CommandType cmdType, string cmdText, IRowMapper rowMapper, string parameterName, Enum dbType, int size, object parameterValue);
        public virtual object QueryForObjectDelegate(CommandType cmdType, string cmdText, RowMapperDelegate rowMapperDelegate);
        public virtual object QueryForObjectDelegate(CommandType cmdType, string cmdText, RowMapperDelegate rowMapperDelegate, ICommandSetter commandSetter);
        public virtual object QueryForObjectDelegate(CommandType cmdType, string cmdText, RowMapperDelegate rowMapperDelegate, IDbParameters 
        public virtual object QueryForObjectDelegate(CommandType cmdType, string cmdText, RowMapperDelegate rowMapperDelegate, string parameterName, Enum dbType, int size, object parameterValue);
        public IDictionary QueryWithCommandCreator(IDbCommandCreator cc, IList namedResultSetProcessors);
        public virtual object QueryWithCommandCreator(IDbCommandCreator cc, IResultSetExtractor rse);
        public virtual void QueryWithCommandCreator(IDbCommandCreator cc, IRowCallback rowCallback);
        public virtual IList QueryWithCommandCreator(IDbCommandCreator cc, IRowMapper rowMapper);

	//(...)
    }
}

It is not a trivial task to find an override to what you are actually trying to achieve e. g. do validation, authorization but not audit logging. Taking all the above into consideration… how can we achieve extendable mechanism which could implement some default behavior which could be tuned to our needs by turning on some things and turning off the other things? I came up with using Fluent API to achieve this default behaviour.

Here is a sample code:

[Transaction]
public EditTaskDetailsResponse EditTaskDetails(EditTaskDetailsRequest request)
{
    var response = new EditTaskDetailsResponse();

    return HandleRequest(request, response)
        .ValidateUsing<EditTaskDetailsRequestValidator>()
        .Authorize(ModuleActionNames.Task_Edit, request.Id)
        .Do((req, resp) =>
        {
            var task = TasksRepository.LoadAndCoarseLock(req.Id);

            task.SetDeadline(req.Deadline);
            task.SetPriority(req.Priority);
            task.SetName(req.Name);
            task.SetDescription(req.Description);
        })
        .Execute();
}

Here is how your Fluent API implementation might look like:

namespace SomeProj.Application.Services
{
    public class ApplicationServiceRequest<TReq, TResp>
        where TReq : ServiceRequest
        where TResp : ServiceResponse
    {
        private Action<TReq, TResp> _serviceMethod;
        private TReq _request;
        private TResp _response;
        private IAuthorizationService _authorizationService;

        private List<AbstractValidator<TReq>> _validators;

        private List<KeyValuePair<string, object[]>> _authorizeActions;

        public ApplicationServiceRequest(TReq request, TResp response, IAuthorizationService authorizationService)
        {
            _authorizationService = authorizationService;
            _response = response;
            _request = request;
            _validators = new List<AbstractValidator<TReq>>();
            _authorizeActions = new List<KeyValuePair<string, object[]>>();
        }

        public ApplicationServiceRequest<TReq, TResp> ValidateUsing<TValidator>()
            where TValidator : AbstractValidator<TReq>, new()
        {
            _validators.Add(Activator.CreateInstance<TValidator>());
            return this;
        }

        public ApplicationServiceRequest<TReq, TResp> ValidateUsing<TValidator>(TValidator instance)
            where TValidator : AbstractValidator<TReq>
        {
            _validators.Add(instance);
            return this;
        }

        public ApplicationServiceRequest<TReq, TResp> Do(Action<TReq, TResp> serviceMethod)
        {
            _serviceMethod = serviceMethod;
            return this;
        }

        public ApplicationServiceRequest<TReq, TResp> Authorize(string actionName)
        {
            _authorizeActions.Add(new KeyValuePair<string, object[]>(actionName, null));
            return this;
        }

        public ApplicationServiceRequest<TReq, TResp> Authorize(string actionName, params object[] parameters)
        {
            _authorizeActions.Add(new KeyValuePair<string, object[]>(actionName, parameters));
            return this;
        }

        public TResp Execute()
        {
            try
            {
                foreach (var action in _authorizeActions)
                    _authorizationService.Authorize(action.Key, action.Value);

                foreach (var validator in _validators)
                    _response.AddValidationResult(validator.Validate(_request));

                if (_response.IsValid)
                    _serviceMethod(_request, _response);

                return _response;
            }
            catch (Exception ex)
            {
                //TODO: LOGGING
                throw;
            }
        }
    }

    public abstract class ApplicationServiceBase
    {
        public IAuthorizationService AuthorizationService { get; set; }

        public ApplicationServiceRequest<TReq, TResp> HandleRequest<TReq, TResp>(TReq request, TResp response)
            where TReq : ServiceRequest
            where TResp : ServiceResponse
        {
            return new ApplicationServiceRequest<TReq, TResp>(request, response, AuthorizationService);
        }
    }
}

I kinda like it. All my exceptions are guaranteed to be logged (of course when I’ll finally take my TODOs into consideration ;)), I can set any number of authorization and validation steps and extend this mechanism to achieve further requirements.

I do not think that everything with “Fluent” in its name is much sexier but I think that it is good to have one aditional solution of a potential problem in your toolkit.

P.S. One could use AOP to achieve similar extensibility but in my opinion it introduces to much indirection to your code base and I somehow dislike the idea of using AOP for business stuff like authorizing and validating – I find it better for solving infrastructure/cross cutting concerns (e.g. methods performance measuring).

I would like to know your thoughts on this.