Middleware Abstraction Layer. Un MAL necesario.

Implementar una arquitectura orientada a servicios (SOA) no es una tarea trivial. Menos aliviar su implementación. Hoy esto es pan caliente para los críticos del buzzword, que intentan demostrar como es casi imposible hacer SOA con estadísticas como : 1 proyecto exitoso por 4 fracasos de cada 5 intentos.

No obstante SOA si es posible. SOA es abstracción, y cuando desarrollamos abstrayendo desarrollamos para reusar. Mientras más capaces seamos de abstraer y desacoplar a un cliente de un servicio, más capaces seremos de reusar y extender el servicio, así como probar el cliente en ambientes desconectados.

Lo que más complica la implementación de SOA es lo díficil que es crear capas de abstracción genéricas y reusables. SOA es caro porque necesita de desarrolladores capaces de crear mucha abstracción.

El proyecto
Hoy estamos intentando adjudicarnos un proyecto de consultoría para revelar el impacto que tendrá llevar a cabo SOA en una empresa cliente. Un problema es que el cliente ya tiene su propia solución de integración basada en una capa middleware que consta de servidores de mensajería y muchos conectores de inbound y outbound para invocar servicios legacy desde otros servicios legacy. Es una solución propietaria, desarrollada por una empresa proveedora. En principio un buen diseño, pero tiene falencias. No fue construida con una una visión SOA, su mantención es cara y en el tiempo será insustentable. No obstante digo que es un problema no por sus falencias (pues en un final funciona) sino porque definitivamente llevar a cabo SOA en este cliente va a estar condicionado en principio por que porciento de la solución actual es reusable cuando se seleccionen las herramientas y se diseñe una plataforma SOA. Y como ya dijimos que abstracción significa reusabilidad, entonces estará condicionado por cuan abstractos hayan sido diseñado los componentes que permiten el acceso a los servicios legacy en el middleware, o sea los conectores. Quedese sintonizado pues de adjudicarnos el proyecto crearemos una bitácora en nuestro blog sobre el trabajo que hagamos.

Middleware Abstraction Layer
Un buen diseño de una solución SOA está condicionado por un buen diseño de la capa de abstracción necesaria para desacoplar los clientes de los servicios. de nada sirve implementar una arquitectura donde los clientes de los servicios conozcan todos los detalles de su invocación como:
- Que tipo de servicio es?
- Cual es el protocolo de invocación?
- Como lo invoco?
- Como parseo la respuesta?
- Que códigos de error retorna?
- Es transaccional?

Los datos mínimos que el cliente de un servicio debe conocer son
- Su nombre.
- Que parámetros recibe como parte del contrato de invocación.
- Que estructura de datos retorna también como parte del contrato de invocación.

Con esto debiera bastar para poder invocarlo. Todo lo demás puede permanecer escondido detrás de la capa de abstracción.

Ejemplo de una capa MAL genérica bien diseñada
Pensando como un desarrollador cliente, invocar un servicio desde un lenguaje de programación debería ser tan sencillo como invocar un método de un objeto en el mismo lenguaje. Un buen diseño de una capa de invocación debe esconder tanto como se pueda del servicio en sí.

Por ejemplo invocar desde Java al servicio para obtener el libro de direcciones de una persona (que vive en una arquitectura SOA, quizas en un ESB) debería ser algo como:

// le pedimos a la fabrica de servicios que instancie
// el servicio getAddressBook
Service service = ServiceFactory.getService("getAddressBook");

// lo invocamos pasando como parámetro el nombre de la persona
// esta información forma parte del contrato de
// invocación de la interface del servicio documentada
Response response = service.call(new Object[] {"Mary Lebow"} );

// addressBook es lo que realmente esperamos
// forma parte del contrato de respuesta
// de la interface del servicio documentada
AddressBook addressBook = new AddressBook();

// le pedimos a la respuesta de la capa MAL
// que via instrospección llene el objeto addressBook
response.parseJSON(addressBook);

LOG.info("Mary Lebow vive en la calle: " + addressBook.getAddress().getStreet());
LOG.info("Su telefono de casa es " + addressBook.getPhoneNumbers()[0]);

Otro ejemplo. En caso que la respuesta venga en formato XML (quizás por definición del servicio) podriamos ejecutar consultas a la respuesta usando XPath, que tal algo como:

// se ejecuta una consulta a través de XPath
// al objeto response que delega en el XML
LOG.info("Su telefono celular es " + response.xPathQuery("/addressbook/phoneNumbers[0]"));

En este caso las clases ServiceFactory y Response pertenecen a la capa MAL. El cliente se abstrae de saber definiciones relacionadas a metadata del servicio. Al mismo tiempo La responsabilidad de la capa MAL es la de traducir la llamada delegando el requerimiento hacia la capa de servicios (por ejemplo invocar un endpoint HTTP como un Web Service REST o colocar un mensaje en una cola MQ) e interpretar la respuesta. Por ejemplo arrojar una excepción si el reason code es diferente de 200, u otro ejemplo invocando diferentes dominios de parsers de la respuesta (JSON, XML, YAML) para formar objetos en el lenguaje nativo (en el ejemplo Java).

Además el diseño de mi capa MAL está basado en la especificación del protocolo HTTP. Por ejemplo el objeto Response puede contener metadata sobre la invocación que podría ser interpretada por clientes agentes para tomar decisiones:
- response.getType() // retorna XML, JSON, HTML, LARGO-POSICION
- response.status() // versión y nombre del protocolo de invocación usado, por ejemplo HttpXmlRequest v1.1
- response.reasonCode() // el código de respuesta, 200 OK, 404 Servicio no encontrado, etc.
- response.authenticate() // si necesita de autenticacion y cual es el m+etodo de hacerlo

Lo que debemos tener es una única capa MAL por plataforma o lenguaje de programación usado como cliente, por ejemplo una capa para Java, otra para Python, otra para C, etc.

Conclusiones
La principal ventaja de SOA es conseguir reusar servicios. Su principal desventaja es lo díficil que es facilitar el uso de los servicios abstrayendo a los clientes de la arquitectura. La abstracción debería estar siempre en la mente de un buen desarrollador, por tanto SOA es posible.

Una aplicación en Rails y 1 billón de visitas al mes.

Este es un video que muestra como LinkedIn construyó una aplicación (Bumpersticker on the Facebook platform) usando “Rails” y “C Ruby” que sirve 1 billón de visitas mensuales.

Espero que con esto se despejen las dudas de si Rails escala. Para mi siempre fue un problema del desarrollo más que del framework y el lenguaje detrás del framework. Quizás fue un problema del diseño de arquitectura donde Rails es una componente más.

La aplicación fue hosteada en joyent, nuestro proveedor SaaS. Esto lo resalta como uno de los proveedores lideres de Cloud Computing mundialmente.

¿Por qué ProtocolBuffer si ya tenemos a JSON?

ProtocolBuffer

Leyendo el blog de un amigo supe de la existencia de ProtocolBuffer. Es un proyecto de Google para crear una forma nueva de serializar estructuras de datos en formato binario y usarlo en el intercambio de datos entre servicios remotos.

Para esto, se compila el archivo fuente que contiene el formato, lo cual genera codigo en el lenguaje destino que saben como parsear y generar el archivo de transferencia.

protoc -I=$SRC_DIR --java_out=$DST_DIR addressbook.proto

Sin duda es un buen proyecto, sobre todo para los que entendemos lo ineficiente que es usar XML como lenguaje de transferencia de datos.

JSON

Sin embargo me pregunto porque ProtocolBuffer y no JSON?.

JSON al igual que la idea de Google es un formato ligero de intercambios de datos creado por Douglas Crockford (Arquitecto de Yahoo !).

JSON es:

  1. Fácil de leer y escribir, explicito y expresivo.
  2. Fácil de parsear y generar por autómatas en diferentes lenguajes.
  3. Está basado en el lenguaje de programación JavaScript no obstante es language-agnostic.
  4. Usa notaciones que le son familiares a los programadores de los lenguajes más usados: Java, C, C++, C#, Python o Perl.
  5. Tiene una especificación creada por “Network Working Group”.
  6. Es el formato de intercambio de datos de facto en Ajax y la Web 2.0 .
  7. Existen parsers para casi todos los lenguajes: ASP, ActionScript, C, C++, C#, ColdFusion, D, Delphi, Erlang, Haskhell, haXe, Java, JavaScript, Lasso, Lisp, Lua, Objetive C, Perl, PHP, Pike, PL/SQL, PowerShell, Prolog, Python, Ruby, Squeak, Scala, Tcl, y otros.

Un ejemplo de un objeto JSON:

{
    "addressbook": {
        "name": "Mary Lebow",
        "address": {
            "street": "5 Main Street"
            "city": "San Diego, CA",
            "zip": 91912,
        },
        "phoneNumbers": ["619 332-3452", "664 223-4667"]
    }
}

Su versión XML:

<addressbook>
 <name>Mary Lebow</name>
 <address>
    <street>5 Main Street</street>
    <city zip="91912"> San Diego, CA </city>
    <phoneNumbers>
      <phone>619 332-3452</phone>
      <phone>664 223-4667</phone>
    </phoneNumbers>
 </address>
</addressbook>

JSON elimina la cantidad de tags de XML, lo que hace incluso más compacto los paquetes a serializar. Pero si solo fuera por esto, no valdría la pena el esfuerzo por crear JSON. Lo que lo hace más atractivo es que para la mayoría de los lenguajes acceder a los datos del objeto JSON es como acceder a atributos de un objeto en el lenguaje que se este usando, por ejemplo en JavaScript, Python o Ruby, acceder a la calle de la direccion del objeto anterior se escribe:

addressbook.address.street

O acceder al primer número telefónico:

addressbook.phoneNumbers[0]

Entonces, en vez de nuevos proyectos como BufferProtocol, me gustaría ver proyectos para crear herramientas o frameworks para intercambiar estructuras de datos en formato JSON entre diferentes lenguajes, que tal algo como:

jsonc -I=$SRC_DIR --java_out=$DST_DIR addressbook.json

y Obtener:

Address.java :

public class Address {
    private String street;
    private String city;
    private int zip;

    public String getStreet() {
        return this.street;
    }
    public String getCity() {
        return this.city;
    }
    public int getZip() {
        return this.zip;
    }
};

AddressBook.java:

public class AddressBook {
    private String name;
    private Address address;
    private String[] phoneNumbers;

    public String getName() {
        return this.name;
    }
    public void parseJSON(String json) {
        // llena address, phoneNumbers
    }
    public String writeJSON() {
        // retorna un objeto addressbook en formato JSON
    }

    public Address getAddress() {
        return this.address;
    }
    public String[] getPhoneNumbers() {
        return this.phoneNumbers;
    }
};

Esto nos permitiría parsear y generar los objetos JSON Address, y AddressBook en Java de forma natural.

Parsear los objetos:

AddressBook ab = new AddressBook();
ab.parseJSON(new FileInputStream("addressbook.json"));

O entonces generar JSON:

AddressBook ab = new AddressBook();
ab.setName("Mary Lebow");
...
String json = ab.writeJSON();

En XML tuvieramos que parsear el documento usando DOM, XPath, XQuery, etc.

Conclusión

JSON nos permite crear documentos con estructuras de objetos para la interoperabilidad de servicios en un formato entendible a simple vista, expresivo y sencillo. En Continuum seleccionamos JSON como estándar substituyendo (siempre que podamos) XML en todos los contextos donde es normalmente usado (configuración, intercambio, metadata, etc).

Sería interesante ver proyectos como ProtocolBuffer pero usando JSON como lenguaje de intercambio.

« Entradas Anteriores

Conócenos

Tel: +56 2 9341951

e-mail: info@continuum.cl

Copyright © 2010 Continuum Ltda.
Coronel Pereira 72. Oficina 903. Las Condes. Santiago. Chile