miércoles, septiembre 10, 2008

Accesos a base de datos: Patrón ActiveRecord

Uno de los problemas con los que nos enfrentamos cuando trabajamos con aplicaciones que hace uso de datos, por ejemplo almacenados en una base de datos relacional, es que estrategia elegir para acceder a ellos y poder consumirlos en nuestra aplicación orientada a objetos.

Este no es un problema trivial, debido a que tratamos de integrar dos paradigmas que manipulan la información de maneras muy diferentes, por un lado tenemos el paradigma relacional, con el cual podemos relacionar nuestros datos mediante uniones de nuestras tablas, y por otro lado tenemos el paradigma orientado a objetos que mediante relaciones entre los objetos podemos "navegar" a través de los datos.

Otros problemas son por ejemplo los diferentes tipos de datos que se manejan en una base de datos relacional y los que se manejan en nuestro lenguaje orientado a objetos; otro problema puede ser la estructura que almacena los datos en la base de datos relacional, puede ser muy diferente a nuestro modelo de objetos en nuestra aplicación orientada a objetos.

A todos estos problemas se le conoce como Impedancia de Discordancia Objeto-Relacional. Existen varias técnicas y patrones para tratar de minimizar los problemas que esta incompatibilidad entre los dos paradigmas crea. Todas las soluciones se centran principalmente en realizar un "mapeo" entre nuestros datos relaciones y nuestros objetos, a esta técnica se le conoce como ORM (Object-Relational Mapping)

En .NET tenemos varias opciones para acceder desde un modelo de objetos a un modelo relacional:

ADO.NET: Son las librerías base que permiten la conexión a datos, esta es la forma mas básica y a mas bajo nivel de como acceder a datos, requiere de una buena cantidad de esfuerzo para hacerla funcionar con nuestra aplicación.

Generadores de código DAL: Existen otras soluciones que generan código fuente a partir de un esquema de base de datos, el código generado generalmente conforma lo que se conoce como un Data Access Layer, la cual representa una capa en nuestra aplicación que contiene todo el comportamiento y lógica para acceder a nuestros datos a partir de nuestros objetos. Como ejemplo de estos generadores esta SubSonic.

ORM: Son librerías que integramos en nuestra aplicación a la cual le alimentamos una descripción de como nuestros objetos "mapean" a nuestros datos relacionales, y la librería ORM se encarga de gestionar la interacción con la base de datos y nuestros objetos de forma transparente para nosotros. Una forma común de indicar como nuestros objetos mapean a nuestra base de datos es mediante archivos XML. Ejemplo de este tipo de librerías es NHibernate y MS LINQ

Para el acceso a datos de nuestra aplicación vamos a hacer uso de librerías ORM. La librería que vamos utilizar hace uso de un patrón llamado ActiveRecord, de ahí el nombre de la librería es Castle ActiveRecord.

ActiveRecord es un patrón en el cual, el objeto contiene los datos que representan a un renglón (o registro) de nuestra tabla o vista, ademas de encapsular la lógica necesaria para acceder a la base de datos. De esta forma el acceso a datos se presenta de manera uniforma a través de la aplicación.



El patrón de ActiveRecord es utilizado en diferentes lenguajes/tecnologias como una manera estándar de acceder a datos, por ejemplo lo podemos encontrar en Ruby on Rails, CakePHP y por supuesto .NET a través de Castle ActiveRecord.

Castle ActiveRecord es una capa implementada encima de la librería ORM NHibernate, NHibernate hace uso de archivos XML para mapear objetos a datos relacionales, lo cual requiere de una curva de aprendizaje relativamente grande; ActiveRecord ayuda a minimizar esta curva, ya que a través de programación declarativa auto-genera los archivos de mapeo XML por nosotros.

Por ejemplo si tenemos la siguiente tabla en nuestra base de datos y deseamos leer un registro en nuestro objeto, la clase tendría que ser declarada de la siguiente forma:


[ActiveRecord]
public class Restaurante : ActiveRecordBase<restaurante>
{
[PrimaryKey(PrimaryKeyType.Native)]
public int Id { get; set; }

[Property]
public string Nombre { get; set; }

[Property]
public string Direccion { get; set; }

[Property]
public string Zona { get; set; }

[Property]
public string Telefono { get; set; }
}
</restaurante>

Los elementos entre [ ] se llaman atributos y es una forma declarativa de indicarle a ActiveRecord que propiedades de nuestra clase van a mapearse a un campo de la tabla. Si el nombre de la propiedad no corresponde con el nombre del campo entonces el atributo property lo podemos declarar como:

[Property("CampoTabla")]
, donde "CampoTabla" corresponde al campo de nuestra tabla.

Otro punto importante a notar aquí es que la clase esta marcada con el atributo [ActiveRecord], con el cual indicamos que nuestra clase va aser manejada por ActiveRecord, al igual que el atributo Property, si el nombre de nuestra clase no corresponde al nombre de la tabla, podemos colocar el atributo de la siguiente forma:

[ActiveRecord("MiTabla")]

Ademas de los atributos, es necesario que nuestra clase herede de la clase ActiveRecordBase, ya que esta clase base nos va a proporcionar el comportamiento necesario para poder realizar operaciones CRUD (Create, Read, Update and Delete; Crear, Leer, Actualizar y Borrar), a través de una serie de métodos estáticos.

Con los métodos estáticos Find* podemos realizar cualquier consulta sobre nuestros registros, por mas compleja que esta sea, pero como buenos desarrolladores que somos vamos agregar una serie de métodos estáticos adicionales sobre consultas que pueden resultar comunes, para quien consuma nuestras clases le sea mas fácil utilizarlas. Por ejemplo una par de consultas comunes pueden ser el buscar un restaurante por su llave primaria o bien por el nombre del restaurante. A continuación se muestran ambas consultas como serian usando únicamente los métodos Find*:

// Busqueda por llave primary, para restaurante con llave 1
Restaurante restaurante = Restaurante.FindByPrimaryKey(1);

// Busqueda por nombre, esta consulta también puede ser escrita con los métodos FindOne o FindFirst
Restaurante[] restaurantes = Restaurante.FindAllByProperty("Nombre", "Nombre del restaurante a buscar");
Restaurante restaurante;
restaurantes.Length > 0 ? restaurante = restaurantes[0] : restaurante = null;

Para ambas consultas podemos agregar los siguientes métodos estáticos a nuestra clase Restaurante:

public static Restaurante GetById(int id)
{
return Restaurante.FindByPrimaryKey(id);
}

public static Restaurante GetByName(string restaurante)
{
var restaurantes = Restaurante.FindAllByProperty("Nombre", restaurante);

return restaurantes.Length > 0 ? restaurantes[0] : null;
}

De forma que los usuarios de nuestra clase solo tengan que escribir:

Restaurante restaurante = Restaurante. GetById(1);

ó

Restaurante restaurante = Restaurante. GetByName("Nombre del restaurante a buscar");

ActiveRecord nos ofrece una serie de métodos para persistir cambios a la base de datos como Create, Delete, Save y Update, todos ellos mantienen nuestros cambios en memoria en un objeto llamado Session, en el momento que la sesión recibe un llamado a un método que en nombre tenga la palabra Flush, los cambios son enviados a la base de datos. SI queremos que nuestros cambios se vayan inmediatamente entonces podemos llamar los métodos CreateAndFlush, DeleteAndFlush, SaveAndFlush y finalmente UpdateAndFlush.

Una de las complicaciones de los datos relacionales al querer manipularlos como objetos, son las relaciones, por ejemplo podemos tener dos o mas tablas que están relacionadas entre si, por medio una llave externa o "Foreign Key", las cuales podemos relacionar mediante un "join", en el caso de los objetos cuando tenemos una relación de, por ejemplo encabezado/detalle.
En nuestros objetos debemos tener una propiedad, en la clase que sirva como encabezado, la cual sea una colección de los objetos que representan el detalle , por ejemplo en el caso de nuestra clase Restaurante, es importante poder acceder a el listado de menús para cada restaurante:

IList<Menu> menus = Restaurante.Menus;

La forma en como podemos representar estas relaciones en ActiveRecord es también a través de programación declarativa, en donde tenemos los atributos HasMany, BelongsTo, OneToOne, HasAndBelong y HasManyToAny, por ejemplo en nuestro caso donde un restaurante puede tener una o mas opciones de menu la vamos a representar con los atributos HasMany y BelongsTo.

Primeramente en nuestra clase Restaurante vamos a agregar una nueva propiedad llamada Menus la cual va a contener una colección de los menús que ese restaurante ofrece:

[HasMany(typeof(Menu), Inverse = true, Cascade = ManyRelationCascadeEnum.All)]
public IList&lt;menu&gt; Menus { get; set; }

Donde a HasMany le indicamos que la relación va a ser con nuestra clase Menu, en nuestra clase Menu también agregamos una nueva propiedad y la marcamos con el atributo BelongsTo donde le indicamos cual es el campo en nuestra tabla Menu que nos va a dar la relación con la tabla Restaurante.

[BelongsTo("RestauranteId")]
internal Restaurante Restaurante { get; set; }

De forma que podemos recorrer nuestra colección de Menu a través de un objeto Restaurante.

Para finalizar es necesario configurar e iniciailizar ActiveRecord en nuestra aplicación. La configuración nos permite indicar a que tipo de base de datos relacional nos vamos a conectar ademas de indicar la cadena de conexión que contenga la información del servidor, base de datos e información de seguridad, esta configuración se realiza por medio de un archivo XML. Un ejemplo de este archivo es el siguiente:

<activerecord>
<config>
<add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver"/>
<add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2005Dialect"/>
<add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider"/>
<add key="hibernate.connection.connection_string" value="Data Source=SERVIDOR;Initial Catalog=BASEDEDATOS;Integrated Security=True"/>
<add key="hibernate.show_sql" value="true"/>
</config></activerecord>

Es importante indicar el nombre de servidor de base de datos correcto, el nombre de la base de datos adecuado y la información de seguridad - usuario y clave si nuestro servidor lo requiere -.

Como ultimo paso es necesario inicializar ActiveRecord en nuestra aplicación, esta inicialización se debe de hacer únicamente una vez durante la ejecución de nuestra aplicación y antes de intentar utilizar alguna de las clases que creamos para usar con ActiveRecord:

var configurationSource = new XmlConfigurationSource("AR.cfg.xml");
ActiveRecordStarter.Initialize(typeof(Restaurante).Assembly, configurationSource);

AR.cfg.xml representa el archivo XML con la configuración para ActiveRecord.

Como conclusión, ActiveRecord es un patrón muy fácil de entender y utilizar en nuestras aplicaciones que requieren de acceso a base de datos, tal es la razón de que es un patrón popular en varios lenguajes.

Referencias e información
La siguiente lista de ligas contiene información mas extensa y detallada sobre ActiveRecord:

No hay comentarios.: