Repository generici con DI e Data Intensive Controller

3

Di solito, considero un gran numero di parametri come un campanello d'allarme che potrebbe esserci un problema di progettazione da qualche parte. Sto usando un repository generico per un'applicazione ASP.NET e ho un controller con un numero crescente di parametri.

public class GenericRepository<T> : IRepository<T> where T : class
{
    protected DbContext Context { get; set; }
    protected DbSet<T> DbSet { get; set; }

    public GenericRepository(DbContext context)
    {
        Context = context;
        DbSet = context.Set<T>();
    }
    ...//methods excluded to keep the question readable

}

Sto usando un contenitore DI per passare DbContext al repository generico. Finora, questo ha soddisfatto i miei bisogni e non ci sono altre impiantazioni concrete di IRepository<T> . Tuttavia, ho dovuto creare un dashboard che utilizza i dati di molte Entità. C'era anche un modulo contenente un paio di elenchi a discesa. Ora, utilizzando il repository generico, i requisiti dei parametri aumentano rapidamente.

Il controller finirà per essere qualcosa di simile a

public HomeController(IRepository<EntityOne> entityOneRepository,
                      IRepository<EntityTwo> entityTwoRepository,
                      IRepository<EntityThree> entityThreeRepository,
                      IRepository<EntityFour> entityFourRepository,
                      ILogError logError,
                      ICurrentUser currentUser)
{
}

Ha circa 6 IRepositories più alcuni altri per includere i dati richiesti e le opzioni dell'elenco a discesa. Nella mia mente ci sono troppi parametri. Dal punto di vista delle prestazioni, c'è solo 1 DBContext per richiesta e il contenitore DI servirà lo stesso DbContext a tutti i repository. Da un punto di vista di standard / leggibilità del codice è brutto.

C'è un modo migliore per gestire questa situazione? È un progetto del mondo reale con vincoli temporali in tempo reale, quindi non mi dilungherò su di esso troppo a lungo, ma dal punto di vista dell'apprendimento sarebbe bello vedere come tali situazioni sono gestite da altri.

    
posta James 19.08.2014 - 11:38
fonte

2 risposte

3

Secondo me un controller dovrebbe essere solo uno strato sottile attorno al businesslogic.

Se il controller necessita di diversi repository che potrebbero essere un indicatore del fatto che il controller sta facendo troppo e la logica dovrebbe essere spostata sui server di servizio per la separazione dei problemi:

Invece di avere

OrderController(productRepository, priceRepository, 
                  availabilityRepository, orderRepository, orderLineRepository)

Avrei servizi che conoscono il proprio repository e sottoservizi che usano.

L'esempio precedente diventerebbe

priceCalculationService(productRepository, priceRepository)
orderService(orderRepository, orderLineRepository, 
          availabilityService, priceCalculationService)

OrderController(orderService)
    
risposta data 19.08.2014 - 12:41
fonte
2

Ho giocato con l'utilizzo di query e classi di comando separate invece di utilizzare repository con più metodi.

Il tuo UserRepository potrebbe avere un metodo GetByEmail(string email) . Invece, ho una classe GetUserByEmailQuery e una classe GetUserByEmailQueryHandler .

Il tuo UserRepository potrebbe avere un metodo UpdatePassword(string newPassword) . Invece, ho una classe UpdateUserPasswordCommand e una classe UpdateUserPasswordCommandHandler .

Tutto viene insieme nei miei controller attraverso un QueryAndCommandDispatcher .

Prima alcune interfacce:

public interface IQueryHandler<in TQueryData, out TResult> where TQueryData : class
{
    TResult Execute(TQueryData query);
}

public interface ICommandHandler<in T> where T : class
{
    void Handle(T command);
}

public interface IQueryAndCommandDispatcher
{
    void ExecuteCommand<T>(T command) where T : class;
    TResult ExecuteQuery<TQueryData, TResult>(TQueryData query) where TQueryData : class; 
}

Come puoi vedere, una query dovrebbe restituire qualcosa mentre un comando non lo è. Un comando dovrebbe cambiare i dati, una query non dovrebbe.

Ecco come appare quella query per ottenere un utente tramite la sua email ( IDataContext è un'interfaccia che descrive il mio DbContext personalizzato):

public class GetUserByEmailQuery
{
    public string Email { get; private set; }

    public GetUserByEmailQuery(string email)
    {
        Email = email;
    }
}

public class GetUserByEmailQueryHandler : IQueryHandler<GetUserByEmailQuery, User>
{
    private readonly IDataContext dataContext;

    public GetUserByEmailQueryHandler(IDataContext dataContext)
    {
        this.dataContext = dataContext;
    }

    public User Execute(GetUserByEmailQuery query)
    {
        return dataContext.Users.First(u => u.Email == query.Email);
    }
}

Ecco come appare il comando per aggiornare la password di un utente:

public class UpdateUserPasswordCommand
{
    public int UserId { get; private set; }
    public string Password { get; private set; }

    public UpdateUserPasswordCommand(int userId, string password)
    {
        UserId = userId;
        Password = password;
    }
}

public class UpdateUserPasswordCommandHandler : ICommandHandler<UpdateUserPasswordCommand>
{
    private readonly IDataContext dataContext;

    public UpdateUserPasswordCommandHandler(IDataContext dataContext)
    {
        this.dataContext = dataContext;
    }

    public void Handle(UpdateUserPasswordCommand command)
    {
        var user = dataContext.Users.Find(command.UserId);
        user.Password = command.Password;

        dataContext.SaveChanges();
    }
}

Ed ecco l'implementazione dell'interfaccia IQueryAndCommandDispatcher :

public class QueryAndCommandDispatcher : IQueryAndCommandDispatcher
{
    private readonly IKernel kernel;

    public QueryAndCommandDispatcher(IKernel kernel)
    {
        this.kernel = kernel;
    }

    public void ExecuteCommand<T>(T command) where T : class
    {
        var handler = kernel.Get<ICommandHandler<T>>();

        handler.Handle(command);
    }

    public TResult ExecuteQuery<TQueryData, TResult>(TQueryData query) where TQueryData : class
    {
        var handler = kernel.Get<IQueryHandler<TQueryData, TResult>>();

        return handler.Execute(query);
    }
}

E tutto si unisce in un controller come questo:

public class HomeController : Controller
{
    private readonly IQueryAndCommandDispatcher dispatcher;

    public HomeController(IQueryAndCommandDispatcher dispatcher)
    {
        this.dispatcher = dispatcher;
    }

    [HttpGet]
    public ActionResult Index(string email)
    {
        var userQuery = new GetUserByEmailQuery(email);
        var user = dispatcher.ExecuteQuery<GetUserByEmailQuery, User>(userQuery);

        return View(user);
    }

    [HttpPost]
    public ActionResult Update(int userId, string password)
    {
        var updateCommand = new UpdateUserPasswordCommand(userId, password);
        dispatcher.ExecuteCommand(updateCommand);

        return View();
    }
}

Riduce il numero di dipendenze di cui i controller necessitano, mantenendo comunque tutto controllabile.

    
risposta data 19.08.2014 - 12:11
fonte

Leggi altre domande sui tag