Hoy entré en un acalorado debate con otro desarrollador de mi organización sobre dónde y cómo agregar métodos a las clases mapeadas de la base de datos. Usamos sqlalchemy
, y una parte importante de la base de código existente en nuestros modelos de base de datos es poco más que una bolsa de propiedades mapeadas con un nombre de clase, una traducción casi mecánica de las tablas de la base de datos a los objetos de Python.
En el argumento, mi posición era que el valor principal de usar un ORM era que se pueden asociar comportamientos y algoritmos de bajo nivel a las clases asignadas. Los modelos son primero las clases y, en segundo lugar, persistentes (podrían ser persistentes utilizando xml en un sistema de archivos, no es necesario que le importe). Su punto de vista era que cualquier comportamiento es "lógica de negocios", y necesariamente pertenece a cualquier lugar que no sea el modelo persistente, que debe usarse solo para la persistencia de la base de datos.
Ciertamente, creo que hay una distinción entre lo que es la lógica de negocios, y debe separarse, ya que tiene cierto aislamiento del nivel inferior de cómo se implementa, y la lógica de dominio, que creo que es la abstracción proporcionada por las clases modelo. discutí en el párrafo anterior, pero estoy teniendo dificultades para señalar qué es eso. Tengo una mejor idea de lo que podría ser la API (que, en nuestro caso, es HTTP "ReSTful"), en el sentido de que los usuarios invocan la API con lo que quieren hacer , distinto de lo que se les permite hacer y cómo se hace.
tl; dr: ¿Qué tipo de cosas pueden o deben ir en un método en una clase asignada cuando se usa un ORM, y qué se debe dejar de lado, para vivir en otra capa de abstracción?
fuente
Respuestas:
Estoy principalmente contigo; su colega parece estar discutiendo ya sea por el antipatrón del modelo de dominio anémico o por duplicar el modelo en un "modelo de persistencia" sin ningún beneficio obvio (estoy trabajando en un proyecto Java donde esto se hizo, y es un dolor de cabeza de mantenimiento masivo, como significa tres veces el trabajo cada vez que algo cambia en el modelo).
Regla general: la clase debe contener una lógica que describa hechos básicos sobre los datos que son verdaderos en todas las circunstancias. La lógica específica de un caso de uso debería estar en otro lugar. Un ejemplo es la validación, hay un artículo interesante de Martin Fowler donde señala que debe considerarse dependiente del contexto.
fuente
Esta es una decisión que realmente depende de su tamaño y escala anticipados de lo que está desarrollando. El enfoque más rígido es limitar los tipos ORM a un componente de acceso a datos y usar POCO en una biblioteca común como tipos referenciados y utilizados por todas las capas. Esto permitiría la separación física futura, así como la separación lógica. También podría decidir que debería existir una capa adicional entre la interfaz de usuario y la capa de lógica de negocios. Esto generalmente se llama una capa Fachada o Interfaz empresarial. Esta capa adicional es donde vive su "código de caso de uso". La capa Facade / BI invoca el código individualmente acoplado (por ejemplo, Facade tiene una función ProcessOrder () que llama a la lógica de negocios 1: M veces para realizar todos los pasos necesarios para procesar realmente el pedido).
Sin embargo, dicho todo esto: muchas veces esta cantidad de arquitectura es simplemente una exageración innecesaria. Por ejemplo, codifique específicamente para un sitio web simple donde no tiene intención de empaquetar sus componentes para su reutilización. Es perfectamente válido crear un sitio web MVC y usar objetos EF para este tipo de solución. Si el sitio necesita escalar más tarde, puede examinar la agrupación o un proceso que a menudo se pierde, llamado "refactorización".
fuente
Solo recuérdele a su colega que no necesita sobreajustar los modelos como si fuera un proyecto Java. Quiero decir, comparar dos objetos persistentes es un comportamiento, pero ninguno está especificado por la capa de persistencia. Entonces la pregunta de la cerveza 6 es: ¿por qué tener clases completamente no relacionadas que describen algo sobre lo mismo? Claro, la persistencia es un aspecto lo suficientemente grande de un modelo para ser tratado por separado, pero no lo suficiente como para garantizar que sea tratado de forma distinta a todo lo demás. Si conduce su automóvil, lo lava o lo revienta, está manipulando su automóvil todo el tiempo.
Entonces, ¿por qué no componer todos estos aspectos diferentes en una sola clase de modelo? Necesita un montón de métodos de clase que se ocupen de objetos persistentes: colóquelos en una clase; tiene un montón de métodos de instancia que se ocupan de la validación; colóquelos en otro. Por último, mezclar los dos y listo! Obtuviste una representación de modelo inteligente, consciente de sí mismo y totalmente contenida allí mismo.
fuente
Además de otras respuestas, preste atención a los cavehats ocultos cuando use modelos de dominio rico con un ORM.
Tuve problemas para inyectar servicios polimórficos en las clases de modelos persistentes cuando intentaba lograr algo como el siguiente pseudocódigo:
En este caso, una organización puede requerir una
HRService
dependencia como constructor (por ejemplo). Por lo general, no puede controlar fácilmente la instanciación de sus clases de modelo cuando usa un ORM.Estaba usando Doctrine ORM y el contenedor de servicio de Symfony. Tuve que parchear el ORM de una manera no tan elegante y no tuve más remedio que separar la persistencia y los modelos comerciales. Todavía no lo he intentado con sqlachemy, pensé. Python podría resultar más flexible que PHP para estas cosas.
fuente