¿Qué tan precisa es "La lógica de negocios debe estar en un servicio, no en un modelo"?

398

Situación

A principios de esta tarde, respondí a una pregunta sobre StackOverflow.

La pregunta:

¿La edición de un objeto existente debe hacerse en la capa de repositorio o en servicio?

Por ejemplo, si tengo un usuario que tiene deudas. Quiero cambiar su deuda. ¿Debo hacerlo en UserRepository o en el servicio, por ejemplo, BuyService obteniendo un objeto, editándolo y guardándolo?

Mi respuesta:

Debe dejar la responsabilidad de mutar un objeto a ese mismo objeto y usar el repositorio para recuperar este objeto.

Situación de ejemplo:

class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

Un comentario que recibí:

La lógica empresarial realmente debería estar en un servicio. No en un modelo.

¿Qué dice internet?

Entonces, esto me llevó a buscar ya que nunca he usado (conscientemente) una capa de servicio. Comencé a leer sobre el patrón de la capa de servicio y el patrón de la unidad de trabajo, pero hasta ahora no puedo decir que esté convencido de que se debe usar una capa de servicio.

Tomemos, por ejemplo, este artículo de Martin Fowler sobre el antipatrón de un modelo de dominio anémico:

Hay objetos, muchos con nombres de nombres en el espacio de dominio, y estos objetos están conectados con las relaciones y la estructura rica que tienen los verdaderos modelos de dominio. El truco se produce cuando observas el comportamiento y te das cuenta de que casi no hay comportamiento en estos objetos, lo que los convierte en poco más que bolsas de captadores y colocadores. De hecho, a menudo estos modelos vienen con reglas de diseño que dicen que no debe poner ninguna lógica de dominio en los objetos de dominio. En cambio, hay un conjunto de objetos de servicio que capturan toda la lógica del dominio. Estos servicios viven sobre el modelo de dominio y usan el modelo de dominio para datos.

(...) La lógica que debería estar en un objeto de dominio es la lógica de dominio: validaciones, cálculos, reglas de negocio, como quiera llamarlo.

Para mí, esto parecía exactamente de qué se trataba la situación: abogaba por la manipulación de los datos de un objeto mediante la introducción de métodos dentro de esa clase que hacen exactamente eso. Sin embargo, me doy cuenta de que esto debería ser de cualquier manera, y probablemente tenga más que ver con cómo se invocan estos métodos (usando un repositorio).

También tuve la sensación de que en ese artículo (ver más abajo), una capa de servicio se considera más como una fachada que delega el trabajo al modelo subyacente, que una capa de trabajo intensivo real.

Capa de aplicación [su nombre para Capa de servicio]: define los trabajos que se supone que debe hacer el software y dirige los objetos de dominio expresivo para resolver problemas. Las tareas de las que es responsable esta capa son significativas para el negocio o necesarias para la interacción con las capas de aplicación de otros sistemas. Esta capa se mantiene delgada. No contiene reglas de negocio o conocimiento, sino que solo coordina tareas y delega el trabajo a colaboraciones de objetos de dominio en la siguiente capa hacia abajo. No tiene un estado que refleje la situación comercial, pero puede tener un estado que refleje el progreso de una tarea para el usuario o el programa.

Lo que se refuerza aquí :

Interfaces de servicio. Los servicios exponen una interfaz de servicio a la que se envían todos los mensajes entrantes. Puede pensar en una interfaz de servicio como una fachada que expone la lógica empresarial implementada en la aplicación (normalmente, la lógica en la capa empresarial) a los consumidores potenciales.

Y aqui :

La capa de servicio debe estar desprovista de cualquier aplicación o lógica empresarial y debe centrarse principalmente en algunas preocupaciones. Debe envolver las llamadas de Business Layer, traducir su Dominio a un lenguaje común que sus clientes puedan entender y manejar el medio de comunicación entre el servidor y el cliente solicitante.

Este es un serio contraste con otros recursos que hablan sobre la capa de servicio:

La capa de servicio debe consistir en clases con métodos que sean unidades de trabajo con acciones que pertenezcan a la misma transacción.

O la segunda respuesta a una pregunta que ya he vinculado:

En algún momento, su aplicación querrá cierta lógica empresarial. Además, es posible que desee validar la entrada para asegurarse de que no se solicite algo malo o que no funcione. Esta lógica pertenece a su capa de servicio.

"Solución"?

Siguiendo las pautas de esta respuesta , se me ocurrió el siguiente enfoque que utiliza una capa de servicio:

class UserController : Controller {
    private UserService _userService;

    public UserController(UserService userService){
        _userService = userService;
    } 

    public ActionResult MakeHimPay(string username, int amount) {
        _userService.MakeHimPay(username, amount);
        return RedirectToAction("ShowUserOverview");
    }

    public ActionResult ShowUserOverview() {
        return View();
    }
}

class UserService {
    private IUserRepository _userRepository;

    public UserService(IUserRepository userRepository) {
        _userRepository = userRepository;
    }

    public void MakeHimPay(username, amount) {
        _userRepository.GetUserByName(username).makePayment(amount);
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

Conclusión

En conjunto, no ha cambiado mucho aquí: el código del controlador se ha movido a la capa de servicio (lo cual es algo bueno, por lo que este enfoque tiene una ventaja). Sin embargo, esto no parece tener nada que ver con mi respuesta original.

Me doy cuenta de que los patrones de diseño son pautas, no reglas establecidas para ser implementadas siempre que sea posible. Sin embargo, no he encontrado una explicación definitiva de la capa de servicio y cómo debería considerarse.

  • ¿Es un medio para simplemente extraer la lógica del controlador y ponerla dentro de un servicio?

  • ¿Se supone que forma un contrato entre el controlador y el dominio?

  • ¿Debería haber una capa entre el dominio y la capa de servicio?

Y, por último pero no menos importante: siguiendo el comentario original

La lógica empresarial realmente debería estar en un servicio. No en un modelo.

  • ¿Es esto correcto?

    • ¿Cómo introduciría mi lógica comercial en un servicio en lugar del modelo?
Jeroen Vannevel
fuente
66
Trato la capa de servicio como el lugar donde colocar la parte inevitable del script de transacción que actúa sobre raíces agregadas. Si mi capa de servicio se vuelve demasiado compleja, eso me indica que me muevo en dirección al modelo anémico y mi modelo de dominio necesita atención y revisión. También trato de poner la lógica en SL que no se va a duplicar por su naturaleza.
Pavel Voronin
Pensé que los servicios eran parte de la capa Modelo. ¿Me equivoco al pensar eso?
Florian Margaine
Yo uso una regla general: no dependas de lo que no necesitas; de lo contrario, podría ser (generalmente negativo) afectado por cambios en la parte que no necesito. Como consecuencia, termino con muchos roles claramente definidos. Mis objetos de datos contienen comportamiento siempre que todos los clientes lo usen. De lo contrario, muevo el comportamiento a clases implementando el rol requerido.
beluchin
1
La lógica de control de flujo de la aplicación pertenece a un controlador. La lógica de acceso a datos pertenece a un repositorio. La lógica de validación pertenece a una capa de servicio. Una capa de servicio es una capa adicional en una aplicación ASP.NET MVC que media la comunicación entre un controlador y la capa de repositorio. La capa de servicio contiene lógica de validación empresarial. repositorio. asp.net/mvc/overview/older-versions-1/models-data/…
Kbdavis07
3
En mi humilde opinión, el modelo de dominio de estilo OOP correcto debería evitar los "servicios empresariales" y mantener la lógica empresarial en el modelo. Pero la práctica muestra que es muy tentador crear una gran capa de negocios con métodos con nombres distintos y también poner una transacción (o unidad de trabajo) alrededor de todo el método. Es mucho más fácil procesar múltiples clases de modelos en un método de servicio comercial que planificar las relaciones entre esas clases de modelos, porque entonces tendría que responder algunas preguntas difíciles: ¿dónde está la raíz agregada? ¿Dónde debo comenzar / confirmar una transacción de base de datos? Etc.
JustAMartin

Respuestas:

369

Para definir cuáles son las responsabilidades de un servicio , primero debe definir qué es un servicio .

El servicio no es un término de software canónico o genérico. De hecho, el sufijo Serviceen el nombre de una clase se parece mucho al muy difamado Administrador : no le dice casi nada sobre lo que el objeto realmente hace .

En realidad, lo que debe hacer un servicio es altamente específico de la arquitectura:

  1. En una arquitectura tradicional en capas, el servicio es literalmente sinónimo de capa de lógica empresarial . Es la capa entre la interfaz de usuario y los datos. Por lo tanto, todas las reglas comerciales entran en los servicios. La capa de datos solo debe comprender las operaciones CRUD básicas, y la capa de interfaz de usuario solo debe ocuparse de la asignación de DTO de presentación hacia y desde los objetos comerciales.

  2. En una arquitectura distribuida de estilo RPC (SOAP, UDDI, BPEL, etc.), el servicio es la versión lógica de un punto final físico . Es esencialmente una colección de operaciones que el responsable de mantenimiento desea proporcionar como una API pública. Varias guías de mejores prácticas explican que una operación de servicio debería ser de hecho una operación de nivel empresarial y no CRUD, y tiendo a estar de acuerdo.

    Sin embargo, debido a que enrutar todo a través de un servicio remoto real puede perjudicar seriamente el rendimiento, normalmente es mejor no hacer que estos servicios implementen la lógica de negocios; en su lugar, deberían envolver un conjunto "interno" de objetos comerciales. Un solo servicio puede involucrar uno o varios objetos comerciales.

  3. En una arquitectura MVP / MVC / MVVM / MV *, los servicios no existen en absoluto. O si lo hacen, el término se usa para referirse a cualquier objeto genérico que se pueda inyectar en un controlador o modelo de vista. La lógica de negocios está en su modelo . Si desea crear "objetos de servicio" para organizar operaciones complicadas, eso se ve como un detalle de implementación. Lamentablemente, muchas personas implementan MVC de esta manera, pero se considera un antipatrón (modelo de dominio anémico ) porque el modelo en sí no hace nada, es solo un conjunto de propiedades para la interfaz de usuario.

    Algunas personas piensan erróneamente que tomar un método de controlador de 100 líneas y ponerlo todo en un servicio de alguna manera mejora la arquitectura. Realmente no lo hace; todo lo que hace es agregar otra capa de indirección, probablemente innecesaria. Hablando en términos prácticos , el controlador todavía está haciendo el trabajo, solo lo está haciendo a través de un objeto "ayudante" mal llamado. Recomiendo encarecidamente la presentación de los Modelos de Dominio Malvado de Jimmy Bogard para un ejemplo claro de cómo convertir un modelo de dominio anémico en uno útil. Implica un examen cuidadoso de los modelos que está exponiendo y qué operaciones son realmente válidas en un contexto comercial .

    Por ejemplo, si su base de datos contiene Órdenes, y tiene una columna para Cantidad total, su aplicación probablemente no debería poder cambiar ese campo a un valor arbitrario, porque (a) es historial y (b) se supone que es determinado por lo que está en el orden, así como quizás por otros datos / reglas sensibles al tiempo. Crear un servicio para administrar pedidos no necesariamente resuelve este problema, porque el código de usuario aún puede tomar el objeto de pedido real y cambiar la cantidad en él. En cambio, el pedido en debería ser responsable de garantizar que solo pueda modificarse de manera segura y coherente.

  4. En DDD, los servicios están destinados específicamente a la situación en la que tiene una operación que no pertenece adecuadamente a ninguna raíz agregada . Debe tener cuidado aquí, porque a menudo la necesidad de un servicio puede implicar que no utilizó las raíces correctas. Pero suponiendo que lo haya hecho, un servicio se usa para coordinar operaciones a través de múltiples raíces, o en ocasiones para manejar inquietudes que no involucran el modelo de dominio (como, tal vez, escribir información en una base de datos de BI / OLAP).

    Un aspecto notable del servicio DDD es que está permitido usar scripts de transacción . Cuando trabaje en aplicaciones grandes, es muy probable que eventualmente se encuentre con instancias en las que es mucho más fácil lograr algo con un procedimiento T-SQL o PL / SQL que preocuparse con el modelo de dominio. Esto está bien y pertenece a un servicio.

    Esta es una desviación radical de la definición de servicios de arquitectura en capas. Una capa de servicio encapsula objetos de dominio; un servicio DDD encapsula lo que no está en los objetos de dominio y no tiene sentido serlo.

  5. En una arquitectura orientada a servicios , un servicio se considera la autoridad técnica para una capacidad comercial. Eso significa que es el propietario exclusivo de un determinado subconjunto de datos comerciales y que nada más puede tocar esos datos, ni siquiera para leerlos .

    Por necesidad, los servicios son en realidad una propuesta de extremo a extremo en una SOA. Es decir, un servicio no es tanto un componente específico como una pila completa , y toda su aplicación (o toda su empresa) es un conjunto de estos servicios que se ejecutan uno al lado del otro sin intersección, excepto en las capas de mensajería y UI. Cada servicio tiene sus propios datos, sus propias reglas comerciales y su propia interfaz de usuario. No necesitan orquestar entre sí porque se supone que están alineados con el negocio, y, al igual que el negocio en sí, cada servicio tiene sus propias responsabilidades y opera de manera más o menos independiente de los demás.

    Entonces, según la definición de SOA, cada pieza de lógica de negocios en cualquier lugar está contenida dentro del servicio, pero, de nuevo, también lo está todo el sistema . Los servicios en una SOA pueden tener componentes y pueden tener puntos finales , pero es bastante peligroso llamar a cualquier pieza de código un servicio porque entra en conflicto con lo que se supone que significa la "S" original.

    Dado que SOA generalmente está muy interesado en la mensajería, las operaciones que podría haber empaquetado en un servicio anteriormente generalmente están encapsuladas en controladores , pero la multiplicidad es diferente. Cada controlador maneja un tipo de mensaje, una operación. Es una interpretación estricta del Principio de Responsabilidad Única , pero ofrece una gran capacidad de mantenimiento porque cada operación posible está en su propia clase. Por lo tanto, realmente no necesita una lógica comercial centralizada, porque los comandos representan operaciones comerciales en lugar de técnicas.

Finalmente, en cualquier arquitectura que elija, habrá algún componente o capa que tenga la mayor parte de la lógica de negocios. Después de todo, si la lógica de negocios está dispersa por todo el lugar, entonces solo tienes un código de espagueti. Pero si llama a ese componente un servicio o no , y cómo está diseñado en términos de cosas como el número o el tamaño de las operaciones, depende de sus objetivos arquitectónicos.

No hay una respuesta correcta o incorrecta, solo lo que se aplica a su situación.

Aaronaught
fuente
12
Gracias por la respuesta muy elaborada, ha aclarado todo lo que puedo pensar. Si bien las otras respuestas también son de buena a excelente calidad, creo que esta respuesta las supera a todas, por lo que aceptaré esta. Lo agregaré aquí para las otras respuestas: calidad e información exquisitas, pero lamentablemente solo podré darle un voto positivo.
Jeroen Vannevel
2
No estoy de acuerdo con el hecho de que en una arquitectura tradicional en capas, el servicio es sinónimo de la capa de lógica de negocios.
CodeART
1
@CodeART: está en una arquitectura de 3 niveles. Yo he visto arquitecturas de 4 niveles donde hay una "capa de aplicación" entre las capas de presentación y de negocios, que a veces también se denomina una capa de "servicio", pero la verdad, los únicos lugares que he visto esta implementado con éxito son enormes en expansión productos de ejecución de su negocio para usted infinitamente configurables de empresas como SAP u Oracle, y no pensé que realmente valiera la pena mencionarlo aquí. Puedo agregar una aclaración si lo desea.
Aaronaught
1
Pero si tomamos un controlador de más de 100 líneas (por ejemplo, ese controlador acepta el mensaje, deserialice el objeto JSON, realice la validación, aplique las reglas de negocio, guarde en db, devuelva el objeto de resultado) y mueva algo de lógica a uno de los llamados métodos de servicio. ¿Eso nos ayuda a probar la unidad por separado sin dolor?
artjom
2
@Aaronaught Quería aclarar una cosa si tenemos objetos de dominio asignados a db a través de ORM y no hay lógica de negocios en ellos. ¿Es este modelo de dominio anémico o no?
artjom
40

En cuanto a su título , no creo que la pregunta tenga sentido. El modelo MVC consta de datos y lógica empresarial. Decir que la lógica debe estar en el Servicio y no en el Modelo es como decir: "El pasajero debe sentarse en el asiento, no en el automóvil".

Por otra parte, el término "Modelo" es un término sobrecargado. Quizás no se refería al modelo MVC pero se refería al modelo en el sentido del objeto de transferencia de datos (DTO). También conocido como una entidad. De esto está hablando Martin Fowler.

A mi modo de ver, Martin Fowler está hablando de cosas en un mundo ideal. En el mundo real de Hibernate y JPA (en tierra Java), los DTO son una abstracción súper permeable. Me encantaría poner mi lógica de negocios en mi entidad. Haría las cosas mucho más limpias. El problema es que estas entidades pueden existir en un estado administrado / en caché que es muy difícil de entender y constantemente evita sus esfuerzos. Para resumir mi opinión: Martin Fowler recomienda la forma correcta, pero los ORM impiden que lo haga.

Creo que Bob Martin tiene una sugerencia más realista y la da en este video que no es gratis . Él habla sobre mantener sus DTO libres de lógica. Simplemente almacenan los datos y los transfieren a otra capa que está mucho más orientada a objetos y no usa los DTO directamente. Esto evita que la abstracción permeable te muerda. La capa con los DTO y los DTO en sí mismos no son OO. Pero una vez que salgas de esa capa, serás tan OO como Martin Fowler defiende.

El beneficio de esta separación es que abstrae la capa de persistencia. Podría cambiar de JPA a JDBC (o viceversa) y ninguna de las lógicas comerciales tendría que cambiar. Solo depende de los DTO, no le importa cómo se llenen esos DTO.

Para cambiar ligeramente los temas, debe tener en cuenta el hecho de que las bases de datos SQL no están orientadas a objetos. Pero los ORM generalmente tienen una entidad, que es un objeto, por tabla. Así que desde el principio ya has perdido una batalla. En mi experiencia, nunca puedes representar a la Entidad de la manera exacta que deseas de una manera orientada a objetos.

En cuanto a " un servicio", Bob Martin estaría en contra de tener una clase llamada FooBarService. Eso no está orientado a objetos. ¿Qué hace un servicio? Cualquier cosa relacionada con FooBars. También puede ser etiquetado FooBarUtils. Creo que abogaría por una capa de servicio (un mejor nombre sería la capa de lógica de negocios) pero cada clase en esa capa tendría un nombre significativo.

Daniel Kaplan
fuente
2
De acuerdo con su punto en ORMs; propagan una mentira de que usted asigna su entidad directamente a la base de datos con ellos, cuando en realidad una entidad puede almacenarse en varias tablas.
Andy
@Daniel Kaplan, ¿sabes cuál es el enlace actualizado para el video de Bob Martin?
Brian Morearty el
25

Estoy trabajando en el proyecto greenfield en este momento y ayer tuvimos que tomar algunas decisiones arquitectónicas. Curiosamente tuve que volver a visitar algunos capítulos de 'Patrones de arquitectura de aplicaciones empresariales'.

Esto es lo que se nos ocurrió:

  • Capa de datos Consultas y actualizaciones de la base de datos. La capa se expone a través de depósitos inyectables.
  • Capa de dominio Aquí es donde vive la lógica de negocios. Esta capa utiliza repositorios inyectables y es responsable de la mayoría de la lógica empresarial. Este es el núcleo de la aplicación que probaremos a fondo.
  • Capa de servicio. Esta capa habla con la capa de dominio y atiende las solicitudes del cliente. En nuestro caso, la capa de servicio es bastante simple: transmite solicitudes a la capa de dominio, maneja la seguridad y algunas otras preocupaciones transversales. Esto no es muy diferente de un controlador en la aplicación MVC: los controladores son pequeños y simples.
  • Capa del cliente Habla con la capa de servicio a través de SOAP.

Terminamos con lo siguiente:

Cliente -> Servicio -> Dominio -> Datos

Podemos reemplazar la capa de cliente, servicio o datos con una cantidad razonable de trabajo. Si su lógica de dominio vivía en el servicio y ha decidido que desea reemplazar o incluso eliminar su capa de servicio, entonces tendría que mover toda la lógica comercial a otro lugar. Tal requisito es raro, pero podría suceder.

Habiendo dicho todo esto, creo que esto está bastante cerca de lo que Martin Fowler quiso decir con

Estos servicios viven sobre el modelo de dominio y usan el modelo de dominio para datos.

El diagrama a continuación ilustra esto bastante bien:

http://martinfowler.com/eaaCatalog/serviceLayer.html

http://martinfowler.com/eaaCatalog/ServiceLayerSketch.gif

CodeART
fuente
2
¿Decidiste por SOAP ayer? ¿Es un requisito o simplemente no tienes una mejor idea?
JensG
1
REST no lo reducirá para nuestros requisitos. SOAP o REST, esto no hace ninguna diferencia en la respuesta. Según tengo entendido, el servicio es una puerta de entrada a la lógica de dominio.
CodeART
Absolutamente de acuerdo con la puerta de enlace. SOAP es bloatware (estandarizado), así que tuve que preguntar. Y sí, tampoco hay impacto en la pregunta / respuesta.
JensG
66
Hazte un favor y elimina tu capa de servicio. Su interfaz de usuario debe usar su dominio directamente. He visto esto antes y su dominio invariablemente se convierte en un montón de dtos anémicos, no en modelos ricos.
Andy
Esas diapositivas cubren la explicación con respecto a la capa de servicio y este gráfico de arriba es bastante ordenado: slideshare.net/ShwetaGhate2/…
Marc Juchli
9

Esta es una de esas cosas que realmente depende del caso de uso. El objetivo general de una capa de servicio es consolidar la lógica de negocios juntos. Esto significa que varios controladores pueden llamar al mismo UserService.MakeHimPay () sin preocuparse realmente de cómo se realiza el pago. Lo que sucede en el servicio puede ser tan simple como modificar una propiedad de objeto o puede estar haciendo una lógica compleja que trata con otros servicios (es decir, llamar a servicios de terceros, llamar a la lógica de validación o incluso simplemente guardar algo en la base de datos). )

Esto no significa que tenga que quitar TODA la lógica de los objetos de dominio. A veces tiene más sentido tener un método en el objeto de dominio que haga algunos cálculos sobre sí mismo. En su ejemplo final, el servicio es una capa redundante sobre el repositorio / objeto de dominio. Proporciona un buen búfer contra los cambios de requisitos, pero realmente no es necesario. Si cree que necesita un servicio, intente que haga la lógica simple "modificar propiedad X en objeto Y" en lugar del objeto de dominio. La lógica en las clases de dominio tiende a caer en el "cálculo de este valor de los campos" en lugar de exponer todos los campos a través de captadores / establecedores.

Firelore
fuente
2
Su postura a favor de una capa de servicio con lógica empresarial tiene mucho sentido, pero esto todavía deja algunas preguntas. En mi publicación he citado varias fuentes respetables que hablan de la capa de servicio como un vacío de fachada de cualquier lógica de negocios. Esto está en contraste directo con tu respuesta, ¿podrías aclarar esta diferencia?
Jeroen Vannevel
55
Creo que realmente depende del TIPO de lógica de negocios y otros factores, como el lenguaje que se utiliza. Parte de la lógica empresarial no se ajusta muy bien a los objetos de dominio. Un ejemplo es el filtrado / clasificación de resultados después de retirarlos de la base de datos. ES lógica de negocios, pero no tiene sentido en el objeto de dominio. Creo que los servicios se utilizan mejor para una lógica simple o para transformar los resultados, y la lógica en el dominio es más útil cuando se trata de guardar datos o calcular datos del objeto.
Firelore
8

La forma más fácil de ilustrar por qué los programadores evitan poner la lógica de dominio en los objetos de dominio es que generalmente se enfrentan a una situación de "¿dónde pongo la lógica de validación?" Tome este objeto de dominio por ejemplo:

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            if(value < 0) throw new ArgumentOutOfRangeException("value");
            this.someProperty = value;
        }
    }
}

Entonces tenemos algo de lógica de validación básica en el setter (no puede ser negativo). El problema es que realmente no puedes reutilizar esta lógica. En algún lugar hay una pantalla o un ViewModel o un Controlador que necesita validación antes de que realmente confirme el cambio al objeto de dominio, porque necesita informar al usuario antes o cuando hace clic en el botón Guardar que no puede hacer eso, y por qué . Probar una excepción cuando llamas al setter es un truco feo porque realmente deberías haber hecho toda la validación antes de comenzar la transacción.

Es por eso que la gente mueve la lógica de validación a algún tipo de servicio, como MyEntityValidator. Entonces, la entidad y la lógica de llamada pueden obtener una referencia al servicio de validación y reutilizarlo.

Si no hace eso y todavía desea reutilizar la lógica de validación, termina poniéndola en métodos estáticos de la clase de entidad:

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            string message;
            if(!TryValidateSomeProperty(value, out message)) 
            {
                throw new ArgumentOutOfRangeException("value", message);
            }
            this.someProperty = value;
        }
    }

    public static bool TryValidateSomeProperty(int value, out string message)
    {
        if(value < 0)
        {
            message = "Some Property cannot be negative.";
            return false;
        }
        message = string.Empty;
        return true;
    }
}

Esto hará que su modelo de dominio sea menos "anémico" y mantenga la lógica de validación al lado de la propiedad, lo cual es genial, pero no creo que a nadie le gusten los métodos estáticos.

Scott Whitlock
fuente
1
Entonces, ¿cuál es la mejor solución para la validación en su opinión?
Flashrunner
3
@Flashrunner: mi lógica de validación está definitivamente en la capa de lógica de negocios, pero en algunos casos también está duplicada en las capas de entidad y base de datos. La capa empresarial lo maneja bien informando al usuario, etc., pero las otras capas simplemente lanzan errores / excepciones y evitan que el programador (yo mismo) cometa errores que corrompan los datos.
Scott Whitlock
6

Creo que la respuesta es clara si lees el artículo del Modelo de dominio anémico de Martin Fowler .

Eliminar la lógica de negocios, que es el dominio, del modelo de dominio es esencialmente romper el diseño orientado a objetos.

Repasemos el concepto orientado a objetos más básico: un objeto encapsula datos y operaciones. Por ejemplo, cerrar una cuenta es una operación que un objeto de cuenta debe realizar en sí mismo; por lo tanto, tener una capa de servicio que realice esa operación no es una solución orientada a objetos. Es de procedimiento, y es a lo que se refiere Martin Fowler cuando habla de un modelo de dominio anémico.

Si tiene una capa de servicio que cierra la cuenta, en lugar de que el objeto de la cuenta se cierre, no tiene un objeto de cuenta real. El "objeto" de su cuenta es simplemente una estructura de datos. Con lo que termina, como sugiere Martin Fowler, es un montón de bolsas con captadores y colocadores.

Carlos A Merighe - Utah
fuente
1
Editado En realidad, encontré esta explicación bastante útil y no creo que merezca votos negativos.
BadHorsie
1
Hay una desventaja en gran parte supervisada de los modelos ricos. Y eso es que los desarrolladores incorporan algo ligeramente relacionado con el modelo. ¿El estado de abierto / cerrado es un atributo de la cuenta? ¿Qué hay del dueño? Y el banco? ¿Deben ser todos referenciados por la cuenta? ¿Cerraría una cuenta hablando con un banco o directamente a través de la cuenta? Con los modelos anémicos, esas conexiones no son una parte inherente de los modelos, sino que se crean cuando se trabaja con esos modelos en otras clases (llámelos servicios o gerentes).
Hubert Grzeskowiak
4

¿Cómo implementaría su lógica de negocios en la capa de servicio? Cuando realiza un pago de un usuario, está creando un pago, no solo deduciendo un valor de una propiedad.

Su método de pago debe crear un registro de pago, aumentar la deuda de ese usuario y mantener todo esto en sus repositorios. Hacer esto en un método de servicio es increíblemente sencillo, y también puede envolver toda la operación en una transacción. Hacer lo mismo en un modelo de dominio agregado es mucho más problemático.

Sr. cochese
fuente
2

La versión tl; dr:
Mis experiencias y opiniones dicen que cualquier objeto que tenga lógica de negocios debería ser parte del modelo de dominio. El modelo de datos probablemente no debería tener ninguna lógica en absoluto. Es probable que los servicios los unan y aborden las preocupaciones transversales (bases de datos, registro, etc.). Sin embargo, la respuesta aceptada es la más práctica.

La versión más larga, a la que otros han aludido, es que existe un error en la palabra "modelo". La publicación cambia entre el modelo de datos y el modelo de dominio como si fueran iguales, lo cual es un error muy común. También puede haber una ligera equivocación en la palabra "servicio".

En términos prácticos, no debe tener un servicio que realice cambios en ningún objeto de dominio; La razón de esto es que su servicio probablemente tendrá algún método para cada propiedad en su objeto a fin de cambiar el valor de esa propiedad. Esto es un problema porque entonces, si tiene una interfaz para su objeto (o incluso si no), el servicio ya no sigue el Principio Abierto-Cerrado; en cambio, cada vez que agrega más datos a su modelo (independientemente del dominio frente a los datos), termina teniendo que agregar más funciones a su servicio. Hay ciertas formas de evitarlo, pero esta es la razón más común por la que he visto fallar las aplicaciones "empresariales", especialmente cuando esas organizaciones piensan que "empresa" significa "tener una interfaz para cada objeto en el sistema". ¿Te imaginas agregar nuevos métodos a una interfaz, luego a dos o tres implementaciones diferentes (¿la en la aplicación, la implementación simulada y la de depuración, la en memoria?), ¿solo para una sola propiedad en su modelo? Suena como una idea terrible para mí.

Aquí hay un problema más largo que no voy a abordar, pero la esencia es esta: la programación orientada a objetos hardcore dice que nadie fuera del objeto relevante debería poder cambiar el valor de una propiedad dentro del objeto, ni siquiera " ver "el valor de la propiedad dentro del objeto. Esto se puede aliviar haciendo que los datos sean de solo lectura. Todavía puede encontrarse con problemas como cuando mucha gente hace uso de los datos incluso como de solo lectura y tiene que cambiar el tipo de datos. Es posible que todos los consumidores tengan que cambiar para adaptarse a eso. Es por eso que, cuando haces que las API sean consumidas por todos y cada uno, se recomienda no tener propiedades / datos públicos o incluso protegidos; Es la razón por la que se inventó la POO, en última instancia.

Creo que la mayoría de las respuestas aquí, aparte de la marcada como aceptada, están nublando el problema. El marcado como aceptado es bueno, pero aún siento la necesidad de responder y acepto que la viñeta 4 es el camino a seguir, en general.

En DDD, los servicios están destinados específicamente a la situación en la que tiene una operación que no pertenece adecuadamente a ninguna raíz agregada. Debe tener cuidado aquí, porque a menudo la necesidad de un servicio puede implicar que no utilizó las raíces correctas. Pero suponiendo que lo haya hecho, un servicio se utiliza para coordinar operaciones a través de múltiples raíces, o en ocasiones para manejar inquietudes que no involucran el modelo de dominio en absoluto ...

WolfgangSenff
fuente
1

La respuesta es que depende del caso de uso. Pero en la mayoría de los escenarios genéricos, me adheriría a la lógica de negocios que se encuentra en la capa de servicio. El ejemplo que ha proporcionado es realmente simple. Sin embargo, una vez que comience a pensar en sistemas o servicios desacoplados y agregue un comportamiento transaccional, realmente querrá que suceda como parte de la capa de servicio.

Las fuentes que ha citado para que la capa de servicio carezca de lógica empresarial introduce otra capa que es la capa de negocios. En muchos escenarios, la capa de servicio y la capa empresarial se comprimen en una sola. Realmente depende de cómo desee diseñar su sistema. Puede hacer el trabajo en tres capas y seguir decorando y agregando ruido.

Lo que idealmente puede hacer es modelar servicios que abarquen la lógica de negocios para trabajar en modelos de dominio para mantener el estado . Debe intentar desacoplar los servicios tanto como sea posible.

soleado
fuente
0

En MVC, el modelo se define como la lógica de negocios. Afirmar que debería estar en otro lugar es incorrecto a menos que no esté usando MVC. Veo las capas de servicio como similares a un sistema de módulos. Le permite agrupar un conjunto de funcionalidades relacionadas en un paquete agradable. Las partes internas de esa capa de servicio tendrían un modelo haciendo el mismo trabajo que el suyo.

El modelo consta de datos de aplicación, reglas comerciales, lógica y funciones. http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

metal de piedra
fuente
0

El concepto de capa de servicio se puede ver desde la perspectiva DDD. Aaronaught lo mencionó en su respuesta, solo lo explico un poco.

Enfoque común es tener un controlador que sea específico para un tipo de cliente. Digamos, podría ser un navegador web, podría ser alguna otra aplicación, podría ser una prueba funcional. Los formatos de solicitud y respuesta pueden variar. Así que uso el servicio de aplicaciones como herramienta para utilizar una arquitectura hexagonal . Inyecto allí clases de infraestructura específicas para una solicitud concreta. Por ejemplo, así podría verse mi controlador que atiende solicitudes de navegador web:

class WebBroserController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new PayPalClient(),
                new OrderRepository()
            )
        ;

        $response = new HtmlView($responseData);
    }
}

Si estoy escribiendo una prueba funcional, quiero usar un cliente de pago falso y probablemente no necesite una respuesta html. Entonces mi controlador podría verse así:

class FunctionalTestController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new FakePayPalClient(),
                new OrderRepository(),
                $data
            )
        ;

        return new JsonData($responseData);
    }
}

Por lo tanto, el servicio de aplicaciones es un entorno que configuré para ejecutar lógica de negocios. Es donde se llaman las clases modelo, agnósticamente de una implementación de infraestructura.

Entonces, respondiendo a sus preguntas desde esta perspectiva:

¿Es un medio para simplemente extraer la lógica del controlador y ponerla dentro de un servicio?

No.

¿Se supone que forma un contrato entre el controlador y el dominio?

Bueno, uno puede llamarlo así.

¿Debería haber una capa entre el dominio y la capa de servicio?

No.


Sin embargo, existe un enfoque radicalmente diferente que niega totalmente el uso de cualquier tipo de servicio. Por ejemplo, David West en su libro Object Thinking afirma que cualquier objeto debe tener todos los recursos necesarios para hacer su trabajo. Este enfoque da como resultado, por ejemplo, el descarte de cualquier ORM .

Zapadlo
fuente
-2

Para el registro.

SRP:

  1. Modelo = Datos, aquí van el colocador y los captadores.
  2. Lógica / Servicios = aquí van las decisiones.
  3. Repositorio / DAO = aquí almacenamos permanentemente o recuperamos la información.

En este caso, está bien seguir los siguientes pasos:

Si la deuda no requiere algún cálculo:

userObject.Debt = 9999;

Sin embargo, si requiere algún cálculo, entonces:

userObject.Debt= UserService.CalculateDebt(userObject)

o tambien

UserService.UpdateDebt(userObject)

Pero también, si el cálculo se realiza en la capa de persistencia, tal procedimiento de almacenamiento entonces

UserRepository.UpdateDebt(userObject)

En este caso, si queremos recuperar al usuario de la base de datos y actualizar la deuda, debemos hacerlo en varios pasos (de hecho, dos) y no es necesario envolverlo / encapsularlo en la función de un servicio.

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)

Y si requiere almacenarlo, entonces podemos agregar un tercer paso

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)
UserRepository.Save(userobject);

Sobre la solución propuesta

a) No debemos tener miedo de dejar que el desarrollador final escriba un par de en lugar de encapsularlo en una función.

b) Y sobre la interfaz, a algunos desarrolladores les encanta la interfaz y están bien, pero en varios casos, no son necesarios en absoluto.

c) El objetivo de un servicio es crear uno sin atributos, principalmente porque podemos usar funciones compartidas / estáticas. También es fácil realizar pruebas unitarias.

magallanes
fuente
¿Cómo responde esto a la pregunta que se hace: ¿Qué tan precisa es "La lógica de negocios debe estar en un servicio, no en un modelo"?
mosquito
3
¿Qué tipo de sentencia es "We shouldn't be afraid to left the end-developer to write a couple of instead of encapsulate it in a function.si no fuera por mi caballo no habría pasado ese año en la universidad ' "Sólo puedo citar Lewis Negro?'.
Malaquías