• No results found

Practising ethics in different fields of R&

2 A Literature Review and Analysis

2.3 Scope and Methodology

2.4.1 Practising ethics in different fields of R&

StorageHomeKeySet

+ getOri ginalSpec() : StorageObjectSpec + keyBuilder(nam e) : StorageHomeKeyBuilder + StorageHomeKeySet(spec)

+ validate(operati on, home) : void

«real ize» -set

DISEÑO DEL SERVICIO 9 - RESTRICCIONES DE CLAVES

El código que se muestra a la izquierda representa como un almacén agrega

una clave compuesta

únicamente por el atributo ‘name’. La implementación del método collectKeys, es generada por el compilador y el usuario nunca debe hacer uso de él directamente, por eso se trata de un método declarado como protegido.

La clase StorageHomeKeySet no implementa el método valídate. Cada conector será responsable de extender dicha clase proveyendo el mecanismo de validación para cada clave definida dentro de la familia de almacenes.

En el caso concreto de la creación de un objeto, el método mencionado anteriormente, será llamado por el mecanismo de creación de objetos almacenados dentro de cada almacén.

DESTRUCCIÓN DE OBJETOS ALMACENADOS

Otro punto donde la especificación es particularmente amplia, es que sucede en el momento de destruir o eliminar objetos con los objetos que referencian al objeto que se está eliminando.

La especificación solamente indica que debe hacerse con las referencias de un objeto que es eliminado. Las referencias a objetos de tipo embedded son directamente destruidas junto con su padre y las referencias por identificadores son destruidas cuando están declaradas como strong o fuertes.

Siguiendo el modelo de delegación, he optado por centralizar las operaciones de destrucción de los objetos en los almacenes, dado que es el almacén, en definitiva, quien tiene más información sobre el estado de los objetos y es realmente la única clase que debe ser generada completamente por el compilador. Por ello, el método destroy_object de la interfase StorageObject, es implementado por el compilador mediante delegación en el almacén al que pertenece el objeto. La clase base AbstractChildStorageHome define los siguientes métodos para la destrucción de los objetos almacenados:

El método destroy está definido en mi interfase extendida de almacén. Se trata de un método declarado final en esta clase, usando el patrón de diseño Template Method [Gamma01]. Los métodos beforeDestroy y afterDesctroy son, en este caso, los template methods. Los almacenes que hereden de esta clase, deberán redefinirlos para marcar como destruidas a aquellas referencias que estén declaradas como fuertes en los objetos almacenados que manejen.

En el momento de la destrucción de los objetos, el identificador es marcado como destruido por el almacén mediante la interfase StorageObjectIdentifier. Los identificadores de las referencias a objetos de tipo embedded tienen su estado definido por medio del estado del identificador del objeto al que pertenecen, por lo que estas referencias quedan automáticamente destruidas.

D

IFERENTES

C

ONECTORES

Ya mencioné anteriormente que he construido dos conectores del servicio de persistencia. La razón principal para ello es la forma que he desarrollado el servicio. El primer conector que provee persistencia temporal, es la implementación más simple que cumple con todos los requerimientos del servicio. Este conector se usó para construir el servicio y el compilador a la par.

CONECTOR DE PERSISTENCIA TEMPORAL

Este conector permite evaluar y comprender las funcionalidades de cualquier servicio de persistencia. La limitación que tiene es que su estado es válido mientras la sesión esté activa. Sin embargo, es funcional y puede ser usado siempre y cuando los requerimientos lo permitan.

class General Schema

ConnectorBase connector:: TransientConnector + T ransientConnector() CommonSessionBase catalog::TransientSession + dettach(StorageObject) : void + T ransientSession() AbstractRootStorageHome memory:: TransientRootStorageHome {leaf} AbstractChildStorageHome memory::TransientStorageHomeBase

+ do_after_create(SO, StorageObjectSpec, T ransientStoragetHom e) : SO + fi nd_ref_by_spec(StorageObjectSpec, T ransientStoragetHom e) : byte[] + T ransientStorageHom eBase()

ExtendedStorageHome «interface»

memory::TransientStoragetHome

+ do_after_create(SO, StorageObjectSpec, Transi entStoragetHome) : SO + find_ref_by_spec(StorageObjectSpec, Transi entStoragetHome) : byte[]

StorageHomeKeySet

memory::TransientStorageHomeKeySet + val idate(String, T ransientStorageHomeBase) : void

ExtendedStorageObject «interface»

memory::TransientStorageObject + matches(StorageObjectSpec, int) : boolean

AbstractTargetCompil er

memory:: TransientTargetCompiler

«reali ze» «realize»

DISEÑO DEL SERVICIO 10 - ESQUEMA GENERAL DEL CONECTOR TEMPORAL

El diagrama anterior muestra el esquema general de las clases que componen al conector. Todo conector debe proveer una contribución al compilador para que este pueda generar código para él. La

public final void destroy(StorageObject storageObject) protected void beforeDestroy(StorageObject storageObject) protected void afterDestroy(StorageObject storageObject)

contribución consiste en la clase TransientTargetCompiler, junto con las plantillas de código que utilizará el compilador para generar el código fuente.

El compilador requiere, además, que la contribución sea registrada en su archivo de configuración (pss_pmi_compiler.properties):

El conector debe tener su registro en el servicio de persitencia, con una clase que implemente la interfase ConnectorRegistryInitializer. La interfase está implementada por el conector mismo, y es:

ar.uba.fi.pmi.corba.pss.connector.TransientConnector

El conector se registra en el archivo pss_pmi_registry.properties, mediante la siguiente línea:

Habiendo agregado esta última línea, el conector ya está listo para ser utilizado en tiempo de ejecución.

FUNCIONAMIENTO

La funcionalidad del conector está provista mayormente por la sesión de trabajo que provee. La clase que representa una sesión en el conector temporal es TransientSession. Esta clase hereda de la implementación base de la sesión mencionada anteriormente: CommonSessionBase, que resuelve la administración de almacenes de una manera simple, por lo que lo único que le queda resolver es la construcción y manejo de objetos almacenados.

MANEJO DE IDENTIFICADORES

El primer problema que se necesita resolver, para la creación de objetos almacenados, es la asignación de identificadores de objeto, tanto para el Pid como para el ShortPid. La especificación indica que para Java los identificadores se traducen en un byte[], lo cual hace complicada su manipulación directa. Para la generación de identificadores que están representados por un byte[], proveo la interfase ByteArraySequencer, que permite obtener secuencias de identificadores en dicho formato.

class utils

BinaryUtils + bytesT oLong(bytes) : long + longT oBytes(num) : byte[]

«interface» ByteArraySequencer ~ next() : byte[] SimpleByteArraySequencer + next() : byte[] + Sim pleByteArraySequencer() + Sim pleByteArraySequencer(initialValue) «realize» «use»

DISEÑO DEL SERVICIO 11 - GENERACIÓN DE IDENTIFICADORES

La implementación más simple de este secuenciador es SimpleByteArraySequencer, que se limita a incrementar un número de tipo long, y traducirlo a su representación binaria mediante la clase

PSSPMIConnector.ar.uba.fi.pmi.corba.pss.connector.TransientConnector=Transient

registered=transient

transient=ar.uba.fi.pmi.corba.pss.psdl.compiler.memory.TransientTargetCompiler

BinaryUtils. La sesión del conector tiene una instancia de esta clase para generar los identificadores globales (Pid) de los objetos almacenados y a su vez cada familia de almacenes que se use en la sesión, tendrá otra instancia de este tipo de secuenciador.

ALMACENES TEMPORALES

El conector temporal extiende la interfase de almacén de CORBA mediante TransientStoragetHome. Esto implica que todo almacén generado por este conector deberá implementar dicha interfase. Existen dos clases base la implementan que son: TransientRootStorageHome y TransientStorageHomeBase. La primera representa la raíz de una familia de almacenes y la segunda la clase base para todo almacén generado por el compilador de este conector. El modelo de almacenes temporales sigue el modelo de delegación propuesto inicialmente por mi servicio.

Cada almacén raíz de una familia de almacenes tiene un atributo SimpleByteArraySequencer, que es utilizado para generar los identificadores objeto (ShortPid).

Para mantener los objetos almacenados dentro de la sesión cada almacén raíz tiene un mapa donde almacena los objetos que fueron creados durante la sesión. Dicho mapa está indexado por el identificador ShortPid.

OPERACIONES DE BÚSQUEDA

La clase AbstractChildStorageHome define el método:

, que permite buscar una referencia a un objeto almacenado que cumpla con la especificación dada. Los conectores deben implementar dicha operación. En el caso de este conector, la implementación más simple es iterar sobre todos los objetos almacenados dentro de la familia del almacén, representados por los valores del mapa de la raiz y verificando si alguno de ellos cumple con la especificación dada. Para ello, la interfase TransientStorageObject, define el método matches, que permite determinar si el objeto almacenado cumple con la especificación dada.

Cualquier operación de búsqueda en este conector se traduce en construir una especificación con los atributos que se estén buscando, para luego llamar al método find_ref_by_spec del almacén donde se realizará el proceso anteriormente descrito.

La clase TransientStorageHomeKeySet es la encargada de realizar las validaciones de claves. Esta clase utiliza el mismo mecanismo de búsqueda para buscar referencias a objetos que cumplan con la definición de la clave.

CONECTOR DE PERSISTENCIA DURABLE

Si bien el conector temporal es funcional, muchas veces carece de utilidad práctica. En general, lo que se busca con el concepto de persistencia es que sea prolongada en el tiempo e independiente de la vida del proceso donde se creó al objeto. Por esto, mi servicio de persistencia provee otro conector que cumple con este objetivo de persistencia durable.

Persistencia durable implica que la información debe ser almacenada en un repositorio permanente y no volátil.

REQUERIMIENTOS PARA UN CONECTOR DE PERSISTENCIA DURABLE

El primer requerimiento es que permita almacenar objetos. Las operaciones deberán ser perdurables, si se crea un objeto en una sesión, al cerrarla y abrir una nueva, se espera que el objeto siga

existiendo en la nueva sesión. Todas las operaciones de consulta definidas por el servicio de persistencia deben también estar soportadas. Y las claves definidas en el modelo deberán ser igualmente respetadas.

El repositorio deberá permitir más de una conexión o sesión concurrente.

Es importante recalcar que el servicio de persistencia de CORBA no tiene el objeto de reemplazar una base de datos, por lo general los repositorios no contendrán una gran cantidad de objetos.

El acceso remoto al repositorio es un adicional, ya que el servicio de persistencia no lo impone como requerimiento, aunque si constituye una cualidad muy útil.

Antes de entrar en los detalles de mi conector persistente, voy a realizar un análisis de las distintas alternativas que consideré para lograr persistencia durable.

DISTINTAS ALTERNATIVAS PARA OBTENER PERSISTENCIA DURABLE

PERSISTENCIA MEDIANTE ARCHIVOS

La alternativa más primitiva es uno o más archivos almacenados en el sistema de archivos del sistema operativo. Esta forma de lograr persistencia tiene como ventaja que no requiere nada más que el conector para funcionar, dado que Java provee los mecanismos necesarios para utilizar archivos. Las principales desventajas son: que requiere la construcción de mecanismos de indexación para acceder de manera eficiente a el o los archivos, se debe hacer una traducción entre los objetos almacenados y el formato de registro que se utilice en el archivo, se debe construir un mecanismo que permita el acceso concurrente del archivo si es que se permite abrir más de una sesión por vez, y el acceso remoto a los archivos requiere de algún soporte externo al servicio tal como el protocolo NFS (Network File System o Sistema de Archivos en Red).

Java provee una forma estándar para almacenar objetos en archivos, denominada Serialization. Este mecanismo permite almacenar en un archivo uno o más objetos junto con todas sus referencias. Podría ser una alternativa válida, siempre y cuando se pueda garantizar que exista memoria suficiente para mantener el respositorio de objetos, y no se requiera acceso concurrente al repositorio (en este caso, el archivo serializado). Esta forma de almacenamiento podría pensarse como una extensión del conector de memoria, donde la sesión de trabajo se almacena en un archivo, al momento de cerrase la sesión.

Este modelo no requiere de mucho trabajo extra, ya que el único requerimiento que Java impone para que un objeto puede ser serializado, es que el mismo implemente la interfase: java.io.Serializable. Dicha interfase sigue el patrón de diseño Marker Interface [GrandM01] o interfase marcadora. En este patrón, la interfase marcadora, que no declara métodos ni atributos, determina que la clase que la implementa, cumple con una condición lógica (implementarla representa verdadero). En el caso de Serialization, una clase que implemente Serializable, indica que el mecanismo de Serialization debe considerar los objetos de dicha clase durante el proceso de serialización.

En conclusión, si sólo se necesita la funcionalidad del conector de memoria con persistencia durable, Serialization es una buena alternativa.

PERSISTENCIA MEDIANTE UNA BASE DE DATOS RELACIONAL

Una forma de hacer un repositorio accesible en forma remota y concurrente, es mediante una base de datos. Las bases de datos más difundidas son las relacionales.

PSS está basado en SQL3, que es una extensión al modelo relacional soportado por el SQL tradicional. El problema que surge al utilizar una base de datos relacional, es que lo que se quiere

almacenar en este caso, no son datos relacionales, sino un modelo de objetos. Se requiere una transformación bidireccional entre los objetos y el modelo relacional de tablas y registros.

Las bases de datos relacionales utilizan el lenguaje de consulta SQL. Las consultas deben realizarse sobre un modelo que las soporte. Dicho modelo deberá ser generado por el compilador y más específicamente, por la contribución del conector persistente relacional al compilador. Si bien el lenguaje de consultas es generalmente estándar, para todas las bases de datos, el lenguaje de definición del modelo (DDL o Data Definition Language) no lo es. Esto implica que no sólo se deberá construir un conector para bases de datos relacionales, sino que el conector deberá considerar cual es la base de datos que se quiere utilizar.

Dado que el compilador será quien defina el modelo relacional a utilizar, se requiere entonces que dicho modelo sea creado en la base de datos propiamente dicha. Hasta que no esté creado el modelo, no se podrá utilizar el servicio de persistencia con el conector. Se agrega entonces un paso posterior a la compilación, para poder utilizar el servicio de persistencia.

Existen herramientas o bibliotecas que simplifican el uso de bases de datos relacionales para la persistencia de objetos. Tal es el caso de: Hibernate [KingG01], Castor[Castor01], Toplink[Toplink01], JDO [RoodsR01], entre otras. Son denominadas genéricamente ORM (Object-relation Mapping) o mapeo de objeto-relación.

En estos modelos, por lo general, una instancia de una clase particular representa un registro en una o más tablas, dependiendo de la jerarquía de la clase. Cada atributo persistente de la clase está representado por una o más columnas en una tabla. Todas ofrecen un mecanismo de consulta de alto nivel, que termina generando consultas SQL con las que se interactúa con la base de datos. De esta forma, el usuario no está obligado a tratar en forma directa con SQL. Algunas de estas herramientas resuelven las diferencias de sintaxis particular de DDL para cada base de datos mediante mecanismos de extensión por el usuario y otras sólo funcionan con algunas bases de datos fijas. La relación entre el modelo de objetos y el modelo relacional, el mapeo de objetos, está definida, por lo general, en una configuración en forma de archivo XML, aunque algunas de ellas soportan este mapeo a través de anotaciones en las clases Java [Annotations01].

Algunas herramientas como Hibernate o Toplink, soportan persistencia transparente, de forma tal que no imponen restricciones o requerimientos sobre las clases que se pueden persistir. A este tipo de persistencia se lo llama genéricamente POJO (Plain Old Java Objects o “Viejos y simples objetos Java”) dado que cualquier objeto común Java puede ser persistido. JDO, en cambio, requiere de un pos- compilador, que modifica las clases generadas por el compilador para que puedan ser persistidas.

O bien mediante el uso de alguna herramienta como las mencionadas, o mediante la construcción de otra herramienta, un conector para una base de datos relacional es perfectamente viable.

El principal problema del uso de una base de datos relacional, es tener que coexistir con dos modelos distintos, pero que representan lo mismo [NewardT01]. Es necesario crear otro modelo (relacional) análogo al modelo de objetos. Si se cambia el modelo de objetos, se debe cambiar el modelo relacional, cada vez que se guarda un objeto se debe traducir al modelo relacional y cuando se lee del modelo relacional se debe volver a transformar en objetos.

Otro punto a considerar es que el modelo en la base de datos relacional es visible al usuario, esto expone los detalles de la implementación del conector. Lo que también podría fomentar que el usuario interactué con el modelo relacional directamente, creando así una dependencia con la implementación

del conector. Esta dependecia de la implementación viola las políticas de CORBA y de la especifcación del servicio de estado persistence.

PERSISTENCIA MEDIANTE UNA BASE DE DATOS ORIENTADA A OBJETOS

Si lo que se quiere guardar son objetos: ¿por qué no guardar objetos directamente? ¿Por qué es necesario transformar los objetos a otro modelo? ¿Por qué no simplemente utilizar una base de datos orientada a objetos?

Cuando se comparan las bases de datos orientadas a objetos con las relacionales, comúnmente se realiza la siguiente analogía:

“Si la base de datos fuera un garaje, y los datos fueran un auto, en una base de datos orientada a objetos simplemente se estaciona el auto, mientras que en una base de datos relacional cada vez que se quiere estacionar el auto, se requiere desarmarlo es sus piezas elementales”. [CKA01]

En el contexto del servicio de persistencia, son mínimos los requerimientos que se tiene sobre una base de datos orientada a objetos. Básicamente, que permita almacenar objetos y para luego poder buscarlos. Los requerimientos particulares del modelo de objetos que propone el servicio, tales como los almacenes y objetos almacenados, junto con sus identificadores en forma de array de bytes, son agregados al modelo de objetos de Java y no tienen porque estar soportados por una base de datos orientada a objetos.

En conclusión se requiere: una base de datos que soporte una cantidad de objetos no tan grande, consultas no tan avanzadas, junto con acceso remoto y que sea de uso libre. Esta base de datos debe soportar el modelo de objetos Java, correr en diversos entornos (tal como lo hace Java), preferiblemente estar desarrollada en Java, para no estar ligado a una plataforma específica.

No son muchas las bases de datos orientadas a objetos que cumplen con estos requerimientos. La única que cumple con todos ellos es: DB4O [DB4O01]. Existe otra base que cumple parcialmente con estos requerimientos llamada Caché [Cache01], pero no posee una licencia de código abierto y solamente está disponible para algunas plataformas como Windows y Linux.

Considerando las dos alternativas de base de datos, he optado por utilizar DB4O.

DB4O

DB4O o Data Base For Objects, sus siglas en inglés, que significan Base de datos para objetos. Se trata de una base de datos orientada a objetos nativa, dado que no está implementada sobre una base de datos relacional. Está desarrollada con licencia de código abierto de tipo GPL (GNU General Public License).

Existen dos versiones, una para Java, y otra para .NET. Ambas permiten ser incrustadas en el sistema que las use. Esto hace que no imponga ningún requerimiento adicional a mi servicio de persistencia, más allá de la propia biblioteca que será incrustada en el servicio. Soporta acceso remoto, transacciones y consultas avanzadas.

ACCESO A LA BASE DE DATOS

El acceso a la base de datos se realiza mediante la clase com.db4o.Db4o. Esta clase hace las veces de fábrica o factory [Gamma01], de conexiones a la base de datos. Todas las operaciones son realizadas mediante métodos estáticos en dicha clase.

class db4o

Db4o

+ configure() : Configuration + licensedTo(String) : void + main(String[]) : void

+ openClient(String, int, String, String) : ObjectContainer + openFile(String) : ObjectContainer

+ openServer(String, int) : ObjectServer + version() : String

«interface»

ObjectServer

+ close() : boolean + ext() : ExtObjectServer + grantAccess(String, String) : void + openClient() : ObjectContainer

«interface»