Sei sulla pagina 1di 11

Propuesta de arquitectura de un Framework de Persistencia

Lic. Esteban Cesar Calabria Universidad Abierta Interamericana - Maestra en Tecnologa Informtica Tpicos Avanzados De Bases de Datos esteban.calabria@gmail.com Abstract
Este trabajo estudia distintas alternativas a la hora de disear un framework de persistencia mapeo objeto relacional (ORM). Se analiza desde el punto de vista arquitectnico mostrando los distintos problemas que surgen y sus soluciones. este trabajo. An 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 tecnologa 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, aclarndose cuando se hable de la implementacin en alguna de esas tecnologas.

1. Introduccin
Al da de hoy, las bases de datos relacionales son la solucin ms utilizada por los sistemas empresariales para almacenar grandes volmenes de informacin. Como contrapartida, ste tipo de sistemas poseen una lgica 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 en memoria desde y hacia un almacenamiento permanente, siendo una base de datos relacional el medio ms 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 diseo y construccin 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 quedarn sentadas en

1.1. Contexto
Para contextualizar la situacin estamos trabajando en un entorno de al menos 3 capas, Presentacin, Lgica de Negocios y Persistencia [Fig 1.1.1]

Presentacin

Lgica 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 programacin orientada a objetos y los patrones de diseo [2]. A los objetos de esta capa, responsable de resolver la problemtica para la cual el sistema fue construido, los llamaremos objetos de negocio o domain objects. Esta es una organizacin conceptual donde discutiremos si conviene que la Lgica de negocios se comunique directamente con la persistencia (existen llamadas a mtodos de la capa de persistencia por parte de objetos de la lgica de negocio) o que si la lgica 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 ms a un gateway [1], encapsulando el acceso a la base de datos.

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 disear y producir un framework de persistencia mapeo objeto relacional ORM (Object Relational Mapping)

3. Arquitectura
La arquitectura del framework de persistencia propuesto se organiza en distintos layers, cada uno con una responsabilidad bien definida [Fig 3.1]

1.2. Tipos de Framework


Antes de comenzar, es importante hacer la distincin entre dos tipos no excluyentes de frameworks de persistencia. Aquellos que llamaremos schema generators y los que estn pensados para trabajar con bases de datos legadas o existentes. Los primeros generan automticamente 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 evolucin 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.

Interfaz

Transacciones

Cache

Persistencia

DAL [Fig 3.1]. Layers del Framework Como trabajamos con un Domain Model, una opcin comn 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 del framework que pueden abarcar identificacin, recuperacin, soporte para la generacin de proxys (como el caso de .NET) y mtodos 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 terico esa clase base nos genera cierto grado acople entre nuestro modelo de negocios y el framework de persistencia, lo cual el diseador puede sentirse reticente a aceptar. Si se desea un modelo de objetos que desconozca el framework de la persistencia se pasar por alto esta opcin. En la prctica, no obstante, a veces puede resultar til disponer 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 estn directamente relacionados. En ese caso no hay razn para evitar implementar un layer supertype que tenga nocin de los mecanismos de persistencias subyacentes.

2. Organizacin del Trabajo


Este trabajo comienza proponiendo una arquitectura del framework de persistencia organizndolo en distintos layers: interfaz, transacciones, cache, Persistencia y Dal. Esto se ver en la seccin 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 continuacin se tratan distintos temas que tienen que ver con la persistencia de objetos. En la seccin 3.6 se tratan los problemas de concurrencia que se nos pueden presentar y cmo solucionarlos. La seccin 3.7 habla de la forma de identificar unvocamente a un objeto mediante un ID. La seccin 3.8 habla de los proxys [1] [2] como mecanismo para manejar el lazy loading [1] [2]. La seccin 3.9 habla de temas de performance a tener en cuenta a la hora de disear un framework de persistencia. La seccin 3.10 habla sobre cmo manejar el tema de las consultas para recuperar objetos de la base de datos. La seccin 3.11 trata sobre cmo 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.

Se sugiere apuntar a que el framework y los objetos de negocio estn lo ms desacoplados posibles. Siempre que nos encontremos en la situacin donde nuestra capa de negocios necesite los servicios del framework podemos hacerlo de forma indirecta aplicando el patrn Separated Interface [1] y plugin [1]. De esa forma la capa de negocios quedar englobada en un paquete donde slo existir una interfaz o clase abstracta que defina los servicios requeridos. Otro paquete distinto ser el que contendr la implementacin. Aplicando alguna tcnica de inyeccin de dependencias o dependency injection [24][25] podemos vincular a ambas en tiempo de configuracin.

3.1. Layer de Interfaz


Esta es la capa visible del framework con la cual interactan 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 de implementacin. Sin quitar la posibilidad de proveer un conjunto de mtodos que permitan parametrizar de alguna forma el funcionamiento. Esta capa internamente implementa la traduccin de excepciones de las capas inferiores, internacionalizacin, adaptacin 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 transaccin mantiene internamente un conjunto de colecciones: los Dirty, los New y los Delete. La coleccin de los objetos Dirty o sucios contiene objetos preexistentes. Al momento de hacer commit de la transaccin se deber actualizar su representacin 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 eliminacin puede tratarse tanto de una baja fsica (mediante la ejecucin de un Delete SQL) o una baja lgica mediante la actualizacin de algn campo de estado. Cada transaccin representa una Unit of Work [1] y soportan como mnimo las operaciones StartTransaction, Commit y Rollback. Se usan para iniciar una transaccin, confirmar los cambios o deshacerlos respectivamente. Dentro del framework todo objeto a ser persistido debe estar dentro de una transaccin y los cambios sobre los objetos de una transaccin 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 relacin entre una transaccin del framework de persistencia y una transaccin de la base de datos, aunque puede no haber relacin directa entre sus operaciones. Las transacciones de objetos pueden mantenerse abiertas sin ser confirmadas un perodo de tiempo mayor. En la base de datos esto no es recomendable. El commit de una transaccin 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 transaccin del framework simplemente opera internamente a nivel framework descartando los cambios en memoria sin necesidad de realizar ninguna notificacin 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 clasificacin posible sobre las transacciones: simultneas y anidadas. Las simultneas son transacciones que se ejecutan en forma paralela e independiente. Cada una acta sobre un conjunto de objetos distintos. Por un motivo de seguridad se prohbe que dos transacciones simultneas modifiquen el mismo objeto. Las transacciones anidadas prevn los casos donde una operacin se puede realizar en una secuencia finita de pasos donde cada uno se puede confirmar en forma independiente. Una transaccin puede lanzar subtransacciones anidadas y de esta forma implementar un mecanismo de SavePoints [26] en memoria. Los cambios en la base se reflejarn en el momento que se ejecute commit en la transaccin 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 implementacin. Cada transaccin anidada debe proveer un mecanismo para serializar o guardarse de alguna manera el estado de los objetos de la transaccin al momento de iniciar la misma, de modo que al hacer rollback de una transaccin anidada el estado de los objetos sea el mismo que tenan antes de comenzarla. En el caso de no disponer de transacciones anidadas pero necesitar de dicha funcionalidad siempre tenemos la opcin de implementarlas a nivel objeto de negocios aplicando el patrn 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 realizarn 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 transaccin. Siendo ese el caso, no existen razones para no usarlo.

3.3. Layer de Cache


La principal tarea del cache es asegurarse que no exista duplicada la representacin de un mismo objeto en memoria. Si se recupera un objeto con un ID determinado y se vuelve a recuperar luego el mismo objeto, debemos tener cuidado de no instanciar dos objetos con la misma informacin. Conceptualmente son el mismo objeto pero si se realizara una modificacin sobre alguno no se veran 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 simultneas modifiquen el mismo objeto. Por ltimo el cache puede servirnos para mejorar la performance de la aplicacin manteniendo los objetos en memoria. Para ello a cada objeto en el cache se le coloca un timestamp que indica cuando fue accedido por ltima vez. Peridicamente un hilo se encarga de recorrer el cache y buscar aquellos objetos que no han sido accedidos en un perodo 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 interacta con la base de datos mediante el framework de persistencia y adems no existen otros sistemas externos que acceden a la base de datos, esta opcin resulta interesante. No obstante si la base de datos se accede concurrentemente por ms de una aplicacin se recomienda eliminar el cache como mecanismo de mejora de performance e incluir algn manejo de concurrencia como los que se tratan en la seccin 3.5.

Estos errores suelen aparecer recin en tiempo de ejecucin y suelen controlarse mediante el uso de testeos unitarios [3]. Describir los mapeos mediante anotaciones o codificarlos a mano acarrean tambin el mismo problema salvo que se use un diccionario de datos fuertemente tipado [28] (una generacin de una jerarqua de clases que repliquen el esquema de la base de datos mediante alguna estrategia de code generation para poder realizar verificaciones de nombres en tiempo de compilacin dentro el entorno de desarrollo). La informacin de los mapeos -junto con otra informacin que veremos ms adelante- recibe el nombre de metadata o metadatos. Cada clase tendr su metadata asociada. Los mapeos ms comunes con los que nos podemos encontrar son a nivel clase son: TableMapping SubclassesMapping SelectMapping CustomClassMapper

3.4. Layer de Persistencia


Esta capa mapa 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 ms 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 programacin. Describir los mapeos en archivos auxiliares es la estrategia ms 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.

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 travs de un setter y un getter. Las propiedades generalmente son parte de la interfaz pblica de una clase y proporciona una sintaxis cmoda 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 algunos soportan 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 actan tambin a nivel propiedad que son: CommonFieldMap TransformFieldMap OneToOneMapping OneToManyMapping AggregateMapping, ConditionalAggregate FieldToQueryMap ConditionalMapping

El mapeo TableMapping define la/s tablas sobre la que se persistirn los datos de una clase debiendo especificrsele como parte del mapeo cual es el campo ID. Los campos ID se discutirn ms en detalle en la seccin 3.5.

Para el mapeo de sublases las alternativas para persistir una jerarqua 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 mapa 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 trasformara en la cadena de caracteres Masculino en el objeto. El OneToOneMapping es el encargado de mapear una relacin uno a uno entre los objetos debiendo realizar las consiguientes transformaciones de claves forneas 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 coleccin 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 pequeos, como por ejemplo un rango de fechas y 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 algn clculo 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 patrn State[2] o Strategy[2] que con otros mecanismos de persistencia resulta difcil 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 slo lectura y no intervendr en ningn momento en la actualizacin del objeto. Adems de los mapeos, los metadatos de los objetos pueden marcar a las propiedades como mutables o no mutables asumindose alguna de ambas posibilidades por defecto.

Una propiedad no mutable asegura que el cdigo de su getter asociado no modifica la representacin interna. Esto se utilizar en las consultas como veremos en la seccin 3.8 y podr ser verificado en los proxys como se estudiar en la seccin 3.6.

3.5. Database Abstraction Layer


Los distintos entornos de desarrollos y lenguajes de programacin traen frameworks y libreras (APIs) para permitir la interaccin con la base de datos. Estas pueden variar segn el vendedor de la base de datos. El objetivo de esta capa es ofrecer una interfaz comn y conocida que para comunicarse con la base de datos. Debe ser capaz de abstraernos de las pequeas 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 segn el vendedor de la base de datos. Por lo tanto en su diseo es importante pensarla en trminos de clase e interfaces abstractas que abstraigan esas diferencias. Las subclases pueden variar segn Base de Datos. Dichas implementacin debern 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 travs de query objects o orientarla a recordset. La primer opcin es bastante directa y no merece mucha mas explicacin 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 prctica y se propone como alternativa generar nuestras propias clases de acceso a datos que adapten las clases que provee el lenguaje para lograr una solucin menos acoplada al entorno de desarrollo con el que se trabaja para facilitar migrar el framework a distintas tecnologas. El otro camino es generar una jerarqua de objetos que representen una sentencia SQL. Tendramos 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 opcin de los recordsets. Un recordset [1] es una representacin en memoria que replica la estructura de la base de datos. Organiza la informacin en forma tabular en tablas, filas (rows) y columnas (columns).

[Fig] Fuente: http://www.martinfowler.com/eaaCatalog/recordSet.html Los entornos de desarrollo modernos proveen una implementacin 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 en sentencias SQL. Esta opcin puede ser recomendable cuando el entorno provee muchas funcionalidades alrededor del recordset, como por ejemplo la generacin automtica de las sentencias de SQL. La desventaja es que de esta manera el framework nos queda ms dependiente de un lenguaje en particular.

sobrescribir los cambios de la segunda, dando como resultado un update perdido. La estrategia de concurrencia ms adecuada puede variar segn el caso y segn el objeto de negocio que se considere. Por eso es muy deseable la posibilidad de definirla para cada objeto en tiempo de configuracin. Esta informacin se puede especificar dentro de los metadatos 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 ningn 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 transaccin. [Fig. 3.6.1]. Su implementacin puede requerir el agregado de un campo timestamp a la tabla de la base de datos que indique el instante de la ultima modificacin cada registro.

3.6. Concurrencia
La concurrencia es uno de los problemas ms complejos a considerar en el diseo de un framework de persistencia. Dentro de un mismo proceso debemos asegurarnos 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 travs del framework de persistencia, es el nico que interacta 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 ms. Este es el caso de varias aplicaciones distintas que acceden a la misma base o incluso en una aplicacin cliente-servidor donde puede haber varios clientes conectados contra la misma base. Es aqu donde se debe tomar la decisin 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 fenmeno de updates perdidos donde una aplicacin recupera un objeto y mientras esta operando con el en memoria una segunda aplicacin interviene, recupera el mismo objeto y lo actualiza antes de que la primer aplicacin confirme sus cambios. Cuando la primera aplicacin en efecto lo haga

[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 veces se opta por crear una tabla aparte que contenga (Nombre de la tabla, ID del registro, el timestamp). En la seccin 3.7 se profundizar sobre el campo ID del registro. Si esta ltima opcin tampoco resulta adecuada la alternativa es en la sentencia SQL update especificar en el

where que el valor de cada uno de los campos coincida con el que tenia antes de realizar las modificaciones. Esta opcin 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 tenan 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]

La alternativa ms comn es asignar a cada objeto un ID sin significado de negocios. Esta estrategia nos provee varias ventajas ya que proporciona una forma genrica 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 convenientemente se 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 cmodo incluir un mtodo esttico encargado de recuperar un objeto de una clase de la forma:
Cliente c = (Cliente) Cliente.Recuperar(1);

[Fig. 3.6.2 ] Fuente:


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

Dos aplicaciones no pueden tomar simultneamente el mismo registro de la base de datos. Si un registro esta tomado se debe esperar que la aplicacin 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 aplicacin falla automticamente el registro es liberado aunque cada vendedor lo implementa de una forma distinta. Sino hay que hacerlo manualmente con algn 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 aplicacin que los tomos sufre un desperfecto que le impide liberarlo. Esto se subsana con un tiempo mximo durante el cual se puede reservar un objeto aunque es un problema complejo de considerar.

3.7. Identificacin
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 unvocamente cada objeto de nuestro sistema.

Esta forma resulta bastante intuitiva para un desarrollador que desee recuperar el objeto de la clase cliente con el ID nmero 1. Cuando hablamos de campos ID entran en juegos varias de decisiones de diseo. 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 incremntales nos proporciona una solucin dependiente del vendedor o vendedor de la base de datos que adems debe provee un mecanismo para obtener el ID del registro a insertar para resolver problemas de claves forneas. Si en vez de los campos auto incrementales utilizamos alguna consulta especial de la forma select max (ID) from tabla debemos prestar atencin de que dicha consulta bloquear la tabla durante lo que dure la transaccin. La tercer opcin es disponer de una tabla adicional con un par de campos (nombre de tabla, ultimo ID) que nos provea un poco ms de control que la opcin 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 da. Un entero de 64 bits resulta ms adecuado. Esta opcin 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 dicha tabla durante una transaccin ya que en caso contrario no se podrn 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

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 trminos de performance al manejar claves alfanumricas. 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 generacin de Ids este planteada como un plugin [1] pudiendo el usuario del framework definir su propia forma de generacin 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 configuracin en lugar de hacerlo en tiempo de compilacin En bases de datos legadas o legacy, sobre todo en aquellas que no contemplaron el mapeo con los objetos, es comn encontrar que la forma de identificar unvocamente a un registro es mediante la concatenacin 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 genrico para recuperar objetos, complica ampliamente el desarrollo del framework y genera cdigo menos natural.

3.8. Proxys
Los objetos no suelen vivir aislados sino que interactan con otros objetos con los que estn directamente relacionados. Esto se traduce que al recuperar una instancia debamos traer en cascada otros objetos. Si esto se realiza sin tomar ninguna precaucin es posible terminar con toda la base de datos en memoria en el peor de los casos y con ms objetos de los que realmente se necesitan en promedio. Para evitarlo se suele postergar la recuperacin del objeto hasta el instante en el que se necesita creando un proxy virtual para cada objeto de negocios de modo que toda interaccin con el mismo slo se realiza a travs de su proxy. El patrn proxy es sumamente difcil de implementar en la prctica 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 mtodos 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 generacin de cdigo, generacin automticas de bytecodes en forma dinmica como en el caso de la librera 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 en la seccin 3.7. Otro objetivo de los proxys puede ser interceptar cualquier mensaje que modifique al objeto a quien envuelve y notificar a la transaccin que el objeto esta modificado para que la misma lo ponga en la coleccin 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 representacin lanzando una excepcin en caso que as sucediera. No es muy complejo idear una batera 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 disear 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 vindose 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 nmero de llamadas nter-proceso realizadas contra el motor de la base de datos. Esto se logra aprovechando cada una para

traer la mayor cantidad de datos tiles potencialmente utilizables siempre dentro de cierto lmite. Esto se puede ver claramente planteando un escenario. Supongamos que tenemos un objeto factura que a su vez posee una coleccin de 20 tems que corresponde al detalle de la misma. Si generamos un proxy para la factura y un proxy para cada tem terminaremos realizando por el mecanismo de lazy loading 21 consultas sobre la base de datos lo cual evidentemente no es performante. Intuitivamente si quisiramos resolver esa consulta seguramente resulte en un solo select que contiene un join entre la tablas que tienen la informacin sobre la factura y los tems lo cual resulta enormemente ms performante. Como estrategia intermedia podemos tener un proxy para la factura y un proxy para la coleccin de tems de modo que el problema se resolvera en dos consultas, uno para la factura y otro para los tems. Esta estrategia si bien es menos performante que la anterior suele ser ms til en los casos donde se tiene una coleccin numerosa de factura 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 informacin 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 ms abierta dependiendo mucho del negocio si conviene o no hacer join entre as tablas. Para las jerarquas es necesario hacer join entre las distintas tablas involucradas en la jerarqua pudiendo llegar a requerir hacer left joins o union para el caso de los mapeos condicionales Salvo para el caso de la jerarqua siempre conviene imponer un lmite 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 cmo se realizan las bsquedas es una caracterstica muy deseable.

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 opcin adherirse al mismo. En caso que optemos por una solucin propietaria podemos seguir tres caminos: definir nuestro propio lenguaje de consultas, definir un objeto predefinido para hacer consultas, armar las consultas como un composites de objetos provistos por el framework como aplicando el patrn interpeter [2] o utilizar named collections. Para definir nuestro propio lenguaje de consultas es importante que sea fcil 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 bsqueda. Se puede, mediante algn mecanismo como se vio con los proxys, asegurar que las propiedades en cuestin sean efectivamente no mutables. En la prctica 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 mapa 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 bsquedas y algn criterio. Para armar las consultas como un composite de objetos se utiliza el patrn interpreter [1] y se escriben consultas de la forma:
Selection s = new selection(typeOf(Cliente)); s.criteria = new FieldEquals(Nombre,Pepe);

3.10. Consultas
El framework de persistencia debe proveer un mecanismo de consultas para recuperar colecciones de objetos bajo algn criterio. Esto ser responsabilidad del motor de consultas del framework cuya complejidad variar segn la potencia y opciones de consultas que se deseen ofrecer. Al momento de disear las consultas es importante analizar si existe algn estndar de consultas para trabajar

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 coleccin de objetos es hacerlo a travs de su nombre. Al recuperar una coleccin de objetos es importante antes de convertir la informacin en un objeto verificar que este no este en el cache para evitar tener representaciones repetidas del mismo objeto como se mencion en la seccin 3.3. Adems hay que contemplar el caso de que un objeto de la coleccin no este siendo modificado por otra transaccin.

3.11. Colecciones
Ser tarea intrnseca del framework trabajar con colecciones de objetos. Lo que en el mundo relacional se

lee como relaciones uno a muchos y claves forneas entre tablas, dentro del software tendremos objetos en memoria que a su vez podrn 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 ms memoria de la que podramos desear atentando contra el desempeo de la aplicacin. Para esos casos lo mejor es construir un mecanismo de paginacin. Por ejemplo se puede aplicar un value list handler [25]

4. Conclusiones
Implementar un framework de persistencia que contemple numerosos escenarios posibles no es una tarea fcil. Por fortuna existen una gran cantidad de productos open source disponible. En el caso de encomendarnos a esa tarea debemos empezar 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 adems de llenar de satisfaccin 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 investigacin 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. Adems de para tener algn 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 replicacin de datos es un tema que se puede incluir donde hay que prestar atencin a la generacin de ids y a que medidas tomar cuando un mismo registro es modificado en dos bases de datos distintas. Segundo la capacidad de deshacer transacciones ya realizadas. Para ello adems de las sentencias SQL habra 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.

[Fig 3.10.1] Fuente http://java.sun.com/blueprints/corej2eepatterns/Patterns/V alueListHandler.html El mecanismo de paginacin nos permite evitar tener la coleccin completa en memoria y solo disponer de un conjunto de la misma. No obstante para el que utiliza la coleccin esto ser transparente y parecer como si estuvieran todos los datos disponibles

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 ObjectOriented Software, Addison Wesley. [Fig 3.10.2] [3] Kent Beck, Test Driven Development: by Example, Addison Wesley.

[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/enus/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-s4654501.html [28] http://www.estebancalabria.com.ar/articuloshtml/Dicciona rioDatosTipado.htm [29] http://en.wikipedia.org/wiki/Aspectoriented_programming

Potrebbero piacerti anche