Não é uma prática recomendada para um controlador chamar um repositório diretamente. Um "serviço" é apenas outra ferramenta, portanto, use-o onde fizer sentido.
NikolaiDante comentou:
... Escolha o padrão certo para a aplicação correta. O que eu diria é que você deve tornar seu aplicativo consistente.
Não acho que a consistência seja o aspecto mais importante. Uma classe "service" deve encapsular alguma lógica de nível superior, para que o controlador não precise implementá-la. Se não houver uma "lógica de nível superior" necessária para uma determinada operação, basta ir diretamente ao repositório.
Para promover boas Separações de Preocupações e testabilidade, o repositório deve ser uma dependência que você injeta no serviço por meio de um construtor:
IFooRepository repository = new FooRepository();
FooService service = new FooService(repository);
service.DoSomething(...);
Se a procura de registros no banco de dados precisar de algum tipo de consulta parametrizada, uma classe de serviço pode ser um bom lugar para levar em seu modelo de exibição e criar uma consulta que será executada pelo repositório.
Da mesma forma, se você tiver um modelo de exibição complexo para um formulário, uma classe de serviço poderá encapsular a lógica de criação, atualização e exclusão de registros chamando métodos em seus Modelos / Entidades de Domínio e persistindo-os usando um repositório.
Indo na direção oposta, se o seu controlador precisar obter um registro por seu ID, delegar a um objeto de serviço é como acertar uma tachinha com uma marreta - é muito mais do que você precisa.
Eu descobri que o controlador está na melhor posição para lidar com a transação, ou um objeto Unit Of Work . O controlador ou o objeto Unit Of Work, em seguida, delegaria objetos de serviço para operações complexas ou iria diretamente ao repositório para operações simples (como encontrar um registro por ID).
public class ShoppingCartsController : Controller
{
[HttpPost]
public ActionResult Edit(int id, ShoppingCartForm model)
{
// Controller initiates a database session and transaction
using (IStoreContext store = new StoreContext())
{
// Controller goes directly to a repository to find a record by Id
ShoppingCart cart = store.ShoppingCarts.Find(id);
// Controller creates the service, and passes the repository and/or
// the current transaction
ShoppingCartService service = new ShoppingCartService(store.ShoppingCarts);
if (cart == null)
return HttpNotFound();
if (ModelState.IsValid)
{
// Controller delegates to a service object to manipulate the
// Domain Model (ShoppingCart)
service.UpdateShoppingCart(model, cart);
// Controller decides to commit changes
store.SaveChanges();
return RedirectToAction("Index", "Home");
}
else
{
return View(model);
}
}
}
}
Eu acho que uma combinação de serviços e trabalhar diretamente com repositórios é perfeitamente aceitável. Você pode encapsular ainda mais a transação em um objeto de Unidade de trabalho, se achar necessário.
A repartição de responsabilidades é assim:
- O controlador controla o fluxo do aplicativo
- Retorna "404 não encontrado" se o carrinho de compras não estiver no banco de dados
- Renderiza novamente o formulário com mensagens de validação, se a validação falhar
- Salva o carrinho de compras se tudo sair
- O controlador delega para uma classe de serviço para executar a lógica comercial nos seus Modelos de Domínio (ou Entidades). Objetos de serviço não devem implementar lógica de negócios! Eles executam a lógica de negócios.
- Os controladores podem delegar diretamente aos repositórios para operações simples
- Os objetos de serviço pegam dados no modelo de exibição e delegam aos Modelos de Domínio para executar a lógica de negócios (por exemplo, o objeto de serviço chama métodos nos Modelos de Domínio antes de chamar métodos no repositório)
- Objetos de serviço delegados a repositórios para persistência de dados
- Os controladores devem:
- Gerenciar a vida útil de uma transação ou
- Crie um objeto Unit Of Work para gerenciar o tempo de vida de uma transação