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.

sábado, 13 de diciembre de 2008

Patrones Enterprise (parte 1)

En los post anteriores, me dediqué a definir qué es una aplicación enterprise, estableciendo la arquitectura de las mismas mediante cuatro capas estándares: Presentación, Acceso al Negocio, Negocio y Persistencia. Luego me detuve en la capa de dominio para resaltar su extrema importancia, hasta el punto de afirmar que el dominio es lo único importante. Gran parte de este segundo post la dediqué a la separación entre aplicación y negocio, y al objetivo primario de conseguir un dominio portable y rehusable entre distintas aplicaciones.

En este post, mostraré la forma de lograr esta deseada separación entre aplicación y negocio, mediante los famosos patrones de diseño de Martin Fowler, que pueden encontrarse en el "Nuevo Testamento" de los Patrones de Diseño titulado: Patterns of Enterprise Application Architecture (si consideramos al Design Patterns de GoF como el "Antiguo Testamento"). Las ideas que aquí explicaré son parte de ese libro y los diagramas y muchas otras ideas más pertenecen al material de la clase de Arquitectura de Software dictada por el Ing. Guillermo Pantaleo en la FIUBA.

Patrones de Dominio

A grandes rasgos, existen por lo menos tres formas de organizar la arquitectura de una aplicación enterprise:

TransactionScript

Supongamos que la aplicación enterprise que tenemos que diseñar es tan simple que no se justifica separar en demasiadas capas. Supongamos que, al querer forzar esta aplicación, incurrimos en un sobrediseño, que acaba complicando en extremo el producto final. Supongamos un negocio simple, una aplicación con pocas pantallas, en las que apenas mostramos lo que hay en una base de datos para que pueda actualizarse y realmente no tenemos relaciones complejas.

Construir un buen modelo de dominio es costoso y requiere un tiempo de setup bastante grande. Si se sabe que la aplicación será simple y que no evolucionará con el tiempo, no tiene sentido invertir en la construcción de un modelo de dominio. Para eso existe el patrón Transaction Script.

Transaction Script es escencialmente un procedimiento que toma el input de la presentación, lo procesa, trabaja con una base de datos o invoca operaciones de otro sistema y devuelve datos a la presentación para que se los entregue a la vista.

El siguiente ejemplo muestra el cálculo del Pago de un Proyecto:



Es interesante destacar que este diseño no es para nada Orientado a Objetos. No tiene nada que ver que haya clases y métodos. No existe un Modelo de Dominio, por ende no tendremos una clase Pago, ni una clase Proyecto, simplemente un ServicioPago que accede a la BD a través de un simple Gateway, que devuelve un ResultSet con los resultados que el Gateway ejecuta.

Como se imaginarán, un Transaction Script es lo más rápido de construir y lo más difícil de mantener.

Table Module

¿Qué tal si el dominio es un poco más complejo? Obviamente si estructuráramos la arquitectura de la aplicación con un Transaction Script al poco tiempo estaríamos en problemas. Pero, ¿y si la aplicación no es lo suficientemente compleja como para justificar la costosa curva de inicio de un Modelo de Dominio? Para eso contamos con un patrón intermedio llamado Table Module.

Con Table Module no tenemos un Modelo de Dominio puro, pero casi. Ya podemos tener nuestra aplicación dividida en capas físicas (tiers), como estuvimos viendo en los post anteriores. Ya tendremos una capa de Negocio y también una capa de Persistencia. En la capa de Negocio vivirán los objetos de dominio, como Proyecto y Pago si seguimos con el ejemplo anterior, pero la diferencia con un Modelo de Dominio puro es que por cada tabla tendremos una instancia de negocio; habrá una instancia de Proyecto que represente a todos los proyectos y una instancia de Pago que represente a todos los pagos.

El siguiente ejemplo muestra cómo sería el cálculo del Pago de un Proyecto con Table Module:



Table Module está diseñado para trabajar con Record Set. La capa de persistencia podría ser un conjunto de Gateways que tiren consultas a la base de datos y devuelvan los resultados en Record Sets (o DataSet, como se muestra en los diagramas), que no son más que una representación en memoria de los resultados que pudo haber arrojado un query de SQL.

Esta forma de trabajar fue muy popular en .NET y también es la que se podría construir encima de un driver JDBC, por ejemplo. Por supuesto que la forma de estructurar el código con este patrón es mucho más ordenada que Transaction Script, hay menos duplicación y es más fácil de mantener, pero aquí también nos enfrentamos al problema de que al no tener un Modelo de Dominio puro, perdemos muchas de las ventajas de la Orientación a Objetos, como la herencia, el polimorfismo, etc. (Imagínense que heredar de Proyecto no nos proporcionaría las mismas ventajas que en una capa de negocio pura, ya que recordemos que un objeto Proyecto en Table Module es algo así como un Singleton, tendremos una única instancia para todas las representaciones de Proyectos).

Domain Model

Llegamos a la estrella de la Orientación a Objetos. El patrón del que venimos hablando hace dos posts: Domain Model.

En un Modelo de Dominio puro tendremos muchas desventajas y muchas ventajas. Como todo en software, Domain Model no es un bala de plata. Implementar un Domain Model implica un costo altísimo de diseño y construcción de código de infraestructura que sólo es rentable si la aplicación es lo suficientemente grande y compleja, y/o se prevee que deberá escalarse en el futuro.

Volvamos al ejemplo del cálculo del Pago de un Proyecto:



Domain Model es el patrón opuesto a Transaction Script. Ya no tendremos un solo Proyecto para todos los proyectos y un solo Pago para todos los pagos. Habrá una representación en memoria para cada instancia de Proyecto y una para cada instancia de Pago. Además que tendremos objetos exclusivos para resolver cuestiones de lógica de negocio como objetos de Estrategias y demás.

Junto con la implementación de un Domain Model vienen muchas complicaciones de temas de infraestructura que son parte del costo al que antes me refería.

Tendremos la complicación de la impedancia entre el mundo de los objetos y el mundo de las tablas, resueltos por ejemplo por el patrón DataMapper (los ORM son ejemplos de implementación de este patrón: Hibernate, TopLink, etc). En la capa de persistencia necesitaremos patrones más complejos que los simples Gateways. Patrones como DataMapper o Active Record, que se encargarán de mapear de alguna forma el Modelo de Datos que vive en la base de datos, generalmente de tablas relacionales, y el Modelo de Dominio que vive en la capa de Negocio.

Tendremos que encontrar una forma óptima de manejar y manipular estos objetos en memoria. Si el modelo cuenta con diez millones de proyectos, no podremos mantener diez millones de representaciones en memoria al mismo tiempo, ya que no sería óptimo ni físicamente posible. Para resolver esta cuestión existen patrones de infraestructura como Lazy Load, que suelen utilizar los Data Mappers para manejar el "paginado" en las relaciones y colecciones de objetos.

Además, tendremos problemas de concurrencia a nivel capa de dominio, ya que los objetos trabajarán desconectados de la base de datos. Aparecerán lo que se llama transacciones de negocio, donde una transacción de negocio puede incluir muchas transacciones de sistemas (accesos a la base de datos). Los objetos de dominio serán modificados, borrados, dados de alta, por distintos usuarios que estarán manipulando las mismas entidades, pero distintas representaciones locales, y todo esto en memoria, sin que la base de datos se entere. Una Unit of Work se encargará de mantener el registro de las modificaciones en los objetos y de impactar dichos cambios en la base de datos al final de cada transacción (con un commit o un rollback), y otros patrones se encargarán de la concurrencia entre las modificaciones de los distintos usuarios, como Optimistic Offline Lock, Pessimistic Offline Lock y/o Coarse-Grained Lock (el último está basado en el concepto de Agregaciones de Eric Evans de su Domain-Driven Design).

Trabajar con objetos es hermoso, pero implica la construcción de una fuerte y sólida infraestructura. Dependiendo de qué tan robusta sea esta infraestructura, serán más o menos transparentes todas estas cuestiones para un diseñador/programador de la capa de dominio.

A la hora de elegir

A la hora de elegir, Fowler nos propone un excelente gráfico cualitativo/comparativo entre los tres patrones de arquitectura:


En él podemos ver que a medida que crece la complejidad de la lógica de dominio de la aplicación, el esfuerzo en el Transaction Script y en el Table Module se dispara al infinito. Sin embargo, el costo de entender la complejidad de un Modelo de Dominio y de diseñar la infraestructura necesaria para soportar el mismo, es demasiado costosa para una aplicación simple. En la práctica, nadie sabe a ciencia cierta qué tan compleja puede ser la lógica de un negocio, no hay métricas ni formas estándares de medir esa complejidad. Por eso no es fácil ponerle valores a los ejes de este gráfico.

Los tres patrones no son mutuamente exclusivos. Algunas de las funcionalidades de una aplicación podrían estar implementadas con Transaction Script y otras con Domain Model. Otro punto para destacar es que los patrones no muestran una implementación por sí mismos. Los diagramas de clases y de secuencias que expuse no son más que representativos. Los patrones de diseño encapsulan ideas, no implementaciones; el qué y no el cómo.