Il dispacciamento di una query e la ricostruzione di un risultato di una query nella stessa classe sono una violazione del principio di responsabilità singola?

2

Nella mia variante del modello command / query (CQRS), definisco quanto segue per un gestore di query:

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);
}

e definisco un dispatcher di query come segue:

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;
}

L'intento dietro IQueryRebuilder e i metodi di ricostruzione corrispondenti nel dispatcher è di gestire la situazione in cui un modello di visualizzazione MVC ha una raccolta di elementi per la costruzione delle opzioni in un elemento select. Dal momento che tali elementi non sono inclusi nel post nel server, devono essere ricostruiti nel modello quando la vista viene restituita dopo la pubblicazione a causa di un errore. Ad esempio, un gestore di query può essere definito come segue:

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
                });
        }
    }
}

E l'azione corrispondente del controller MVC sarà simile alla seguente:

    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);
    }

Ho ricevuto un riscontro da un collega che se il dispatcher delle query implementa entrambi i metodi di invio e ricostruzione è una violazione del principio di responsabilità singola e che IQueryDispatcher deve essere diviso in due interfacce: una per IQueryDispatcher e una per IQueryRebuilderDispatcher. Almeno una parte del contesto per il suo feedback è quando questo viene usato per Web Api dove la ricostruzione di un modello di vista non si applica, avendo i metodi aggiuntivi sul dispatcher che non possono essere usati e non saranno usati potrebbe essere un problema. / p>

Quindi la mia domanda è se il dispatcher della query gestisce sia la distribuzione di una query sia la ricostruzione di un risultato della query violano il Principio di Responsabilità Unica e l'interfaccia del dispatcher deve essere divisa in un'unica interfaccia per la distribuzione e una per la ricostruzione?

    
posta TheOtherTimDuncan 17.12.2016 - 15:00
fonte

1 risposta

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...

Il tuo collega probabilmente ha torto.

Pensa a CRUD per un momento. Il tuo collega pensa davvero che hai bisogno di quattro classi per implementare CRUD, uno per ogni lettera nell'acronimo?

Questo è essenzialmente ciò che la filosofia del tuo collaboratore equivale a: un metodo per interfaccia. Questo non è ciò che significa SRP; era così? potremmo fare a meno delle interfacce e utilizzare solo funzioni di prima classe (non necessariamente una strategia sbagliata, tra l'altro).

    
risposta data 17.12.2016 - 23:53
fonte

Leggi altre domande sui tag