11
 Propuesta de arquitectura de un Framework de Persistencia Lic. Esteban Cesar Calabria Universidad Abierta Interamericana - Maestría en Tecnología Informática Tópicos Avanzados De Bases de Datos [email protected] Abstract  Este trabajo estudia distintas alternativas a la hora de diseñar un framework de persistencia mapeo objeto relacional (ORM). Se analiza desde el punto de vista arquitectónico mostrando los distintos problemas que  surgen y sus soluciones. 1. Introducción Al día de hoy, las bases de datos relacionales son la solución más utilizada por los sistemas empresariales para almacenar grandes volúmenes de información. Como contrapartida, éste tipo de sistemas poseen una lógica de negocios que suele recurrir al paradigma orientado a objetos como marco para modelar y resolver su complejidad inherente. Esto genera un problema. El mundo de las bases de datos y los objetos no siempre se llevan tan bien como uno deseara. Esto requiere un es fuerzo importante para compatibilizarlos. Un framework de persistencia transporta objeto s en memoria desde y hacia un almacenamiento permanente, siendo una base de datos relacional el medio más utilizado. El framework maneja el mapeo de los objetos contra la base de datos. Abstrae al desarrollador del SQL y resuelve diversos temas como el manejo de concurrencia. Existen numerosos frameworks de persistencia tanto open source como comerciales. En el primer rubro  podemos citar a Hibernate[4], Apache ObjectRelationalBridge (OJB) [5] , Toque[6], Cayene[7], TJDO[8], jaxor[9], JDBM[10], pBeans[11], SimpleORM[12], Java Ultra-Lite Persistence [13], Jpox[14], IBatis[15], Smyle[16], Speedo[17], XORM[18], JDBC Persistence[19] y Persistence Application Toolkit (PAT) [20]. Este paper analiza la persistencia de objetos vista desde el diseño y construcción de un framework. Surge del trabajo de campo realizado en el desarrollo de uno en Delphi para una empresa que buscaba realizar futuros desarrollos con la herramienta. Durante su desarrollo fueron varias las lecciones aprendidas, muchas de las cuales quedarán sentadas en este trabajo. Aún así, no es el objetivo propuesto solamente describir la arquitectura de es e framework sino  presentar un estudio detallado sobre la persistencia de objetos. Tampoco se apunta a una tecnología en particular sino que se tratará de tener la amplitud suficiente para poder abarcar conceptos aplicables a varias plataformas. Como  principales opciones se hará referencia a Java, Delphi y .NET, aclarándose cuando se hable de la implementación en alguna de esas tecnologías. 1.1. Contexto Para contextualizar la situación estamos trabajando en un entorno de al menos 3 capas, Presentación, Lógica de  Negocios y Persistencia [Fig 1.1.1] Presentación Lógica de Negocios Persistencia [Fig 1.1.1] Esquema en tres capas Hemos optado por realizar un Domain Module [1] o modelo de negocios complejo, aprovechando todas las ventajas que nos provee la programación orientada a objetos y los patrones de diseño [2]. A los objetos de esta capa, responsable de resolver la problemática para la cual el sistema fue construido, los llamaremos objetos de negocio o domain objects. Esta es una organización conceptual donde discutiremos si conviene que la Lógica de negocios se comunique directamente con la persistencia (existen llamadas a métodos de la capa de persistencia por parte de objetos de la lógica de negocio) o que si la lógica de negocios esté aislada de la capa de persistencia desconociendo la existencia de la misma. En el primer caso la capa de persistencia se asemejará más a un  gateway [1], encapsulando el acceso a la base de datos.

Arquitectura Framework Persistencia

  • Upload
    esteban

  • View
    255

  • Download
    0

Embed Size (px)

DESCRIPTION

El trabajo propone un posible diseño de un framework de persistencia.

Citation preview

5/11/2018 Arquitectura Framework Persistencia - slidepdf.com

http://slidepdf.com/reader/full/arquitectura-framework-persistencia 1/11

Propuesta de arquitectura de un Framework de Persistencia

Lic. Esteban Cesar Calabria

Universidad Abierta Interamericana - Maestría en Tecnología Informática

Tópicos Avanzados De Bases de Datos

[email protected] 

Abstract

 Este trabajo estudia distintas alternativas a la hora de

diseñar un framework de persistencia mapeo objeto

relacional (ORM). Se analiza desde el punto de vista

arquitectónico mostrando los distintos problemas que

 surgen y sus soluciones.

1. Introducción

Al día de hoy, las bases de datos relacionales son la

solución más utilizada por los sistemas empresariales para

almacenar grandes volúmenes de información. Como

contrapartida, éste tipo de sistemas poseen una lógica de

negocios que suele recurrir al paradigma orientado a

objetos como marco para modelar y resolver su

complejidad inherente.

Esto genera un problema. El mundo de las bases de

datos y los objetos no siempre se llevan tan bien como

uno deseara. Esto requiere un esfuerzo importante para

compatibilizarlos.

Un framework de persistencia transporta objetos enmemoria desde y hacia un almacenamiento permanente,

siendo una base de datos relacional el medio más

utilizado. El framework maneja el mapeo de los objetos

contra la base de datos. Abstrae al desarrollador del SQL

y resuelve diversos temas como el manejo de

concurrencia.

Existen numerosos frameworks de persistencia tanto

open source como comerciales. En el primer rubro

 podemos citar a Hibernate[4], Apache

ObjectRelationalBridge (OJB) [5] , Toque[6], Cayene[7],

TJDO[8], jaxor[9], JDBM[10], pBeans[11],

SimpleORM[12], Java Ultra-Lite Persistence [13],

Jpox[14], IBatis[15], Smyle[16], Speedo[17], XORM[18],JDBC Persistence[19] y Persistence Application Toolkit

(PAT) [20].

Este paper analiza la persistencia de objetos vista

desde el diseño y construcción de un framework. Surge

del trabajo de campo realizado en el desarrollo de uno en

Delphi para una empresa que buscaba realizar futuros

desarrollos con la herramienta.

Durante su desarrollo fueron varias las lecciones

aprendidas, muchas de las cuales quedarán sentadas en

este trabajo. Aún así, no es el objetivo propuesto

solamente describir la arquitectura de ese framework sino

 presentar un estudio detallado sobre la persistencia de

objetos.

Tampoco se apunta a una tecnología en particular sino

que se tratará de tener la amplitud suficiente para poder 

abarcar conceptos aplicables a varias plataformas. Como

 principales opciones se hará referencia a Java, Delphi y

.NET, aclarándose cuando se hable de la implementación

en alguna de esas tecnologías.

1.1. Contexto

Para contextualizar la situación estamos trabajando en

un entorno de al menos 3 capas, Presentación, Lógica de

 Negocios y Persistencia [Fig 1.1.1]

Presentación

Lógica de Negocios

Persistencia

[Fig 1.1.1] Esquema en tres capas 

Hemos optado por realizar un Domain Module [1] o

modelo de negocios complejo, aprovechando todas las

ventajas que nos provee la programación orientada a

objetos y los patrones de diseño [2]. A los objetos de esta

capa, responsable de resolver la problemática para la cual

el sistema fue construido, los llamaremos objetos de

negocio o domain objects.

Esta es una organización conceptual donde

discutiremos si conviene que la Lógica de negocios se

comunique directamente con la persistencia (existen

llamadas a métodos de la capa de persistencia por parte de

objetos de la lógica de negocio) o que si la lógica de

negocios esté aislada de la capa de persistencia

desconociendo la existencia de la misma. En el primer 

caso la capa de persistencia se asemejará más a un

 gateway [1], encapsulando el acceso a la base de datos.

5/11/2018 Arquitectura Framework Persistencia - slidepdf.com

http://slidepdf.com/reader/full/arquitectura-framework-persistencia 2/11

En el otro será un Mapper  [1] que media entre las dos

capas de modo que ambas se mantengan independientes

entre si.

En este contexto nos centraremos en la capa de

  persistencia y estudiaremos como diseñar y producir un

framework de persistencia mapeo objeto relacional ORM

(Object Relational Mapping)

1.2. Tipos de Framework 

Antes de comenzar, es importante hacer la distinción

entre dos tipos no excluyentes de frameworks de

 persistencia. Aquellos que llamaremos schema generators 

y los que están pensados para trabajar con bases de datos

legadas o existentes.

Los primeros generan automáticamente el esquema de la

  base de datos a partir del modelo de clases y es muy

importante en ese caso estudiar como se manejará la

evolución de dicho esquema en las sucesivas versiones del

sistema.En los frameworks preparados para trabajar con bases

de datos existentes (y en particular con bases de datos

legadas) se define por un lado el esquema de la base de

datos y por el otro los metadatos (generalmente en

archivos xml) que describen como mapear los objetos

contra ese esquema.

2. Organización del Trabajo

Este trabajo comienza proponiendo una arquitectura

del framework de persistencia organizándolo en distintos

layers: interfaz, transacciones, cache, Persistencia y Dal.

Esto se verá en la sección 3.Las siguientes secciones 3.1, 3.2, 3.3, 3.4 y 3.5

describen en profundidad cada uno de los layers y su

responsabilidad.

A continuación se tratan distintos temas que tienen que

ver con la persistencia de objetos.

En la sección 3.6 se tratan los problemas de

concurrencia que se nos pueden presentar y cómo

solucionarlos.

La sección 3.7 habla de la forma de identificar 

unívocamente a un objeto mediante un ID.

La sección 3.8 habla de los proxys [1] [2] como

mecanismo para manejar el lazy loading [1] [2].

La sección 3.9 habla de temas de performance a tener en cuenta a la hora de diseñar un framework de

 persistencia.

La sección 3.10 habla sobre cómo manejar el tema de

las consultas para recuperar objetos de la base de datos.

La sección 3.11 trata sobre cómo lidiar con aquellas

colecciones de objetos que debido a la cantidad de

elementos que poseen no son adecuadas para tenerlas en

su totalidad en la memoria principal.

3. Arquitectura

La arquitectura del framework de persistencia

  propuesto se organiza en distintos layers, cada uno con

una responsabilidad bien definida [Fig 3.1]

Interfaz

Transacciones

Cache

Persistencia

DAL

[Fig 3.1]. Layers del Framework  

Como trabajamos con un Domain Model, una opción

común es implementar un Layer Supertype [1], es decir,

un objeto base de quien hereden todos nuestros objetos de

negocio. Una de las primeras decisiones importantes es si

el framework de persistencia nos proveerá de un layer 

Supertype [1] para todos nuestros objetos de negocio

  persistentes: una clase base que podremos dar de llamar 

ObjetoPersistente u ObjetoDeNegociosPersitente. En esta

clase encontraremos  ciertos servicios propios delframework que pueden abarcar identificación,

recuperación, soporte para la generación de proxys (como

el caso de .NET) y métodos para saber el estado de

 persistencia del objeto (como por ejemplo si está sucio).

Profundizaremos cada caso en particular en el desarrollo

del presente trabajo.

Desde un punto de vista teórico esa clase base nos

genera cierto grado acople entre nuestro modelo de

negocios y el framework de persistencia, lo cual el

diseñador puede sentirse reticente a aceptar. Si se desea

un modelo de objetos que desconozca el framework de la

 persistencia se pasará por alto esta opción.

En la práctica, no obstante, a veces puede resultar útildisponer de un layer supertype provisto por el framework.

Puede ser que incluso se permita a los objetos de negocios

interactuar directamente con el framework para recuperar 

objetos con los que no están directamente relacionados.

En ese caso no hay razón para evitar implementar un layer 

supertype que tenga noción de los mecanismos de

 persistencias subyacentes.

5/11/2018 Arquitectura Framework Persistencia - slidepdf.com

http://slidepdf.com/reader/full/arquitectura-framework-persistencia 3/11

Se sugiere apuntar a que el framework y los objetos de

negocio estén lo más desacoplados posibles. Siempre que

nos encontremos en la situación donde nuestra capa de

negocios necesite los servicios del framework podemos

hacerlo de forma indirecta aplicando el patrón Separated

Interface [1] y plugin [1]. De esa forma la capa de

negocios quedará englobada en un paquete donde sóloexistirá una interfaz o clase abstracta que defina los

servicios requeridos. Otro paquete distinto será el que

contendrá la implementación. Aplicando alguna técnica

de inyección de dependencias o dependency injection 

[24][25] podemos vincular a ambas en tiempo de

configuración.

3.1. Layer de Interfaz

Esta es la capa visible del framework con la cual

interactúan los usuarios del mismo y por lo tanto provee

todos los servicios necesarios para hacerlo, tratando de

ocultar, en la medida de lo posible, los detalles deimplementación. Sin quitar la posibilidad de proveer un

conjunto de métodos que permitan parametrizar de alguna

forma el funcionamiento.

Esta capa internamente implementa la traducción de

excepciones de las capas inferiores, internacionalización,

adaptación de los mensajes de error y soporte de logging.

3.2. Layer de Transacciones

El objetivo de esta capa es coordinar la escritura de los

datos resolviendo problemas de concurrencia. Cada

transacción mantiene internamente un conjunto de

colecciones: los Dirty, los New y los Delete.La colección de los objetos  Dirty o sucios contiene

objetos preexistentes. Al momento de hacer  commit de la

transacción se deberá actualizar su representación en la

 base de datos.

Los objetos New son objetos nuevos a ser insertados en

la base de datos.

Por último los objetos Delete van a ser eliminados de la

 base de datos. La eliminación puede tratarse tanto de una

  baja física (mediante la ejecución de un Delete SQL) o

una baja lógica mediante la actualización de algún campo

de estado.

Cada transacción representa una Unit of Work  [1] y

soportan como mínimo las operaciones StartTransaction,Commit y Rollback . Se usan para iniciar una transacción,

confirmar los cambios o deshacerlos respectivamente.

Dentro del framework todo objeto a ser persistido debe

estar dentro de una transacción y los cambios sobre los

objetos de una transacción se realizan todos o no se

realiza ninguno tratando de respetar las propiedades

ACID (Atomicidad, Consistencia, Aislamiento,

Durabilidad) de las transacciones de la bases de datos.

Existe una relación entre una transacción del

framework de persistencia y una transacción de la base de

datos, aunque puede no haber relación directa entre sus

operaciones. Las transacciones de objetos pueden

mantenerse abiertas sin ser confirmadas un período de

tiempo mayor. En la base de datos esto no es

recomendable.El commit  de una transacción del framework puede

disparar un start transaction, ejecutar un conjunto de

sentencias SQL y realizar commit contra la base de datos.

Por otra parte el rollback  de la transacción del

framework simplemente opera internamente a nivel

framework descartando los cambios en memoria sin

necesidad de realizar ninguna notificación a la base de

datos.

Se pueden clasificar las transacciones entre commit 

enabled  o readonly. Este último caso aplica cuando se

recuperan un conjunto de objetos pero no se permite

realizar (o se ignoran) operaciones que modifiquen el

estado de los mismos.Existe otro tipo de clasificación posible sobre las

transacciones: simultáneas y anidadas.

Las simultáneas son transacciones que se ejecutan en

forma paralela e independiente. Cada una actúa sobre un

conjunto de objetos distintos. Por un motivo de seguridad

se prohíbe que dos transacciones simultáneas modifiquen

el mismo objeto.

Las transacciones anidadas prevén los casos donde una

operación se puede realizar en una secuencia finita de

  pasos donde cada uno se puede confirmar en forma

independiente. Una transacción puede lanzar 

subtransacciones anidadas y de esta forma implementar un

mecanismo de SavePoints [26] en memoria. Los cambiosen la base se reflejarán en el momento que se ejecute

commit en la transacción de nivel superior . 

Implementar transacciones anidadas puede llegar a ser 

muy útil si se está dispuesto a pagar el costo en

complejidad y tiempo que implica su implementación.

Cada transacción anidada debe proveer un mecanismo

 para serializar o guardarse de alguna manera el estado de

los objetos de la transacción al momento de iniciar la

misma, de modo que al hacer rollback de una transacción

anidada el estado de los objetos sea el mismo que tenían

antes de comenzarla.

En el caso de no disponer de transacciones anidadas pero

necesitar de dicha funcionalidad siempre tenemos laopción de implementarlas a nivel objeto de negocios

aplicando el patrón memento [2].

Coordinar la escritura de los datos es otra de las

responsabilidades de las transacciones. Esto implica

determinar el orden en el que se realizarán los INSERTS

 para que no haya problemas con las constraints definidas

en la base de datos. Hay motores que permiten verificar 

las constraints de la base al final de cada transacción.

Siendo ese el caso, no existen razones para no usarlo.

5/11/2018 Arquitectura Framework Persistencia - slidepdf.com

http://slidepdf.com/reader/full/arquitectura-framework-persistencia 4/11

 3.3. Layer de Cache

La principal tarea del cache es asegurarse que no exista

duplicada la representación de un mismo objeto en

memoria. Si se recupera un objeto con un ID determinado

y se vuelve a recuperar luego el mismo objeto, debemostener cuidado de no instanciar dos objetos con la misma

información. Conceptualmente son el mismo objeto pero

si se realizara una modificación sobre alguno no se verían

los cambios realizados sobre el otro. Al momento de

actualizarlos en la base de datos esto puede resultar en

updates perdidos o lost  updates [27].

Otra funcionalidad que puede ofrecernos el cache es la

  posibilidad de bloquear objetos por parte de las capas

superiores de modo de evitar que dos transacciones

simultáneas modifiquen el mismo objeto.

Por último el cache puede servirnos para mejorar la

 performance de la aplicación manteniendo los objetos en

memoria. Para ello a cada objeto en el cache se le colocaun timestamp que indica cuando fue accedido por última

vez.

Periódicamente un hilo se encarga de recorrer el cache y

 buscar aquellos objetos que no han sido accedidos en un

 período de tiempo y libera la memoria o le asigna un valor 

nulo a la referencia para que el   garbage collector lo

reclame.

Si todo el sistema interactúa con la base de datos

mediante el framework de persistencia y además no

existen otros sistemas externos que acceden a la base de

datos, esta opción resulta interesante. No obstante si la

 base de datos se accede concurrentemente por más de una

aplicación se recomienda eliminar el cache como

mecanismo de mejora de performance e incluir algún

manejo de concurrencia como los que se tratan en la

sección 3.5.

3.4. Layer de Persistencia

Esta capa mapéa los objetos contra la base de datos y

viceversa. Para ellos conoce una serie de mapeos que

describen como se realiza el pasaje. Los mapeos pueden

ser descriptos de tres formas distintas:

1) Mediante archivos externos, siendo el formato XML

el más adoptado para tal fin,2) Mediante anotaciones o atributos colocados dentro

del fuente de los objetos como en el caso de los atributos

en C# o las anotations en Java.

3) Codificados manualmente en el mismo lenguaje de

 programación.

Describir los mapeos en archivos auxiliares es la

estrategia más flexible, aunque se dificulta hacer chequeos

de sintaxis y encontrar errores de tipeo como por ejemplo

el nombre de un campo de la base de datos mal escrito.

Estos errores suelen aparecer recién en tiempo de

ejecución y suelen controlarse mediante el uso de testeos

unitarios [3].

Describir los mapeos mediante anotaciones o

codificarlos a mano acarrean también el mismo problema

salvo que se use un diccionario de datos fuertemente

tipado [28] (una generación de una jerarquía de clases querepliquen el esquema de la base de datos mediante alguna

estrategia de code generation para poder realizar 

verificaciones de nombres en tiempo de compilación

dentro el entorno de desarrollo).

La información de los mapeos -junto con otra

información que veremos más adelante- recibe el nombre

de metadata o metadatos. Cada clase tendrá su metadata 

asociada.

Los mapeos más comunes con los que nos podemos

encontrar son a nivel clase son:

•  TableMapping

•  SubclassesMapping•  SelectMapping

•  CustomClassMapper 

Algunos lenguajes como C# y Delphi manejan el

concepto de propiedad de un objeto. Esta recibe un

nombre, un tipo y encapsula el acceso a los atributos

 privados de un objeto a través de un setter y un getter. Las

 propiedades generalmente son parte de la interfaz pública

de una clase y proporciona una sintaxis cómoda al

 programador para trabajar con ella.

En este trabajo asumiremos que los mapeos se

realizaran principalmente sobre propiedades de los

objetos. Hay que considerar que mientras algunossoportan las propiedades como parte del lenguaje mientras

que otros como Java no lo hacen y allí se podrá tener 

alguna nomenclatura de nombres como que por ejemplo

todos los getters comienzan con la palabra get .

Los mapeos que actúan también a nivel propiedad que

son:

•  CommonFieldMap

•  TransformFieldMap

•  OneToOneMapping

•  OneToManyMapping

•  AggregateMapping,

•  ConditionalAggregate

•  FieldToQueryMap

•  ConditionalMapping

El mapeo TableMapping define la/s tablas sobre la que

se persistirán los datos de una clase debiendo

especificársele como parte del mapeo cual es el campo ID.

Los campos ID se discutirán más en detalle en la sección

3.5.

5/11/2018 Arquitectura Framework Persistencia - slidepdf.com

http://slidepdf.com/reader/full/arquitectura-framework-persistencia 5/11

Para el mapeo de sublases las alternativas para persistir 

una jerarquía son Single Table Inheritance [1], Class

Table Inheritance [1], Concrete Table Inheritance [1] 

El mapeo SelectMapping define una consulta sql que

se utilizara para obtener los datos. Dada la naturaleza de

este mapeo generalmente se trata de objetos de solo

lectura salvo que por medio de un CustomClassMapper  se le especifique como persistir los datos.

El CustomClassMapper define el nombre de una clase

que respeta una interfaz definida por el framework en la

cual se codifica en forma manual un DataMapper [1] que

especifica la forma de persistir y recuperar los objetos

desde y hacia la base de datos.

El CommonFieldMap mapéa una propiedad del objeto

contra un campo de la base de datos debiendo

especificarse el nombre de ambos.

Como contrapartida el TransformFieldMap permite

mapear un campo de la base contra una propiedad del

objeto realizando ciertas transformaciones en el medio

como que por ejemplo el valor 0 del campo se trasformaraen la cadena de caracteres Masculino en el objeto.

El OneToOneMapping es el encargado de mapear una

relación uno a uno entre los objetos debiendo realizar las

consiguientes transformaciones de claves foráneas en el

mundo de la base de datos a referencias en el mundo de

los objetos.

El OneToManyMapping corresponde al mapeo uno a

muchos donde hay en el medio una colección de objetos

involucrada.

Dentro de un   Domain Model [1] tenemos un tipo de

objetos especiales que se suelen recibir el nombre de

llama  Aggregates o Value Objects [1]. Son objetos

simples y pequeños, como por ejemplo un rango de fechasy generalmente no tienen ID. Su ciclo de vida es

generalmente controlado por el objeto contenedor en el

sentido que solo tiene sentido que existan si son

contenidos por alguien (o para algún cálculo interno).

El  AggregateMapping  que se aplica sobre los Value

Objects. Es un mapeo compuesto que internamente

contiene el resto de los mapeos mencionados.

El ConditionalAggregateMapping  es un caso

extendido del  AggregateMapping donde un campo de la

  base de datos especifica la subclase concreta del

 Aggregate. Este mapeo resulta sumamente útil para

mapear un patrón State[2] o Strategy[2] que con otros

mecanismos de persistencia resulta difícil de implementar.El  FieldToQueryMap mapea una propiedad de solo

lectura del objeto contra un query o consulta contra la

 base de datos que será de sólo lectura y no intervendrá en

ningún momento en la actualización del objeto.

Además de los mapeos, los metadatos de los objetos

  pueden marcar a las propiedades como mutables o no

mutables asumiéndose alguna de ambas posibilidades por 

defecto.

Una propiedad no mutable asegura que el código de su

getter asociado no modifica la representación interna. Esto

se utilizará en las consultas como veremos en la sección

3.8 y podrá ser verificado en los proxys como se estudiará

en la sección 3.6.

3.5. Database Abstraction Layer

Los distintos entornos de desarrollos y lenguajes de

  programación traen frameworks y librerías (APIs) para

 permitir la interacción con la base de datos. Estas pueden

variar según el vendedor de la base de datos.

El objetivo de esta capa es ofrecer una interfaz común

y conocida que para comunicarse con la base de datos.

Debe ser capaz de abstraernos de las pequeñas diferencias

 propias de cada motor, por ejemplo en las variaciones de

la sintaxis de las consultas SQL.

Esta capa es candidata a recibir implementaciones

alternativas según el vendedor de la base de datos. Por lo

tanto en su diseño es importante pensarla en términos declase e interfaces abstractas que abstraigan esas

diferencias.

Las subclases pueden variar según Base de Datos.

Dichas implementación deberán ser intercambiables y se

debe prever alguna forma de configurar cual se utilizará.

Podemos esta capa alrededor de tres formas no

excluyentes. Como una Fachada [1] o API, a través de

query objects o orientarla a recordset.

La primer opción es bastante directa y no merece

mucha mas explicación

Cuando hablamos de realizar las consultas mediante

query objects nos referimos en realidad a dos caminos

distintos. Una es utilizar un objeto que reciba una cadena

de caracteres con la sentencia SQL y la ejecute contra el

motor de la base de datos. Los entornos de desarrollo nos

  proveen una clase como el SqlCommand de ADO.NET,

Statement de Java o TQuery de Delphi para tal fin.

Si bien es posible utilizarlos directamente es una buena

  práctica y se propone como alternativa generar nuestras

  propias clases de acceso a datos que adapten las clases

que provee el lenguaje para lograr una solución menos

acoplada al entorno de desarrollo con el que se trabaja

 para facilitar migrar el framework a distintas tecnologías.

El otro camino es generar una jerarquía de objetos que

representen una sentencia SQL. Tendríamos el objeto

Select , Update,  Insert  y  Delete que tienen propiedades

como las tablas y los campos para generar las sentencias

SQL mediante estos objetos en lugar de trabajar sobre una

cadena de caracteres.

Por ultimo nos queda la opción de los recordsets. Un

recordset [1] es una representación en memoria que

replica la estructura de la base de datos. Organiza la

información en forma tabular en tablas, filas (rows) y

columnas (columns).

5/11/2018 Arquitectura Framework Persistencia - slidepdf.com

http://slidepdf.com/reader/full/arquitectura-framework-persistencia 6/11

 [Fig]

Fuente: http://www.martinfowler.com/eaaCatalog/recordSet.html 

Los entornos de desarrollo modernos proveen una

implementación propia de del recordset como el DataSet

de ADO.NET, el Rowset de Java y el TDataSet de Delphi

y generalmente son independiente del vendedor de la

 base de datos.

Si esta capa funciona orientada a recordset, esta capa

se comunica con recordsets en memoria que replican la

estructura de las bases de datos los cuales entrega a las

capas superiores quien una vez que los llenan con los

datos se los devuelve a esta capa quien los convierte ensentencias SQL.

Esta opción puede ser recomendable cuando el entorno

  provee muchas funcionalidades alrededor del recordset,

como por ejemplo la generación automática de las

sentencias de SQL. La desventaja es que de esta manera el

framework nos queda más dependiente de un lenguaje en

 particular.

3.6. Concurrencia

La concurrencia es uno de los problemas más

complejos a considerar en el diseño de un framework de

  persistencia. Dentro de un mismo proceso debemosasegurarnos que no haya dos transacciones actuando sobre

el mismo objeto. El cache nos parece un punto adecuado

 para permitir bloquear objetos. Si nuestro sistema, a través

del framework de persistencia, es el único que interactúa

con la base de datos iremos bien.

Si por otra parte, como mencionamos antes, subimos

un nivel y hablamos de concurrencia entre distintos

 procesos la cosa se suele complicar un poco más. Este es

el caso de varias aplicaciones distintas que acceden a la

misma base o incluso en una aplicación cliente-servidor 

donde puede haber varios clientes conectados contra la

misma base. Es aquí donde se debe tomar la decisión si

seguir una estrategia de concurrencia optimista [1],

 pesimista [1] o ninguna.

Cuando hablamos de concurrencia a este nivel lo que

se busca es evitar el fenómeno de updates perdidos donde

una aplicación recupera un objeto y mientras esta

operando con el en memoria una segunda aplicación

interviene, recupera el mismo objeto y lo actualiza antes

de que la primer aplicación confirme sus cambios.

Cuando la primera aplicación en efecto lo haga

sobrescribirá los cambios de la segunda, dando como

resultado un update perdido.

La estrategia de concurrencia más adecuada puede

variar según el caso y según el objeto de negocio que se

considere. Por eso es muy deseable la posibilidad de

definirla para cada objeto en tiempo de configuración.

Esta información se puede especificar dentro de losmetadatos de cada clase.

Es posible el caso se juzgue que para un objeto

determinado la posibilidad de que ocurra un update

  perdido es muy baja y en el caso de que ocurra no

ocasione ningún impacto negativo en el negocio. En ese

caso es aceptable no tomar ninguna estrategia siempre y

cuando ese juicio sea acertado.

La estrategia optimista suele una alternativa viable para

  prevenir conflictos entre transacciones concurrentes

detectando el conflicto y haciendo rollback de la

transacción. [Fig. 3.6.1]. Su implementación puede

requerir el agregado de un campo timestamp a la tabla de

la base de datos que indique el instante de la ultimamodificación cada registro.

[Fig 3.6.1]

Fuente: http://martinfowler.com/eaaCatalog/optimisticOfflineLock.html  

Agregar el timestamp a cada tabla de la base de datos

resulta una tarea bastante tediosa por lo que muchas vecesse opta por crear una tabla aparte que contenga

(Nombre de la tabla, ID del registro,

el timestamp).

En la sección 3.7 se profundizará sobre el campo  ID

del registro.

Si esta última opción tampoco resulta adecuada la

alternativa es en la sentencia SQL update especificar en el

5/11/2018 Arquitectura Framework Persistencia - slidepdf.com

http://slidepdf.com/reader/full/arquitectura-framework-persistencia 7/11

where que el valor de cada uno de los campos coincida

con el que tenia antes de realizar las modificaciones. Esta

opción no requiere el agregado de tablas adicionales pero

  puede hacer que los updates sean un poco mas lentos y

requiere la tarea adicional de almacenar los valores que se

tenían originalmente al momento del recuperar el objeto.

La persistencia pesimista es un poco mas compleja porque requiere un trabajo en conjunto. [Fig. 3.6.2]

[Fig. 3.6.2 ]

Fuente: http://martinfowler.com/eaaCatalog/pessimisticOfflineLock.html 

Dos aplicaciones no pueden tomar simultáneamente el

mismo registro de la base de datos. Si un registro esta

tomado se debe esperar que la aplicación que lo bloqueó

lo libere. Algunos motores de base de datos proveen

alguna forma de implementar una persistencia pesimista.

La ventaja de que lo implemente el motor es que si una

aplicación falla automáticamente el registro es liberado

aunque cada vendedor lo implementa de una forma

distinta.

Sino hay que hacerlo manualmente con algún campo o

tabla donde se marquen los registros tomados. Esto

requiere una tarea conjunta entre las distintas aplicaciones

que acceden a la base de datos y puede hacer que queden

objetos tomados que nunca se liberan si la aplicación que

los tomos sufre un desperfecto que le impide liberarlo.

Esto se subsana con un tiempo máximo durante el cual

se puede reservar un objeto aunque es un problema

complejo de considerar.

3.7. Identificación

Al considerar recuperar objetos con el framework de

 persistencia, el primer caso a tener en cuenta es recuperar 

un objeto en particular. Este escenario surge la necesidad

de disponer de un mecanismo para poder identificar 

unívocamente cada objeto de nuestro sistema.

La alternativa más común es asignar a cada objeto un ID

sin significado de negocios. Esta estrategia nos provee

varias ventajas ya que proporciona una forma genérica de

identificar un objeto independiente del problema de

negocios a resolver.

Para soportarla, en la base de datos debe existir un

campo para mapear contra ese ID que convenientementese recomienda que coincida con la clave primaria de la

tabla y por consiguiente estar indexada.

Si se dispone de un Layer Supertype [1] para todos los

objetos de negocios y el mismo sabe de la existencia del

framework de persistencia suele resultar cómodo incluir 

un método estático encargado de recuperar un objeto de

una clase de la forma:

Cliente c = (Cliente) Cliente.Recuperar(1);

Esta forma resulta bastante intuitiva para un

desarrollador que desee recuperar el objeto de la clase

cliente con el ID número 1.

Cuando hablamos de campos ID entran en juegos varias

de decisiones de diseño. Primero si haremos un ID único

 por tabla o un ID único para todo el sistema.

Para el primer caso la alternativa de los campos auto

increméntales nos proporciona una solución dependiente

del vendedor o vendedor de la base de datos que además

debe provee un mecanismo para obtener el ID del registro

a insertar para resolver problemas de claves foráneas.

Si en vez de los campos auto incrementales utilizamos

alguna consulta especial de la forma select max (ID)

from tabla debemos prestar atención de que dicha

consulta bloqueará la tabla durante lo que dure la

transacción.La tercer opción es disponer de una tabla adicional con

un par de campos (nombre de tabla, ultimo

ID) que nos provea un poco más de control que la opción

anterior.

Para el caso de tener un ID global para todo el sistema,

si es un valor entero tiene que ser lo suficientemente

grande como para no quedarnos sin ID disponibles. Un

entero de 32 bits no suele ser suficiente para las

magnitudes de datos que manejan las bases de datos hoy

en día. Un entero de 64 bits resulta más adecuado.

Esta opción generalmente se suele implementar con una

tabla adicional que guarda el ultimo ID global. En este

caso hay que tener mucho cuidad de no bloquear dichatabla durante una transacción ya que en caso contrario no

se podrán tener transacciones simultaneas en la base de

datos.

Una alternativa posible es el uso de GUIDS (Global

Unique Identifier) como ID. Los GUID son valores

  pseudo aleatorios generados por un algoritmo de tal

manera que la posibilidad de obtener dos valores

duplicados es muy baja. Los GUID son escritos

5/11/2018 Arquitectura Framework Persistencia - slidepdf.com

http://slidepdf.com/reader/full/arquitectura-framework-persistencia 8/11

empleando una palabra de cuatro bytes, tres palabras de

dos bytes y una palabra de seis bytes, como por ejemplo

{3F2504E0-4F89-11D3-9A0C-0305E82C3301} .

Debido a su longitud se suelen almacenar directamente

como una cadena de caracteres por lo cual es conveniente

verificar como se comporta el motor de datos en términos

de performance al manejar claves alfanuméricas.Claramente todas estas opciones discutidas no cubren el

universo de alternativas posibles a la hora de considerar 

Ids. Es por ello que es conveniente que la parte del

framework responsable de la generación de Ids este

  planteada como un plugin [1] pudiendo el usuario del

framework definir su propia forma de generación de Ids.

[Fig. 3.7.1]

Fuente: http://www.martinfowler.com/eaaCatalog/plugin.html 

La idea del plugin [1] es vincular las clases en tiempo de

configuración en lugar de hacerlo en tiempo de

compilación

En bases de datos legadas o legacy, sobre todo en

aquellas que no contemplaron el mapeo con los objetos, es

común encontrar que la forma de identificar unívocamente

a un registro es mediante la concatenación de varios

campos de la tabla. A la hora de mapear ese registro a un

objeto nos encontramos que para recuperarlos debemos

  proveer al framework de varios valores. Esto dificulta

 proveer de un mecanismo genérico para recuperar objetos,

complica ampliamente el desarrollo del framework ygenera código menos natural.

3.8. Proxys

Los objetos no suelen vivir aislados sino que

interactúan con otros objetos con los que están

directamente relacionados. Esto se traduce que al

recuperar una instancia debamos traer en cascada otros

objetos. Si esto se realiza sin tomar ninguna precaución es

 posible terminar con toda la base de datos en memoria en

el peor de los casos y con más objetos de los que

realmente se necesitan en promedio.

Para evitarlo se suele postergar la recuperación del

objeto hasta el instante en el que se necesita creando un

 proxy virtual para cada objeto de negocios de modo que

toda interacción con el mismo sólo se realiza a través de

su proxy.

El patrón proxy es sumamente difícil de implementar 

en la práctica por lo que ciertas alternativas como realizar 

el lazy loading manualmente o utilizar un value holder [1]

muchas veces son preferibles.

Para empezar los proxys muchas veces imponen

restricciones sobre la forma en el que escribimos los

objetos de negocios por ejemplo exigiendo que todos los

métodos sean virtuales.

Como escribir el proxy para cada objetos de negocio es

una tarea tediosa y sumamente repetitiva se suelen optar 

 por distintas alternativas que van desde la generación decódigo, generación automáticas de bytecodes en forma

dinámica como en el caso de la librería CGLIB [21] para

el caso de java o heredar de un objeto del entorno de

desarrollo que nos permita implementar   join points [29]

como en AOP (aspect oriented programming (AOP).

En el caso de .NET se puede lograr esta funcionalidad

heredando nuestros objetos de negocio del la clase

ContextBoundObject [22].

Por otra parte, si los proxys se implementan sin

miramientos realizando un proxy por cada objeto de

negocios que maneje el lazy loading del mismo se puede

terminar con problemas graves de performance, sobre

todo para el caso de las colecciones como se estudiará enla sección 3.7.

Otro objetivo de los proxys puede ser interceptar 

cualquier mensaje que modifique al objeto a quien

envuelve y notificar a la transacción que el objeto esta

modificado para que la misma lo ponga en la colección de

Dirty. Esta alternativa se contrapone con otras como

Caller Registration [1], Self Registration [1] o Unit of 

Work controller [1].

Una posibilidad es aprovechar los proxys para verificar 

que una propiedad marcada como no mutable

efectivamente lo sea. Para ello el proxy intercepta la

llamada al getter y luego de ejecutarla verifica si el objeto

cambió su representación lanzando una excepción en casoque así sucediera. No es muy complejo idear una batería

de pruebas unitarias que para cada objeto de nuestro

modelo de negocios verifique que se cumpla en contrato

de mutabilidad declarado en los metadatos de cada clase.

3.9. Performance

La performance ala hora de diseñar nuestro framework 

de persistencia cobra especial importancia debido a que se

espera que el mismo resuelva las consultas abstrayendo al

usuario del mismo de las sentencias SQL subyacentes. Los

  beneficios de esto tienen como contrapartida que

generalmente el usuario tendrá poco control de este  proceso viéndose limitado en posibilidades a la hora de

tunear las sentencias SQL.

Si bien es aceptable que la performance de los sqls

generados por el framework no sea óptima, si debe tener 

ciertas consideraciones y seguir ciertas reglas.

Para empezar debe tratar de minimizar el número de

llamadas ínter-proceso realizadas contra el motor de la

 base de datos. Esto se logra aprovechando cada una para

5/11/2018 Arquitectura Framework Persistencia - slidepdf.com

http://slidepdf.com/reader/full/arquitectura-framework-persistencia 9/11

traer la mayor cantidad de datos útiles potencialmente

utilizables siempre dentro de cierto límite.

Esto se puede ver claramente planteando un escenario.

Supongamos que tenemos un objeto factura que a su vez

  posee una colección de 20 ítems que corresponde al

detalle de la misma.

Si generamos un proxy para la factura y un proxy paracada ítem terminaremos realizando por el mecanismo de

lazy loading 21 consultas sobre la base de datos lo cual

evidentemente no es performante.

Intuitivamente si quisiéramos resolver esa consulta

seguramente resulte en un solo select que contiene un join

entre la tablas que tienen la información sobre la factura y

los ítems lo cual resulta enormemente más performante.

Como estrategia intermedia podemos tener un proxy

 para la factura y un proxy para la colección de ítems de

modo que el problema se resolvería en dos consultas, uno

 para la factura y otro para los ítems. Esta estrategia si bien

es menos performante que la anterior suele ser más útil en

los casos donde se tiene una colección numerosa defactura y solo se accederá a los ítems en un caso

 particular.

Como regla general se puede decir que para el caso de

los mapeos uno a muchos conviene traer toda la

información haciendo join entre las tablas involucradas

siendo deseable la posibilidad de configurar que trabaje

como en el último caso especificando proxys para las

colecciones.

Para las relaciones uno a uno donde hay mas de una

tabla involucrada la cosa es más abierta dependiendo

mucho del negocio si conviene o no hacer join entre as

tablas.

Para las jerarquías es necesario hacer join entre lasdistintas tablas involucradas en la jerarquía pudiendo

llegar a requerir hacer  left joins o union para el caso de

los mapeos condicionales

Salvo para el caso de la jerarquía siempre conviene

imponer un límite en la cantidad de tablas sobre las que se

hacen los join estando los motores de bases de datos

generalmente optimizados para 3 o 4 tablas

Dar la posibilidad al usuario del framework de tener 

cierto control de cómo se realizan las búsquedas es una

característica muy deseable.

3.10. Consultas

El framework de persistencia debe proveer un

mecanismo de consultas para recuperar colecciones de

objetos bajo algún criterio. Esto será responsabilidad del

motor de consultas del framework cuya complejidad

variará según la potencia y opciones de consultas que se

deseen ofrecer.

Al momento de diseñar las consultas es importante

analizar si existe algún estándar de consultas para trabajar 

con objetos en el entorno sobre el cual se esta

desarrollando como es el caso de JDO para java o Linq

 para .NET. En caso afirmativo tal vez resulte una buena

opción adherirse al mismo.

En caso que optemos por una solución propietaria

  podemos seguir tres caminos: definir nuestro propio

lenguaje de consultas, definir un objeto predefinido parahacer consultas, armar las consultas como un composites

de objetos provistos por el framework como aplicando el

 patrón interpeter [2] o utilizar named collections.

Para definir nuestro propio lenguaje de consultas es

importante que sea fácil de comprender por parte de quien

lo va a usar y por ello se recomienda elegir una sintaxis

similar al SQL.

El desafió es asegurar que dicho lenguaje sea ídem

 potente, es decir que al ejecutar varias veces la misma

consulta devuelva siempre lo mismo. Para ello se asume

que solo se pueden realizar consultas que permitan

especificar propiedades no mutables como parte del

criterio de búsqueda.Se puede, mediante algún mecanismo como se vio con

los proxys, asegurar que las propiedades en cuestión sean

efectivamente no mutables. En la práctica no obstante este

tema suele quedar relegado y se asume que las

  propiedades utilizadas no son mutables y se utiliza los

metadatos de la clase para saber con que campo mapéa

esa propiedad y armar el SQL.

En el caso que optemos por ofrecer un objeto para

hacer consultas este se tratará de un objeto al cual se le

definen ciertas propiedades como el nombre de la clase

sobre el cual realizaremos las búsquedas y algún criterio.

Para armar las consultas como un composite de objetos

se utiliza el patrón interpreter [1] y se escriben consultasde la forma:

Selection s = new selection(typeOf(Cliente));

s.criteria = new FieldEquals(Nombre,’Pepe’);

Por ultimo si no definimos un lenguaje de consultas

  podemos definir metadatos especiales que contentan

colecciones conocidas o named collections de modo que

la única forma de recuperar una colección de objetos es

hacerlo a través de su nombre.

Al recuperar una colección de objetos es importante

antes de convertir la información en un objeto verificar 

que este no este en el cache para evitar tener 

representaciones repetidas del mismo objeto como semencionó en la sección 3.3. Además hay que contemplar 

el caso de que un objeto de la colección no este siendo

modificado por otra transacción.

3.11. Colecciones

Será tarea intrínseca del framework trabajar con

colecciones de objetos. Lo que en el mundo relacional se

5/11/2018 Arquitectura Framework Persistencia - slidepdf.com

http://slidepdf.com/reader/full/arquitectura-framework-persistencia 10/11

lee como relaciones uno a muchos y claves foráneas entre

tablas, dentro del software tendremos objetos en memoria

que a su vez podrán referenciar colecciones de otros

objetos en memoria

El problema de las colecciones ocurre cuando la

cantidad de elementos de la misma es muy grande y

ocupan más memoria de la que podríamos desear atentando contra el desempeño de la aplicación. Para esos

casos lo mejor es construir un mecanismo de paginación.

Por ejemplo se puede aplicar un value list handler [25]

[Fig 3.10.1]

Fuente

http://java.sun.com/blueprints/corej2eepatterns/Patterns/V

alueListHandler.html 

El mecanismo de paginación nos permite evitar tener la

colección completa en memoria y solo disponer de un

conjunto de la misma. No obstante para el que utiliza la

colección esto será transparente y parecerá como si

estuvieran todos los datos disponibles

[Fig 3.10.2]

4. Conclusiones

Implementar un framework de persistencia que

contemple numerosos escenarios posibles no es una tarea

fácil. Por fortuna existen una gran cantidad de productos

open source disponible.

En el caso de encomendarnos a esa tarea debemosempezar analizando los requerimientos del software que

utilizará el framework de persistencia. Aunque el objetivo

sea el framework en si se recomienda hacer un proyecto

de prueba que utilice el framework y redactar un caso de

  prueba y/o test unitarios [3] para cada escenario posible

que se considere.

Lograr un producto funcional y que cumpla con las

exigencias del mercado es hecho que además de llenar de

satisfacción a los involucrados puede mejorar la calidad

de los desarrollos que se realicen con él.

5. Trabajos Futuros

En este trabajo se dejaron afuera que se sugiere como

  posibles temas de investigación relacionados con la

 persistencia de objetos.

Primero es interesante estudiar un mecanismo de SQL

logging para almacenar en la base de datos las sentencias

SQL ejecutadas. Además de para tener algún tipo de

auditoria existen algunos usos interesantes que se le puede

dar a esas funcionalidades

Primero la capacidad de replicar datos entre distintas

 bases de datos. La replicación de datos es un tema que se

  puede incluir donde hay que prestar atención a la

generación de ids y a que medidas tomar cuando un

mismo registro es modificado en dos bases de datosdistintas.

Segundo la capacidad de deshacer transacciones ya

realizadas. Para ello además de las sentencias SQL habría

que almacenar las sentencias SQL opuestas que devuelven

la base de datos al estado anterior.

Algunos motores de base de datos implementas estas

funcionalidades pero poco se ha escrito como hacerlo

desde el software y en el contexto de un framework de

 persistencia.

6. Referencias

[1] Martin Fowler,   Patterns Of Enterprise Application

 Architecture, Addison Wesley.

[2] Erich Gamma, Richard Helm, Ralph Johnson, John M.

Vlissides, Design Patterns: Elements of Reusable Object-

Oriented Software, Addison Wesley.

[3] Kent Beck, Test Driven Development: by Example,

Addison Wesley.

5/11/2018 Arquitectura Framework Persistencia - slidepdf.com

http://slidepdf.com/reader/full/arquitectura-framework-persistencia 11/11

 

[4] www.hibernate.org 

[5] http://db.apache.org/ojb/ 

[6] http://db.apache.org/torque/ 

[7] http://db.apache.org/torque/ 

[8] http://tjdo.sourceforge.net/ 

[9] http://java-source.net/open-source/persistence/jaxor  

[10] http://jdbm.sourceforge.net/ 

[11] http://pbeans.sourceforge.net/ 

[12] http://www.simpleorm.org/ 

[13] http://julp.sourceforge.net/ 

[14] http://www.jpox.org/index.jsp 

[15] http://ibatis.apache.org/ 

[16] http://www.drjava.de/smyle/index.html 

[17] http://speedo.objectweb.org/index.html 

[18] http://xorm.sourceforge.net/ 

[19] http://www.jdbcpersistence.org/ 

[20] http://patsystem.sourceforge.net/ 

[21] http://cglib.sourceforge.net/  

[22]http://msdn.microsoft.com/en-

us/library/system.contextboundobject.aspx 

[23] http://martinfowler.com/articles/injection.html 

[24]http://www.estebancalabria.com.ar/articuloshtml/Depende

ncyInjection.html 

[25]

http://java.sun.com/blueprints/corej2eepatterns/Patterns/V

alueListHandler.html 

[26] http://en.wikipedia.org/wiki/Savepoint 

[27]

http://www.ianywhere.com/developer/product_manuals/sq

lanywhere/1000/en/html/dbpgen10/pg-sqlapp-s-

4654501.html 

[28]

http://www.estebancalabria.com.ar/articuloshtml/Dicciona

rioDatosTipado.htm 

[29]

http://en.wikipedia.org/wiki/Aspect-

oriented_programming