jueves, 18 de diciembre de 2008

Patrones Enterprise (parte 2)

En el post anterior de Patrones Enterprise, me enfoqué principalmente en los Patrones de Dominio del libro de Martin Fowler: Patterns of Enterprise Application Architecture (ver catálogo). Para continuar con el tema, escribiré sobre los distintos patrones de persistencia, prestándole especial atención a los patrones de arquitectura y su relación con los patrones de dominio ya vistos.

Patrones de Persistencia


Dentro del universo de los patrones de Fowler, encontramos tres distintos tipos de patrones de Persistencia.

  • Patrones de Mapeo
  • Patrones de Comportamiento
  • Patrones de Arquitectura

Patrones de Mapeo

Dentro de los patrones de Mapeo tenemos también una subdivisión importante entre:
  • Patrones de Mapeo de Relaciones
  • Patrones de Mapeo de Jerarquías
Los patrones de Mapeo de Relaciones centran su atención en la forma en que los objetos se relacionan en memoria y la forma en que las tablas se relacionan en la base de datos. Cuando la gente habla de mapeos objeto-relacional, en general se refiere a estos patrones (y también a los de Mapeos de Jerarquías).

Principalmente, dentro de esta categoría, Fowler explica tres patrones:
  • Identity Field: consiste en generar un número y administrarlo; puede ser, por ejemplo, un contador en la base de datos, o un atributo de una clase genérica en memoria.
  • Foreign Key Mapping: sirve para mapear relaciones de uno a muchos; una relación de este tipo, que en memoria consiste en un objeto A con una colección de objetos B dentro, se mapea con una clave foránea del lado de la entidad contenida; la relación se invierte en la base de datos.
  • Association Table Mapping: sirve para mapear relaciones de muchos a muchos; en una base de datos esta relación se resuelve introduciendo una tercera tabla que contenga las claves primarias de ambas entidades involucradas en la relación.
Los patrones de Mapeo de Jerarquías, como su nombre lo indica, se dedican a resolver la representación de la herencia de clases/objetos en disco. Los tres patrones sugieren distintas formas de llevar a cabo esta proyección del dominio en la base de datos:
  • Single Table Inheritance: mapea toda la jerarquía en una única tabla, duplicando muchos datos, pero facilitando las búsquedas.
  • Class Table Inheritance: es exactamente el opuesto; mapea cada clase de la jerarquía en una tabla individual, sin ninguna duplicación de campos.
  • Concrete Table Inheritance: es una solución intermedia, en la que sólo se mapean las clases concretas y las abstractas simplemente duplican campos en sus clases hijas.

Patrones de Comportamiento

Los patrones de Comportamiento resuelven problemas de infreaestructura relacionados directamente con funcionalidades de soporte para el dominio.
  • Unit of Work: sirve para mantener un registro de todos los objetos que fueron modificados durante una transacción de negocio, es un controlador del mapeo a la base de datos y administra la transacción de negocio (esto significa: cuando tiene que ejecutar un commit, pide una conexión del pool de conexiones de la base de datos y repite las operaciones que fue registrando desde el begin).
  • Identity Map: sirve para evitar tener en memoria dos representaciones distintas del mismo objeto en una transacción de negocio; funciona como una caché de objetos de negocio; como corolario, minimiza las lecturas a la base de datos (el ciclo de vida de un Identity Map es una transacción de negocio).
  • Lazy Load: carga tardía o perezosa; resuelve problemas de carga desmesurada y dependencias circulares; el ejemplo clásico es una relación uno a muchos, donde un objeto de negocio posee una colección de otros objetos; cuando el objeto A es solicitado, se trae a memoria el objeto A y sólo el objeto A, con la colección de objetos B vacía; luego, los objetos B serán leídos cuando realmente se necesiten.

Patrones de Arquitectura

No me he detenido demasiado en los dos anteriores tipos de patrones de persistencia porque los primeros son muy sencillos y los segundos son bastante más complicados (una explicación más prolija merece un post propio, en el que debería explicar los conceptos de transacciones y concurrencia). Los patrones de arquitectura son los que en este momento más me interesan porque son los que más se relacionan con los patrones de dominio.

A grandes rasgos, podemos hacer una primera clasificación de estos patrones, considerando como característica determinante el tipo de relación que éstos establecen entre la capa de dominio y la capa de persistencia (de ahí su nombre de Arquitectura). Considerando esto tenemos los:


La dependencia es de izquierda a derecha; es el Dominio quien conoce a la Persistencia.
  • Mapper: son aquellos que invierten la dependencia (como estuvimos viendo en los post anteriores) mediante el uso de interfaces alojadas en el dominio; ejemplo: Data Mapper.


La dependencia es de derecha a izquierda; es la Persistencia quien conoce al Dominio, y no al revés.
  • Sin capa de persistencia: en este caso no existe una capa de persistencia; las clases del dominio saben cómo persistirse; la lógica entera de acceso a la base de datos se encuentra dentro de los mismos objetos de negocio; el acoplamiento con la base es infinito; ejemplo: Active Record.

Gateway

Table Data Gateway es un ejemplo que funciona a la perfección con el patrón de dominio Table Module. De hecho, Table Data Gateway es un Table Module sin la lógica de negocio. En el ejemplo que vimos para Table Module (en el post anterior), ProyectoGateway y PagoGateway eran dos ejemplos de Table Data Gateway. Existirá un Gateway por cada tabla/entidad en la base de datos.


Por otra parte, tenemos Row Data Gateway que se orienta un poco mejor a objetos y no a tablas, aunque igual no deja de ser un Gateway a la base de datos. Como su nombre lo indica, Row Data Gateway es una representación de un registro y no de una tabla como Table Data Gateway. Row Data Gateway puede funcionar con Transaction Script (aunque esto es discutible, ya que en un Transaction Script puro no tendríamos ni capa de persistencia, ni el concepto de entidad de negocio) o con un Domain Model extremadamente simple. Bajo ningún concepto, podría servir para la persistencia de un Table Module, porque no está orientado a tablas.


Active Record

En el mercado hay varias implementaciones de este patrón, y muy populares. Tenemos por ejemplo Ruby On Rails, el conocido framework para aplicaciones web de Ruby, o CakePHP, un framework para PHP. Como antes mencioné, el acoplamiento con la base es infinito, ya que no hay capa de Persistencia. Cada objeto de negocio sabe como persistirse.


Dado que son objetos de negocio de una capa de dominio, Active Record sólo funciona para cuando se está trabajando con un Domain Model. Por supuesto que, al igual que con Row Data Gateway, hablamos de dominios simples, sin lógica de negocio compleja, ya que de escalar, una aplicación hecha con Active Record, sería imposible de mantener.

Data Mapper

El Data Mapper es la estrella de la orientación a objetos. Es el patrón que soporta Modelos de Dominios complejos y enormes. Son los famosos ORM, como Hibernate o Top Link, que transforman de forma transparente los objetos del dominio en tablas y entidades del mundo relacional de las bases de datos.

A continuación, un ejemplo de implementación de un Data Mapper extraído de los apuntes de las clases de Arquitectura de Software de Guillermo Pantaleo, que aclarará un poco las cosas:



Lo más interesante que podemos ver en estos diagramas, creo yo, es la independencia de la capa de dominio. Si observamos con atención el diagrama de secuencia, podemos ver que el PersonMapper es quien se encarga de instanciar un objeto de dominio Person y de llenarlo con los datos que va a buscar a la tabla de la base de datos. PersonMapper implementa una interfase que está en la capa de dominio, que es la PersonFinder que contiene la especificación de los métodos necesarios para que se puedan hacer búsquedas sobre una persona específica de forma transparente. Noten que el cliente obtiene un PersonFinder a partir del DataMapper, que es entregado por la Registry, un patrón de base que todavía no he mencionado, y a través del cual obtiene la persona que necesita:


PersonFinder finder = Registry.getPersonFinder();
Person person = finder.find("Adrian Paredes");


A través de la interfase PersonFinder es que el Mapper logra invertir la dependencia del dominio.

Conclusión

Aislar el Dominio del resto de la aplicación no es una tarea sencilla. Para lograrlo se requiere un fuerte trabajo de diseño. Esto no significa que haya que reinventar la rueda. Los patrones de diseño que estuvimos viendo y los frameworks que hay en el mercado (muchos de ellos open source), que implementan varios de estos patrones, nos ayudan enormemente en esta tarea de construir aplicaciones enterprise escalables.

Martin Fowler, en su famoso libro Patterns of Enterprise Application Architecture, clasifica los patrones enterprise en patrones de:
  • Dominio
  • Persistencia
  • Concurrencia
  • Distribución
  • Presentación
  • Soporte


Y establece relaciones entre ellos:


Este diagrama de relaciones nos dice, por ejemplo, que si tenemos una aplicación simple podemos usar un Transaction Script o un Table Module. Si usamos Table Module, podemos usar Table Data Gateway. Si construimos un Domain Model y contamos con una lógica de negocio simple, podemos usar Row Data Gateway o Active Record, y sino Data Mapper. Si usamos un Data Mapper inevitablemente entramos en la complejidad inherente de un Domain Model y se nos abre una gama bastante compleja de patrones, que nos ayudan a resolver distintas cuestiones de una aplicación enterprise de gran volumen.