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.

Advertisements

5 Responses to Fluent API – coding monotony remedy?

  1. Pingback: dotnetomaniak.pl

  2. Paweł K says:

    I use similiar approach and find it very powerful. The key in this whole thing is to have a central place that defines a scheme for business logic execution, like your ApplicationServiceRequest.Execute method. Being fluent or not is more like a matter of taste.

    Personally, I don’t like to repeat myself anyhow, so I push even validation&authorization into infrastructure layer (that allows per case customization, when it’s needed).

  3. Robert says:

    I like your code. Do you have a full blown example?
    Maybe I could the missing peaces figure out myself, but it would be easier 😉

    • karczas says:

      I’m glad to hear your opinion.
      The only missing pieces are IMO validation and authorization logic which are used when “ValidateUsing” and “Authorize” fluent methods are called.
      These pieces are infrastructure specific – I use my custom authorization service.
      When you call “ValidateUsing” method in code I presented, you need to use FluentValidation (http://fluentvalidation.codeplex.com) based types.

      In my post I wanted to show potential approach without messing it up with lots of infrastructure dependencies.
      I hope that you will find my explanations helpful.

      Best regards,
      mkarczewski

      • Robert says:

        Thanks for the quick reply on this.
        I will bring it to work, thanks for the hint with the fluentvalidation, a library I wanted to use anyway.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: