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
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