Funcionalidades de la capa de persistencia

LIBP-0013 (Libro de pautas)

La persistencia de la información es la parte más crítica en una aplicación de software.

Si la aplicación está diseñada con orientación a objetos, la persistencia se logra por serialización del objeto o almacenamiento en una base de datos. Las bases de datos más populares hoy en día son relacionales.

El modelo de objetos difiere en muchos aspectos del modelo relacional. La interfaz que une esos dos modelos se llama asociación objeto-relacional (ORM en inglés). Una capa de persistencia encapsula el comportamiento necesario para mantener los objetos. O sea: leer, escribir y borrar objetos en el almacenamiento persistente (base de datos).

Pautas

TítuloCarácter
Asociación Objeto-RelacionalObligatoria
Uso del patrón DAORecomendada
Manejo de la cacheRecomendada
Permitir la concurrencia de usuariosObligatoria
Evitar las referencias circulares entre objetos Obligatoria
Buen uso de la información oculta Recomendada
Actualización en cascadaRecomendada

Asociación Objeto-Relacional

Se debe usar un mapeador Objeto Relacional para implementar la capa de persistencia

La asociación objeto-relacional (más conocido por su nombre en inglés, Object-Relational Mapping, o sus siglas O/RM, ORM, y O/R mapping) es una técnica de programación para convertir datos entre el sistema de tipos utilizado en un lenguaje de programación orientado a objetos y el utilizado en una base de datos relacional. En la práctica esto crea una base de datos orientada a objetos virtual, sobre la base de datos relacional. Esto posibilita el uso de las características propias de la orientación a objetos (básicamente herencia y polimorfismo). Hay paquetes comerciales y de uso libre disponibles que desarrollan la asociación relacional de objetos, aunque algunos programadores prefieren crear sus propias herramientas ORM.

Un objeto está compuesto de propiedades y métodos. Como las propiedades representan a la parte estática de ese objeto, son las partes que se dotan de persistencia. Cada propiedad puede ser simple o compleja.

  • Por simple, se entiende que tiene algún tipo de datos nativos como por ejemplo: entero, coma flotante o cadena de caracteres.
  • Por complejo se entiende algún tipo definido por el usuario, ya sean objetos o estructuras.

Por relación se entiende asociación, herencia o agregación. Para dotar de persistencia las relaciones, se usan transacciones, ya que los cambios pueden incluir varias tablas.

Para vincular las relaciones, se usan los identificadores de objetos (OID). Estos OID se agregan como una columna más en la tabla donde se quiere establecer la relación. Dicha columna es una clave foránea a la tabla con la que se está relacionada. Así, queda asignada la relación. Recordar que las relaciones en el modelo relacional son siempre bidireccionales.

Uso del patrón DAO

Se recomienda crear una clase DAO por cada objeto de negocio del sistema

El patrón CRUD, reconocido como el patrón mas importante del acceso a datos indica que cada objeto debe ser creado en base de datos para que sea persistente. Para esto es necesario asegurar que existen operaciones que permiten a la capa inferior (de acceso a datos) leerlo, actualizarlo o simplemente borrarlo.

Mediante la implementación de DAO's pueden proporcionarse las operaciones CRUD necesarias para cada aplicación. No es obligatorio que cada DAO implemente todas las operaciones CRUD (puede no ser necesario en la lógica funcional del sistema). En general (aunque esto es una decisión de diseño), por cada objeto de negocio en el sistema, crearemos un DAO distinto.

Manejo de la cache

Se recomienda el uso de la cache en las aplicaciones, para reducir tiempos de lectura. Pero con precaución ante los posibles problemas de integridad de datos que se pueden dar.

En la mayoría de las aplicaciones, se aplica la regla del 80-20 en cuanto al acceso a datos, el 80% de accesos de lectura accede al 20% de los datos de la aplicación. Esto significa que hay un conjunto de datos dinámicos que son relevantes a todos los usuarios del sistema, y por lo tanto accedido con mas frecuencia. Las aplicaciones de sincronización de caché normalmente necesitan escalarse para manejar grandes cargas transaccionales. Así, múltiples instancias se pueden procesar simultáneamente.

Es un problema serio para el acceso a datos desde la aplicación, especialmente cuando los datos involucrados necesitan actualizarse dinámicamente a través de esas instancias. Para asegurar la integridad de datos, la base de datos comúnmente juega el rol de árbitro para todos los datos de la aplicación. Es un rol muy importante dado que los datos representan la parte de valor más significativa de una organización. Desafortunadamente, este rol no está fácilmente distribuido sin introducir problemas importantes, especialmente en un entorno transaccional.

Es común para la base de datos usar replicación para lograr datos sincronizados, pero comúnmente ofrece una copia offline del estado de los datos más que una instancia secundaria activa. Es posible usar bases de datos que puedan soportar múltiples instancias activas, pero se pueden volver caras en cuanto a mantenimiento y escalabilidad, debido a que introducen el bloqueo de objetos y la latencia de distribución. La mayoría de los sistemas usan una única base de datos activa, con múltiples servidores conectados directamente a ella, soportando un número variable de clientes.

En esta arquitectura, la carga en la base de datos se incrementará linealmente con el número de instancias de la aplicación en uso, a menos que se emplee alguna caché. Pero implementar un mecanismo de caché en esta arquitectura puede traer muchos problemas, incluso corrupción en los datos, porque la caché en el servidor 1 no conocerá los cambios en el servidor 2.

Permitir la concurrencia de usuarios

Par la mayoría de los casos se recomienda el uso del bloqueo optimista, que es soportado por la mayoría de los motores de persistencia como la opción por defecto. El bloqueo pesimista se empleará solo en casos concretos.

La capa de persistencia debe permitir que múltiples usuarios trabajen en la misma base de datos y proteger los datos de ser escritos erróneamente. También es importante minimizar las restricciones en su capacidad concurrente para ver y acceder.

La integridad de datos es un riesgo cuando dos sesiones trabajan sobre la misma tupla: la pérdida de alguna actualización está asegurada. También se puede dar el caso, cuando una sesión está leyendo los datos y la otra los está editando: una lectura inconsistente es muy probable.

Hay dos técnicas principales para el problema: bloqueo pesimista y bloqueo optimista. Con el primero, se bloquea todo acceso desde que el usuario empieza a cambiar los datos hasta que se hace COMMIT en la transacción. Mientras que en el optimista, el bloqueo se aplica cuando los datos son aplicados y se van verificando mientras los datos son escritos.

En la mayoría de los casos el bloqueo optimista es suficiente, controlando los lost-updates. Para casos concretos, por ejemplo en los que se necesite generar algo como un número de factura sin que queden huecos entre uno y otro se podría usar el bloqueo pesimista.

Evitar las referencias circulares entre objetos

Evitar las referencias circulares entre objatos de la capa de persistencia.

Se debe de intentar evitar las referencias circulares, facilitando la localización de los objetos. De esta manera, se devuelve el objeto solicitado sin necesidad de realizar un recorrido para localizarlo, lo que supone un acceso más efectivo.

Buen uso de la información oculta

No mostrar aquellos datos que se almacenan en un objeto, por motivos internos al sistema, pero que no pertenecen al modelo de datos en sí.

En ocasiones hay columnas en la tabla de BBDD que no necesitan ser vinculadas a una propiedad del objeto. Columnas que contienen información necesaria pero que no forman parte del modelo de objetos. En esta categoría entran los mecanismos de concurrencia: fecha y versión de objeto. Al leer el objeto para mostrarlo, esta información que es mantenida por el framework no tiene por que mostrarse.

Actualización en cascada

Utilizar el borrado en cascada siempre que sea posible

Utilizar la posibilidad que ofrecen los frameworks de la actualización en cascada de objetos. Una actualización en cascada permite que las modificaciones hechas a un objeto se repliquen en los objetos relacionados. De esta manera se mejora el mantenimiento de los objetos, se asegura que los cambios introducidos se replican de manera eficiente manteniendo la integridad de los datos.