Artículos

miércoles, 25 de enero de 2017

La materia pendiente de muchos proyectos: buenos Unit Test y Code Coverage

Cuando no vemos la relación entre bugs y tests unitarios




Introducción


Este artículo trataré de exponer mis pensamientos respecto del tratamiento de bugs y la utilización de tests para tratar de mitigarlos

Escenario


Para trasmitir mejor la idea voy a armar el siguiente escenario

Complejidad del sistema

Supongamos que somos el arquitecto de un proyecto donde estamos construyendo una aplicación Legacy que utilizarán al menos 60 usuarios en forma diaria distribuidos en diferentes partes del mundo (con diferentes usos horarios). Además esta aplicación deberá tomar información de diferentes bases de datos, consumir servicios web externos y exponer sus propios servicios para otros sistemas. Por último, en la capa de negocio hay que desarrollar un motor de reglas que analizará los datos de servicios externos y despachará mensajes en una cola de trabajo (Ej. MSMQ).

Estado del proyecto

Supongamos que nos toca ingresar en un momento donde el proyecto aún está en estado de desarrollo (Beta) a 1 mes de ponerse en producción (Release 1.0)
Mientras estamos en pleno desarrollo se supone que el equipo de testing estuvo probando la aplicación y nos fue devolviendo aquellos errores detectados por el uso manual del sistema, los cuales se fueron corrigiendo al finalizar cada sprint.


Tamaño del equipo


Por último, nuestro equipo está compuesto de 14 desarrolladores, 2 analistas funcionales 3 testers, 1 líder de proyecto y 1 arquitecto (nosotros, porque el anterior renunció el mes pasado).


Puesta en marcha



Hasta que nos acomodamos para entender el negocio, la arquitectura y el estado del proyecto se pone en producción el Release 1.0....y a la semana comienzan a llover bugs. Pero no bugs relacionados con la interfaz gráfica (scripts rotos, urls erróneas, fallas en la maquetación, etc.) sino fallas en el procesamiento de la misión del sistema: produce información errónea.
El cliente se pone nervioso y no entiende cómo puede suceder tanta cantidad de bugs con la primera versión del producto. Nosotros que ya tenemos experiencia en desarrollo tratamos de explicarle que normalmente los primeros releases tienen más bugs que los siguientes pero que pronto se reducirán.

Pero al mirar bien el proyecto de Test nos damos cuentas que más del 50% de los tests se marcaron como obsoletos...porque cuando se crearon los requerimientos estaban muy inmaduros y cambiaban constantemente. Con lo cual el costo de actualizarlos era altísimo.


Análisis


Ante esta situación queremos ver la foto completa del problema y ejecutamos el análisis de Code Coverage de Visual Studio. Inmediatamente vemos que en la capa de negocio y particularmente donde está el motor de reglas el porcentaje de cobertura es muy bajo.
Por si fuera poco, los test unitarios que están activos no siguen una convención de nombres. Y al inspeccionar más a fondo vemos que muchos de ellos tienen múltiples Asserts y se están utilizando Mocks (en lugar de Stubs) para verificar métodos ejecutados que no son relevantes.

Tomando el toro por las astas


Ante una situación como la mencionada tenemos que tomar decisiones muy importantes, algunas de ellas opuestas o mutuamente excluyentes:

  • Dejamos que el área de QA canalice todos los bugs y los reporte a medida que continuamos con el desarrollo del sistema para corregirlos en cada sprint.
  • Le explicamos al cliente el problema que hay en los tests automáticos debido a la baja calidad que hubo en los requerimientos en la fase de construcción del primer release y pedimos usar dentro del sprint ciertos story points para corregir los tests.
  • Descartamos los tests obsoletos y solo trabajamos con lo que reporte QA.
  • No le decimos nada al cliente para no develar la falla pero inflamos la estimación de otros casos para meter por debajo arreglos de los tests obsoletos.
Estas u otras acciones que se pudieran tomar tienen de fondo una cuestión fundamental:

Asumir que hay una relación directa entre los bugs que se presentan y la falta de unit tests en la capa de negocio para tratar de solucionarlos ... o .... pensar que los bugs se deben a la falta de eficacia del área de QA y por lo tanto dejar como están los tests obsoletos.

Personalmente tengo la convicción de que gran parte de los bugs vinculados a las reglas de negocio se pueden mitigar con la creación de una batería de tests unitarios que además se ejecuten como parte del proceso de integración continua y ante cualquier falla en los test se rompa el build directamente ( o incluso se rompa porque el porcentaje de cobertura de código en la capa de negocio sea menor a un límite establecido).

Lo que sucede es que hacer este tipo de test lleva bastante tiempo, comprensión de negocio y , por que no decirlo, son algo que a los desarrolladores no les gusta hacer en general.

La elaboración de tests unitarios que formen parte del proceso de integración continua tiene muchísimos beneficios. Tener la tranquilidad de que cualquier modificación o agregado de funcionalidad está siendo evaluado por un conjunto de pruebas unitarias nos garantiza que si rompemos alguna lógica ya definida será en plena etapa de desarrollo y no en el entorno de producción o QA.

TDD: una herramienta que nos pueden ayudar.


¿Cómo hacer para que los desarrolladores incorporen y valoren la generación de tests unitarios en las capas críticas de la aplicación?. ¿Cómo detectar aquellas partes que deben tener la mayor parte de Code Coverage?

Una buena forma de incorporar el hábito de realizar pruebas unitarias es siguiendo la metodología TDD (Test Driven Design). Muchos creen que esta metodología solo es aplicable en un contexto XP pero la realidad es que no es así.

Lo más importante de esta metodología radica en seguir estas etapas de madurez del código para cada historia que vamos a codificar:

Make it Fail: en esta etapa lo que estamos buscando es el código mínimo necesario para poder escribir una prueba y que ésta falle naturalmente porque el código está recién iniciado (es como crear solo el API donde cada método / propiedad tiene un "throw new NotImplementedException()". En este momento nos concentramos en el negocio, sus complejidad / bifurcaciones que deberán ser cubiertas con pruebas que fallen.

Make it Work: luego de construir un set de pruebas que tengan un nivel de cobertura suficiente entramos en esta etapa donde el objetivo es poner el código necesario para superar todas las pruebas que escribimos primero. El código es solo el necesario para superar los tests.



Make it Better: una vez que alcanzamos superar los test podemos concentrarnos en hacer del código una solución más elegante o más eficaz incluso. Aquí sí podemos pensar en aplicar patrones de diseño o cualquier refactor que prepare el escenario a casos similares (si es que realmente vemos van a existir). Lo bueno es que para el final del sprint tenemos asegurado el funcionamiento de la historia (o la parte de la historia que el código resuelve) como mínimo si es que no se llegó a superar esta etapa de Make it Better.

Por ejemplo, dentro de un sprint un desarrollador puede tomar un PBI (Product Backlog Item) y puede empezar a codificar por las pruebas unitarias (Make it Fail). Nadie puede prohibirle esto en tanto al fin del sprint el PBI esté terminado. De esta forma creará solo el código que es estrictamente necesario para cumplir con las pruebas (Make it Work) habiéndose concentrado en el negocio y evitando así violar los principios de desarrollo YAGNY, KISS y de alguna forma Single Responsibility. Al final del sprint este desarrollador podrá junto con sus colegas, líder técnico / arquitecto plantear un refactor para el siguiente sprint que permita aplicar un diseño más robusto para los siguientes casos similares (Make it Better)

En cuanto a qué parte de la aplicación debe tener mayor Code Coverage, en mi opinión (y no digo que está bien para todos los casos) depende del tipo de software a desarrollar.

En las aplicaciones Legacy (que es donde tengo mayor experiencia) es fundamental tener cubierto la mayor parte de casos para las reglas de negocio. Con esto me refiero al conjunto clases / métodos que ejecutan la parte lógica más crítica de una aplicación. Siguiendo el ejemplo del escenario planteado, el motor de reglas debe resolver un conjunto amplio de entradas combinadas con datos de algún origen para producir resultados en consecuencia de esta combinación. Todas estas variedades, combinaciones posibles de salidas que produce el motor deben tener la mayor cobertura de código posible.

Es cierto que a primera vista suena algo tedioso y llevará bastantes story points crear estos escenarios para las pruebas unitarias pero la inversión será directamente proporcional al nivel de calidad y solidez del producto (calidad en términos de reducción de bugs).
Por el contrario, cuanto menor sea el nivel de code coverage sobre estas reglas de negocio más carga de trabajo quedará del lado de QA con la casi inevitable perdida de tiempo en desarrollo por arreglar esos bugs. Pero lo peor de esta situación es que aquellas combinaciones raras que la gente de QA no llegue probar impactarán directamente como Bugs en producción (con toda la urgencia y presión que esto implica).

Legibilidad de los test unitarios


Es importante respetar algún tipo de convención en los nombres de los TestMethods para tener una clara legibilidad a simple vista.

Una estructura estándar es por ejemplo "MethodToTest_ExpectedBehavior_Scenario", la cual se pude ejemplicar como: "AnalyzeInput_MustReturnFalse_WhenDueDateIsEqualOrLessThanToday".

Como se puede apreciar, el nombre del método a testear es AnalyzeInput, lo que debe dar por resultado es False y la situación por la que debe fallar es porque la fecha de vencimiento es menor o igual a hoy.

Mantenibilidad


Un método a testear debería seguir las siguientes convenciones:

Arrange: en esta parte del método se preparan los mock / stubs necesarios, se arman los fake objects para tomar como input y todo aquello que haga falta para ejecutar el método en cuestión

Act: en esta parte se lleva adelante la ejecución misma del método a testear 

Assert: en esta parte se codifica el tipo de aserción que corresponda (IsTrue, IsNull, IsNotNull, etc.) en función de la ejecución de la parte Act.

Por supuesto que si al observar el conjunto de unit test a implementar requiere mayor preparación para el escenario de pruebas se pueden crear clases base para los unit test donde se resuelvan todos aquellos aspectos comunes a diversos test, además de clases auxiliares para estos test.

Confianza


Supongamos que meses más tarde de escribir un test unitario otro desarrollador tiene que modificar el código que generamos. Al terminar de ejecutar sus propios unit tests debería ejecutar todos los unit tests asociados a la compilación en el servidor de integración contínua para no romper el build y en ese momento quizás se de cuenta de que sus tests funcionan pero se rompe el que escribimos nosotros.
Esta situación es un excelente síntoma de que: o su código va en contra de otra condición de negocio que se debe respetar....o esa condición de negocio que se validaba ahora cambió y, por lo tanto, también hay que ajustar el unit test relacionado.

Y cómo llevar esta metodología a la práctica...?


El factor principal que no se mencionó aquí es el rol del arquitecto o líder técnico del equipo. Estas dos figuras deben ser promotores y guardianes de usar TDD en la elaboración de las historias dentro de un sprint. Esto implica que ellos deben estar al tanto de las historias que se intentan meter en una iteración y observar que allí se elaboren los criterios de aceptación necesarios, cada uno de los cuales tendrá probablemente una prueba unitaria asociada.

Es decir, ellos deben explicarle a cada desarollador que ingresa al equipo la forma de trabajo, ayudarlos con los primeros sprint a utilizar esta metodología ya que probablemente no estén acostumbrados a trabajar así.
Una buena ayuda es hacer code review a medida que los desarrolladores van avanzando para ver que se va entendiendo y cumpliendo la práctica.

La forma de medir la efectividad de esta forma de trabajo es observando los porcentajes de code coverage sobre las reglas de negocio críticas. Además si los bugs reportados por QA o por el cliente estan correctamente clasificados se podrá establecer la relación de cantidad funcionalidad entregada sobre la cantidad de bugs reportados.


Espero este artículo les haya sido de su interés y pueda haber ayudado un poco a clarificar algunas ideas respecto de cómo implementar TDD en el ciclo de desarrollo.

Hasta la próxima!


martes, 14 de julio de 2015

La usabilidad no es opcional

La cruda verdad: la usabilidad no es opcional


Introducción


En el año 2007 se produjo un hecho que cambiaría radicalmente el dominio de los programadores en el desarrollo de interfaces gráficas: la aparición de IPhone.

Mas allá de todas las críticas que se puedan realizar sobre aquella primera versión, lo que es indiscutible es que transformó la experiencia de los usuarios al usar teléfonos y marcó un camino que luego todos los fabricantes trataron de seguir.

La experiencia del usuario


Hasta aquel momento los teléfonos eran muy diversos y la forma de comunicarse de esas interfaces gráficas estaban todavía dentro del dominio de los programadores


Es cierto que ya en aquel momento había personas que se dedicaban a hacer interfaces gráficas más amigables. Pero lo destacable es el papel del usuario hasta ese momento: se conformaba con lo que le daban para usar. El usuario podía comprar otras marcas y entonces tener que adaptarse a la experiencia que esa marca brindaba con el dispositivo adquirido.

En el mudo de las páginas web y las aplicaciones comerciales pasaba más o menos lo mismo. Es decir, el usuario se debía adaptar al sistema.

Tomemos por ejemplo la siguiente pantalla:


¿Es necesario tener al mismo tiempo todas esas opciones disponibles? ¿ Todos esos campos son realmente necesarios ? ¿Y cada campo a completar tiene la misma importancia?

Tomemos por ejemplo el siguiente sitio web:



¿Es necesario todo ese nivel de información en la Home del sitio?¿Cuál es el objetivo del sitio?¿Qué puedo hacer con él?

Estas pantallas reflejan el tipo de interfaz gráfica con la que el usuario interactuaba a diario.

La experiencia de los desarrolladores


Quienes estábamos en el mundo de desarrollo por aquellos años, y sobre todo si trabajábamos con tecnología .Net, estábamos fascinados por las maravillas que WebForms nos brindaba: controles auto-generados, generación de HTML, CSS y Javascript automático, programación orientada a objetos en el mundo web.

Hacer web por aquellos años con esta tecnología no era tan complejo y, de hecho, así lo propiciaba Microsoft. Teníamos que centrarnos en el código del servidor ya que la tecnología puramente Web no era estrictamente necesaria de aprender.

¿Y entonces qué cambió?


Lo que sucedió es que la web luego de la burbuja comenzó a crecer cada vez más y nuevos jugadores comenzaron a aparecer en escena: Google y Apple.

Ambas companías se focalizaron en brindar al usuario experiencias diferentes con las aplicaciones que ofrecían. Incluso la experiencia de usuario se vio afectada por la forma en que estas empresas permitían comprar, descargar y actualizar el software.

Con el correr de los años los usuarios se acostumbraron a estas nuevas características y se fue estableciendo una especie de "piso", de "mínimo" esperable para las interfaces gráficas, la manera de comprar, instalar y actualizar el software.

De esta forma la tendencia de generar una experiencia de usuario más enriquecida implicó que emergieran nuevos especialistas: los analistas de User Experience (UX).

Estos profesionales deben combinar una serie de técnicas y conocimientos que provienen de diferentes áreas: marketing, psicología, diseño, sociología, diseño industrial, comunicación y sistemas.
Pero además generan actividades y conocimientos que le son propios y no compartidos por otras áreas.

¿Cómo nos afecta en el proceso de desarrollo de software?


El cambio al incorporar un equipo de UX en el proceso de desarrollo es muy importante.
En primer lugar porque ellos deben comprender los objetivos del cliente, del producto. Deben poder definir cada paso de la interacción del usuario, flujo lógicos de información por medio la interfaz. 
Y como resultado de este trabajo ellos generan prototipos de dos tipos: alta y baja fidelidad.

Tradicionalmente este tipo de tarea fue responsabilidad del analista funcional. Y esto es sumamente interesante porque justamente es la parte donde se complementan ambas posiciones. 
Un buen analista funcional hoy en día tiene que tener conocimientos y llevar adelante prácticas de UX. Pero lo mismo resulta para los analistas de UX: tienen que incorporar algunas técnicas y metodologías que tradicionalmente usaron los analistas funcionales.
Esto se debe a que ambos tienen que comprender el producto a desarrollar/mejorar con un enfoque sistemico: entrada, procesamiento, salida y retroalimentación. La diferencia está en que los analistas funcionales se centran en el sistema y los analistas de UX se centran en el usuario



El prototipo en baja fidelidad (Sketch o Wireframe) que producen los analistas de UX es un input sumamente valioso para el desarrollo de sistemas.Con ellos se puede identificar inmediatamente los tipos de entidades y atributos que existen, sus  vinculaciones, los procesos de transformación de información, validaciones y workflows subyacentes.

¡Ah! ¿Entonces le ponemos unas horas de UX a nuestro proyecto y listo?


Esta pregunta creo que se puede responder en parte con la siguiente imagen:



Creer que UX se puede ofrecer por horas es pensar que esta actividad es un condimento que se le puede agregar a la comida que estamos elaborando.
Siguiendo con esta metáfora, no estamos viendo que UX no es un condimento sino un instrumento de cocina tan útil como un cuchillo. ¿Podemos cocinar sin cuchillo? Sí, pero seguramente algo no estará del todo bien...y nos preguntaremos por qué el cliente nunca está satisfecho...

La misión de UX es el usuario, su experiencia. Es el tipo de cuchillo que siempre hemos necesitado en desarrollo. 

En otro artículo mostraré como personalmente entiendo que interactúan las disciplinas de UX con el proceso de desarrollo de software ágil.

Hasta la próxima!









lunes, 16 de marzo de 2015

Arquitectura REST

Desafíos de una arquitectura REST

Introducción


En un artículo anterior explique desde mi punto de vista las diferencias entre SOAP y REST.

En esta ocasión vamos quiero explicar los desafíos que representa para el arquitecto de software la aplicación de este tipo de arquitectura.

Escenario


Asumiendo que lo que vamos a exponer son recursos con los cuales vamos a interactuar por medio de diferentes verbos, lo primero que tenemos que evitar es abrir nuestra capa de aplicación en métodos que ofrezcan la funcionalidad al mundo exterior. 

Supongamos el siguiente ejemplo:



Aquí tenemos un cliente HTTP que consume la capa de negocio de una aplicación por medio de una capa de servicio REST que trabaja con los verbos get, put, post, delete y search.

El resultado de esto es una aplicación cuya capa de presentación se encuentra desacoplada lo máximo posible del negocio. Así la capa de presentación puede ser diseñada, maquetada y programada por personas ajenas a cualquier tecnología propietaria de back end.

Desafíos 


Sin embargo tener una capa de servicios en formato REST no es necesario para lograr los mismos beneficios de esta arquitectura.
Esta capa podría implementarse perfectamente como WebService en formato SOAP o Json y mantendría los mismos beneficios.

La pregunta entonces es ¿ qué implicancias y desafíos tiene hacer una capa REST?

El principal cambio, desde mi punto de vista, es que lo que se expone con REST son recursos y el tipo de acción a aplicar sobre estos recursos son indicados por los verbos HTTP.

Si la capa de negocio tiene una buena arquitectura con patrones como Facade, la funcionalidad debería estar expuesta en forma de métodos que representan acciones de negocio. Y esto es muy distinto de lo que debemos hacer cuando pensamos en REST. Aquí los verbos están limitados a los estándares del protocolo HTTP (los más usados quizás son: get, put, delete, post, search) y lo que tenemos que pensar es QUÉ recurso vamos a exponer.

Por ejemplo, en una aplicación que maneja órdenes de compra y un usuario puede tener asignado para gestionar muchas órdenes de compra, es probable que todas las acciones que se puedan realizar sobre la orden sean métodos encapsulados de alguna forma por una o más fachadas (independientemente de la forma de despliegue).

Pero si queremos exponer esta fachada con una capa REST debemos pensar que el recurso en sí sería uno solo (la orden); la forma de leer las órdenes sería sobrecargas del verbo GET; las formas de buscar una orden serían sobrecargas del verbo SEARCH; y las respuestas a cada una de estas llamadas siempre debería ser un objeto de tipo orden (independientemente si se devuelve como Json o XML).
Si la orden tiene como atributo otro objeto más, como por ejemplo una sucursal, ésta debería ser en si misma otro recurso, y por lo tanto el cliente HTTP que quiera interactuar esta entidad tendría que usar los mismos verbos.

Consecuencias


Si continuamos con este análisis nos daremos cuenta que utilizar una capa de servicios REST implica centrarnos fuertemente en las entidades que vamos a compartir, porque esas entidades serían los recursos y lo que querramos hacer con ellas serán los verbos HTTP que vamos codificar y exponer.

Esto significa que si una entidad "Usuario" no tiene codificado una respuesta para el verbo SEARCH no se debería poder buscar esta entidad.

Otra aspecto que también sale a la luz es que claramente una capa de presentación cuya interacción con el back end sea por medio de servicios REST debe gestionar por si sola el renderizado y bindeo de todas estas entidades sueltas.
Por ejemplo, una sola página HTML puede que requiera disparar 4 llamadas GET, una para cada tipo de entidad que deba ser mostrada, ya sea en formato de combo, grillas o algún control combinado.


Conclusiones


A simple vista, generar una capa de servicios con WebServices parece mucho más simple y natural si es que estamos acostumbrados a la programación orientada a objetos.

REST se acerca más a la forma ideal con que fue concebida la web. Es una excelente forma de estandarizar la comunicación entre sistemas pero es posible que sea muy exigente para ser consumida directamente por una capa de presentación. 
Esta exigencia se debe a que la renderización e interacción de una página requiere utilizar múltiples llamadas con diferentes verbos.

Con REST es conceptualmente incompatible exponer como recurso ViewModels ya que por definición éstos son recortes del modelo especialmente creados para las vistas.

En síntesis, considero que utilizar REST implica un trabajo de análisis mayor por parte del arquitecto de software para no cometer errores o usos indebidos / forzados de esta maravillosa técnica.

miércoles, 28 de agosto de 2013

Herencia y bases de datos: casos aplicables y formas de persitencia

Introducción   

En el análisis orientado a objetos encontramos que algunas entidades tienen un tipo de relación especial: herencia. A la hora de establecer estas relaciones se suele tener en cuenta aspectos tales como: árboles de jerarquías, especializaciones de entidades, tipo de dato y hasta la evaluación de usar "composición / agregación" en lugar de  herencia (como lo sugieren algunos patrones GOF; ej.: Bridge, Visitor)

Sin embargo, un aspecto que suele quedar por fuera del análisis es cómo persistir una relación de herencia entre clases usando un modelo de tablas relacionales.

El objetivo de este artículo es analizar este aspecto y proponer soluciones concretas para aplicar en nuestros sistemas.

Relación entre análisis y programación orientada a objetos

En primer lugar es importante establecer una diferenciación entre el análisis orientado a objetos y la programación orientada a objetos. De esta manera podremos darnos cuenta que una actividad debería estar acompañada de la otra generalmente aunque esto no es un requisito excluyente.

El análisis orientado a objetos es una etapa donde no estamos construyendo un producto sino que estamos entendiendo su complejidad y características.
Esta actividad supone que un problema dado es entendido como un conjunto de entidades que tienen características, acciones y reacciones. Adicionalmente esto permite entender las relaciones que existen entre estas entidades. 
De esta forma no interesa si la solución de software estará programada orientada a objetos o no, ya que el foco está puesto en entender y explicar el problema. 
Este enfoque es válido, a mi juicio, para cualquier tipo de problema que analicemos dado que, como seres humanos, construimos nuestra realidad en base a conceptos descritos por un nuestro lenguaje. Y toda nuestra realidad se construye a partir de las relaciones que existen entre estos conceptos.

La programación orientada a objetos es una solución concreta basada en un paradigma que utiliza los mismos conceptos teóricos que el análisis orientado a objetos pero ello no significa  que deba existir una correspondencia 1 a 1 entre el análisis y la solución. Es decir que las entidades que un analista funcional encontró para describir un  problema no es obligatorio que sean las mismas  a la hora de desarrollar la solución de software. En este sentido, dentro de la ingeniería de software, algunas técnicas de desarrollo proponen hacer un desarrollo dirigido por dominio (DDD) lo cual significa darle el máximo de protagonismo a las entidades del dominio del problema y hacer de ello la capa de software que dirige el resto de la arquitectura.

Herencia aplicada


Tanto desde el análisis como desde la programación, la herencia se utiliza para los mismos fines:

  • Generar una jerarquía de entidades
  • Crear especializaciones de entidades
  • Reutilizar conceptos / código de programación
  • Extender modelos
Cuando una clase hereda de otra se dice que debe responder de manera inequívoca la siguiente pregunta: "Un A es un B ?". Por ejemplo: "Un trabajador es una persona?".
Si la respuesta es un SI entonces estamos en condiciones de aplicar el concepto de herencia. Pero si la respuesta es "puede ser", "depende" o cualquier otra que no sea un SI entonces hay que analizar con cuidado si estamos ante una relación de herencia o no.
La identidad de un objeto se define por combinación de sus características, su forma. Por esta razón, para responder a la pregunta anterior es necesario conocer la identidad de las dos entidades. Si me preguntan a mi si es lo mismo una Accion de Bolsa que un Bono de Canje....la respuesta es no sé!. Si me definen las características de ambos podré responder correctamente.

También es posible que la respuesta sea un SI pero al revisar las clases "hermanas" notemos algo extraño: que no parezca correcto que sean clases hermanas. En este caso lo que sucede es que una entidad no debe heredar directamente de otra sino que deben crearse otros eslabones en la jerarquía de clases.
Supongamos el siguiente caso: tengo una clase ControlVisual, de la cual hereda una clase Button; necesito agregar una clase TextBox y entonces pregunto: un TextBox es un ControlVisual?; al ser la respuesta un SI me debo preguntar también si es correcto que un TextBox esté a la par de un Button; si bien tiene características compartidas, cada uno podría tener una clase base distinta para formar, por ejemplo, una jerarquía como esta:
  • ControlVisual >ControlPresionable>Button
  • ControlVisual>ControlEditable>TextBox
De esta forma cuando necesite crear la clase MultiLineTextBox en lugar de descender de ControlVisual descenderá de ControlEditable, lo cual será mejor desde el punto de vista que compartirá características con la clase TextBox.

Persistencia de la herencia 

Es fundamental aclarar que lo que sigue solo se aplica para los casos de herencia donde la clase padre no es una clase abstracta. Si así lo fuera significaría que la entidad en sí no existe sino que actúa como molde o plantilla para otras entidades. Por esta razón las entidades abstractas no se persisten.

En el modelo de base de datos relacionales, una tabla puede tener su propia identidad, la cual se expresa por medio de una clave primaria. Con esta solución es posible individualizar cada uno de los registros dentro de la tabla porque cada uno tiene una identidad distinta (no casualmente al campo clave primaria se lo suele denominar ID ).

La relación que existe entre el modelo relacional y el modelo de objetos se manifiesta de esta forma:
  • Cada instancia de un objeto se representa en el modelo relacional con un registro de una tabla
  • La forma de un objeto está determinada por su Clase y en el modelo relacional la forma de un registro está determinada por la tabla que lo contiene
  • Si cada registro de una tabla tiene su propia identidad (ID) entonces el objeto en memoria tendrá un atributo que actuará de la misma forma.
 Dado que la Clase se expresa como equivalente a una Tabla, la relaciones de herencia entre clases se expresará también como relaciones entre tablas.

Tomemos el siguiente ejemplo de herencia de clases:




Lo primero que hay que entender es que la relación de herencia no significa que una persona tiene un empleado o que un empleado tiene un vendedor. Lo que realmente significa es que un empleado ES UNA persona y un vendedor ES UN empleado y por lo tanto también una persona.

La identidad de la clase empleado ES la suma de todos sus atributos más los de la clase persona y lo mismo sucede con vendedor.

En términos de persistencia esto significa que un registro de la tabla empleados no tiene sentido si no existe el registro relacionado de la tabla personas. Es decir, no hay empleado si no hay persona.

En el modelo relacional para aplicar este concepto lo que debemos hacer es que la tabla empleados no tenga un ID propio, sino el mismo ID que personas, aplicando además una restricción de borrado en cascada para mantener la integridad de los datos. Y en el caso de la tabla vendedores se debería aplicar la misma inteligencia. La cardinalidad de las relaciones que reflejan herencia sería de 1 a 1.






El problema de la impedancia

Si necesito representar un conjunto de empleados en memoria podría armar una lista de empleados de esta forma:

List<Empleado> = new List();

Cada elemento de la colección Empleados tiene los atributos de Persona y Empleado, por lo tanto para llenar la colección con los datos del modelo relacional tendría un problema de impedancia.
Esto se debe a que ,desde el punto de vista de los objetos, tengo los atributos de dos entidades reunidos en una (porque represento solo herencia de un nivel), pero desde el punto de vista relacional tengo dos tablas distintas unidas por una clave primaria en común. Es decir que si pensaba que podía mapear una Clase con una Tabla aquí me doy cuenta que no. En este caso necesito dos tablas para mapear una Clase.

Para cargar con valores de la base de datos una instancia de Empleado tendría que ejecutar consultas en múltiples tablas (dos en este caso). Si quiero automatizar el proceso de carga de datos en objetos debería conocer de alguna forma que este objeto se divide en dos tablas, y esa información debería estar en alguna parte del objeto.

Para solucionar este problema y asociar un objeto con un solo elemento de la base de datos sería conveniente crear una VISTA justo después de crear las dos tabla en que se divide en empleado, la cual traiga todos los atributos de ambas tablas y se llame Empleados. Para ello la segunda tabla que antes habíamos pensado llamarla así ahora debería llamarse por ejemplo Empleados_TBL.

Y para el caso de la Clase Vendedor habría que hacer lo mismo: es decir, renombrar la tabla como Vendedores_TBL y crear una vista llamada Vendedores. Pero en este caso, como se trata de una relación de tres niveles de herencia habría que hacer un JOIN entre Vendedores_TBL y Empleados (la vista) de manera que la unión con Personas sea implícita.

Empleados:


La vista tendría tanto el IdPersona de la tabla Personas como el IdPersona de la tabla Empleados, solo que este último habría que ponerle un alias, por ejemplo, IdEmpleados (lo importante es que la forma de asignar el alias sea una regla constante en todos los casos: "Id"  + Nombre de la tabla

Vendedores:


Aquí se aplica la misma lógica que en el caso anterior, es decir, incorporo el IdPersona de Empleados y Vendedores pero este último lo llamo IdVendedores.

De esta forma ya logramos tener una correspondencia de 1 a 1 para las relaciones de herencia, donde una Clase se corresponde con una Tabla o con una Vista que toma información de varias tablas.
Como ya es sabido, a la hora de realizar un Insert es perfectamente realizable sobre la una vista si es que tiene todo los campos necesarios.

El problema de la cardinalidad


¿Que sucedería si en realidad lo que necesito persistir es que una persona puede tener múltiples empleos a lo largo del tiempo?. Es decir, quiero que un registro de la tabla personas  pueda estar relacionado con varios registros de la tabla empleados (ej.: una persona que en 10 años ingresó dos veces a la empresa como empleado).

Desde el modelo relacional lo que necesitaría es que la tabla empleados tenga su propio ID y mantener un ID externo que apunte a la tabla personas, aplicando así la relación de uno a muchos mirando desde personas a empleados.



Pero en el mundo orientado a objetos esto genera un cambio importante. El cambio es tan importante que en realidad un empleado YA NO ES una persona sino que una persona pasa a tener N empleados asociados.
Es decir, debería dejar de tener la relación de herencia entre ambos y pasaría a tener una relación de agregación, donde en Persona tendría un atributo de tipo lista de empleado (List).
Este cambio además implica que la clase Empleado ya tiene su propia identidad y se vincula con Persona en una relación pero existe más allá de la relación. De esta forma la clase Empleado también puede relacionarse con PlanVacaciones, siendo que una instancia de PlanVacaciones puede tener muchas instancias de Empleados.





jueves, 4 de julio de 2013

Influencia de SOAP y REST en la creación de arquitecturas SOA y Web Services

Introducción 


¿Cuál es la mejor forma armar una arquitectura SOA y cuál para crear Web Services abiertos a Internet?

Desde hace tiempo que vengo leyendo información sobre la exposición de Web Services en formato  SOAP y REST, prestando especial atención a sus características pero también a sus objetivos.
Y en este punto es donde me voy a detener para presentar en este breve ensayo mi postura respecto de la elección de uno u otro formato a la hora de construir una arquitectura SOA o simplemente exponer servicios web

Supuestos


Para responder a las preguntas de la introducción voy a manejarme con definiciones solo conceptuales sobre cada tema sin entrar en detalles técnicos de cada uno dado que abunda la información en Internet y libros respecto de los detalles de SOAP, REST, SOA y Web Services.

En caso de no encontrarse el lector familiarizado con estos conceptos o sus pormenores dejo enlaces de interés que brindan tal información. Sin embargo aconsejo revisar múltiples fuentes de información para completar mejor el conocimiento.






Desarrollo


Es muy importante tener en claro cuáles son los fundamentos y diferencias que existen entre SOA y Web Services a la hora de decidir aplicar REST o SOAP

SOA


Como sabemos, SOA es una arquitectura de software que busca dar soluciones a los proceso de negocio de una empresa que atraviesan múltiples sistemas informáticos para su cometido.
Por ejemplo, una empresa adquiere un nuevo beneficio para sus empleados (por ejemplo, contrata los servicios de un restauran que le da 20% de descuento en los almuerzos a sus empleados). Este proceso de negocio: "adquirir beneficio corporativo" si bien generalmente es disparado por el área de RRHH tiene implicancias en otras áreas de la empresa. Por ejemplo podría afectar al área de proveedores, facturación, marketing, etc.
Y esta afectación se ve reflejada cuando cada una de estas áreas tiene que cargar en sus sistemas alguna información proveniente de esta adquisición u otras áreas.
El proceso es uno , "adquirir beneficios corporativos" y las implicancias son muchas.

Sin una arquitectura SOA la solución a esta situación son dos: empleados de diferentes áreas cargan datos en sistemas por separado (incluso a veces datos repetidos) y se mandan entre sí archivos  para completar otros sistemas, o el mismo proceso pero automatizado, es decir, cada sistema prepara una "integración" con otro sistema y se ponen de acuerdo en la forma en que se trasmitirán los datos. En muchos casos esto es una solución que consiste en inventar la rueda con cada sistema nuevo a integrarse.

Con una arquitectura SOA los sistemas implicados deberían exponer como servicios bien definidos, basados en protocolos estándar (nada de cosas a medida o propietarias) aquellas funcionalidades que son importantes para la organización y ésta tendría la posibilidad de interconectar estos servicios diversos para cumplir con un proceso de negocio.
En este sentido,  "adquirir beneficios corporativos" podría ejecutarse desde una interfaz gráfica centralizada ( o distribuida en varias dependiendo de la necesidad )  y detrás se encontrarían llamadas a diversos servicios expuestos por los sistemas propietarios de cada área.
Estas llamadas requieren, por supuesto, un proceso de orquestación de parte de quien los integra y esto lo podría realizar personal propio de la empresa o encargar el trabajo a alguna empresa con experiencia en este tipo de arquitecturas.

Entonces podemos comprender que SOA en realidad tiene, al menos, dos grandes desafíos: 

  • Hacer que los sistemas ( los más importantes de cara a la empresa) expongan su funcionalidad como servicios con protocolos estándar y bien definidos (documentados)
  • Orquestar los servicios en función de los diferentes procesos de negocio que atraviesan múltiples áreas ( implica un proceso previo de relevamiento para detectar estos procesos y la prioridad de los mismos ) 

Web Services


Desde un punto de vista de alto nivel, los Web Services son una excelente forma de implementar llamadas a procedimientos remotos (RPC). La excelencia está dada por sus características técnicas (protocolo HTTP, independencia entre cliente y servidor, etc.) y la idea subyacente es realmente muy simple: ofrecer funcionalidad remota a quien la necesite para construir así sistemas cada vez más grandes y más desacoplados.

Supongamos este escenario: una empresa de energía utiliza un sistema de cobranzas que operan los empleados para cobrarle a los clientes. Como todo sistema, requiere algún gasto de mantenimiento y/o soporte, de manera que, en realidad, el sistema es un activo de la empresa pero que genera costo. Si se aplicare un proceso de re-ingeniería que permitiere exponer como Web Services las funcionalidades más importantes del sistema, la empresa podría ofrecer estos servicios a otras empresas y abrir así su canal de cobranza (por ejemplo, permitir que se pueda cobrar los servicios en un supermecado).
De esta forma se necesitarían menos puntos de cobranza exclusivos de la empresa y se abrirían nuevos puntos variables, generando así ahorro de gastos y hasta podría llegar a comercializar estos Web Services para obtener algún otro ingreso adicional.

Las características técnicas de los Web Services implican un desafío importante en la toma de  decisiones de arquitectura porque se debe aplicar un formato de comunicación lo más estandarizado y eficiente posible.

Dado que estamos hablando de la posibilidad de hacer llamadas a procedimientos remoto basadas en estándares es natural comprender que la implementación de SOA generalmente se hace por medio de Web Services. Pero es igual de cierto que no toda implementación de Web Services es sinónimo de estar aplicando una arquitectura SOA.

SOAP y REST


¿De dónde nace entonces la dicotomía SOAP / REST?

REST es una arquitectura, no un protocolo en particular o una tecnología. Básicamente parte de una premisa fundamentalmente distinta de la de SOAP y esta más alineado con el funcionamiento ideal del protocolo HTTP.
Esta premisa supone que todo lo que existe en la web ( o intranet ) en realidad son recursos (URL) y por lo tanto podemos aplicar distintas acciones sobre ellos (verbos de HTTP).
En un nivel mayor de abstracción entonces nos encontramos que quienes pensaron el protocolo HTTP pensaron que si los recursos son sustantivos, las acciones serían verbos que se aplican sobre estos sustantivos ( ver http://blog.tordek.com.ar/2008/03/como-le-explique-rest-a-mi-esposa/ ).
Por eso la propuesta de REST es muy natural a la forma en que los humanos interactuamos con las  cosas.
Los verbos Delete, Put, Get, Set, en esta perspectiva cobran sentido si pienso que al utilizarlos le estoy diciendo al servidor (borrar NNN, obtener NNN, etc.).
Sin embargo no debemos perder de vista la premisa fundamental de REST: lo que hay remoto son recursos (sustantivos), no operaciones.

SOAP es un protocolo basado en XML que sirve para el transporte de mensajes y respuestas entre cliente y servidor que, junto con WSDL, generan un contrato, una especificación que dice claramente cómo debe realizarse una llamada a un procedimiento remoto, cual será el tipo de dato devuelto y cuáles son los tipos y cantidad de parámetros a recibir.
Pero lo más importante es que parte de una premisa fundamentalmente distinta a REST: aquí el protocolo HTTP es solo un medio de transporte de mensajes.
La operación (o verbo ) no se hace explícita en la llamada HTTP, sino que se encuentra implícita dentro del procedimiento remoto que atiende la llamada.
No le digo al servidor "borrar XXX" sino "llamar a XXX y darle YYY", siendo YYY el mensaje para que XXX haga lo que tenga que hacer (por ejemplo un borrado).

La dicotomía entonces se presenta porque hay quienes argumentan la creación de Web Services en favor de uno u otro, con premisas válidas en todos los casos, pero que a mi juicio no consideran el objetivo de lo que estamos hablando.
Generalmente el debate pasa por cuestiones técnicas: que si me ahorro más Bytes, que si tengo menor acoplamiento entre cliente y servidor, que puedo usar objetos tipados, que la seguridad, etc.
Planteada la discusión en esos términos la conclusión generalmente es que termina siendo casi una cuestión de gustos o tendencia ( me decepciona creerlo pero lo he visto, hay quienes siguen patrones o metodologías solo porque lo hace la mayoría solamente ).

No estoy planteando que  hay que desconocer las tendencias, sino que hay que usar la herramienta/técnica adecuada para el problema/momento indicado, no solo porque nos gana la pereza y entonces hacemos lo que hacen todos.

Conclusión


Si nuestro objetivo es utilizar la herramienta más indicada no solo por cuestiones técnicas, podré valorar todas las opciones, reconocer las adecuadas para cada momento y aplicarlas cuando sea necesario.

En este sentido creo que si lo que necesitamos es una arquitectura SOA (es decir, darle solución de integración a sistemas para atender los procesos de negocio) la solución sería que mis sistemas ofrezcan operaciones cerradas, atómicas, bien definidas, basadas en estándares que sean fácil y seguras de interconectar. Es decir, la premisa de SOAP (exponer operaciones por medio de mensajes) se acomoda más naturalmente a la solución que necesito.

Pero si lo que estoy buscando es una arquitectura orientada a Web Services, los cuales puedan ofrecer recursos concretos (más allá de HTML incluso, por ejemplo imágenes, archivos, etc.) entonces mi enfoque se ajusta más a REST, donde puedo pensar una arquitectura basada en recursos a los cuales según mi necesidad puedo aplicarle diferentes acciones (Get, Put, etc.).

Ahora, ¿cómo decidir si necesito SOA o solo Web Services?.

A mi juicio, si la aplicación va a estar en  Internet y permitirá realizar operaciones sobre diferentes recursos, donde quienes quieran consumir estos recursos deberán seguir mi estándar y documentación para interpretarlos con el formato JSON correspondiente, creo que corresponde una arquitectura orientada a Web Services en formato REST.

Pero si mi aplicación utiliza el entorno Web solo como medio de despliegue, pero en realidad es una aplicación comercial que busco poder integrarla por medio de operaciones con otras aplicaciones comerciales de manera segura, basada en estándares, entonces creo que corresponde una arquitectura orientada a SOA con Web Services en formato SOAP.

Las cuestiones técnicas y las modas/tendencias no deberían ser, a mi juicio, el factor decisivo para tomar una decisión de Arquitectura tan importante.

Fuentes






Rest Vs Web Services: Rafael Navarro Marset. ELP-DSIC-UPV
Modelado, Diseño e Implementación de Servicios Web 2006-07


miércoles, 22 de mayo de 2013

Ejemplos prácticos de Asociación, Agregación, Composición y Dependencia con C# .Net

Motivación

En la actualidad (Mayo 2013) existe mucho material en Internet que habla sobre asociación, composición, agregación y dependencia pero en la mayoría no se pone énfasis en la implementación de los conceptos sino en el análisis conceptual.

La realidad es que la forma de implementar cada tipo de relación es diferente (aunque parezcan mínimas las diferencias) pero es muy importante para un programador conocerlas a la hora de leer un diagrama de clases.

Es importante también aclarar que un diagrama de clases realizado por un analista funcional no es un modelo que se debe implementar así directamente, sino que debe ser analizado y comprendido por el programador para producir el verdadero diagrama de clases a implementar.

Eso es así debido a que durante el análisis y relevamiento se piensa en "describir" y no en solucionar. Así, el diagrama del analista funcional explica el domino del problema, mientras que el diagrama que realiza el programador explica el dominio de la solución. En este último pueden surgir clases que originalmente no estaban pero son necesarias para realizar una buena implementación  (cuestiones de arquitectura, patrones, etc.).

Aplicación práctica y conceptual


Es necesario acostumbrarse al término implementación, el cual se refiere al código de programación concreto de algo.
Por ejemplo, si dicen "esta clase es la implementación de aquella " están diciendo "este es el código fuente específico que aplica lo que dice aquella clase abstracta (o interfaz) ".
Por tal motivo, en adelante, me referiré directamente al término implementación.

Tanto la asociación, agregación, composición y dependencia son formas de representar las relaciones que existen entre clases.

Por ejemplo el siguiente diagrama:



La clase Persona tiene una relación de composición con la clase Domicilio.

Conceptualmente esto significa que los domicilios son una parte inseparable de la persona, por lo que si no existiera una persona entonces el domicilio de la misma debería desaparecer.
Si analizamos más en profundidad encontramos también que, si hubiera que persistir esta relación en una base datos tendríamos una tabla Domicilios cuyo ID sería IdPersona, y una tabla Personas con el mismo ID.
El hecho de que la tabla Domicilios no tenga su propio ID sino el de la otra tabla significa que cada registro de la tabla no tiene el peso propio suficiente, que depende 100% de la existencia del mismo ID en la tabla de Personas.
Si se llegase a borrar un registro de la tabla Personas habría que borrar su correspondiente registro de la tabla Domicilios para mantener la integridad de la información.
Por esta razón también se dice que la relación de composición es una relación fuerte, ya que una instancia arrastra a la otra en caso de eliminación (tanto de objetos en memoria como de registros en base de datos).

Ahora, es válido preguntarse ¿por que razón si es algo inseparable de la persona no lo pongo como un atributo más de la clase persona, por ejemplo de tipo string y no me complico tanto?

Efectivamente la clase Domicilio es un atributo de la clase Persona, y justamente la línea que las conecta es lo que indica la presencia del atributo. Domicilio existe como clase aparte porque en realidad no es un simple string sino un conjunto de atributos, por ejemplo: calle, localidad, numeración, piso, departamento, etc. Todos esos atributos forman parte de una entidad, el Domicilio, y no sería correcto dejarlos sueltos dentro de la clase Persona. Por eso también se persisten en tablas separadas si fuera necesario.

La clase Persona tiene una relación de Agregación con Categoría.

Conceptualmente esto significa que las categorías existen independientemente de la persona que la tenga asignada. En el modelo conceptual esto se corresponde con un enunciado como "una persona puede tener una categoría pero una categoría puede estar presente en muchas personas".
En un modelo de persistencia relacional tendríamos por un lado la tabla Categorías con su IdCategoría y la tabla Persona con un IdCategoría que señale la relación entre ambas tablas.
A diferencia de la composición, en la agregación la clave primaria de la tabla Categorías es independiente de la clave primaria de la persona, lo cual significa que se puede eliminar un registro de Personas sin que ello afecte la integridad de la tabla Categorías.

Ambos tipos de relación muestran que la forma del objeto (persona en este caso) está formado por partes externas.

La clase Persona tiene una relación de Dependencia con Postulación.

Conceptualmente esto significa que la Postulación es un objeto que la Persona utiliza para algún fin, dentro de alguna operación que ella realice (por ejemplo Postularse a un cargo). Pero una Persona no tiene en su interior una Postulación, sino que solo lo utiliza para realizar ciertas operaciones.
Esto es lo más importante y diferenciador respecto de las otras dos relaciones; aquí se pone énfasis en el uso de clases dentro de operaciones, es decir, para que una Persona pueda enviar una postulación depende la clase Postulación , quien es capaz realmente de realizar esa operación.
Al no tratarse de una relación que vincula la forma de los objetos, no existe una forma de persistir esta relación.

La clase Persona tiene una relación de Asociación con Sucursal.

Conceptualmente la asociación en un diagrama de clases implica transitividad y bidirección de clases. Por ejemplo una persona tiene como atributo interno a una Sucursal, pero (y aquí está la diferencia) una Sucursal también tiene un atributo de tipo Persona; la cardinalidad de la asociación indicará si Sucursal tiene una o muchas instancias de Persona, con lo cual en realidad el atributo de Sucursal podría ser una Lista o Vector de Personas.
Otra característica fundamental es que la vida de las instancias de ambas clases no dependen una de la otra.
En un modelo de persistencia relacional podrían suceder dos cosas: si se trata de una cardinalidad de 1 a 1 tendríamos la tabla Personas con su IdPersona más un IDSucusal y la tabla Sucursales con su IdSucursal más un IdPersona.
Cualquier otra cardinalidad requerirá la tabla Personas y Sucursales con sus Id individuales más una tabla que unifique ambas entidades (tabla de relación) con los campos IdPersona e IdSucursal, la cual permite la navegación en dos direcciones sin invadir a ninguna de las tablas reales.

El caso especial se presenta cuando en una asociación es necesario reflejar atributos de información. Por ejemplo, es necesario registrar la fecha de inicio y la fecha de fin de la vinculación entre empleados y sucursales.
Desde el punto de vista de la persistencia no es una complicación ya que la tabla intermedia que vincula a Personas y Sucursales tendrá más campos de información.
Pero desde el punto de vistas de entidades hay un cambio más importante: es necesario crear una nueva entidad. Si un vínculo necesita guardar información deja de ser un vínculo y pasa a ser una entidad nueva, con derecho a tener su propio nombre (no simplemente la unión de los nombres Persona y Sucursal) porque en el paradigma orientado a objetos todo lo que tenga atributos (características) debe ser una entidad. Sin embargo en el modelo de persistencia la tabla intermedia sí suele tener por nombre simplemente la unión de las dos tablas.



Lo que tiene de particular esta entidad es que solo tiene sentido si existen las dos partes, con lo cual en el modelo de persistencia no debe tener clave primaria a menos que esto no sea cierto y la información del vínculo deba persistir más allá de la existencia de las partes que participaron (si así lo dice el negocio...). En este supuesto sí correspondería agregarle una clave primaria a la tabla intermedia.

La implementación de esta entidad intermedia puede resolverse con una clase "TiempoEnSucursal" (disculpen por el nombre, importa ver que tiene nombre propio), la cual no tendrá un identificador propio, pero tendrá una propiedad de tipo Sucursal y otra de tipo Persona (además de la fecha de inicio y fecha de fin propias de la clase).
Por último, estas propiedades de Sucursal y Persona deberían provenir desde afuera de la clase PasoPorSucursal, es decir, por medio de algún constructor o en el "set" de las propiedades.





Para finalizar les dejo la implementación de las clases donde se puede ver en detalle como se aplican los tres tipos de relaciones.



Espero les sea de utilidad este artículo y cualquier inquietud/observación me avisen.

Aquí les dejo todo el fuente más la base de datos.

https://skydrive.live.com/redir?resid=4285FB631C82DC8C!572&authkey=!AM9HFlz_uXOOTUE


Hasta la próxima entrega.


Diego Camacho
dcamacho31@hotmail.com



miércoles, 24 de abril de 2013

Patrón Observer con Delegados y Eventos

Resumen

El patrón observer es un excelente recurso que todo buen desarrollador debe saber utilizar cuando se presente la ocasión.

La síntesis del patrón es que permite que un objeto se entere y reaccione (observador) ante el cambio en otro objeto (observado), sin generar un acoplamiento entre el observado y el observador (mecanismo de suscripción).

Supongamos este ejemplo de la vida real: 

Yo soy un objeto profesor y ustedes son objetos alumno. Ustedes están interesados en saber si mi propiedad "Va a dar clases" está en false para no venir a clases en tal caso. Entonces me llaman por teléfono para corroborar el estado de mi propiedad.
Pero como no saben en que momento esta propiedad puede pasar de estar en true a estar en false entonces me llaman varias veces al día (léase, consultan la propiedad varias veces).

Claramente esto produce un nivel de llamadas y estrés innecesario en el objeto profesor.

Entonces como estoy cansado les propongo un cambio.

Les pido que ya no me llamen para saber si voy a dar clases y me anoto el número de cada uno de ustedes para llamarlos si cambio de estado true a false.
Aquí mejoramos un poco pero igual seguimos con alto nivel de acoplamiento porque el objeto profesor tendría que tener una instancia de cada uno de ustedes para consultar la propiedad "teléfono" y llamarlos cuando ocurra el cambio en mi estado.

Finalmente encontramos la solución. Metemos entre nosotros un intermediario, la recepción del instituto y además agregamos en evento en mi objeto llamado "NoDoyClases" que lo disparo cuando algo me impida asistir al curso. Pero este evento es administrado (notificado) solo a este intermediario, es decir, a la recepción.
Cuando la recepción recibe el evento se fija todos los objetos alumnos que están suscriptos al evento (los observadores) y les notifica a cada uno que ocurrió el evento del profesor para que cada uno "reaccione" como quiera.

Implementación técnica en .Net

Supongamos que tenemos una clase impresora con un método imprimir que en cada llamada descuenta del total de papel disponible las hojas que se quieren imprimir.

namespace EjemploEventos
{
 
    //Declaro el intermediario al cual le voy a notificar el evento que necesito
    delegate void ManejadorDeEventos ();
 
    class Impresora
    {
 
        int cantidadHojas = 10;
 
        //Declaro el evento que se va a disparar cuando mi regla de negocio así lo indique
        public event ManejadorDeEventos NoHayMasPapel;
 
        
        // Método que aplica la regla de negocio que indica: "Disparar un aviso cuando la impresora se quede sin papel"        
        public bool Imprimir(int cantidad)
        {
            bool respuesta = false;
 
            if (cantidadHojas < cantidad)
                //Si se cumple esta condición disparo el evento
                NoHayMasPapel();
            else
            {
                cantidadHojas -= cantidad;
                respuesta = true;
            }
 
            return respuesta;
 
        }
 
    }
}

Ahora necesito un formulario que utilice un objeto de la impresora y "LO SUSCRIBO" al evento "NoHayMasPapel" para mostrar un mensaje por pantalla si no hay más papel.

namespace EjemploEventos
{
    public partial class FrmImpresion : Form
    {
        //Creo un campo con la impresosa
        Impresora objImpresora = new Impresora();
 
        public FrmImpresion()
        {
            InitializeComponent();
            //En el constructor me suscribo a los eventos que me interesa por medio del delegado 
            //y propongo un método como reacción ante la ocurrencia de ese evento
            objImpresora.NoHayMasPapel += new ManejadorDeEventos(objImpresora_NoHayMasPapel);
        }
 
        //Solo se dispara este código si el evento ocurre
        void objImpresora_NoHayMasPapel()
        {
            MessageBox.Show("No hay papel suficiente para esa cantidad de hojas.");
        }
 
        //Solo se dispara si el usuario presiona el botón
        private void button1_Click(object sender, EventArgs e)
        {
            if (objImpresora.Imprimir(int.Parse(textBox1.Text)))
                //Si se pudo disparar el evento entoces muestro el mensaje 
                MessageBox.Show("Impresion finalizada");
        }
    }
}




Conclusiones

Programar con el patrón Observer por medio de Delegados y Eventos permite crear una arquitectura basada en componentes independientes. Podemos tener objetos con estado (propiedades), operaciones (métodos) y reacciones (eventos).
Esto significa que puedo propagar la ejecución de código en otros componentes sin conocer o pensar en ellos al momento de crear la clase observada (dueña del evento).

Aquí les dejo el código fuente completo del proyecto para descargar.



https://skydrive.live.com/redir?resid=4285FB631C82DC8C!501&authkey=!AM9HFlz_uXOOTUE




Hasta la próxima entrega.

Diego Camacho
dcamacho31@hotmail.com