Changes in chapter 6 and 1.
[PFCLatex/.git] / capitulo6 / capitulo6.tex
1 \chapter{Desarrollo}
2
3 Durante la fase de desarrollo se implementará y documentará la aplicación. Para la implementación de la aplicación Android se eligió el IDE Eclipse con el plugin Android para el desarrollo de aplicaciones para esta plataforma. En cambio, para la Web, se usó PHP como lenguaje de programación y como IDE vim\footnote{Para conocer más acerca del mejor editor de textos del mundo (¡y no provoca dolores en los dedos meñiques!) ver: \url{http://www.vim.org/}} con ctags. En realidad para Android (programación Java) también podría haberse utilizado vim pero el desarrollador deseaba experimentar con el popular y no tan poderoso Eclipse.
4
5 No es objetivo de esta memoria explicar cada una de las líneas de código de las que se componen las aplicaciones implementadas, por tanto, en este capítulo lo que se mostrará será todos los pasos que con lleva la realización de una determinada acción que se ha considerado es representativa del funcionamiento global del sistema. Junto a esta acción se mostrarán y explicarán algunas partes del código que sirven para que el lector entienda qué elementos software ha habido que desarrollar para que dicha acción llegue a buen puerto.
6 a documentación ha sido realizada durante la codificación de la aplicaciones usando Javadoc y phpDocumentor 2. Ambos se basan en la interpretación de los comentarios escritos en las clases y métodos para crear documentación en diversos formatos que pueden ser accedidos por usuarios que quieran conocer o tengan que usar o reutilizar el código implementado.
7
8 \section{Proceso de recepción de nuevos anuncios}
9
10 Se ha seleccionado la acción de recepción de anuncios ante variaciones en la localización del usuario como un proceso que sirve para describir el sistema implementado de una forma que permita al lector hacerse una idea de todos los elementos que intervienen en en dicha acción mediante la explicación de partes del código que ha tenido que ser escrito para llevarla a cabo.
11
12 \subsection{Peticiones GET HTTP desde cliente Android}
13
14 Desde el cliente y tras un cambio de posición geográfica de un usuario, el dispositivo móvil envía las coordenadas mediante una petición GET HTTP al servidor Web (al servicio Web RESTful) Primero la aplicación creará un nuevo hilo para que puedan ejecutarse varias peticiones HTTP en paralelo, tal y como se muestra en el Listado~\ref{list:JavaUpdateLocation}). Se recibe la nueva localización enviada por el sistema operativo Android cuando ésta varía y después se lanza una tarea mediante la instancia exec que es un objeto del tipo ExecutorService proporcionado por Java para poder lanzar y gestionar hilos de una forma sencilla. Básicamente mediante el método execute estamos diciendo a Android que construya un nuevo hilo donde las tareas batch o en segundo plano serán ejecutadas.
15
16 La instancia local URLAuth contiene la dirección donde el servicio Web RESTful (ver sección \ref{cap5:ModeloWSDL}) está a la escucha, en este caso y tal y como dicta Symfony\footnote{Web Services Symfony 1.4: \url{http://symfony.com/legacy/doc/jobeet/1_4/en/15?orm=Doctrine}} es necesario especificar en la propia URL el tipo de dato que deseamos recibir (en este caso, JSON)
17
18
19 \lstset{language=Java, basicstyle=\small, breaklines=true, float=[P], floatplacement={P}, frame=single, captionpos=b, caption={Código para la ejecución de tareas en paralelo}, label={list:JavaUpdateLocation}}
20 \lstinputlisting{source/JavaUpdateLocation.java}
21
22 Posteriormente se lanza la petición al servidor Web en el hilo creado mediante el método execute del Listado~\ref{list:JavaUpdateLocation}), esta petición es llevada a cabo por una instancia de la clase AndroidHttpClient proporcionada por la API Android. Esta instancia se muestra en el Listado~\ref{list:JavaHTTPGet}) con el nombre httpClient. Como parámetros de entrada necesita un handler que trate la respuesta del servidor y una instancia representando la petición HTTP a realizar (GET, PUT, DELETE o POST). El código del handler se muestra en el Listado~\ref{list:JavaHTTPGetHandler}) y el resultado devuelto por dicho handler es almacenado como un objeto del tipo StringBuilder retornado por httpClient.
23
24 \lstset{language=Java, basicstyle=\small, breaklines=true, float=[P], floatplacement={P}, frame=single, captionpos=b, caption={Código para la conexión con el servidor Web}, label={list:JavaHTTPGet}}
25 \lstinputlisting{source/JavaHTTPGet.java}
26
27 \lstset{language=Java, basicstyle=\small, breaklines=true, float=[P], floatplacement={P}, frame=single, captionpos=b, caption={Código para la recepción de datos desde el servidor Web}, label={list:JavaHTTPGetHandler}}
28 \lstinputlisting{source/JavaHTTPGetHandler.java}
29
30 El StringBuilder contiene la respuesta JSON devuelta por el servidor Web. En el Listado~\ref{list:JavaHTTPJSONtreatment}) se muestra como el StringBuilder es transformado a un Array de objetos JSON (el servicio Web responde con una secuencia donde cada objeto JSON contiene los datos de un anuncio) El método updatedIndexer actualizará tal y como su nombre indica la base de datos con la información sobre el nuevo anuncio y si todo va bien mediante otra conexión HTTP realizada por el método downloadImage la aplicación Android descargará la imagen que está asociada con un anuncio. En caso de que el anuncio ya estuviera descargado, se encontraría en la base de datos y updatedIndexer devolvería null y por tanto no se descargaría nada ni se actualizaría la base de datos con información que ya existía previamente.
31
32 En el Listado~\ref{list:JavaHTTPJSONtreatment}) se observa también la existencia del método sendBroadcast. Habíamos mencionado previamente que la tarea de descarga de anuncios se ejecutaba en segundo plano por parte de un Servicio Android multihilo. Necesitamos un mecanismo para por un lado actualizar la interfaz del usuario mostrando los nuevos anuncios recibidos y por otro generar una notificación en la barra de tareas del dispositivo Android. Para llevar a cabo estos requerimientos se hace uso de un BroadCastReceiver proporcionado por la API Android que notificará a las Activities que escuchen dicho broadcast sobre la necesidad de actualizar los datos que están mostrando al usuario.
33
34 \lstset{language=Java, basicstyle=\small, breaklines=true, float=[P], floatplacement={P}, frame=single, captionpos=b, caption={Código para la persistencia de los datos recibidos}, label={list:JavaHTTPJSONtreatment}}
35 \lstinputlisting{source/JavaHTTPJSONtreatment.java}
36
37 \subsection{Tratamiento de las peticiones en el servidor Web}
38
39 La petición realizada por el cliente (un simple GET HTTP con la latitud, longitud y el tipo de contenido esperado en la propia URL) llega al servidor. El tratamiento de esta petición depende del framework, lenguaje, etc que estemos utilizando en la aplicación que actúa como servidor Web. En nuestro caso, se trata de una aplicación escrita en PHP y que usa el Framework Symfony, por tanto debemos ceñirnos (si queremos desarrollar un código mantenible y legible) al modo que Symfony nos permite implementar servicios Web RESTful.
40
41 En el Listado~\ref{list:PHPWebServiceRouting}) se muestra la parte del archivo routing.yml utilizado en este proyecto para configurar en Symfony el servicio Web RESTful que recibe la posición actual del usuario. Explicado de forma sencilla el archivo routing.yml en Symfony permite configurar diferentes URLs en las que la aplicación Web escuchará y asociar estas URLs a clases PHP que se encuentran en nuestra aplicación y que tratarán la petición del usuario y devolverán (si es necesario) una respuesta. En este caso es un servicio Web RESTful lo que se está configurando, pero en la práctica podría ser cualquier cosa, por ejemplo, una simple página web escrita en HTML.
42
43 \lstset{language=bash, basicstyle=\small, breaklines=true, float=[P], floatplacement={P}, frame=single, captionpos=b, caption={Servicio Web RESTful en Symfony: definición}, label={list:PHPWebServiceRouting}}
44 \lstinputlisting{source/PHPWebServiceRouting.yml}
45
46 \begin{itemize}
47     \item La entrada \emph{url} define el formato de la dirección que manejará el servicio web (su URL) indicando mediante el uso de el carácter ``:'' que ese campo es una variable cuyo valor no es conocido a priori y tiene que venir dado por la petición GET HTTP desde el cliente.
48     \item La entrada \emph{param} indica el método que se ejecutará cuando llega una petición GET HTTP a la URL definida anteriormente.
49     \item \emph{sf\_format} indica el formato de los datos devueltos por el servicio Web. En nuestro caso se permite XML, JSON o YAML pero finalmente solo ha sido implementado JSON.
50     \item Por último la entrada \emph{sf\_method} hace referencia al tipo de método HTTP que se permitirá para hacer uso de este servicio Web.
51 \end{itemize}
52
53 Posteriormente hay que atrapar la petición GET HTTP para tratarla y devolver un resultado, este proceso se muestra en el Listado~\ref{list:PHPWebServiceActions}) donde se ejecuta el método definido en el Listado~\ref{list:PHPWebServiceRouting}). RESTful permite usar cookies pero únicamente para indicar si un usuario está autenticado o no, para este servicio Web en concreto es necesario que el usuario haya hecho \emph{log in} previamente en el servidor Web. Tal tarea se lleva a cabo en la aplicación Android, si el \emph{log in} es satisfactorio guardará la cookie recibida desde el servidor Web (desde la aplicación PHP) y la adjuntará en la cabecera de posteriores peticiones GET HTTP (usando AndroidHttpClient tal y como fue mostrado en el Listado~\ref{list:JavaHTTPGet}) Como el usuario está autenticado podemos averiguar el id asociado a ese usuario dentro del sistema (en la base de datos de la aplicación Web); en caso de no estar autenticado este método fallará y no devolverá nada.
54
55 \lstset{language=PHP, basicstyle=\small, breaklines=true, float=[P], floatplacement={P}, frame=single, captionpos=b, caption={Código para el tratamiento del servicio Web}, label={list:PHPWebServiceActions}}
56 \lstinputlisting{source/PHPWebServiceActions.php}
57
58 El método que lleva a cabo todo el peso de búsqueda en base de datos de anuncios localizados en las cercanías del usuario es getAdsByGPSAndUserIdAndLanguageId. Si este método no devuelve nada, significa que o bien no hay anuncios en esas coordenadas geográficas o el usuario no se ha subscrito a categorías relacionadas con anuncios localizados en ese área en el cual se encuentra actualmente. El método getParameters() devuelve un array con los parámetros de entrada suministrados en el GET HTTP. En este caso, dichos parámetros se insertan directamente en la URL y fueron definidos en el Listado~\ref{list:PHPWebServiceRouting}) dentro de la entrada url como \emph{longitude} y \emph{latitude}.
59
60 El Listado~\ref{list:PHPWebServiceModel}) muestra como haciendo uso del id del usuario dentro del sistema (lo conocemos porque tuvo que hacer previamente \emph{log in} y envía su cookie en cada petición GET HTTP al servicio Web), la latitud y la longitud se puede mediante peticiones a la base de datos averiguar si existen anuncios de interés en las cercanías del usuario. Actualmente el radio de acción al rededor del cual se buscará en la base de datos PostGIS por posibles anuncios de interés se define directamente en el código PHP; en posteriores implementaciones sería posible por ejemplo, que en función de si el servicio es premium o no, el usuario pueda seleccionar el radio de acción al rededor del cual desea que se busquen anuncios en su posición actual.
61
62 \lstset{language=PHP, basicstyle=\small, breaklines=true, float=[P], floatplacement={P}, frame=single, captionpos=b, caption={Código para la búsqueda de anuncios en la base de datos PostgreSQL/PostGIS}, label={list:PHPWebServiceModel}}
63 \lstinputlisting{source/PHPWebServiceModel.php}
64
65 El método ST\_DWithin del Listado~\ref{list:PHPWebServiceModel}) es un método especial definido por PostGIS que permite hacer búsquedas en la base de datos PostgreSQL de posiciones geográficas que se encuentren dentro de una circunferencia con radio \emph{radius} y centro marcado por el tipo SRID 4326 o también llamado WGS 84 que son las siglas en Inglés para \emph{World Geodetic System 84}. WGS 84\footnote{La Wikipedia proporciona una buena introducción al estándar WGS: \url{http://en.wikipedia.org/wiki/WGS84}} es el sistema usado por el \emph{Global Positioning System} o GPS que es precisamente desde el cual estamos recibiendo datos a través del dispositivo móvil utilizado por el usuario.
66
67 Como también se puede observar en el Listado~\ref{list:PHPWebServiceModel}) realizamos peticiones directamente a la base de datos. El objetivo de llevar a cabo las peticiones de este modo es disminuir la sobrecarga de código y por tanto de ciclos de CPU para implementar en el servidor cada petición al servicio Web realizada por los clientes. Este código será usado por muchos usuarios de forma concurrente, por tanto es necesario que se ejecute tan rápidamente como sea posible. Ganamos velocidad pero perdemos legibilidad y mantenibilidad del código.
68
69 Se requiere de varias peticiones a la base de datos para poder obtener al final un array con los anuncios (findOneByAdIdAndLanguageId devuelve objetos PHP del tipo Doctrine\_Record\footnote{Sobre objetos Doctrine ver: \url{http://docs.doctrine-project.org/projects/doctrine1/en/latest/en/manual/component-overview.html}}) que se encuentran alrededor de la localización geográfica del usuario (primera consulta a la base de datos) y que están relacionados con categorías a las cuales el usuario se encuentra asociado (segunda consulta a la base de datos).
70
71 Y finalmente en el Listado~\ref{list:PHPWebServiceView}) se genera el código JSON que será recibido y procesado por la aplicación cliente Android tal y como se mostró en el Listado~\ref{list:JavaHTTPJSONtreatment})
72
73 \lstset{language=PHP, basicstyle=\small, breaklines=true, float=[P], floatplacement={P}, frame=single, captionpos=b, caption={Código para la generación de datos en formato JSON}, label={list:PHPWebServiceView}}
74 \lstinputlisting{source/PHPWebServiceView.php}