Dec

14

Primera aplicación Grails en Cloud Foundry

Posted by : hopcroft | On : 14/12/2011

cloud-foundry-logo

 

En el anterior post, Despliegue de aplicaciones Grails en Cloud Foundry, veíamos como comenzar con Cloud Foundry y Grails. En ese post os comenté que iba a hablar algo más sobre una de las aplicaciones que había subido. En concreto la aplicación se llama cityFinder y sirve para encontrar información como el tiempo que va a hacer, fotos de la ciudad o eventos que se van a desarrollar en ella.

 

La aplicación en sí no tiene demasiada lógica pero me ha servido en el proceso de aprendizaje sobre Grails y Cloud Computing en el que estoy metido últimamente. Desarrollando esta aplicación he aprendido nuevos conceptos sobre Grails como:

  • Uso de servicios RESTful en Grails.
  • Manejo y parseo de la respuesta devuelta por los servicios web.
  • Paso de parámetros a la parte de vista de la aplicación con GSP’s (Groovy Server Pages).
  • Aunque queda fuera de lo que puede ser Grails está aplicación también me ha servido para conocer un poco más sobre JQuery.
  • Configuración de la aplicación.

 

Uso de servicios RESTful en Grails.

Utilizar servicios en Grails es muy sencillo. Si tienes un poco de experiencia con Grails habrás visto que cuando generamos una aplicación desde línea de comandos con grails create-app o a través de tu editor favorito como Eclipse o STS, se nos genera distintas capas dentro de la aplicación como Domain, Controller, View o Services. Es en está capa donde tenemos que definir e implementar nuestros servicios.

Lo ideal siempre que realicemos un nuevo servicio es que definamos la parte de interfaz con los métodos que va a declarar nuestro contrato y que después hagamos una ó varias implementaciones de éste si lo necesitamos.

En mi caso y al tratarse de una aplicación poco menos que de juguete, he implementado directamente los servicios y más tarde los he inyectado dentro de mi controlador. Si quisiera hacerlo todo usando buenas prácticas debería refactorizar mi código.

He utilizado varios servicios web disponibles para realizar la aplicación. Desde un principio no me quería complicar mucho con tema de base de datos. Así, en lugar de recoger toda o parte de la información desde una base de datos, toda la información de la aplicación la recoge de servicios web. Los servicios web que he usado son:

  • Servicios de localización:  la aplicación básicamente recibe el nombre de una ciudad y devuelve información sobre ella. Uno de los métodos para identificar a una ciudad dentro de los servicios web es el servicio de Geolocalización de Yahoo llamado GeoPlanet. Estamos hablando de un servicio RESTal que le pasamos en una petición http el nombre de la ciudad y nos va a devolver un valor Woeid que identificará uniquivocamente a dicha ciudad.
    	def url = "http://where.yahooapis.com/v1/places.q('" + city + "')?appid=" + yahoo_appid;
    	def xml = url.toURL().text

En el caso de ciudades cuyo nombre se repita el servicio nos devolverá varias respuestas. En el ejemplo de debajo vamos a ver que devolvería el servicio para una petición con el nombre de la ciudad de Madrid. Además del nombre del woeid de la ciudad vamos a encontrar otros valores como la longitud y latitud que nos va a servir en otros servicios web como los de Google Maps.

<places xmlns="http://where.yahooapis.com/v1/schema.rng" xmlns:yahoo="http://www.yahooapis.com/v1/base.rng" yahoo:start="0" yahoo:count="1" yahoo:total="14">
	<place yahoo:uri="http://where.yahooapis.com/v1/place/766273" xml:lang="es-ES">
		<woeid>766273</woeid>
		<placeTypeName code="7">Pueblo</placeTypeName>
		<name>Madrid</name>
		<country type="País" code="ES">España</country>
		<admin1 type="Comunidad Autónoma" code="">Comunidad de Madrid</admin1>
		<admin2 type="Provincia" code="ES-M">Madrid</admin2>
		<admin3/>
		<locality1 type="Pueblo">Madrid</locality1>
		<locality2/>
		<postal/>
		<centroid>
			<latitude>40.420300</latitude>
			<longitude>-3.705770</longitude>
		</centroid>
		<boundingBox>
			<southWest>
				<latitude>40.312012</latitude>
				<longitude>-3.888810</longitude>
			</southWest>
			<northEast>
				<latitude>40.643299</latitude>
				<longitude>-3.518210</longitude>
			</northEast>
		</boundingBox>
		<areaRank>5</areaRank>
		<popRank>13</popRank>
	</place>
</places>
  • Servicios de forecast: Yahoo nos da un servicio web de forecastque nos permite conseguir los datos climatológicos de una localización. Para ello es necesario pasar el woeid de esa localización. Por eso era importante el anterior servicio de localización.
    def yahooWeather = "http://weather.yahooapis.com/forecastrss?w=" + woeid + "&u=c"
  • Servicio de noticias.  En lugar de seguir probando los servicios de Yahoo he preferido en este caso utilizar Google News. Puedes hacer una petición al servicio de Google News pasandole una ciudad y éste te devolverá las noticias de dicha ciudad. Además puedes definir una serie de propiedades sobre las noticias que quieres recibir. Recomiendo echarle un ojo al API… aunque este deprecated. En mi caso la única opción que he solicitado es que la salida venga en formato RSS.
    def url_news ='http://news.google.com/news?q=' + encodedCity + '&output=rss'
  • Servicio de eventos. Para los recoger los eventos que suceden en una ciudad he usado dos proveedores distintos:
    • YQL (Yahoo Query Language). La verdad es que fue un descubrimiento googleando. Yahoo te da acceso a una serie de tablas desde las cuales (echar un ojo a su consola) se puede acceder a distinto tipo de información. Entre otras tablas se puede acceder a upcoming.events que nos devolverá información de una ciudad pasando como parametro su woeid y una consulta SQL. ¿Inyección SQL?. Yahoo dirá …En mi caso he solicitado que la respuesta venga en formato JSON.
      def yahoo_events_url = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20upcoming.events%20where%20woeid%20in%20(" + woeid + ")%20limit%2020&format=json"
    • Eventful. Eventful pone a nuestra disposición una serie de servicios web para acceder a los eventos (conciertos, eventos deportivos, …). Necesitamos registrarnos y obtener una clave, pero merece la pena ya que dispone de muchas opciones de parametrización y además las bases de datos de las que recoge información devuelven mucha información, que al fin y al cabo es lo que interesa :D .
      def url_eventful = "http://api.eventful.com/rest/events/search?app_key=XXXXXXXXXX&location=" + encodedCity + "&date=Future&page_size=10&sort_order=relevance"

 

Parseo de la respuesta de los servicios web con XmlSlurper y JsonSlurper.

Una vez recibida la respuesta, ¿qué hago con ella?. En Grails tenemos varios métodos para parsear una respuesta, en concreto nos vamos a centrar en XMLSlurper y JsonSlurper. Ambas son clases que nos permiten pasarles un texto y serán ellas las que se encargarán de parsear el texto, de modo que el objeto resultante será del tipo GPath. Dentro de este objeto GPath podremos navegar de manera similar a como lo hacemos con XPath. Por ejemplo podemos acceder a un nodo hijo con el operador ‘.’ ó a un atributo del nodo actual con ‘@’.

Por ejemplo, para los servicios de noticias usaremos XmlSlurper. En la siguiente captura podemos ver como construimos la url de petición, llamaremos al método toUrl().text que será el encargado de construir la url y hacer la petición al servicio respectivamente. Ahora quedará pasarle a nuestro XmlSlurper esa respuesta y él se encargará de convertirlo en un GPathResult por el podemos navegar como vemos en el código.

class NewsService {

    static transactional = true

    def List getGoogleNews(String encodedCity) {
		def url_news ='http://news.google.com/news?q=' + encodedCity + '&output=rss'
		def xml_news = url_news.toURL().text
		def rss_news = new XmlSlurper().parseText(xml_news)
		def news = []
		rss_news.channel.item.each {
			news << it
		}
		return news
    }

En el caso de JsoSlurper el procedimiento es similar, lo único que nuestra respuesta ha sido devuelta con formato JSON.

	def List<Event> getEventfulEvents(String encodedCity, List options) {
		List<Event> events = new ArrayList<Event>()
		def url_eventful = "http://api.eventful.com/rest/events/search?app_key=XXXXXXXXXXXX&location=" + encodedCity + "&date=Future&page_size=10&sort_order=relevance"
		def url_eventful_text = url_eventful.toURL().text
		def eventful = new XmlSlurper().parseText(url_eventful_text)
		eventful.events.event.each {
			Event event = new Event()
			def html = it.title.toString() + " - Venue " + it.venue_name.toString()
			event.html = html
			event.latitude = it.latitude
			event.longitude = it.longitude
			event.title = it.title + " - Venue " + it.venue_name
			event.start = it.start_time.toString()
			event.end = it.stop_time.toString()
			event.allDay = false
			events << event
		}
		return events
	}

 

 

Juntando todo en el controlador y pasando información a las GSPs.

Para insertar nuestros servicios en nuestro controlador solamente tendremos que inyectarlos de la siguiente forma:

¿Simple, verdad?. Ahora lo único que tenemos que hacer es llamar a los métodos implementados en los servicios desde el controlador como podemos ver aquí abajo.

class CityController {
	def weatherService
	def locationService
	def newsService
	def eventService
....

Una vez que hayamos devuelto la información desde los servicios nos tocará pasar la información a las GSP’s. Tenemos varias opciones, podemos pasar dentro del modelo del método render ó bien dentro de un parámetro con scope flash ó request (ver scopes en Grails aquí). En esta aplicación pasamos los objetos usando el scope flash.

def List<Event> events = eventService.getYahooEvents(woeid.toString(), null)
events += eventService.getEventfulEvents(encodedCity, null)
def jsonEvents = events as JSON
flash.events = jsonEvents

 

 

Páginas GSP y componentes JQuery.

Ahora toca mostrar los datos recogidos en las páginas GSP para ello he decidido utilizar diversos componentes JQuery. En concreto los componentes son los siguientes:

  • Search Box Filter: componente JQuery que nos permite buscar y asociar un filtro a esa búsqueda.
    cityfindersearch
  • Gmap: es un plugin JQuery que permite incluir Google Maps en nuestras páginas. Gmap puede recibir una serie de opciones, entre ellos los eventos recogidos con nuestros servicios y renderizarlos en nuestra GSP.
    eventospopup
  • zWeatherFeed: este plugin JQuery permite renderizar un panel con la información meteorologica de una ciudad, para ello necesita conocer el código obtenido del servicio de forecast.
  • Galleria:  es un framework para galerías de imagenes que permite mostrar imagenes de distintas fuentes flickr, picassa, ….
    weather-galleria
  • zFlickrFeed: similar al plugin anterior. Obtendremos imagenes desde flickr.
    zflickr
  • FullCalendar: nos permite incluir un calendario con el look&feel de Google Calendar en nuestra aplicación. Podemos definir una serie de eventos que serán cargados en él.
    fullCalendar
  • Google News: simplemente se reenderiza las noticias obtenidas.
    googlenews

Configuración de la aplicación.

La configuración de la aplicación no es muy complicada. Ya he comentado que no estoy utilizando bases de datos lo cual minimiza mucho la configuración.

Lo más destacable es que en el fichero buildConfig.groovy he tenido que añadir los plugins de Resources y JQuery. Además como no he estado utilizando la última versión del compilador de Groovy que trae incluido JSonSlurper, he tenido que agregarlo a mi proyecto como dependencia.

   dependencies {
        // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
        // runtime 'mysql:mysql-connector-java:5.1.16'
		compile(group:'nekohtml', name:'nekohtml', version:'1.9.6.2') {
			excludes([ group: 'xerces'])
		}
		compile(group:'xerces', name:'xercesImpl', version:'2.9.1') { transitive = false }
		compile(group:'net.sf.json-lib', name:'json-lib', version:'2.4')
	}

	plugins {
		compile ":hibernate:$grailsVersion"
		compile ":jquery:1.6.1.1"
		compile ":resources:1.1.1"
                build ":tomcat:$grailsVersion"
    }

 

No sólo he probado está aplicación en local ó cloudFoundry también la he subido al tomcat que tengo en mi hosting y añadido una base de datos MySQL. Y hay una cosa que quiero comentar al respecto, para usar una base de datos MySQL en nuestra aplicación de nuestro hosting tendremos que modificar el fichero Config.groovy de la siguiente forma.

environments {
    development {
        grails.logging.jul.usebridge = true
    }
    production {
        grails.logging.jul.usebridge = false
        grails.serverURL = "http://java4developers.com"
    }
}

Pero lo que quiero resaltar para hacer que funcione en local, no es nada acerca de Groovy o Grails, sino de configuración del servidor. Para hacer que funcione nos tendremos que ir a la consola de nuestro hosting y en el servidor de MySQL remote tendremos que añadir la ip de nuestra máquina local mientras que hacemos nuestras pruebas en nuestro entorno local.

remote-database-access

Bueno, esto es todo espero que os sea interesante y sobre todo útil. La aplicación es accesible desde http://mycityfinder.cloudfoundry.com/

 

  • Pingback: Primera aplicación Grails en Cloud Foundry « hop2croft's software development Blog

  • Shadonwk

    Hola amigo muy buena info, me interesaria mas sobre como subir mi propia aplicación a cloudfoundry micro pues no tengo cuenta de la versión completa.

    • Anonymous

      Hola Shadowk,

      gracias por el comentario. Por lo que estoy viendo si quieres bajarte la imagen de micro cloud foundry hace falta tener una cuenta autorizada de cloudFoundry. No he encontrado ningún link con descarga directa de micro cf sino pasando por el registro. Cuando yo solicité usuario me tardo un día o dos. Además es completamente gratuita, te animo a registrarte.

      Una vez bajada la imagen de micro cloudfoundry te recomiendo el manual oficial que puedes encontrar en esta página http://support.cloudfoundry.com/entries/20316811-micro-cloud-foundry-installation-setup. Es bastante intuitiva y solo necesitas seguir el asistente de configuración. En cualquier caso si tienes algún problema de configuración no dudes en postear.

      Saludos.