| Diseño por Contrato con OVal y Spring |
|
|
|
| Escrito por Leonardo De Seta |
| Viernes 08 de Mayo de 2009 19:37 |
|
¿Qué es el Diseño por Contrato?El Diseño Por Contrato (Design by Contract en inglés, también conocido por las siglas DbC) es una metodología para el diseño e implementación de aplicaciones y componentes popularizada por el lenguaje de programación Eiffel. En Estados Unidos "Design by Contract" es una marca registrada, por lo que muchos desarrolladores lo llaman Programming by Contract (Programación por Contratos), o Contract Programming (Programación Contractual), o contract-first development (desarrollo con contrato primero). La idea central de DbC es una metáfora sobre cómo interactuan los elementos de un sistema de software para colaborar entre si, basándose en obligaciones y beneficios mutuos. La metáfora proviene del mundo de los negocios, en donde un "cliente" y un "proveedor" firman un "contrato" que, por ejemplo, define:
De manera similar, si una método en una clase brinda cierta funcionalidad, podría:
El contrato es la formalización de estres obligaciones y beneficios. Se podría resumir al Diseño por Contrato por estas tres preguntas que el diseñador del compomente debe preguntarse:
DbC considera que los contratos son cruciales para crear software correcto, y que deben ser parte del proceso de diseño del software. De hecho, DbC fometa escribir primero las asersiones y luego el código. DbC en JavaJava no tiene soporte a nivel de lenguaje para hacer DbC (si bien los assert pueden usarse de forma muy limitada para algo parecido, no es recomendable). Es por esto que hay varias librerias externas que brindan utilidades para lograr declarar precondiciones, postcondiciones e invariantes, de distintas maneras. La mayoría de los frameworks disponibles utilizan anotaciones en las clases, en las cuales se indican las condiciones que tienen que cumplirse antes de invocar al método, el valor de retorno del método, y las invariantes de la clase. Luego, utilizando AOP, estas anotaciones se traducen en código ejecutable en tiempo de ejecución. A continuación vamos a ver una introducción a OVal, una librería que implementa DbC utilizando anotaciones. Intregraremos OVal a Spring para facilitar el uso de AOP. Introducción a OValOVal es un framework pragmático y extensible para validar cualquier tipo de objetos Java (y no sólo JavaBeans). Se pueden declarar restricciones usando anotaciones (@NotNull, @MaxLength, etc), POJOs y XML. También se pueden crear restricciones personalizadas, usando clases Java o expresiones en lenguajes de scripting, como JavaScript, Goovy, BeanShell, OGNL o MVEL. Además de validaciones a nivel de atributos, OVal permite implementar el Diseño por Contratos, aplicando restricciones a métodos usando la técnica de AOP a través de AspectJ. De esta manera se pueden validar argumentos de un método en tiempo de ejecución. Entonces, OVal permite:
OVal y el Diseño por ContratosOVal nos permite implementar un Diseño por Contratos de manera simple, ya que nos brinda mucha funcionalidad que caracteriza al DbC:
Los primeros pasos con OVal y SpringVamos a integrar OVal con Spring, de manera que se encargue de realizar validaciones a un bean cualquiera declarado en Spring. Para esto, los pasos a seguir serán:
Elegimos hacer las tareas en este órden por cuestiones didácticas. Sin embargo, en una implementación real usando DbC, lo ideal sería comenzar escribiendo las validaciones de nuestro servicio, y luego el código del mismo. ¡Manos a la obra! 1. El "servicio" de personasVamos a crear entonces las 3 clases de negocio que nos importan. Primero, el POJO Persona, que será nuestro objeto de dominio. public class Persona { private String nombre; private String apellido; public Persona(String nombre, String apellido) { this.nombre = nombre; this.apellido = apellido; } public Persona() {} //getters y setters a continuacion ... } A continuación creamos la interfaz PersonaService que contiene los métodos de negocio. public interface PersonaService { void guardarPersona(Persona p); Persona crearPersona(String nombre); } Por último, una implementación del negocio con la clase PersonaServiceImpl. Más tarde agregaremos las restricciones de OVal a esta clase.
public class PersonaServiceImpl implements PersonaService {
public void guardarPersona(Persona p) {
System.out.println("Guardando persona: " + p);
}
public Persona crearPersona(String nombre) {
if (nombre == null) {
return null;
}
return new Persona(nombre, "Apellido no especificado");
}
}
2. Configurar Spring para PersonaServiceVamos a crear el archivo application.xml de Spring en donde configuramos al bean PersonaService. <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <!-- Nuestro servicio de Persona. --> <bean id="service.Persona" class="com.dosideas.oval.PersonaServiceImpl"/> </beans> ¡Listo! Con esto tenemos un servicio de Persona configurado en Spring como un bean normal. La magia de OVal comienza a continuación. 3. Configurar el interceptor de OVal en SpringOVal necesita intercetpar las clases que tengan restricciones para poder realizar las verificaciones correspondientes. Al usar Spring se facilita mucho esta intercepción: basta con configurar el interceptor ya provisto por OVal y decirle cuáles beans serán verificados por OVal. Entonces, agregamos las siguientes líneas al archivo application.xml de Spring: <bean id="ovalGuardInterceptor" class="net.sf.oval.guard.GuardInterceptor" /> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="proxyTargetClass" value="true" /> <property name="beanNames" value="service*" /> <property name="interceptorNames"> <list> <value>ovalGuardInterceptor</value> </list> </property> </bean> Como vemos en el atributo beanNames, estamos interceptando todos los beans de Spring cuyo nombre comience con "service". Así, nuestra clase PersonaServiceImpl será interceptada por OVal y podrán ocurrir las comprobaciones. Claro, todavía nuestra clase no tiene ninguna verificación... así que vamos a agregarlas! 4. Agregar restricciones con OValOVal nos permite agregar restricciones usando anotaciones. Hay muchas anotaciones disponibles, entre ellas podemos destacar:
Hay varias anotaciones más disponibles. Vamos entonces a agregarle varias validaciones a nuestra clase PersonaServiceImpl. Uso de @NotNullComo primera restricción haremos que el parámetro del método guardarPersona(Persona p) no sea null. Esto es muy sencillo: public void guardarPersona(@NotNull Persona p) { ... } Si intentamos invocar al método con un null, surgirá una excepción net.sf.oval.exception.ConstraintsViolatedException. Uso de @PreEvidentemente la expresión @NotNull es muy útil, pero no alcanza para realizar validaciones más complejas. Por suerte tenemos la anotación @Pre, que nos permite crear toda una expresión en alguno de los lenguajes de scripting soportados. Esta expresión se evalúa antes de la invocación al método y después de las verificaciones a nivel de atributo. Si la evaluación de la expresión da falso, se lanzará una excepción net.sf.oval.exception.ConstraintsViolatedException. Entonces, si queremos validar que el nombre y el apellido de la persona tampoco sean nulos agregamos:
@Pre(expr = "_args[0].nombre != null && _args[0].apellido != null",
lang = "groovy")
public void guardarPersona(@NotNull Persona p) { ... }
La variable _args representa un array de parámetros para el método. Sabemos que _args[0] no es null porque tenemos la anotación @NotNull. Si no usaramos la anotación @NotNull deberemos antes validar en la expresión que el parámetro no sea null. Dentro de la expresión tenemos accesos a varias variables implícitas:
Uso de @PostTambién es posible evaluar el valor de retorno de un método. Para esto utilizamos la anotación @Post, que se ejecuta después de la invocación del método (y antes de devolverle el valor al cliente que realizó la invocación). Esta anotación utiliza una expresión similar a la anotación @Pre. Si la evaluación de la expresión da falso, se lanzará una excepción java.lang.AssertionError. Vamos a agregar una postcondición al método crearPersona(String nombre) para asegurar que el método nunca pueda retornar falso.
@Post(expr = "_returns != null", lang = "groovy")
public Persona crearPersona(String nombre) { ... }
La variable implícita _returns representa al objeto de que se está retornando. Dentro de una expresión de @Post tenemos acceso a varias variables implícitas:
Probando que todo funcione...Y esto es todo. Con esos cambios nuestro PersonaService tendrá validaciones que se ejecutarán automáticamente cuando se invoquen los métodos. Creamos una prueba con JUnit muy simple para validar el comportamiento: @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:application.xml" }) public class PersonaServiceTest { @Autowired private PersonaService personaService; @Test public void guardarPersona() { Persona p = new Persona("Invasor", "Zim"); personaService.guardarPersona(p); } @Test(expected=ConstraintsViolatedException.class) public void guardarPersona_personaNull() { personaService.guardarPersona(null); fail("Debería haber ocurrido una excepcion de validación"); } @Test(expected=ConstraintsViolatedException.class) public void guardarPersona_nombreNull() { Persona p = new Persona(null, "Zim"); personaService.guardarPersona(p); fail("Debería haber ocurrido una excepcion de validación"); } ... ... } ConclusiónOVal es un framework para validación de objetos, que permite utilizar la técnica de Diseño por Contratos de manera muy simple y efectiva. OVal tiene muchas funciones y facilidades más de las que vimos hasta ahora, como ser control de invariantes, verificación de constructores y atributos, utilización de otros lenguajes de scripting y más. Pueden leer la documentación de OVal para aprender más sobre este interesante framework de validación.
|