Entonces, una situación con la que me encuentro razonablemente a menudo es una en la que mis modelos comienzan a:
- Conviértete en monstruos con toneladas y toneladas de métodos.
O
- Permitirle pasar fragmentos de SQL para que sean lo suficientemente flexibles como para no requerir un millón de métodos diferentes
Por ejemplo, supongamos que tenemos un modelo de "widget". Comenzamos con algunos métodos básicos:
- obtener ($ id)
- insertar ($ registro)
- actualización ($ id, $ record)
- eliminar ($ id)
- getList () // obtiene una lista de widgets
Eso está muy bien, pero luego necesitamos algunos informes:
- listCreatedBetween ($ fecha_inicio, $ fecha_final)
- listPurchasedBetween ($ fecha_inicio, $ fecha_final)
- listOfPending ()
Y luego los informes comienzan a complicarse:
- listPendingCreatedBetween ($ fecha_inicio, $ fecha_final)
- listForCustomer ($ customer_id)
- listPendingCreatedBetweenForCustomer ($ customer_id, $ start_date, $ end_date)
Puedes ver dónde está creciendo ... eventualmente tenemos tantos requisitos de consulta específicos que necesito implementar toneladas y toneladas de métodos, o algún tipo de objeto de "consulta" que puedo pasar a una sola consulta> consulta método $ query) ...
... o simplemente muerde la bala y comienza a hacer algo como esto:
- list = MyModel-> query ("fecha_inicio> X Y fecha_final <Y Y pendiente = 1 Y cliente_id = Z")
Hay un cierto atractivo para tener un método como ese en lugar de 50 millones de otros métodos más específicos ... pero a veces se siente "mal" meter un montón de lo que es básicamente SQL en el controlador.
¿Hay una forma "correcta" de manejar situaciones como esta? ¿Parece aceptable incluir consultas como esa en un método genérico -> query ()?
¿Hay mejores estrategias?
fuente
Respuestas:
Los patrones de arquitectura de aplicaciones empresariales de Martin Fowler describen una serie de patrones relacionados con ORM, incluido el uso del objeto de consulta, que es lo que sugeriría.
Los objetos de consulta le permiten seguir el principio de Responsabilidad única, al separar la lógica de cada consulta en objetos de estrategia administrados y mantenidos individualmente. O bien su controlador puede administrar su uso directamente o delegarlo a un controlador secundario u objeto auxiliar.
¿Tendrás muchos de ellos? Ciertamente. ¿Se pueden agrupar algunos en consultas genéricas? Si de nuevo.
¿Se puede usar la inyección de dependencia para crear los objetos a partir de metadatos? Eso es lo que hacen la mayoría de las herramientas ORM.
fuente
No hay una forma correcta de hacer esto. Muchas personas usan ORM para abstraer toda la complejidad. Algunos de los ORM más avanzados traducen expresiones de código en declaraciones SQL complicadas. Los ORM también tienen sus desventajas, sin embargo, para muchas aplicaciones, los beneficios superan los costos.
Si no está trabajando con un conjunto de datos masivo, lo más simple es seleccionar toda la tabla en la memoria y filtrar el código.
Para las aplicaciones de informes internos, este enfoque probablemente esté bien. Si el conjunto de datos es realmente grande, comenzará a necesitar muchos métodos personalizados, así como índices adecuados en su tabla.
fuente
Algunos ORM le permiten construir consultas complejas a partir de métodos básicos. Por ejemplo
es una consulta perfectamente válida en el Django ORM .
La idea es que tenga algún generador de consultas (en este caso
Purchase.objects
) cuyo estado interno represente información sobre una consulta. Métodos comoget
,filter
,exclude
,order_by
son válidos y devuelven un nuevo generador de consultas con un estado actualizado. Estos objetos implementan una interfaz iterable, de modo que cuando itera sobre ellos, se realiza la consulta y obtiene los resultados de la consulta construida hasta ahora. Aunque este ejemplo está tomado de Django, verá la misma estructura en muchos otros ORM.fuente
Hay un tercer enfoque.
Su ejemplo específico exhibe un crecimiento exponencial en la cantidad de métodos necesarios a medida que crece la cantidad de características requeridas: queremos la capacidad de ofrecer consultas avanzadas, combinando cada función de consulta ... si lo hacemos agregando métodos, tenemos un método para un consulta básica, dos si agregamos una función opcional, cuatro si agregamos dos, ocho si agregamos tres, 2 ^ n si agregamos n funciones.
Obviamente, eso no se puede mantener más allá de tres o cuatro características, y hay un mal olor de muchos códigos estrechamente relacionados que casi se pegan entre los métodos.
Puede evitar eso agregando un objeto de datos para contener los parámetros y tener un único método que construya la consulta en función del conjunto de parámetros proporcionados (o no proporcionados). En ese caso, agregar una nueva característica como un rango de fechas es tan simple como agregar setters y getters para el rango de fechas a su objeto de datos, y luego agregar un poco de código donde se construye la consulta parametrizada:
... y donde se agregan los parámetros a la consulta:
Este enfoque permite el crecimiento lineal del código a medida que se agregan características, sin tener que permitir consultas arbitrarias y no parametrizadas.
fuente
Creo que el consenso general es mantener el mayor acceso posible a los datos en sus modelos en MVC. Uno de los otros principios de diseño es mover algunas de sus consultas más genéricas (las que no están directamente relacionadas con su modelo) a un nivel más alto y más abstracto donde puede permitir que otros modelos también lo utilicen. (En RoR, tenemos algo llamado marco) También hay otra cosa que debe tener en cuenta y es la capacidad de mantenimiento de su código. A medida que su proyecto crezca, si tiene acceso a los datos en los controladores, será cada vez más difícil rastrearlo (actualmente estamos enfrentando este problema en un gran proyecto) Los modelos, aunque repletos de métodos, proporcionan un único punto de contacto para cualquier controlador que podría terminar haciendo preguntas desde las tablas. (Esto también puede conducir a una reutilización del código que a su vez es beneficioso)
fuente
La interfaz de la capa de servicio puede tener muchos métodos, pero la llamada a la base de datos puede tener solo uno.
Una base de datos tiene 4 operaciones principales
Otro método opcional puede ser ejecutar alguna operación de base de datos que no se encuentre dentro de las operaciones básicas de DB. Llamemos a eso Ejecutar.
Insertar y Actualizaciones se pueden combinar en una sola operación, llamada Guardar.
Muchos de sus métodos son de consulta. Por lo tanto, puede crear una interfaz genérica para satisfacer la mayoría de las necesidades inmediatas. Aquí hay una interfaz genérica de muestra:
El objeto de transferencia de datos es genérico y contendría todos sus filtros, parámetros, ordenación, etc. La capa de datos sería responsable de analizar y extraer esto y configurar la operación en la base de datos a través de procedimientos almacenados, sql parametrizado, linq, etc. Por lo tanto, SQL no se pasa entre capas. Esto suele ser lo que hace un ORM, pero puede rodar el suyo y tener su propio mapeo.
Entonces, en tu caso tienes Widgets. Los widgets implementarían la interfaz IPOCO.
Entonces, en su modelo de capa de servicio tendría
getList().
Necesitaría una capa de mapeo para manejar la
getList
transformación eny viceversa. Como otros han mencionado, en algún momento esto se hace a través de un ORM, pero finalmente terminas con un montón de código repetitivo, especialmente si tienes cientos de tablas. El ORM crea mágicamente SQL parametizado y lo ejecuta contra la base de datos. Si realiza el suyo propio, adicionalmente en la capa de datos, se necesitarían mapeadores para configurar el SP, linq, etc. (Básicamente, el sql va a la base de datos).
Como se mencionó anteriormente, el DTO es un objeto compuesto por composición. Quizás uno de los objetos que contiene es un objeto llamado QueryParameters. Estos serían todos los parámetros para la consulta que la consulta configuraría y utilizaría. Otro objeto sería una Lista de objetos devueltos de consultas, actualizaciones, ext. Esta es la carga útil. En este caso, la carga útil sería una Lista de widgets.
Entonces, la estrategia básica es:
En su caso, creo que el modelo podría tener muchos métodos, pero de manera óptima desea que la llamada a la base de datos sea genérica. Todavía terminas con un montón de código de mapeo repetitivo (especialmente con SP) o código ORM mágico que está creando dinámicamente el SQL parametizado para ti.
fuente