Servicios REST usando Silex micro-framework 3/3 – Cliente

Ya hace un tiempo que escribí el primer y segundo artículo de esta serie sobre Servicios REST y Silex. En esa época había creado un repositorio en mi cuenta de GitHub para almacenar un ejemplo de como crear un servidor REST utilizando Silex el micro-framework PHP que sería como el hermano menor de Symfony.

Utilizo Silex porque es realmente fácil crear rápidamente un proyecto de ejemplo pero en realidad de expone la idea para utilizarlo con PHP, pudiéndose adaptar rápidamente el ejemplo a algún otro framework como Symfony2 o incluso hacerlo desde cero con PHP.

Con ayuda de algunas personas, a quienes agradezco por los comentarios que dejaron en los artículos, he realizado algunas modificaciones y creo que nos ha quedado un proyecto base interesante para tenerlo como estructura base.

Hay que tener en cuenta que hoy en día la nueva versión de Silex ha
cambiado un poco en cuento a su instalación con relación al último
artículo. Utilizaremos para este artículo la nueva versión y con el
tiempo haremos las modificaciones en el ejemplo del servidor.

Hoy quiero hablar sobre como podemos consumir ese servicio creado, es decir, crear un cliente para REST utilizando Silex. Hay que recordar que nuestro servicio REST nos devuelve siempre respuestas por medio de los códigos de estados del protocolo HTTP (puedes verlo en la wiki) y en los casos que tiene que devolvernos datos como sería la ruta /ver-comentarios.json, lo hará utilizando el formato JSON. Esto es importante saber ya que para que nuestro cliente obtenga los comentarios del ejemplo tenemos que saber que obtendremos una respuesta JSON la cual tendremos que procesar para mostrarlo en nuestro cliente.

Objetivo y alcance del Proyecto

Este artículo lo haremos siguiendo el mismo concepto que los anteriores.

  • Crearemos OTRO proyecto Silex, diferente al anterior que será mantenido en GitHub en esta dirección: https://github.com/micayael/com.micayael.blog.ClienteRestSilex

  • Tendremos la misma estructura del proyecto anterior con relación a los archivos y carpetas para seguir un estándar salvo pequeñas diferencias.

  • Al proyecto anterior hemos agregado las librerías para trabajar con Doctrine ya que como tiene que trabajar con la base de datos esto es sumamente útil pero para este proyecto nuevo no será necesario ya que, por más que trabajará con los mismos datos, no se accederá a la base de datos sino que los datos serán obtenidos desde el servicio y enviados al mismo (ServidorRestSilex). Esto significa que para este proyecto (ClienteRestSilex) como mencioné no utilizaremos las librerías de Doctrine pero sí incluiremos las librerías para trabajar con Twig, nuestro motor de plantillas, ya que la idea es mostrar los datos en las páginas.

Configuraciones iniciales

Para instalar Silex con esta nueva versión crearemos nuestra carpeta ClienteRestSilex y dentro crearemos un archivo “composer.json” ya que ahora Silex usa el proyecto Composer para descargar las dependencias. El contenido del archivo sería el siguiente

{
    "require": {
        "silex/silex": "dev-master",
        "twig/twig": ">=1.8,<2.0-dev"
    }
}

El contenido debe estar en formato JSON y con la clave “require” le decimos que deberá descargar lo necesario para trabajar con Silex y Twig. Una vez que hemos creado este archivo abrimos la terminal y entramos hasta nuestra carpeta para descargar el programa Composer y decirle que nos descargue lo que definimos en el composer.json:

Tienen que tener en cuenta que para ejecutar el comando curl tienen
que tenerlo instalado y también aprovechemos para verificar que
tengamos instalada la extensión curl para PHP. Para instar ambos en
Ubuntu lo pueden hacer con apt-get install curl php5-curl.

cd ~/development/github/ClienteRestSilex
curl -s http://getcomposer.org/installer | php
php composer.phar install

Con el comando curl, en la línea 2, descargaremos del sitio oficial el archivo composer.phar, el cual ejecutaremos con la línea 3 usando el parámetro install que lo que hará será revisar cuales dependencias requerimos en archivo composer.json y realizará las descargar dentro de una carpeta vendor creando además un archivo composer.lock que contendrá las versiones de cada librería que descargó.

Estructura de archivos y carpetas del proyecto Cliente

Los archivos serán muy similares al proyecto anterior. La estructura para este proyecto será la siguiente:

  • vendor/: Así como el proyecto anterior serán los archivos de Silex y Twig. Hay que notar que no tendremos Doctrine como ya mencionamos sino Twig.

  • web/: Archivos públicos del proyecto como las páginas que serán accedidas por los usuarios clientes (controladores frontales), imágenes, css, js, etc.

    • index.php: Archivo de ingreso al proyecto. Podríamos llegar a crear más archivos como estos si se necesita más adelante. Este archivo sería un controlador frontal conocido dentro de los proyectos Symfony2.

    • .htaccess: Nos permitirá obviar en la URL el nombre del index.php. Para que esto funcione se debe tener activo el mod_rewrite del Apache.

  • src/: Archivo del código del proyecto

    • app.php: los controladores frontales (index.php) invocarán a este archivo para centralizar las importaciones necesarias y levantar configuraciones propias del proyecto. Este archivo importará los otros 4 contenidos en esta misma carpeta

    • bootstrap.php: este archivo será ejecutado para levantar el framework y las configuraciones vinculadas al mismo

    • config.php: Archivo para definir constantes de configuración

    • controllers.php: Aquí se encontrarán los actions a ser ejecutados

    • Rest.php: Esta es una clase que fuí creando para usar CURL para acceder a los servicios web. Hablaremos de esta clase más adelante

  • views/: Aquí iran nuestras plantillas Twig

    • base.html.twig: Estructura básica HTML5

    • comentarios.html.twig: Plantilla para mostrar un listado de comentarios. Extiende a base.html.twig

La clase Rest.php

Esta clase Rest la estoy construyendo usando las funcionalidades de CURL. Al instanciarla podemos pasar o no una cadena de autenticación al constructor. En esta caso nos basamos en el Basic HTTP Authentication por lo que utilizaremos el usuario y clave para acceso al servidor y lo debemos concatenar para que quede como “user:pass” y pasarlo al constructor.

Métodos

  1. public function url($url): nos permite ingresar la URL del servicio

  2. public function get(): setea las propiedades CURL necesarias para hacer una llamada de tipo GET. Si se desean pasar parámetros por GET estos deben ir concatenados en la url que se pasa como parámetro al método anterior.

  3. protected function execute(): Ejecuta la petición y retorna los resultados en formato JSON. Este método no es público ya que se ejecuta dentro de los otros por ejemplo dentro del método get().

Los demás métodos los iré creando en GitHub y documentándolos ahí mismo.

Utilizando el patrón de injección de dependencias, al objeto Rest lo cargaremos dentro de un servicio para no tener que instanciarlo cada vez sino que accederemos a él por medio de $app['rest']. Esto lo hacemos en el bootstrap.php. Cada vez que llamemos a $app['rest'] estaremos obteniendo la instancia de new Rest() de la siguiente manera:

$app['rest']->url($url)->get();

Procesar la respuesta JSON

Una vez que tengamos el código JSON devuelto simplemente con un json_decode() lo transformaremos en un array que podremos procesarlo para pasar los datos a la plantilla. Esto no es nada complicado, simplemente lo hacemos agregando así en el controlador:

$comentarios = json_decode($app['rest']->url($url)->get());
 
return $app['twig']->render('comentarios.html.twig', array(
    'comentarios' => $comentarios
));

Hay que recordar que en este proyecto no tenemos acceso a la base de datos ya que el cliente debe consultar al servidor REST por lo que, como ya hablamos, no tenemos la librería Doctrine aquí. Por esta razón no tenemos Entities ni representaciones de objetos para los datos.

En un proyecto tradicional donde tenemos todo en uno, tendríamos Entities que mapean cada tabla de la base de datos y fijándonos en el ejemplo de arriba para los comentarios terminaríamos teniendo un array de objetos Comentario. Aquí no tendríamos que hacer esa conversión (a lo que Doctrine llama Hydration) por lo que, según mi opinión, yo preferiría trabajar directamente con los arrays ya que el proceso de transformar los datos a objetos es costoso. En lugar de tener un array de objetos Comentario, tendríamos un array de arrays asociativos que contienen los datos de los comentarios (lo que sería la idea del Data Hydrator: Scalar del artículo mencionado).

Ejemplo para consultar comentarios

Para dejar un ejemplo mientras continúo con el proyecto vamos a utilizar el servicio con la ruta /ver-comentarios.json del proyecto ServidorRestSilex y por medio de nuestro cliente obtendremos los datos para mostrarlos en una tabla.

Estos ejemplos son tomando en cuenta que ya han leído los dos
artículos anteriores y entienden lo que el servidor hace, por lo que
si no los haz leído es un buen momento para ir a leerlos ( primer
artículo
y segundo artículo)

Los trozos de código aquí mostrados son solo porciones de los archivos
contenidos en el repositorio de GitHub para explicar los conceptos
principales. Para ver todo el contenido debes ir a la dirección de
GitHub y ahí encontrarás los códigos con las documentaciones que y el
proyecto que iré actualizando.

Para esto tendremos el siguiente código dentro del controlador:

$app->get('/ver-comentarios.html', function() use($app){
 
    $url = $app['rest.host'] . 'ver-comentarios.json';
    $comentarios = json_decode($app['rest']->url($url)->get());
 
    return $app['twig']->render('comentarios.html.twig', array(
        'comentarios' => $comentarios
    ));
 
});

Como podemos ver en las líneas 3 y 4 obtenemos los datos del servicio y en la línea 7 pasamos el array a la plantilla. Hay que notar que $app['rest.host'] contendrá la dirección del servidor que en este caso sería http://local.servidorrestsilex.

Como vemos no hay interacción con la base de datos sino simplemente invocamos al servicio y obtenemos los datos. La responsabilidad de buscar los datos de la base de datos la tiene el proyecto ServidorRestSilex.

Ahora bien, para mostrar los datos en la plantilla ya es algo que no tiene nada que ver con servicios REST sino simplemente Twig. Para esto el código de la plantilla comentario.html.twig sería el siguiente:

{% extends "base.html.twig" %}
 
{% block body %}
 
<table border="1">
    <tr>
        <th>id</th>
        <th>author</th>
        <th>email</th>
        <th>content</th>
        <th>created_at</th>
        <th>updated_at</th>
    </tr>
 
    {% for comentario in comentarios %}
    <tr>
        <td>{{ comentario.id }}</td>
        <td>{{ comentario.author }}</td>
        <td>{{ comentario.email }}</td>
        <td>{{ comentario.content }}</td>
        <td>{{ comentario.created_at | date("d/m/Y H:i:s") }}</td>
        <td>{{ comentario.updated_at | date("d/m/Y H:i:s") }}</td>
    </tr>
    {% endfor %}
 
</table>
 
{% endblock %}

En la línea 1 extendemos la estructura HTML base de la plantilla base.html.twig. Y en las líneas 15 a la 24 iteramos el array de comentarios para mostrar los datos en una tabla.

Visto desde el lado de la arquitectura

Una de las mejores posibilidades que nos da esto a nivel de arquitectura, y una de las que más me gustan, es que por ejemplo en un sistema grande con muchos usuarios en simultaneo donde la performance podría ser un recurso vital, podríamos separar los proyectos en servidores diferentes haciendo que cada servidor (o máquina virtual) trabaje exclusivamente para lo que tiene que hacer.

Arquitectura REST

Como vemos en la imagen la aplicación ServidorRestSilex podría estar en un servidor mientras que la aplicación ClienteRestSilex podría estar en otra máquina.

El Servidor Rest es el único que accede a la base de datos por lo que el tiene que saber como hablar con el motor (MySQL, PosgreSQL, Oracle o cualquier otro). Cualquier comunicación con este servidor ya será por medio de un formato de transmisión de datos como JSON o XML.

Los usuarios finales obtendrán y enviaran datos al Cliente REST quién teniendo por ejemplo un Apache enviará la página a los usuarios en PCs (linux, windows, MAC) y también puede haber una versión web móvil (con jQuery mobile por ejemplo) para mostrar la página en dispositivos móviles.

Por supuesto no olvidemos a los usuarios de dispositivos móviles que no acceden a los datos por medio de una página sino por medio de una aplicación propia del dispositivo móvil (movil app) como por ejemplo una aplicación android quienes pueden acceder a los datos por medio de JSON o XML también.

Ninguna de los cuatro usuarios finales que vemos en la imagen (las dos PCs, la web móvil y la móvil App) sabrán la lógica de obtención y procesamiento de datos como así tampoco la lógica del negocio, sino que simplemente pedirán los datos, ya procesados, al Servidor REST (quien sí conoce dicha lógica).

Ahora bien, tomando el caso de un sistema con muchas conexiones concurrentes podríamos tener la siguiente arquitectura

Arquitectura REST con distribución de carga

Como vemos todo es igual al anterior, con la gran diferencia que, a parte de que las aplicaciones ServidorRestSilex y ClienteRestSilex están en máquinas diferentes, el Servidor REST está formado por un clúster de servidores diferentes (servidores físico o virtuales) donde por medio de un Apache se distribuye la carga de los Requests que llegan desde el Cliente REST o de la Móvil App.

Con la distribución de carga la primera conexión va al server 1, la segunda al server 2, la tercera al server 3, la cuarta al server 4 y la quinta vuelve a empezar por el server 1.

De esta manera tenemos varios servidores chicos pero que hacen una tarea específica mientras que tenemos a la aplicación Cliente REST en otro servidor que por supuesto también podría tener distribución de carga :-)

Como podemos ver las posibilidades de tener en diferentes capas nuestras aplicaciones nos permite distribuir bastante la carga de los servidores.

Resumen Final

Ha lo largo de estos artículos hemos creado DOS proyectos:

  1. ServidorRestSilex: Actúa como servidor de Datos. Lo utilizamos como una capa de abstracción ya que este proyecto sabe como trabajar con nuestros datos almacenados en un motor de base de datos. Todo lo referente a la lógica de como obtener y/o manipular datos del motor de datos es responsabilidad de este proyecto. Este proyecto no tiene como objetico mostrar los datos directamente el usuario final sino que debe ser accedido por un cliente ya que devuelve datos en formato JSON

  2. ClienteRestSilex: Actúa como cliente del servicio REST. Tiene como principal responsabilidad mostrar los datos al usuario final en un formato visual entendible como por ejemplo HTML 5. Se encargará de conectarse al servicio (ServidorRestSilex), obtener los datos en formato JSON o entregarlos al servicio y presentará la respuesta.

Por último hemos hablado sobre la distribución que podríamos lograr con esta arquitectura en capas y nos quedaría pendiente un ejemplo creando un cliente móvil para lo cual me gustaría usar jQuery mobile reutilizando nuestro servicio REST. Esto lo veremos en un siguiente artículo.

Espero sus comentarios abajo.

Comenta este artículo