Cómo crear un mejor código OO en una aplicación basada en una base de datos relacional donde la base de datos está mal diseñada

19

Estoy escribiendo una aplicación web Java que consiste principalmente en un grupo de páginas similares en las que cada página tiene varias tablas y un filtro que se aplica a esas tablas. Los datos en estas tablas provienen de una base de datos SQL.

Estoy usando myBatis como ORM, que puede no ser la mejor opción en mi caso, ya que la base de datos está mal diseñada y mybatis es una herramienta más orientada a la base de datos.

Estoy descubriendo que estoy escribiendo mucho código duplicado porque, debido al mal diseño de la base de datos, tengo que escribir consultas diferentes para cosas similares, ya que esas consultas pueden ser muy diferentes. Es decir, no puedo parametrizar fácilmente las consultas. Esto se propaga a mi código y en lugar de llenar filas en columnas en mi tabla con un bucle simple, tengo un código como:

obtener datos A (p1, ..., pi);

obtener datos B (p1, ..., pi);

obtener datos de C (p1, ..., pi);

obtener datos D (p1, ..., pi); ...

Y esto pronto explota cuando tenemos diferentes tablas con diferentes columnas.

También agrega a la complejidad el hecho de que estoy usando "wicket", que es, en efecto, un mapeo de objetos a elementos html en la página. Por lo tanto, mi código Java se convierte en un adaptador entre la base de datos y el front-end, lo que me hace crear una gran cantidad de cableado, código repetitivo con cierta lógica entremezclada.

¿La solución correcta sería envolver los mapeadores de ORM con una capa adicional que presenta una interfaz más homogénea a la base de datos o hay una mejor manera de lidiar con este código de espagueti que estoy escribiendo?

EDITAR: más información sobre la base de datos

La base de datos contiene principalmente información de llamadas telefónicas. El diseño pobre consiste en:

Tablas con una ID artificial como clave principal que no tiene nada que ver con el conocimiento del dominio.

No hay disparadores únicos, controles o claves foráneas de ningún tipo.

Campos con un nombre genérico que coinciden con diferentes conceptos para diferentes registros.

Registros que se pueden clasificar solo cruzando con otras tablas con diferentes condiciones.

Columnas que deberían ser números o fechas almacenadas como cadenas.

Para resumir, un diseño desordenado / vago por todas partes.

DPM
fuente
77
¿Corregir el diseño de la base de datos es una opción?
RMalke
1
Por favor, explique cómo está mal diseñada la base de datos.
Tulains Córdova
@Renan Malke Stigliani Lamentablemente no, ya que existe un software heredado que depende de él, sin embargo, he reflejado algunas de las tablas con un diseño ligeramente diferente y las he completado, lo que simplifica el código. Sin embargo, no estoy orgulloso de esto y prefiero no duplicar tablas indiscriminadamente
DPM
1
Este libro puede darle algunas ideas sobre cómo puede comenzar a solucionar el problema de la base de datos y mantener el código heredado funcionando: amazon.com/…
HLGEM
44
La mayoría de los problemas que enumeras. . . no lo son El uso de claves sustitutas en lugar de claves naturales es en realidad una recomendación bastante estándar hoy en día; no es "diseño pobre" en absoluto. La falta de restricciones y el uso de tipos de columna inapropiados es un mejor ejemplo en lo que respecta al "diseño deficiente", pero en realidad no debería afectar el código de su aplicación (¿a menos que planee abusar de estos problemas?).
ruakh

Respuestas:

53

La orientación a objetos es valiosa específicamente porque surgen este tipo de escenarios y le brinda herramientas para diseñar razonablemente abstracciones que le permitan encapsular la complejidad.

La verdadera pregunta aquí es, ¿dónde encapsulas esa complejidad?

Así que déjame retroceder un momento y hablar sobre a qué 'complejidad' me refiero aquí. Su problema (tal como lo entiendo; corríjame si me equivoco) es un modelo de persistencia que no es un modelo efectivamente utilizable para las tareas que necesita completar con los datos. Puede ser efectivo y utilizable para otras tareas, pero no para sus tareas.

Entonces, ¿qué hacemos cuando tenemos datos que no presentan un buen modelo para nuestros medios?

Traducir. Es lo único que puedes hacer. Esa traducción es la 'complejidad' a la que me refiero anteriormente. Entonces, ahora que aceptamos que vamos a traducir el modelo, tenemos que decidir sobre un par de factores.

¿Necesitamos traducir ambas direcciones? ¿Ambas direcciones se traducirán de la misma manera que en:

(Tbl A, Tbl B) -> Obj X (leer)

Obj X -> (Tbl A, Tbl B) (escribir)

o ¿las actividades de inserción / actualización / eliminación representan un tipo diferente de objeto de modo que lea los datos como Obj X, pero los datos se insertan / actualizan desde Obj Y? Cuál de estas dos formas desea seguir, o si no es posible actualizar / insertar / eliminar son factores importantes en el lugar donde desea colocar la traducción.


¿Dónde traduces?

Volviendo a la primera declaración que hice en esta respuesta; OO le permite encapsular la complejidad y a lo que me refiero aquí es al hecho de que no solo debería hacerlo, sino que debe encapsular esa complejidad si desea asegurarse de que no se filtre y se filtre en todo su código. Al mismo tiempo, es importante reconocer que no puedes tener una abstracción perfecta, así que preocúpate menos por eso que por tener una muy efectiva y útil.

De nuevo ahora; tu problema es: ¿dónde pones esta complejidad? Pues tienes opciones.

Puede hacerlo en la base de datos utilizando procedimientos almacenados. Esto tiene el inconveniente de que a menudo no juega muy bien con ORM, pero eso no siempre es cierto. Los procedimientos almacenados ofrecen algunos beneficios, incluido el rendimiento a menudo. Sin embargo, los procedimientos almacenados pueden requerir mucho mantenimiento, pero depende de usted analizar su escenario particular y decir si el mantenimiento será más o menos que otras opciones. Personalmente, soy muy hábil con los procedimientos almacenados y, como tal, este hecho de talento disponible reduce los gastos generales; Nunca subestime el valor de tomar decisiones basadas en lo que usted no sabe. A veces, la solución subóptima puede ser más óptima que la solución correcta porque usted o su equipo saben cómo crearla y mantenerla mejor que la solución óptima.

Otra opción en la base de datos son las vistas. Dependiendo del servidor de su base de datos, estos pueden ser muy óptimos, subóptimos o incluso poco efectivos, uno de los inconvenientes puede ser el tiempo de consulta dependiendo de las opciones de indexación disponibles en su base de datos. Las vistas se convierten en una opción aún mejor si nunca necesita hacer ninguna modificación de datos (insertar / actualizar / eliminar).

Pasando por la base de datos, tiene el antiguo modo de espera de usar el patrón de repositorio. Este es un enfoque probado en el tiempo que puede ser muy efectivo. Los inconvenientes tienden a incluir placa de caldera, pero los repositorios bien factorizados pueden evitar cierta cantidad de esto, e incluso cuando estos dan como resultado cantidades desafortunadas de placa de caldera, los repositorios tienden a ser un código simple que es fácil de entender y mantener, además de presentar una buena API /abstracción. También los repositorios pueden ser buenos para su capacidad de prueba unitaria que se pierde con las opciones en la base de datos.

Existen herramientas como el mapeador automático que pueden hacer posible el uso de un ORM donde puedan hacer la traducción entre el modelo de base de datos de orm a modelos utilizables, pero algunas de estas herramientas pueden ser difíciles de mantener / entender comportándose más como magia; aunque crean un mínimo de código de gastos generales que resulta en menos gastos de mantenimiento cuando se entiende bien.

A continuación, se aleja cada vez más de la base de datos , lo que significa que habrá una mayor cantidad de código que se ocupará del modelo de persistencia no traducido, que será realmente desagradable. En estos escenarios, hablas de poner la capa de traducción en tu interfaz de usuario, lo que parece que puedes estar haciendo ahora. Esta es generalmente una muy mala idea, y se descompone terriblemente con el tiempo.


Ahora comencemos a hablar loco .

La Objectno es la única abstracción final que existe. Ha habido una profundidad de abstracciones desarrolladas a lo largo de los años en que la informática ha sido estudiada e incluso antes del estudio de las matemáticas. Si vamos a comenzar a ser creativos, comencemos hablando de abstracciones conocidas disponibles que hayan sido estudiadas.

Ahí está el modelo de actor.Este es un enfoque interesante porque dice que todo lo que debe hacer es enviar mensajes a otro código que delegue efectivamente todo el trabajo a ese otro código, que es muy efectivo para encapsular la complejidad de todo su código. Esto podría funcionar en la medida en que envíe un mensaje a un actor diciendo "Necesito que se envíe el Obj X a Y" y tenga un receptáculo esperando una respuesta en la ubicación Y que luego procese el Obj X. Incluso podría enviar un mensaje que indique "Necesito el Obj X y el cálculo Y, Z", y luego ni siquiera necesitas esperar; la traducción se produce al otro lado de ese mensaje y puede continuar si no necesita leer el resultado. Esto puede ser un leve abuso del modelo de actor para sus propósitos, pero todo depende;

Otro límite de encapsulación son los límites del proceso. Estos pueden usarse para segregar la complejidad de manera muy efectiva. Puede crear el código de traducción como un servicio web donde la comunicación es simple HTTP, usando SOAP, REST, o si realmente quiere su propio protocolo (no sugerido). STOMP no es del todo un mal protocolo nuevo. O utilice un servicio de daemon normal con un canal de memoria publicitado local del sistema para comunicarse nuevamente muy rápidamente utilizando el protocolo que elija. Esto en realidad tiene algunos beneficios bastante buenos:

  • Puede tener varios procesos en ejecución que hacen la traducción para el soporte de versiones anteriores y nuevas al mismo tiempo, lo que le permite actualizar el servicio de traducción para publicitar un modelo de objeto V2, y luego, por separado, en un punto posterior, actualice el código consumidor para trabajar con el nuevo objeto modelo.
  • Puede hacer cosas interesantes como fijar el proceso en un núcleo para el rendimiento, también obtiene una cantidad de seguridad de seguridad en este enfoque al hacer que sea el único proceso que se ejecuta con los privilegios de seguridad para tocar esos datos.
  • Obtendrá un límite muy fuerte cuando habla de límites de proceso que se mantendrán fijos asegurando una fuga mínima de su abstracción durante mucho tiempo porque el código de escritura en el espacio de traducción no podrá llamarse fuera del espacio de traducción ya que no compartirá el alcance del proceso, asegurando un conjunto fijo de escenarios de uso por contrato.
  • La capacidad para actualizaciones asincrónicas / sin bloqueo es más simple.

Obviamente, los inconvenientes son más mantenimiento del necesario, ya que la sobrecarga de comunicación afecta el rendimiento y el mantenimiento.


Hay una gran variedad de formas de encapsular la complejidad que pueden permitir que esa complejidad se coloque en lugares cada vez más extraños y curiosos en su sistema. Usando formas de funciones de orden superior (a menudo falsificadas usando patrones de estrategia u otras formas extrañas de patrones de objetos), puede hacer algunas cosas muy interesantes.

Así es, comencemos hablando de una mónada. Puede crear esta capa de traducción de una manera muy independiente de pequeñas funciones específicas que hacen las traducciones independientes necesarias, pero oculta todas esas funciones de traducción fuera de lo visible, por lo que son difícilmente accesibles para el código externo. Esto tiene el beneficio de reducir la dependencia de ellos, lo que les permite cambiar fácilmente sin afectar mucho código externo. Luego crea una clase que acepta funciones de orden superior (funciones anónimas, funciones lambda, objetos de estrategia, sin embargo, necesita estructurarlas) que funcionan en cualquiera de los bonitos objetos tipo modelo OO. Luego, deja que el código subyacente que acepta esas funciones realice la ejecución literal utilizando los métodos de traducción apropiados.

Esto crea un límite donde toda la traducción no solo existe en el otro lado del límite, lejos de todo su código; solo se usa en ese lado, lo que permite que el resto de su código ni siquiera sepa nada más que no sea dónde está el punto de entrada para ese límite.

Ok, sí, eso realmente es una locura, pero quién sabe; puede que estés tan loco (en serio, no emprendas mónadas con un índice de locura inferior al 88%, existe un riesgo real de lesiones corporales).

Jimmy Hoffa
fuente
44
Wow, qué respuesta extraordinariamente completa. Votaría esto más de una vez si solo SE me lo permitiera.
Marjan Venema
11
¿Cuándo sale la versión de la película?
yannis
3
@JimmyHoffa Bravo señor !!! Voy a marcar esta respuesta y mostrarle a mi hija cuando crezca.
Tombatron
4

Mi sugerencia:

Crear vistas de bases de datos que:

  1. Dar nombres significativos a las columnas.
  2. Haga el "cruce con otras tablas con diferentes condiciones" para que pueda tener esa complejidad oculta.
  3. Convierta números o fechas almacenados como cadenas en números y fechas respectivamente.
  4. Crea unicidad donde no la hay, según algunos criterios.

La idea es crear una fachada que emule un mejor diseño sobre el malo.

Luego, haga que el ORM se relacione con esa fachada en lugar de con las tablas reales.

Sin embargo, esto no simplifica las inserciones.

Tulains Córdova
fuente
El uso de vistas de bases de datos parece una gran idea y el curso de acciones más elegante que abstrae la fealdad en el nivel más bajo, por alguna razón no lo había considerado. Gracias.
DPM
3

Puedo ver cómo su esquema de base de datos existente hace que escriba código más específico y consultas para tareas que de otro modo podrían resumirse con un esquema mejor diseñado, pero no debería obstaculizar su capacidad para escribir un buen código orientado a objetos.

  • Recuerda los principios SÓLIDOS .
  • Escriba código que pueda probarse fácilmente en la unidad (que a menudo viene siguiendo los principios SÓLIDOS).
  • Mantenga su lógica de negocios separada de su lógica de visualización.
  • Lea la documentación y ejemplos de Apache Wicket : este marco probablemente puede ahorrarle más código repetitivo de lo que cree, así que aprenda a usarlo de manera efectiva.
  • Mantenga la lógica que tiene que lidiar con la base de datos en una capa separada que proporciona una interfaz limpia con la que su lógica de negocios puede trabajar. De esta manera, si usted (o un futuro mantenedor) alguna vez tiene la oportunidad de mejorar el esquema, puede hacerlo sin demasiados cambios en la lógica empresarial.

Cuando se encuentra trabajando con un esquema de base de datos que no es perfecto, es fácil quejarse de todas las formas en que dificulta su trabajo, pero en algún momento tiene que dejar de lado esas quejas y sacar el máximo provecho.

Piense en ello como una oportunidad para usar su creatividad para escribir código limpio, reutilizable y fácil de mantener a pesar del esquema imperfecto.

Mike Partridge
fuente
1

Respondiendo a su pregunta inicial sobre un mejor código orientado a objetos, sugiero usar objetos que hablen SQL . ORM intrínsecamente va en contra de los principios orientados a objetos, ya que opera sobre un objeto, y el objeto en OOP es una entidad autosuficiente, que tiene todos los recursos para lograr su objetivo. Estoy seguro de que este enfoque podría simplificar su código.

Hablando sobre el espacio problemático, es decir, su dominio, trataría de identificar las raíces agregadas . Estos son límites de consistencia de su dominio. Límites que deben mantener su consistencia en todo momento. Los agregados se comunican a través de eventos de dominio. Si tiene un sistema lo suficientemente grande, probablemente debería comenzar a dividirlo en subsistemas (llámelo SOA, Microservicio, Sistemas autónomos, etc.)

También consideraría usar CQRS: puede simplificar enormemente tanto su lado de escritura como el de lectura. Asegúrese de leer el artículo de Udi Dahan sobre este tema.

Zapadlo
fuente