jueves, 25 de noviembre de 2010

RESTful - Parte 2: Manejando un solo objeto

Hemos visto en el anterior post cómo hacer un servicio REST solo para producir y consumir un texto simple. Ahora bien, en la vida real no son textos simples, sino estructuras de datos algo complicadas. Pero para ir lentos pero seguros, aprenderemos cómo hacer un servicio REST pero para manejar un solo objeto.

Afortunadamente para nuestros proyectos, no debemos crear ningún XML, ni tener algún "parser" que convierta nuestros objetos en formato XML o algo parecido para enviar y recibir objetos por la red. Solo necesitamos crear nuestros JavaBeans... y ponerle algunos tags.

Manos a la obra...

Supongamos que tenemos creado el proyecto web PersonaRESTWeb sobre GlassFish v3 y sin ningún framework adicional. Recordemos que nuestro proyecto se autoconfigura en REST cuando se aplica una anotación especial.

Creando un javaBean

Una vez creado el proyecto, crearemos un JavaBean llamado Persona


public class Persona {

    private int idPersona;
    private String nombre;
    private java.util.Date fechaNacimiento;
    private boolean trabajador;
    private char sexo;
//...

... con sus respectivos set y get.

Ahora, este JavaBean lo entendemos muy bien en Java, pero recordemos de que un servicio web debe ser compatible para otros lenguajes, y que la estructura de datos más "compatible" es el XML. Así que vamos hacer que este JavaBean se convierta en XML. Bastará con poner la anotación @XmlRootElement al inicio de la declaración de la clase.


@XmlRootElement
public class Persona {

    private int idPersona;
    private String nombre;
    private java.util.Date fechaNacimiento;
    private boolean trabajador;
    private char sexo;
//...

Creando recurso manejador de Persona

El diseño de este Servicio obliga a que exista un solo recurso manejador por cada Entidad. Por tanto, debemos crear la clase PersonaResource. Y para que administre un JavaBean, declararemos un objeto static.

Nota: Lo normal aquí es usar un manejador de persistencia (sea JPA, JDBC, etc..), pero como el objetivo de este tutorial es ver cómo funciona un REST, no gastaremos esfuerzo por conectarnos a una base de datos.

Este es el código fuente del recurso PersonaResource


@Stateless
@Path("/personas")
public class PersonaResource {

    static Persona persona;
}


Ahora, necesitamos implementar los métodos para registrar un objeto Persona desde el cliente, y leer el objeto desde el hacía hasta el cliente. Por tanto, crearemos dos métodos: registrar y leer.

Leer valor del objeto

Esto ya lo hemos visto. Es declarar un método y declararlo con la anotación @GET


//...
    @GET
    public Persona leer(){
        return persona;
    } 
//...

Sí, nada más (por ahora)

Guardar valor al objeto

Esto debería ser sencillo. Bastaría con poner este código


//...
    @POST
    public void guardar(Persona p) {
        persona = p;

    }
//...

Pero no es así. Porque toda petición que se hace a un recurso web (el que sea) siempre debe devolver algo.. así sea un error, pero debe devolver algo. En REST debe devolver un objeto javax.ws.rs.core.Response que contiene el estado de la petición: si está OK, si hay error de restricción, si no responde, etc.. todos los errores que conocemos para HTTP están contenidos en ese objeto. Pero para nuestro caso, vamos a devolver el valor "ok" de la siguiente manera:


//...
    @POST
    public Response guardar(Persona p) {
        persona = p;
        return Response.ok(p).build();
    }
//...

Ahora sí... a desplegarlo y a probar... pero si lo probamos en este momento, nos mostrará la siguiente ventana.



y.. qué pondremos en la caja de texto? Pues el objeto a enviar... pero ¿cómo? Pues un dato estándar, como XML o JSON. Por ejemplo, este código


<persona>
       <idPersona>20</idPersona>
       <nombre>Albert</nombre>
       <trabajador>true</trabajador>
</persona> 

Lo probamos y... error!!! ¿qué pasó?

Pues nuestro servicio REST no sabe si la data que va a recibir es un XML, o un JSON. Hay que decirle al método de ese recurso cómo va a recibir los datos. Por ahora, vamos a poner esta anotación en el método:

//...
    @POST
    @Consumes("application/xml")
    public Response guardar(Persona p) {
        persona = p;
        return Response.ok(p).build();
    }
//...

Ahora sí, desplegamos, ejecutamos el Test y... vemos que ahora el método POST dice qué tipo permitirá:



Escribimos nuevamente el texto y voila!! Status: 200 (OK)

"Bien, el XML funciona, pero el formato es un poco grande ¿se puede usar JSON?" Sí.. y lo mejor, es bien sencillo activar esa opción.


//...
    @POST
    @Consumes({"application/xml","application/json"})
    public Response guardar(Persona p) {
        persona = p;
        return Response.ok(p).build();
    }
//...

Listo, ahora nuestro método guardar() permite recibir tanto JSON como XML. Probemos ahora colocando el siguiente valor en el módulo de prueba:


{  "idPersona":"20",
   "nombre":"Bernard",
   "trabajador":"true"
}

... seleccionamos el tipo JSON...





... y listo.. funciona!

Si no están seguros de que guardó correctamente, ejecutemos el método GET para ver si lo guardó en el objeto



Modificando el tipo de formato para leer el objeto

Ya vimos que se puede establecer el tipo que el Servicio recibirá por la red usando @Consumes. Y cuando probamos la lectura, lo convierte siempre a XML ¿se puede cambiar para que sea JSON? Por su puesto, y es igual de simple:


//...
    @GET
    @Produces({"application/json","application/xml"})
    public Persona leer() {
        return persona;
    }
//...

Por omisión usará el primer tipo especificado (en este caso "json"), o - dependiendo cómo se indique en el cliente - puede utilizar el formato XML.






Modificando la estructura de los datos

Nuestro bean utiliza la propiedad idPersona, y el REST lo procesa correctamente. Pero, si el estándar de los proyectos donde se va a utilizar, dice que debe ser id_persona y el campo nombre sea nombre_persona ¿Cómo modificamos esto?

Esto también es fácil. Por cada método "get" que debemos cambiar el formato, le agregamos la anotación @XmlElement seguido del nombre como deberá ser manejado. Vayamos al Bean Persona y pongamos esto:


//...

    @XmlElement(name = "id_persona")
    public int getIdPersona() {
        return idPersona;
    }

    @XmlElement(name = "nombre_persona")
    public String getNombre() {
        return nombre;
    }
//...


Ahora, probemos el método "GET".



¿Y si, el id_persona tiene que ser un atributo del XML? Cambiamos la notación @XmlElement por @XmlAttribute


//...

    @XmlElement(name = "id_persona")
    public int getIdPersona() {
        return idPersona;
    }

//...


Ojo, cuando es atributo y se desea enviar en formato JSON, se debe considerar ql nombre del atributo antepuesto por un @.


{  "@id_persona":"20",
   "nombre_persona":"Bernard",
   "trabajador":"true"
}



"En ningún momento aparece el nombre del método guardar() y leer() ¿Cómo sabe qué método utilizar?"

Pues por el mismo método HTTP utilizado: Si se hace "POST", ejecuta el método que está asociado a @POST, si se hace "GET", utiliza el @GET.

¿"Y si quiero diferenciar un 'update' de un 'create' ? " Pues utilizar otro método. Como comenté en el anterior post, el POST debe estar asociado al "create",el GET asociado a la búsqueda, el "PUT" al "update" y el "DELETE" al borrar.

Si hay que hacer otro tipo de "POST" o de "DELETE", debemos utilizar otro objeto Recurso (con anotación @Path) ya que será accedido desde otra ruta.

Por ello comenté en el anterior post de que este diseño asegura de que los métodos de mantenimiento están asociados a una sola entidad.. y nos evitará tener métodos en servicios web que no corresponde, por ejemplo, no habrá un manejo de proveedores en un servicio de clientes.

Código del proyecto

Aquí se encuentra el código fuente del proyecto web:

http://kenai.com/projects/apuntes/downloads/download/PersonaRESTWeb%252FPersonaRESTWeb.tar.gz


Aquí el código fuente de una aplicación Java Desktop:

http://kenai.com/projects/apuntes/downloads/download/PersonaRESTWeb%252FPersonaRESTClient.tar.gz