¿Dónde encaja la “capa de lógica empresarial” en una aplicación MVC?

86

Primero, antes de que alguien gritara engañado, me costó mucho resumirlo en un título simple. Otro título podría haber sido "¿Cuál es la diferencia entre un modelo de dominio y un modelo MVC?" o "¿Qué es un modelo?"

Conceptualmente, entiendo que un modelo son los datos utilizados por las vistas y el controlador. Más allá de eso, parece haber una gran cantidad de opiniones diferentes sobre lo que compone el modelo. ¿Qué es un modelo de dominio, frente a un modelo de aplicación, frente a un modelo de vista, frente a un modelo de servicio, etc.?

Por ejemplo, en una pregunta reciente que hice sobre el patrón del repositorio, me dijeron a quemarropa que el repositorio es parte del modelo. Sin embargo, he leído otras opiniones de que el modelo debe separarse del modelo de persistencia y la capa de lógica empresarial. Después de todo, ¿no se supone que el patrón Repository desacopla el método de persistencia concreto del modelo? Otras personas dicen que hay una diferencia entre el modelo de dominio y el modelo MVC.

Tomemos un ejemplo sencillo. AccountController que se incluye con el proyecto predeterminado de MVC. He leído varias opiniones de que el código de cuenta incluido es de diseño deficiente, viola SRP, etc., etc. Si uno diseñara un modelo de membresía "adecuado" para una aplicación MVC, ¿cuál sería?

¿Cómo separaría los servicios ASP.NET (proveedor de membresía, proveedor de roles, etc.) del modelo? ¿O lo harías en absoluto?

A mi modo de ver, el modelo debería ser "puro", quizás con lógica de validación ... pero debería estar separado de las reglas de negocio (aparte de la validación). Por ejemplo, supongamos que tiene una regla comercial que dice que alguien debe recibir un correo electrónico cuando se crea una nueva cuenta. Eso realmente no pertenece al modelo en mi opinión. Entonces, ¿a dónde pertenece?

¿Alguien quiere arrojar algo de luz sobre este tema?

Erik Funkenbusch
fuente
1
Es por eso que debe hacer cuatro preguntas distintas.
John Farrell
3
La palabra clave es "casi". En realidad, es la misma pregunta, quizás con subpreguntas utilizadas para ilustrar la pregunta principal.
Erik Funkenbusch
3
Modelo - Vista - Controlador. ¿Es repositorio / vista BL? No. ¿Es el controlador? No. ¿Qué queda :)? Es MVC, no MSVC, no MRVC, no MBLVC. Solo hay tres capas. Entonces, el repositorio es parte del modelo, BL es parte del modelo. Y puede hacer una separación adicional, pero se hace dentro de la capa del modelo.
LukLed
3
@LukeLed, @bslm - En realidad, no. MVC no dice que no puede haber otras capas con las que el controlador o modelo interactúe.
John Farrell
3
@LukLed - En desacuerdo - MVC es simplemente un patrón de capa de presentación. No tiene ningún impacto en cómo estructura sus otras capas como BLL y DAL.
Cory House

Respuestas:

69

La forma en que lo he hecho, y no digo que sea correcta o incorrecta, es tener mi Vista y luego un modelo que se aplica a mi vista. Este modelo solo tiene lo que es relevante para mi vista, incluidas las anotaciones de datos y las reglas de validación. El controlador solo alberga la lógica para construir el modelo. Tengo una capa de servicio que alberga toda la lógica empresarial. Mis controladores llaman a mi capa de servicio. Más allá de eso está mi capa de repositorio.

Mis objetos de dominio se alojan por separado (en su propio proyecto, en realidad). Tienen sus propias anotaciones de datos y reglas de validación. Mi repositorio valida los objetos de mi dominio antes de guardarlos en la base de datos. Debido a que cada objeto en mi dominio hereda de una clase base que tiene validación incorporada, mi repositorio es genérico y valida todo (y requiere que herede de la clase base).

Podría pensar que tener dos conjuntos de modelos es una duplicación de código, y hasta cierto punto lo es. Pero hay casos perfectamente razonables en los que el objeto de dominio no es apropiado para la vista.

Un ejemplo es cuando se trabaja con tarjetas de crédito: tengo que solicitar un cvv al procesar un pago, pero no puedo almacenar el cvv (es una multa de $ 50,000 para hacerlo). Pero también quiero que pueda editar su tarjeta de crédito: cambio de dirección, nombre o fecha de vencimiento. Pero no me va a dar el número o el CVV cuando lo edite, y ciertamente no voy a poner su número de tarjeta de crédito en texto plano en la página. Mi dominio tiene estos valores requeridos para guardar una nueva tarjeta de crédito porque ustedes me los dan, pero mi modelo de edición ni siquiera incluye el número de tarjeta o cvv.

Otro beneficio de tantas capas es que si se diseña correctamente, puede usar un mapa de estructura u otro contenedor de IoC y cambiar piezas sin afectar negativamente a su aplicación.

En mi opinión, el código del controlador solo debe ser un código dirigido a la vista. Muestre esto, oculte aquello, etc. La capa de servicio debe albergar la lógica empresarial de su aplicación. Me gusta tenerlo todo en un solo lugar, por lo que es fácil cambiar o modificar una regla comercial. La capa de repositorio debe ser relativamente tonta, desprovista de lógica empresarial y solo consulta sus datos y devuelve sus objetos de dominio. Al separar los modelos de vista del modelo de dominio, tiene mucha más flexibilidad cuando se trata de reglas de validación personalizadas. También significa que no tiene que volcar todos los datos en su vista en campos ocultos y empujarlos hacia adelante y hacia atrás entre el cliente y el servidor (o reconstruirlos en el backend).

<% if (!String.IsNullOrEmpty(Model.SomeObject.SomeProperty) && 
    Model.SomeObject.SomeInt == 3 && ...) { %>

Si bien todo parece desparramado y superpuesto, tiene el propósito de ser diseñado de esta manera. Es perfecto realmente no. Pero lo prefiero a algunos diseños anteriores de llamar a repositorios desde el controlador y tener lógica empresarial mezclada en el controlador, el repositorio y el modelo.

Josh
fuente
Casi un espejo de lo que tengo en nuestra aplicación MVC empresarial. Una arquitectura de N-Tier. La aplicación MVC solo interactúa con objetos y servicios comerciales en las áreas de N-Tier.
Ed DeGagne
Casi lo mismo aquí. Proyectos separados para definiciones, modelos, modelos de vista, DAL, etc. La única diferencia es que mi DAL incluye lógica para aplanar datos para la web para optimizar la distribución de datos complejos para informes o vistas personalizadas de clientes. Ahora evito guardar cosas en el caché de la aplicación para tablas de búsqueda, etc., con granjas web y nubes de Azure en juego.
Robert Achmann
1
@Josh, ¿sería útil si pudiera mostrar una captura de pantalla de su proyecto de muestra?
shaijut
@Josh, ¿qué pasa si su proyecto no tiene una base de datos? Interactúa con referencias de servicios. Todas las clases de dominio y métodos provienen de estas referencias. ¿Este escenario es adecuado para una estructura en capas?
user6395764
17

Con demasiada frecuencia me preguntaba cómo encajan exactamente los elementos MVC en una estructura de aplicación web tradicional, donde tiene vistas (páginas), controladores, servicios y objetos de datos (modelo). Como dijiste, hay muchas versiones de eso.

Creo que la confusión existe debido a la arquitectura mencionada anteriormente, ampliamente aceptada, que utiliza el "modelo de dominio anémico" (supuesto) -patrón anti. No entraré en muchos detalles sobre la "antipatrón" del modelo de datos anémicos (puede ver un esfuerzo mío para explicar las cosas aquí (basado en Java, pero relevante para cualquier lenguaje)). Pero, en resumen, significa que nuestro modelo solo contiene datos y la lógica empresarial se coloca en servicios / administradores.

Pero supongamos que tenemos una arquitectura impulsada por dominios , y nuestros objetos de dominio son como se espera que sean, con lógica de estado y de negocio. Y en esta perspectiva impulsada por el dominio, las cosas entran en su lugar:

  • la vista es la interfaz de usuario
  • el controlador recopila las entradas de la interfaz de usuario, invoca métodos en el modelo y envía una respuesta a la interfaz de usuario
  • el modelo son nuestros componentes comerciales, que contienen los datos, pero también tienen lógica comercial.

Supongo que eso responde a tus principales preguntas. Las cosas se complican cuando agregamos algunas capas más, como la capa de repositorio. A menudo se sugiere que debe ser invocado por la lógica empresarial colocada en el modelo (y por lo tanto, cada objeto de dominio tiene una referencia a un repositorio). En el artículo mío que vinculé, sostengo que esta no es una buena práctica. Y que de hecho no es malo tener una capa de servicio. Por cierto, el diseño impulsado por dominios no excluye la capa de servicio, pero se supone que es 'delgada' y solo coordina objetos de dominio (por lo que no hay lógica comercial allí).

Para el paradigma anémico del modelo de datos, que es ampliamente adoptado (para bien o para mal), el modelo sería tanto la capa de servicio como sus objetos de datos.

Bozho
fuente
¡Excelente punto! Un comentario: hay el mismo lío con los Servicios. Al menos los servicios pueden ser servicios de aplicaciones y servicios de dominio. El servicio de aplicación es solo una envoltura delgada que recopila información de repositorios, etc. El servicio de dominio proporciona lógica comercial, es decir, utiliza una combinación de modelos de dominio o simplemente cosas que no siempre se ajustan al modelo de dominio.
Artru
¿Qué pasa si su proyecto no tiene una base de datos? Interactúa con referencias de servicios. Todas las clases de dominio y métodos provienen de estas referencias. ¿Este escenario es adecuado para una estructura en capas?
user6395764
3

En mi opinión,

Modelo -

No debe contener lógica empresarial, debe ser conectable (escenario similar a WCF). Se usa para enlazar a la vista, por lo que debería tener propiedades.

Lógica de negocios -

Debe colocarse en "Capa de servicios de dominio", es una capa completamente separada. Además, agregará una capa más aquí "Servicios de aplicación".

Los servicios de aplicaciones se comunican con la capa de servicios de dominio para aplicar la lógica empresarial y, por último, devolver el modelo.

Entonces, el controlador le pedirá al servicio de aplicaciones el modelo y el flujo será como,

    Controller->Application Services(using domain services)->Model
py2020
fuente
2

El patrón MVC y el marco Asp.net no distinguen cuál debería ser el modelo.

Los propios ejemplos de MS incluyen clases de persistencia en el modelo. Su pregunta sobre la membresía en el modelo. Esto depende. ¿Las clases de tu modelo son propiedad de algo? ¿Existe un vínculo entre quién inicia sesión y qué datos se muestran? ¿El filtrado de datos forma parte de un sistema de permisos que es editable? ¿Quién actualizó o editó un objeto por última vez es parte de su dominio, ya que alguien más necesita verlo o algo para soporte de backend?

El ejemplo del correo electrónico también depende. ¿Está familiarizado con eventos de dominio o eventos en particular? ¿Tiene un servicio separado para enviar correos electrónicos? ¿El acto de enviar un correo electrónico es parte de su dominio o es una preocupación a nivel de aplicación fuera del alcance de su sistema? ¿La interfaz de usuario necesita saber si un correo electrónico se envió correctamente o no? ¿Los correos electrónicos que no se envían necesitan reintentos? ¿Es necesario almacenar el contenido del correo electrónico enviado para soporte o para los requisitos de servicio al cliente?

Este tipo de preguntas son demasiado amplias y subjetivas, pero las estoy respondiendo para que usted y todos los que lo votaron puedan entenderlo.

Sus requisitos, cronogramas y recursos se integran en la arquitectura de su sistema. Incluso el modelo de ingresos puede tener un efecto. También debes considerar el patrón que estás buscando. DDD es muy diferente a las aplicaciones de persistencia como modelo y todas las pendientes intermedias también son válidas para ciertas aplicaciones. ¿Estás disparando para probar la aplicación? Todo esto tiene un efecto.

John Farrell
fuente