"Último recurso" o por qué se necesita el primer diseño de base de datos

En este último artículo, explicaré por qué, en mi opinión, en la mayoría de los casos, al desarrollar un modelo de datos para una aplicación, debe seguir el primer enfoque de la base de datos. En lugar del enfoque "Java [cualquier otro idioma] primero", que lo lleva a una larga ruta llena de dolor y sufrimiento, tan pronto como el proyecto comienza a crecer.


imagen
"Demasiado ocupado para ser mejor" CC con licencia de Alan O'Rourke / Audience Stack . Imagen original


Este artículo está inspirado en una pregunta reciente de StackOverflow .


Interesantes discusiones en reddit / r / java y / r / programacion .


Generación de código


Para mi sorpresa, un pequeño grupo de usuarios parece haberse sorprendido por el hecho de que jOOQ está fuertemente vinculado a la generación de código fuente.


Si bien puede usar jOOQ exactamente como lo desee, la forma preferida (de acuerdo con la documentación) es comenzar con el esquema de base de datos existente, luego generar las clases de cliente necesarias (correspondientes a sus tablas) usando jOOQ, y después de eso es fácil escribir con seguridad de escritura consultas para estas tablas:


for (Record2<String, String> record : DSL.using(configuration) // ^^^^^^^^^^^^^^^^^^^^^^^ Type information derived from the // generated code referenced from the below SELECT clause .select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME) // vvvvv ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ Generated names .from(ACTOR) .orderBy(1, 2)) { // ... } 

El código se puede generar manualmente fuera del ensamblaje o automáticamente con cada ensamblaje. Por ejemplo, dicha generación puede ocurrir inmediatamente después de instalar las migraciones de Flyway , que también se pueden iniciar de forma manual o automática.


Generación de código fuente


Existen diferentes filosofías, ventajas y desventajas con respecto a estos enfoques para la generación de código que no quiero discutir en este artículo. Pero, en esencia, el significado del código generado es que es una representación de Java de lo que consideramos una especie de "estándar" (tanto dentro como fuera de nuestro sistema). En cierto modo, los compiladores hacen lo mismo cuando generan bytecode, código de máquina o algún otro código fuente de la fuente; como resultado, tenemos una idea de nuestro "estándar" en otro lenguaje específico.


Hay bastantes generadores de código de este tipo. Por ejemplo, XJC puede generar código Java a partir de archivos XSD o WSDL . El principio es siempre el mismo:


  • Existe algún estándar (externo o interno), como especificaciones, modelo de datos, etc.
  • Es necesario tener su propia idea de este estándar en nuestro lenguaje de programación habitual.

Y casi siempre tiene sentido generar esta vista para evitar trabajos innecesarios y errores innecesarios.


Proveedores de tipos y procesamiento de anotaciones


Cabe mencionar que otro enfoque más moderno para la generación de código en jOOQ es Type Providers ( como se hace en F # ), donde el compilador genera el código en la compilación y nunca existe en la forma original. Una herramienta similar (pero menos sofisticada) en Java son los procesadores de anotaciones como Lombok .


En ambos casos, todo es igual que en la generación de código normal, excepto:


  • No ve el código generado (¿quizás para muchos esto es una gran ventaja?)
  • Debe asegurarse de que su "referencia" esté disponible en cada compilación. Esto no causa ningún problema en el caso de Lombok, que anota directamente el código fuente, que es el "estándar" en este caso. Un poco más complicado con los modelos de bases de datos que dependen de una conexión en vivo siempre activa.

¿Cuál es el problema con la generación de código?


Además de la difícil cuestión de si generar el código de forma manual o automática, algunas personas piensan que no es necesario generar el código. La razón por la que escucho con más frecuencia es que esa generación es difícil de implementar en la tubería de CI / CD. Y sí, es verdad, porque Obtenemos gastos generales por crear y soportar infraestructura adicional, especialmente si usted es nuevo en las herramientas utilizadas (jOOQ, JAXB, Hibernate, etc.).


Si la sobrecarga de estudiar el generador de código es demasiado alta, entonces realmente habrá pocos beneficios. Pero este es el único argumento en contra. En la mayoría de los otros casos, no tiene ningún sentido escribir código manualmente, que es la representación habitual de un modelo de algo.


Muchas personas afirman que no tienen tiempo para esto, porque En este momento, debe implementar otro MVP lo antes posible. Y podrán finalizar su canalización de CI / CD en algún momento posterior. En tales casos, generalmente digo: "Estás demasiado ocupado para mejorar".


"Pero Hibernate / JPA hace que el primer desarrollo de Java sea mucho más fácil".


Si es verdad Esto es tanto una alegría como un dolor para los usuarios de Hibernate. Con él, simplemente puede escribir varios objetos del formulario:


 @Entity class Book { @Id int id; String title; } 

Y eso casi está hecho. A continuación, Hibernate asumirá toda la rutina sobre cómo definir este objeto en DDL y en el dialecto SQL deseado:


 CREATE TABLE book ( id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, title VARCHAR(50), CONSTRAINT pk_book PRIMARY KEY (id) ); CREATE INDEX i_book_title ON book (title); 

Esta es realmente una excelente manera de iniciar rápidamente el desarrollo: solo tiene que iniciar la aplicación.


Pero no todo es tan color de rosa. Todavía hay muchas preguntas:


  • ¿Hibernate generará el nombre que necesito para la clave primaria?
  • ¿Crearé el índice que necesito en el campo TÍTULO?
  • ¿Se generará un valor de ID único para cada registro?

Parece que no Pero mientras el proyecto está en desarrollo, siempre puede tirar su base de datos actual y generar todo desde cero agregando las anotaciones necesarias al modelo.
Entonces, la clase Libro en su forma final se verá así:


 @Entity @Table(name = "book", indexes = { @Index(name = "i_book_title", columnList = "title") }) class Book { @Id @GeneratedValue(strategy = IDENTITY) int id; String title; } 

Pero lo pagarás un poco más tarde


Tarde o temprano, su aplicación entra en producción y el esquema descrito dejará de funcionar:


En un sistema vivo y real, ya no puede simplemente recoger y soltar su base de datos, porque se utilizan datos y pueden costar mucho dinero.

A partir de ahora, debe escribir scripts de migración para cada cambio en el modelo de datos, por ejemplo, utilizando Flyway . Sin embargo, ¿qué pasa con las clases de tus clientes? Puede adaptarlos manualmente (lo que conducirá a un doble trabajo) o pedirle a Hibernate que los genere (pero ¿qué posibilidades hay de que los resultados de dicha generación cumplan con las expectativas?). Como resultado, puede esperar grandes problemas.


Tan pronto como el código entre en producción, es casi inmediatamente necesario hacer correcciones, y lo más rápido posible.


Y porque la instalación de las migraciones de la base de datos no está integrada en su línea de ensamblaje; deberá instalar dichos parches manualmente bajo su propio riesgo. No habrá tiempo suficiente para regresar y hacer todo bien. Solo es suficiente culpar a Hibernate por todos sus problemas.


En cambio, podría haber actuado de manera bastante diferente desde el principio. Es decir, use ruedas redondas en lugar de cuadradas.


Ir a la base de datos primero


La referencia y el control del esquema de datos se encuentra en la oficina de su DBMS. Una base de datos es el único lugar donde se define un esquema, y ​​todos los clientes tienen una copia de ese esquema, pero no al revés. Los datos están en su base de datos, y no en su cliente, por lo que tiene sentido proporcionar control del esquema y su integridad exactamente donde están los datos.


Esta es la vieja sabiduría, nada nuevo. Las claves primarias y únicas son buenas. Las claves foráneas son hermosas. Verificar las restricciones en el lado de la base de datos es maravilloso. La afirmación (cuando finalmente se implementan) son geniales.


Y eso no es todo. Por ejemplo, si está utilizando Oracle, puede especificar:


  • ¿En qué espacio de tabla está tu mesa?
  • ¿Cuál es el significado de PCTFREE que tiene?
  • ¿Cuál es el tamaño de la secuencia de caché?

Quizás todo esto no importa en sistemas pequeños, pero en sistemas más grandes no tiene que seguir el camino de los "grandes datos" hasta que exprima todos los jugos de su almacenamiento actual. Ni un solo ORM que haya visto (incluido jOOQ) le permitirá utilizar el conjunto completo de parámetros DDL que proporciona su DBMS. Los ORM ofrecen solo algunas herramientas para ayudarlo a escribir DDL.


Finalmente, un esquema bien diseñado solo debe escribirse manualmente usando un DDL específico de DBMS. Todos los DDL generados automáticamente son solo una aproximación a esto.


¿Qué pasa con el modelo del cliente?


Como se mencionó anteriormente, necesitará una cierta representación del esquema de la base de datos en el lado del cliente. No hace falta decir que esta vista debe estar sincronizada con el modelo real. Como hacerlo Por supuesto, usando generadores de código.


Todas las bases de datos proporcionan acceso a su metainformación a través del buen SQL anterior. Entonces, por ejemplo, puede obtener una lista de todas las tablas de diferentes bases de datos:


 -- H2, HSQLDB, MySQL, PostgreSQL, SQL Server SELECT table_schema, table_name FROM information_schema.tables -- DB2 SELECT tabschema, tabname FROM syscat.tables -- Oracle SELECT owner, table_name FROM all_tables -- SQLite SELECT name FROM sqlite_master -- Teradata SELECT databasename, tablename FROM dbc.tables 

Esas consultas (así como consultas similares para vistas, vistas materializadas y funciones de tabla) se ejecutan cuando se llama al método DatabaseMetaData.getTables () de un controlador JDBC específico, o en el módulo jOOQ-meta.


A partir de los resultados de tales consultas, es relativamente fácil crear cualquier representación del cliente del modelo de base de datos, independientemente de la tecnología de acceso a datos utilizada.


  • Si usa JDBC o Spring, puede crear un grupo de constantes de cadena
  • Si usa JPA, puede crear objetos usted mismo
  • Si usa jOOQ, puede crear metamodelos jOOQ

Dependiendo de la cantidad de características que ofrece su API de acceso a datos (jOOQ, JPA u otra cosa), el metamodelo generado puede ser realmente rico y completo. Como ejemplo, la función de unión implícita en jOOQ 3.11, que se basa en metainformación sobre las relaciones de claves foráneas entre sus tablas .


Ahora, cualquier cambio en el esquema de la base de datos conducirá automáticamente a una actualización del código del cliente.


Imagine que necesita cambiar el nombre de una columna en una tabla:


 ALTER TABLE book RENAME COLUMN title TO book_title; 

¿Estás seguro de que quieres hacer este trabajo dos veces? De ninguna manera Simplemente confirme este DDL, ejecute la compilación y disfrute del objeto actualizado:


 @Entity @Table(name = "book", indexes = { // Would you have thought of this? @Index(name = "i_book_title", columnList = "book_title") }) class Book { @Id @GeneratedValue(strategy = IDENTITY) int id; @Column("book_title") String **bookTitle**; } 

Además, el cliente recibido no necesita ser compilado cada vez (al menos hasta el próximo cambio en el esquema de la base de datos), ¡lo que ya puede ser una gran ventaja!
La mayoría de los cambios DDL también son cambios semánticos, no solo sintácticos. Por lo tanto, es genial ver en el código del cliente generado cuáles son exactamente los últimos cambios en la base de datos afectados.


La verdad siempre esta sola


No importa qué tecnología use, siempre debe haber un solo modelo, que es el estándar para el subsistema. O, al menos, debemos esforzarnos por esto y evitar confusiones en los negocios, donde el "estándar" está en todas partes y en ninguna parte al mismo tiempo. Hace que todo sea mucho más fácil. Por ejemplo, si está compartiendo archivos XML con algún otro sistema, probablemente esté utilizando XSD. Como metamodelo INFORMATION_SCHEMA jOOQ en formato XML: https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd


  • XSD se entiende bien
  • XSD describe perfectamente el contenido XML y permite la validación en todos los idiomas del cliente
  • XSD hace que el control de versiones sea fácil y compatible con versiones anteriores
  • XSD se puede convertir en código Java usando XJC

Prestamos especial atención al último punto. Cuando nos comunicamos con un sistema externo a través de mensajes XML, debemos estar seguros de la validez de los mensajes. Y es realmente muy fácil de hacer con cosas como JAXB, XJC y XSD. Sería una locura pensar en la idoneidad del enfoque Java-first en este caso. El XML generado en base a los objetos XML será de baja calidad, estará pobremente documentado y será difícil de extender. Y si hay un SLA para dicha interacción, se sentirá decepcionado.


Honestamente, esto es similar a lo que está sucediendo con las diversas API de JSON ahora, pero esta es una historia completamente diferente ...


¿Qué empeora las bases de datos?


Cuando se trabaja con una base de datos, todo es igual aquí. La base de datos posee los datos y también debe ser el maestro del esquema de datos. Todas las modificaciones de esquema deben realizarse directamente a través de DDL para actualizar la referencia.


Después de actualizar la referencia, todos los clientes deben actualizar sus ideas sobre el modelo. Algunos clientes pueden escribirse en Java utilizando jOOQ y / o Hibernate o JDBC. Se pueden escribir otros clientes en Perl (buena suerte para ellos) o incluso en C #. No importa El modelo principal está en la base de datos. Si bien los modelos creados con ORM son de baja calidad, no están bien documentados y son difíciles de ampliar.


Por lo tanto, no haga esto, y desde el comienzo del desarrollo. En cambio, comience con una base de datos. Cree una canalización automatizada de CI / CD. Use la generación de código para generar automáticamente un modelo de base de datos para clientes para cada compilación. Y deja de preocuparte, todo estará bien. Todo lo que se requiere es un pequeño esfuerzo inicial para configurar la infraestructura, pero como resultado obtendrá una ganancia en el proceso de desarrollo para el resto de su proyecto en los próximos años.


No gracias


Explicaciones


Para consolidar: este artículo de ninguna manera afirma que el modelo de base de datos debe aplicarse a todo su sistema (área temática, lógica de negocios, etc.). Mis declaraciones consisten solo en el hecho de que el código del cliente que interactúa con la base de datos debe ser solo una representación del esquema de la base de datos, pero no definirlo y formarlo de ninguna manera.


En las arquitecturas de dos niveles que todavía tienen un lugar para estar, el esquema de la base de datos puede ser la única fuente de información sobre el modelo de su sistema. Sin embargo, en la mayoría de los sistemas, veo el nivel de acceso a datos como un "subsistema" que encapsula un modelo de base de datos. Algo asi.


Excepciones


Como en cualquier otra buena regla, la nuestra también tiene sus excepciones (y ya advertí que el primer enfoque de la base de datos y la generación de código no siempre es la opción correcta). Estas excepciones (quizás la lista no está completa):


  • Cuando el circuito no se conoce de antemano y necesita ser investigado. Por ejemplo, usted es un proveedor de una herramienta para ayudar a los usuarios a navegar cualquier esquema. Por supuesto, no puede haber generación de código. Pero, en cualquier caso, debe lidiar con la base de datos en sí y su esquema.
  • Cuando para alguna tarea necesita crear un esquema sobre la marcha. Esto puede ser similar a una de las variaciones del patrón Entity-attribute-value , como no tienes un patrón claramente definido. Además, no hay certeza de que RDBMS en este caso sea la elección correcta.

La peculiaridad de estas excepciones es que rara vez se encuentran en la vida silvestre. En la mayoría de los casos, cuando se usan bases de datos relacionales, el esquema se conoce de antemano y es el "estándar" de su modelo, y los clientes deben trabajar con una copia de este modelo generada usando generadores de código.

Source: https://habr.com/ru/post/es413597/


All Articles