2008-12-03 25 views
17

Los controladores en mi aplicación web ASP.NET MVC están empezando a estar un poco abultados con la lógica empresarial. Los ejemplos en la web muestran simples acciones de controlador que simplemente extraen datos de un repositorio y lo pasan a la vista. Pero ¿y si también necesitas apoyar la lógica de negocios además de eso?¿Deberían los controladores de una aplicación web ASP.NET MVC solicitar repositorios, servicios o ambos?

Digamos, por ejemplo, que una acción que cumple una orden también necesita enviar un correo electrónico. ¿Pego esto en el controlador y copio/pego esta lógica a cualquier otra acción que también cumpla con las órdenes? Mi primera intuición sería crear un servicio como OrderFulfillerService que se encargaría de toda esta lógica y haría que la acción de controlador lo llamara. Sin embargo, para operaciones simples como recuperar una lista de usuarios u órdenes de la base de datos, me gustaría interactuar directamente con el repositorio en lugar de tener esa llamada envuelta por un servicio.

¿Es este un patrón de diseño aceptable? ¿Las acciones del controlador llaman a los servicios cuando necesitan lógica empresarial y repositorios cuando solo necesitan acceso a los datos?

Respuesta

0

Su lógica de negocio debe estar encapsulada en objetos comerciales; si tiene un objeto Order (y lo hace, ¿no?), Y una regla comercial indica que se debe enviar un correo electrónico cuando se complete el pedido. su método Fulfill (o, si es más apropiado, el setter para IsFulfilled) debería desencadenar esa acción. Probablemente tendré información de configuración que apunte el objeto comercial a un servicio de correo electrónico apropiado para la aplicación, o más generalmente a un servicio "notificador" para que se puedan agregar otros tipos de notificación cuando sea necesario.

2

Si va a tener una capa empresarial, entonces creo que es mejor tener solo la capa de negocio hablar con la capa de datos. Puedo entender por qué en un caso de uso simple, la capa de presentación (el controlador) habla directamente con la capa de datos, pero una vez que identifica la necesidad de una capa empresarial aislada, se mezcla el uso de ambos en niveles superiores. ser peligroso.

Por ejemplo, ¿qué ocurre si el controlador A llama a un método en la capa empresarial para obtener una Lista del Objeto A (y este método aplica las reglas comerciales, tal vez algún filtro o clasificación), pero luego aparece el Controlador B, misma información, pero se olvida de la capa de negocios y llama a la capa de datos directamente?

1

Podría parecer molesto ver mucho de esto en los servicios empresariales:

public Customer GetCustomer(int id) 
{ 
    return customerRepository.Get(id); 
} 

Y es natural tener un fuerte deseo de eludir el servicio. Pero a largo plazo es mejor que permita que sus servicios comerciales sean intermedios entre controladores y repositorios.

Ahora, para una aplicación de tipo CRUD muy simple, puede hacer que sus controladores consuman repositorios directamente en lugar de recurrir a servicios comerciales. Todavía podría tener algo como un EmailerService, pero IMO cuando se trata de buscar y hacer cosas con entidades, lo mejor es no mezclar y combinar el servicio comercial y las llamadas al repositorio en sus controladores.

En cuanto a tener servicios de llamadas de entidades (objetos comerciales) o cualquier componente de infraestructura, no haría eso. Prefiero mantener las entidades POCO y libres de dependencias.

1

Sería bueno si pudiéramos dejar de ver este ejemplo una y otra vez ...

public ActionResult Index() 
{ 
    var widgetContext = new WidgetDataContext(); 
    var widgets = from w in widgetContext.Widget 
       select w; 
    return View(widgets); 
} 

Soy consciente de que esto no es útil a su pregunta, pero parece ser parte de una gran cantidad de demostración que creo que puede ser engañosa.

+0

De acuerdo. Está "bien" como un ejemplo, supongo, pero estaría bien si mencionaran que no es una buena práctica desde la perspectiva de la arquitectura. –

25

Sus controladores (en el proyecto MVC) deberían llamar a sus objetos en el proyecto de servicio. El proyecto de servicios es donde se maneja toda la lógica de negocios.

Un buen ejemplo es la siguiente:

public ActionResult Index() 
{ 
    ProductServices productServices = new ProductServices(); 

    // top 10 products, for example. 
    IList<Product> productList productServices.GetProducts(10); 

    // Set this data into the custom viewdata. 
    ViewData.Model = new ProductViewData 
         { 
          ProductList = productList; 
         }; 

    return View(); 
} 

o con la inyección de dependencias (mi favorito)

// Field with the reference to all product services (aka. business logic) 
private readonly ProductServices _productServices; 

// 'Greedy' constructor, which Dependency Injection auto finds and therefore 
// will use. 
public ProductController(ProductServices productServices) 
{ 
    _productServices = productServices; 
} 

public ActionResult Index() 
{ 
    // top 10 products, for example. 
    // NOTE: The services instance was automagically created by the DI 
    //  so i din't have to worry about it NOT being instansiated. 
    IList<Product> productList _productServices.GetProducts(10); 

    // Set this data into the custom viewdata. 
    ViewData.Model = new ProductViewData 
         { 
          ProductList = productList; 
         }; 

    return View(); 
} 

Ahora .. ¿cuál es el proyecto de servicio (o lo que es ProductServices)? esa es una biblioteca de clases con su lógica de negocios. Por ejemplo.

public class ProductServices : IProductServices 
{ 
    private readonly ProductRepository _productRepository; 
    public ProductServices(ProductRepository productRepository) 
    { 
     _productRepository = productRepository; 
    } 

    public IList<Product> GetProducts(int numberOfProducts) 
    { 
     // GetProducts() and OrderByMostRecent() are custom linq helpers... 
     return _productRepository.GetProducts() 
      .OrderByMostRecent() 
      .Take(numberOfProducts) 
      .ToList(); 
    } 
} 

pero que podría ser todo tan incondicional y confuso ... lo que una versión sencilla de la clase ServiceProduct podría ser (pero yo no recomendaría) ...

public class ProductServices 
{ 
    public IList<Product> GetProducts(int numberOfProducts) 
    { 
     using (DB db = new Linq2SqlDb()) 
     { 
      return (from p in db.Products 
        orderby p.DateCreated ascending 
        select p).Take(10).ToList(); 
     } 
    } 
} 

Así que hay anda tu. Puede ver que toda la lógica está en los proyectos del Servicio, lo que significa que puede reutilizar ese código en otros lugares.

¿Dónde aprendí esto?

De Rob ConeryMVC StoreFront medios y tutorials. Lo mejor desde el pan en rodajas. Sus tutoriales explican (lo que hice) en detalle con ejemplos completos de código de solución. Él usa Dependency Injection, que es SOO kewl ahora que he visto cómo lo usa, en MVC.

HTH.

+0

Bien explicado – redsquare

+1

aplausos compañero :) gracias Rob Conery/Phil Haack/Scott Hanselman, etc. por enseñarme. –

+1

¿Dónde surgió este patrón de dividir entidades en 2 objetos (entidades y servicios/controladores)? Lo he visto en un par de lugares (principalmente basados ​​en Microsoft), y realmente no lo entiendo. De hecho, he visto que en realidad causa problemas en un proyecto de desarrollo web ASP.NET. –

5

No estoy seguro sobre el uso de servicios para esto.

Según tengo entendido, uno de los principios de DDD (que estoy leyendo en este momento) es que los Objetos del Dominio están organizados en Agregados y que cuando se crea una instancia de la raíz del Agregado, puede solo trate directamente con los objetos dentro del Agregado (para ayudar a mantener un claro sentido de responsabilidad).

Creación del agregado debe cumplir cualquier invariantes etc.

Tomando un ejemplo de una clase Cliente, éste podría ser la raíz agregada y otra clase dentro del agregado podrían ser de direcciones.

Ahora, si desea crear un nuevo Cliente, debería poder hacerlo utilizando el constructor del Cliente o una fábrica. Hacer esto debería devolver un objeto que sea completamente funcional dentro del límite Agregado (por lo que no puede tratar con Productos ya que estos no son parte del Agregado pero puede manejar Direcciones).

La base de datos es una preocupación secundaria y solo entra en juego para persistir el Agregado en la base de datos o recuperarla de la base de datos.

Para evitar la interacción con la base de datos directamente, puede crear una interfaz de repositorio (como se explicó) que dado una instancia de Cliente (que incluye una referencia a Dirección) debería poder conservar el Agregado en la base de datos.

El punto es que la interfaz del repositorio ES parte de su modelo/capa de dominio (la implementación del repositorio no lo es). El otro factor es que el repositorio probablemente debería terminar llamando al mismo método de "creación" como si estuvieras creando un nuevo objeto (para mantener invariantes, etc.).Si está utilizando un constructor, esto es bastante simple, ya que terminará llamando al constructor cuando el repositorio "crea" el objeto a partir de datos de todos modos.

La capa de aplicación puede comunicarse directamente con el dominio (incluida la interfaz del repositorio).

Por lo tanto, si desea crear una nueva instancia de un objeto, puede p. Ej.

Customer customer = new Customer(); 

Si la aplicación necesita para recuperar una instancia del cliente desde el repositorio, no hay ninguna razón en particular que se me ocurre para que no llame ...

Customer customer = _custRepository.GetById(1) 

o ...

Customer customer = _custRepository.GetByKey("AlanSmith1") 

En última instancia, terminará con una instancia del objeto Cliente que funciona dentro de sus propios límites y reglas como lo haría si creara el nuevo objeto Cliente directamente.

Creo que los servicios deben reservarse para cuando la "cosa" con la que intenta trabajar no es un objeto. La mayoría de las reglas (restricciones, etc.) se pueden escribir como parte del Objeto del Dominio.

Un buen ejemplo está en el DDD PDF rápido que estoy leyendo en este momento. Allí tienen una restricción en un objeto Bookshelf, por lo que solo puede agregar tantos libros como contenga el estante.

Llamar al método AddBook en el objeto BookShelf comprueba que el espacio esté disponible antes de agregar el libro a la colección BookShelf de objetos Book. Un ejemplo simple, pero la regla de negocio es impuesta por el objeto de dominio en sí.

¡No estoy diciendo que alguno de los anteriores sea correcto por cierto! ¡Estoy tratando de entender todo esto en este momento!

+0

sí, la capa de aplicación puede comunicarse directamente con la interfaz del repositorio; sin embargo, si llega al punto en el que incluye la lógica allí (más que simples llamadas al repositorio), entonces lo refactoriza en un Servicio – BlackTigerX

1

Bueno, depende de ti, me gusta mantener los controladores lo más simple posible, y para archivar esto necesito encapsular la lógica de negocios en una capa separada, y aquí está lo importante, principalmente tienes 2 opciones, asumiendo que usted está utilizando Linq2SQL o Entity Framework:

  • se pueden utilizar los métodos de extensión y clase parcial para validar sus modelos justo antes de guardar los cambios (ganchos método, se puede ver un ejemplo de esto en la muestra de Nerdinner por Scott Gu).

  • La otra forma (y mi favorito, porque me siento más control sobre el flujo de la aplicación), es el uso de una capa totalmente separada para negocio lógica como servicios de la capa (se puede ve este aprroach en el serie de tutoriales por Stephen Walther en asp.net/mvc zone).

Con estos dos métodos obtiene DRY y limpia los controladores que de otro modo serían desordenados.

Cuestiones relacionadas