2

In my own variation of the command/query (CQRS) pattern, I define the following for a query handler:

public interface IQueryHandler<in TParameter, TResult> where TParameter : IQuery<TResult>
{
    TResult Execute(TParameter query);
}

public interface IAsyncQueryHandler<in TParameter, TResult> where TParameter : IAsyncQuery<TResult>
{
    Task<TResult> ExecuteAsync(TParameter query);
}

public interface IQueryRebuilder<TResult> where TResult : class
{
    void Rebuild(TResult queryResult);
}

public interface IAsyncQueryRebuilder<TResult> where TResult : class
{
    Task RebuildAsync(TResult queryResult);
}

and I define a query dispatcher as follows:

public interface IQueryDispatcher
{
    Task<TResult> DispatchAsync<TResult>(IAsyncQuery<TResult> query);
    TResult Dispatch<TResult>(IQuery<TResult> query);

    Task RebuildAsync<TResult>(TResult queryResult) where TResult : class;
    void Rebuild<TResult>(TResult queryResult) where TResult : class;
}

The intent behind IQueryRebuilder and the corresponding rebuild methods in the dispatcher is to handle the situation where an MVC view model has a collection of items for building the options in a select element. Since those items aren't included in the post to the server, they need rebuilt in the model when the view is returned after post because of an error. For example, a query handler could be defined as follows:

public class CategoryEditQuery : IAsyncQuery<CategoryEditModel>
{
    public int? CategoryID
    {
        get;
        set;
    }
}

public class CategoryEditQueryHandler : IAsyncQueryHandler<CategoryEditQuery, CategoryEditModel>, IAsyncQueryRebuilder<CategoryEditModel>
{
    private IStorageContext<Category> _storageCategories;
    private IStorageContext<CategoryType> _storageCategoryTypes;

    public CategoryEditQueryHandler(IStorageContext<Category> storageCategories, IStorageContext<CategoryType> storageCategoryTypes)
    {
        this._storageCategories = storageCategories;
        this._storageCategoryTypes = storageCategoryTypes;
    }

    public async Task<CategoryEditModel> ExecuteAsync(CategoryEditQuery query)
    {
        CategoryEditModel model = new CategoryEditModel();

        Category category;
        if (query.CategoryID != null)
        {
            category = await _storageCategories.Entities
                .AsReadOnly()
                .FindByIDAsync(query.CategoryID.Value);
        }
        else
        {
            category = await _storageCategories.Entities
                .AsReadOnly()
                .FirstOrDefaultAsync();
        }

        if (category != null)
        {
            model.CategoryID = category.ID;
            model.Category = new CategoryModel()
            {
                ID = category.ID,
                Name = category.Name,
                OriginalName = category.Name,
                CategoryTypeID = category.CategoryTypeID
            };
        }

        await RebuildAsync(model);

        return model;
    }

    public async Task RebuildAsync(CategoryEditModel model)
    {
        model.Categories = await _storageCategories.Entities
            .AsReadOnly()
            .SelectToListAsync(x => new SelectOptionModel()
            {
                Value = x.ID,
                Text = x.Name,
                IsSelected = (x.ID == model.CategoryID)
            });

        if (model.Category != null)
        {
            model.Category.CategoryTypeItems = await _storageCategoryTypes.Entities
                .AsReadOnly()
                .SelectToListAsync(x => new SelectListItem()
                {
                    Value = x.ID.ToString(),
                    Text = x.Name
                });
        }
    }
}

And the corresponding MVC controller action would look like the following:

    public async Task<ActionResult> Categories(CategoryEditQuery query)
    {
        CategoryEditModel model = await QueryDispatcher.DispatchAsync(query);
        return PartialView(model);
    }

    [HttpPost]
    public async Task<ActionResult> Categories(CategoryEditModel model)
    {
        if (ModelState.IsValid)
        {
            CommandResult commandResult = await CommandDispatcher.DispatchAsync(model);
            return new HttpStatusCodeResult(HttpStatusCode.NoContent);
        }

        await QueryDispatcher.RebuildAsync(model);

        return PartialView(model);
    }

I've received some feedback from a coworker that having the query dispatcher implement both the dispatch and rebuild methods is a violation of the Single Responsibility Principle and that the IQueryDispatcher needs split into two interfaces - one for IQueryDispatcher and one for IQueryRebuilderDispatcher. At least part of the context for his feedback is when this is used for Web Api where rebuilding a view model doesn't apply, having the extra methods on the dispatcher that can't and won't be used could be a problem.

So my question is does having the query dispatcher handle both dispatching a query and rebuilding a query result violate the Single Responsibility Principle, and should the dispatcher interface be split into one interface for dispatching and one for rebuilding?

migrated from codereview.stackexchange.com Dec 17 '16 at 15:39

This question came from our site for peer programmer code reviews.

0

I've received some feedback from a coworker that having the query dispatcher implement both the dispatch and rebuild methods is a violation of the Single Responsibility Principle...

Your coworker is probably wrong.

Think about CRUD for a moment. Does your coworker really think you need four classes to implement CRUD, one for each letter in the acronym?

That's essentially what your coworker's philosophy amounts to: one method per interface. That's not what SRP means; were that the case; we could dispense with interfaces altogether and just use first-class functions (not necessarily a bad strategy, by the way).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.