Spring boot : Spring data+ Spring mvc + H2, utilizando bases de datos en memoria


Spring boot se ha convertido en uno de los frameworks más fáciles de utilizar en Java para distintos tipos de aplicaciones, en este post explicaremos la integración de Spring boot con la base de datos H2 para el desarrollo y pruebas de nuestras aplicaciones.

¿Qué es H2?

H2 es una base de datos relacional llamada In memory database (Base de datos en memoria), esto significa que los datos solo vivirán durante la ejecución de nuestra aplicación y cuando esta termine se perderán. El uso de este tipo de bases de datos es muy común para desarrollar pruebas de concepto y realizar pruebas unitarias.

Las bases de datos en memoria son diferentes a las bases de datos normales por lo siguiente, al usar una base de datos común hacemos lo siguiente:

common-db

Como podemos ver  la aplicación y la base de datos son independientes, esto significa que se debe crear la base de datos y mantenerla para poder tener acceso a ella. A diferencia de esto, una base de datos en memoria funcionará del siguiente modo:

in memory-db

Como vemos una vez que iniciemos nuestra aplicación se iniciará nuestra base de datos y esta vivirá durante el tiempo que nuestra aplicación funcione, una vez que la aplicación se detiene los datos se pierden, es por esto que el uso de este tipo de bases de datos es ideal para el desarrollo de pruebas de concepto y tests unitarios dentro de nuestras aplicaciones.

Ventajas

El uso de H2 proporciona las siguientes ventajas:

  • No es necesario invertir en infraestructura
  • No es necesario invertir en configuración
  • No es necesario dar mantenimiento
  • La configuración con Spring boot es super simple

Funcionamiento con Spring boot

Una vez que entendemos el propósito de H2 veamos como funciona con Spring boot.

Paso 1: Configuración

El primer paso será configurar nuestra aplicación Spring boot, veamos las entradas en nuestro archivo pom.xml:


	org.springframework.boot
	spring-boot-starter-parent
	2.0.3.RELEASE

Define el proyecto padre de nuestra aplicación (Configuración común de spring boot).


	
		org.springframework.boot
		spring-boot-starter-data-jpa
	
	
		org.springframework.boot
		spring-boot-starter-web
	
	
		com.h2database
		h2
		runtime
	

Las anteriores son las dependencias necesarias de nuestro proyecto, en este caso solo explicaremos como trabajar con h2 utilizando el api de spring-data.


	
		
			maven-compiler-plugin
			
				1.8
				1.8
			
		
		
			org.springframework.boot
			spring-boot-maven-plugin
		
	

Por último vemos la definición de plugins los cuál nos permitirán ejecutar nuestra aplicación y definir la versión de java a utilizar.

Paso 2 : Definir nuestra clase aplicación

Una vez que definimos nuestras dependencias el siguiente paso será crear nuestra clase Application.


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author raidentrance
 *
 */
@SpringBootApplication
@EnableTransactionManagement
@EnableJpaRepositories("com.devs4j.app.repositories")<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>
public class H2SampleApplication {
	public static void main(String[] args) {
		SpringApplication.run(H2SampleApplication.class, args);
	}
}

Ejecutaremos esta clase para iniciar nuestra aplicación.

Paso 4: Crear una Entity

En este ejemplo utilizaremos Spring data para ver el funcionamiento de H2, para esto crearemos la siguiente Entity:

@Entity
public class Person {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;

	@Column(name = "name", nullable = false)
	private String name;

	@Column(name = "nickname", nullable = true)
	private String nickName;

	@Column(name = "age", nullable = false)
	private Integer age;
....Getters and setters
}

La clase anterior define una entidad persona con las siguientes características:

  • Un Id de tipo entero autoincremental
  • Un nombre de tipo String que debe ser not null
  • Un nickName de tipo String que puede ser null
  • Una edad de tipo Integer que debe ser not null

Paso 6 : Crear repository JPA

Como sabemos Spring Data nos permite simplificar el código de acceso a base de datos a través de Repositories, veamos el repositorio a crear:


import org.springframework.data.jpa.repository.JpaRepository;

import com.devs4j.app.entities.Person;

/**
 * @author raidentrance
 *
 */
public interface PersonRepository extends JpaRepository{
}

Como se puede ver PersonRepository nos permitirá manejar entidades de tipo Person, más adelante veremos como utilizaremos esta interfaz en nuestro código.

Paso 7 : Creando el servicio

El siguiente paso será crear un servicio de Spring, estos servicios son diseñados para escribir la lógica de negocio que necesitemos, en este caso no es tan necesario ya que solo ejecutará la llamada a un repository, pero en casos en los que hace llamadas a multiples repositorios y ejecuta alguna lógica sobre los datos, es muy útil.


import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.devs4j.app.entities.Person;
import com.devs4j.app.repositories.PersonRepository;

/**
 * @author raidentrance
 *
 */
@Service
public class PersonService {

	@Autowired
	private PersonRepository repository;

	public List getPeople() {
		return repository.findAll();
	}
}

Como se puede ver a través de la anotación @Autowired inyectamos el repository dentro de nuestro servicio, recordemos que no es necesario escribir la implementación del repository ya que Spring Data lo hace por nosotros, lo se es hermoso :).

Paso 8 : Creando el controller

El último paso para completar nuestra aplicación será crear un Controller para exponer la información, veamos el código a continuación:


import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.devs4j.app.entities.Person;
import com.devs4j.app.services.PersonService;

/**
 * @author raidentrance
 *
 */
@RestController
@RequestMapping("api")
public class PersonController {

	@Autowired
	private PersonService personService;

	@RequestMapping("/people")
	@ResponseBody
	public ResponseEntity<List> getPeople() {
		return new ResponseEntity(personService.getPeople(), HttpStatus.OK);
	}

}

Como vemos nuestro endpoint /api/people devolverá todos los registros que se encuentren en la tabla person.

Paso 9: Ejecutando nuestra aplicación

En este paso ejecutaremos la clase H2SampleApplication.java y llamaremos la url http://localhost:8080/api/people, al hacer esto obtendremos la siguiente salida:

[ ]

Triste ¿no lo creen? no vemos ningún registro porque nuestras tablas en memoria están vacías, veamos algunas formas de llenarlas.

Trabajando con H2

Como comentamos al principio del post, H2 es una base de datos en memoria, lo cual significa que mantendrá nuestros registros mientras nuestra aplicación esté en ejecución, lo primero que haremos será ver como acceder a la base de datos mientras la aplicación esta en ejecución.

Accediendo a la consola de H2

Lo primero que debemos aprender es a utilizar la consola de H2, para esto agregaremos la siguiente línea a nuestro archivo /src/main/resources/application.properties:

spring.h2.console.enabled=true

Esto nos permitirá habilitar la consola de administración de H2 mientras nuestra aplicación se ejecuta. Una vez hecho esto iniciaremos nuestra aplicación y accederemos a la URL http://localhost:8080/h2-console, esto nos mostrará la siguiente vista:

Captura de pantalla 2018-06-29 a las 1.49.32 p.m.

Es importante cambiar la JDBC URL a jdbc:h2:mem:testdb y oprimir el botón connect, una vez hecho esto veremos lo siguiente:

captura-de-pantalla-2018-06-29-a-las-2-03-34-p-m.png

Como pueden ver hay una tabla llamada PERSON, nosotros no creamos esta tabla si no que fue creada de forma automática por H2 y spring boot, desde esta consola podremos ejecutar sentencias sql para agregar valores, modificarlos o realizar consultas durante la ejecución de nuestra aplicación.

Lo anterior nos da flexibilidad para realizar modificaciones en nuestros datos, pero si nuestra aplicación require que se carguen algunos scripts o que se inserten algunos valores de ejemplo tenemos otra forma de hacerlo, a través de los siguientes archivos:

  • schema.sql : Permite ejecutar sentencias DDL antes de ejecutar la aplicación
  • init.sql : Permite realizar sentencias DML una vez que nuestro archivo schema.sql se ejecutó de forma correcta.

Veamos un ejemplo, crearemos el archivo /src/main/resources/data.sql con las siguientes sentencias:

insert into person (id,name,nickname,age)values(1, 'alex','raidentrance',29);
insert into person (id,name,nickname,age)values(2, 'juan','juanito dubalin',80);
insert into person (id,name,nickname,age)values(3, 'pedro','mascara sagrada',29);

Como se puede ver solo son sentencias insert que generarán algunos valores de ejemplo en nuestra base de datos.

A continuación solo debemos detener nuestra aplicación y ejecutarla de nuevo, al hacerlo podremos ejecutar el endpoint http://localhost:8080/api/people y veremos la siguiente salida:

[
    {
        "id": 1,
        "name": "alex",
        "nickName": "raidentrance",
        "age": 29
    },
    {
        "id": 2,
        "name": "juan",
        "nickName": "juanito dubalin",
        "age": 80
    },
    {
        "id": 3,
        "name": "pedro",
        "nickName": "mascara sagrada",
        "age": 29
    }
]

Como ven al momento de ejecutar la aplicación Spring boot detectó que usamos H2 y que existe un archivo llamado data.sql y ejecutó ese archivo en nuestra base de datos en memoria, esto permitió que nuestro servicio contara con datos desde el principio sin necesidad de cargar ningún valor en la base de datos manualmente.

En siguientes posts explicaremos como ejecutar transacciones y manejarlas utilizando H2 o cualquier otra base de datos.

Para enterarte sobre futuros posts te recomendamos seguirnos en nuestras redes sociales: https://twitter.com/devs4j y https://www.facebook.com/devs4j/.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

 

 

Crea un API REST con Node.js y Express


Probablemente has escuchado de Node.js y te has preguntado que es y como usarlo, en este post vamos a abordar ambos temas.

Básicamente, Node.js es un entorno en tiempo de ejecución multi plataforma de código abierto, para la capa del servidor que se auxilia del El motor V8 JavaScript que google usa en su navegador Chrome para interpretar y ejecutar código Javascript, pero ahora del lado del servidor. Sin embargo, Node definitivamente No es como Apache Tomcat. Esos servidores básicamente son productos para servidor listos para instalar y que están listos para implementar aplicaciones instantáneamente, sin embargo esto no implica que sea mas complejo en uso o que tenga menos capacidades.

Entre las bondades que podemos encontrar usando Node estan:

  • Está basado en eventos,  y en un modelo asíncrono, que no bloquea la ejecución del hilo mientras se realizan operaciones de lectura/ escritura, lo que le permite servir una gran cantidad de peticiones al mismo tiempo.
  • Permite utilizar el mismo lenguaje (Javascript) tanto en el cliente como en el servidor.
  • Tiene una gran gestión de paquetes gracias a NPM (si quieres hacer algo, probablemente exista una librería/paquete que ya lo hacen).
  • Nos permite hacer en el servidor todo lo que necesitamos (acceso a ficheros, a bases de datos, conexiones de clientes.. )

Ahora si bien Node ya incluye un set de instrucciones para poder crear un API REST,   ocuparemos Express.js el cual es un framework ligero, flexible y robusto que se acopla muy bien a Node y que facilitará el desarrollo de nuestras apis.

Para instalar Node en nuestra PC es muy sencillo , solo debemos ir a la pagina oficial de Node.js  descargar el instalador de la ultima version recomendada.

Una vez se allá instalado Node, abriremos una terminal y escribiremos el siguiente comando para verificar la instalación de node.

node -v

Esto nos dará come resultado la version de Node.js que hallamos instalado.

Ahora procederemos a instalar Express.js de modo global, así como su herramienta para generar el esqueleto de nuestro proyecto REST ,para lo cual  ejecutaremos los siguientes comandos en la consola.

npm install express -g
npm install express-generator -g

Una vez se hallan ejecutados ambos comando tendremos Express.js y express generator de manera global y lo único que necesitamos para crear nuestro proyecto es colocarnos en alguna carpeta que destinada para nuestros proyectos de node y ejecutar en la consola.

express --view=ejs geeks-mexico

Una vez se ejecute este comando , crearemos el esqueleto de nuestro proyecto, usando como nuestro manejador de vistas a EJS (aunque hay soporte para otros incluiremos este por ser mas sencillo y familiar).

El siguiente paso sera  ubicarnos dentro de la carpteta  raíz de nuestro proyecto que en este caso es /geeks-mexico, y sera necesario descargar  la dependencias de nuestro proyecto  ejecutando.

npm install

Las dependencias de nuestro proyecto se encuentran declaradas en nuestra  archivo package.json (si has usado Maven se puede decir que este es el POM de Node), el cual si lo vemos en detalle aparte de declarar las dependencias del proyecto, también declara el nombre del proyecto, al versión y un script que le indica a en este caso a node, donde se encuentra el archivo que arranca todo el proyecto.

{
  "name": "geeks-mexico",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.18.2",
    "cookie-parser": "~1.4.3",
    "debug": "~2.6.9",
    "ejs": "~2.5.7",
    "express": "~4.15.5",
    "morgan": "~1.9.0",
    "serve-favicon": "~2.4.5"
  }
}

Antes de arrancar la aplicación vamos a explorar con mas detalle la estructura del proyecto.

.
├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.ejs
└──index.ejs

Y como puedes ver el proyecto esta segmentado en 4 carpetas base

  • /bin . Aquí se encuentra el archivo que arranca nuestro proyecto.
  • /public . Aquí ubicaremos todos los recursos estáticos para un proyecto web, es decir CSS, Javascript en el front e imágenes.
  • /routes. Aquí se encuentran declarados los endpoints de nuestro API Rest.
  • /views. Como su nombre lo indica las vistas usadas en nuestro proyecto.

Pues bien entonces procederemos a modificar el archivo index.js y users.js, y como se muestra a continuación.

//index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title:'Hello world to geeks readers' });
});

module.exports = router;
 Aquí por medio de Express estamos haciendo 2 cosas,  la primera es renderizar la vista index.ejs sobre la raíz de nuestro servidor y pasando como parámetro a la vista el valor de title.
var express = require('express');
var router = express.Router();
//simulation of our resources
var users = [];

/* GET users listing. */
router.get('/', function(req, res, next) {
res.json(users);
});

//Create a new user {"user":#,"name":"something"}
router.post('/', function(req, res, next) {
users.push(req.body);
res.json(req.body);
});

//Update an existing user {"user":#,"name":"something"}
router.put('/', function(req, res, next) {
var user=users.filter(function(user){ return user.user==req.body.user}).pop();//find the user
if(user!=null&&user!=undefined){
   user.name=req.body.name;//update name by reference
   res.json(user);
}else{
   res.status(404);
   res.json("User to update not found");
}
});

module.exports = router;
 En este archivo estamos declarando 3 endpoints, el primero solo devuelve la lista de los usuarios que tenemos, el segundo nos permite agregar un usuario a la lista y el tercero nos permite actualizar algún usuario existente, buscándolo por medio de campo user.
Finalmente para arrancar nuestro proyecto, solo nos colocamos en la carpeta raíz de nuestro proyecto y desde la consola ejecutamos.
npm start
Comando que por detrás ejecuta, el o los scripts declarados en el package.json, que en este caso seria equivalente a  ejecutar.
node ./bin/www
Con esto habremos iniciado nuestro proyecto, y podemos acceder  a este sobre localhost:3000.
Podemos agregar usuarios si hacemos una llamada POST a localhost:3000/users
{ "user":1, "name":"user1"}
Y actualizarlo por medio de la misma ruta pero haciendo un PUT.
El repositorio de git con el código de ejemplo lo puedes clonar o descargar aquí.
Por el momento es todo amigos, pero muy pronto seguiremos con este ejemplo y mostraremos como acoplar este proyecto para una aplicación WEB con angular 5.
Autor : Hugo Alberto Avelino Hernández
Contacto :  hugo.avelinoh@gmail.com

Aprende a hacer tests funcionales utilizando rest-assured


Un problema común en la mayor parte de las aplicaciones es escribir pruebas funcionales, en este post explicaremos como hacer esas pruebas sobre servicios REST utilizando la herramienta rest-assured.io.

Paso 1 Configuración

El primer paso será configurar nuestro proyecto, para esto veamos nuestro archivo pom.xml.

Como se puede ver la dependencia de rest-assured tiene el scope de test, esto para que solo sea utilizada para realizar las pruebas del proyecto y no sea incluida al construir el proyecto.

Paso 2 Analizando el endpoint a probar

Para que puedas ejecutar los tests en tu máquina los realizaremos sobre una api pública que todos podemos consultar, en este caso utilizaremos el api rest de bitso para realizar nuestras pruebas.

El endpoint que vamos a probar será el siguiente:

endpoint : https://api.bitso.com/v3/available_books/

Respuesta ejemplo :

{
    "success": true,
    "payload": [
        {
            "book": "btc_mxn",
            "minimum_price": "500.00",
            "maximum_price": "16000000.00",
            "minimum_amount": "0.00015",
            "maximum_amount": "500.00000000",
            "minimum_value": "5",
            "maximum_value": "10000000.00"
        },
        {
            "book": "eth_mxn",
            "minimum_price": "10.00",
            "maximum_price": "200000.00",
            "minimum_amount": "0.001",
            "maximum_amount": "100000.00000000",
            "minimum_value": "5.00",
            "maximum_value": "10000000.00"
        },
        {
            "book": "xrp_btc",
            "minimum_price": "0.00000100",
            "maximum_price": "5000.00000000",
            "minimum_amount": "0.00000100",
            "maximum_amount": "100000.00000000",
            "minimum_value": "0.00000100",
            "maximum_value": "100000.00000000"
        },
        {
            "book": "xrp_mxn",
            "minimum_price": "0.0000100",
            "maximum_price": "5000.00000000",
            "minimum_amount": "0.5",
            "maximum_amount": "500000.00000000",
            "minimum_value": "5",
            "maximum_value": "10000000.00000000"
        },
        {
            "book": "eth_btc",
            "minimum_price": "0.00000100",
            "maximum_price": "5000.00000000",
            "minimum_amount": "0.00000100",
            "maximum_amount": "1000.00000000",
            "minimum_value": "0.00000100",
            "maximum_value": "1000.00000000"
        },
        {
            "book": "bch_btc",
            "minimum_price": "0.0001",
            "maximum_price": "8000.00",
            "minimum_amount": "0.0001",
            "maximum_amount": "8000.00",
            "minimum_value": "0.0001",
            "maximum_value": "8000.00"
        },
        {
            "book": "ltc_btc",
            "minimum_price": "0.01",
            "maximum_price": "80000.00",
            "minimum_amount": "0.00015",
            "maximum_amount": "500.00000000",
            "minimum_value": "0.001",
            "maximum_value": "100.00"
        },
        {
            "book": "ltc_mxn",
            "minimum_price": "10",
            "maximum_price": "50000.00",
            "minimum_amount": "0.001",
            "maximum_amount": "100000.00000000",
            "minimum_value": "5",
            "maximum_value": "10000000.00"
        }
    ]
}

La respuesta anterior representa los precios de las criptomonedas cuando este post fue escrito.

Paso 3 Iniciar nuestras pruebas

Para crear nuestras pruebas utilizaremos el framework JUnit junto con rest-assured, veamos el primer test:

Prueba 1: valida que la bandera success en la respuesta es true:

En esta prueba se validará que la bandera success que se encuentra en la respuesta tiene el valor de verdadero, en caso contrario el test fallará.


import static io.restassured.RestAssured.get;
import static org.hamcrest.Matchers.equalTo;

import org.junit.Test;

/**
 * @author raidentrance
 *
 */
public class AvailableBooksTest {
	@Test
	public void testGetAvailableBooks() {
		get("https://api.bitso.com/v3/available_books/").then().body("success", equalTo(true));
	}
}

En este ejemplo podemos ver los siguientes puntos:

  • static imports: Como en la mayor parte de framework de pruebas rest-assured define algunos static imports que son necesarios para que nuestra aplicación funcione correctamente, en este caso estamos utilizando los siguientes:
    • import static io.restassured.RestAssured.get;
    • import static org.hamcrest.Matchers.equalTo;
  • Nuestra prueba funcional: Como se puede ver, es posible hacer nuestra primera prueba funcional con una sola línea de código, en esta definimos los siguientes puntos:
    • Url: La url del endpoint que vamos a probar.
    • Método http: al utilizar el método get estamos definiendo que la petición http a realizarse será de tipo get.
    • Validación: Como se puede ver en el método body definimos que el valor de la propiedad success debe ser igual a true, en caso contrario el test fallará

Prueba 1: valida que al obtener el precio de una crypto moneda devuelve un status http 200

@Test
public void getTickerByBook() {
	given().param("book", "xrp_mxn").get("https://api.bitso.com/v3/ticker").then().statusCode(200);
}

En este ejemplo se puede ver que le pasamos como parámetro el nombre de la crypto moneda al endpoint ticker y que validamos que el estatus http es 200.

Prueba 3 Valida que al obtener el precio de una crypto moneda el estatus http es 200 y que en el cuerpo de la respuesta el precio es diferente de null.

@Test
public void validateGetTickerPrice() {
	given().param("book", "xrp_mxn").get("https://api.bitso.com/v3/ticker").then().statusCode(200).and()
		.body("payload.ask", notNullValue());
}

En el código anterior se puede ver que es posible utilizar más de una validación durante nuestra prueba.

Conclusión

En este post podemos ver los beneficios de utilizar rest assured para las pruebas funcionales de nuestras aplicaciones, esto debido a que no tenemos que crear clientes http, des serializar las respuestas entre muchas otras tareas.

Puedes encontrar el código completo en la siguiente url https://github.com/raidentrance/rest-assured-example.

Si te gusta el contenido y quieres enterarte cuando realicemos un post nuevo síguenos en nuestras redes sociales https://twitter.com/geeks_mx y https://www.facebook.com/geeksJavaMexico/.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Utiliza tu Facebook desde java utilizando facebook4j


Cada vez es más común utilizar redes sociales en nuestras aplicaciones, en este ejemplo explicaremos como acceder a facebook desde java utilizando el api facebook4j.

Paso 1 Crear la cuenta

Sabemos que muchos de nosotros ya contamos con nuestra cuenta, pero es necesario registrar nuestra aplicación dentro del sitio de desarrolladores de Facebook en el siguiente link https://developers.facebook.com/docs/graph-api.

Una vez hecho esto debemos contar con los siguientes datos:

  • Application id :
  • Secret id:
  • Token id: Puedes generarlo aquí

Paso 2 Configurar el proyecto

El siguiente paso será configurar el proyecto para esto veamos nuestro archivo pom.xml. Y agregar las siguientes líneas para configurar nuestro cliente de Facebook:

Facebook facebook = new FacebookFactory().getInstance();
		facebook.setOAuthAppId("app id", "secret id");
		facebook.setOAuthAccessToken(new AccessToken("your access tocken"));

Con las líneas anteriores tendremos listo para usar nuestro cliente de Facebook.

Paso 3 Utilizar el cliente de Facebook

En esta sección mostraremos algunos de los métodos disponibles en el api.

Ver amigos

Para ver la lista de tus amigos ejecutarás lo siguiente:

ResponseList friends = facebook.getFriends();
for (Friend friend : friends) {
	System.out.println(friend);
}

Salida :

FriendJSONImpl extends UserJSONImpl [id=1111111, name=Friend name, firstName=null, middleName=null, lastName=null, gender=null, locale=null, languages=[], link=null, username=null, thirdPartyId=null, timezone=null, updatedTime=null, verified=null, bio=null, birthday=null, cover=null, education=[], email=null, hometown=null, interestedIn=[], location=null, political=null, favoriteAthletes=[], favoriteTeams=[], picture=null, quotes=null, relationshipStatus=null, religion=null, tokenForBusiness=null, significantOther=null, videoUploadLimits=null, website=null, work=[], ageRange=null]
.....

En la salida anterior podemos darnos cuenta del tipo de información que podremos obtener.

Obtener mis posts

System.out.println("\tGetting my posts\n");
ResponseList feed = facebook.getFeed();
for (Post post : feed) {
	System.out.println(post);
}

Lo anterior mostrará una salida como la siguiente:

PostJSONImpl{id='100000436331886_1821178564573337', from=null, to=[], message='Actúa natural para la foto 😅', messageTags=[], picture=null, fullPicture=null, link=null, name='null', caption='null', description='null', source=null, properties=[], icon='null', actions=[], privacy=null, type='null', sharesCount=null, likes=PagableListImpl{count=null, paging=null, summary=null, []}, place=null, statusType='null', story='null', storyTags={}, withTags=[], comments=PagableListImpl{count=null, paging=null, summary=null, []}, attachments=[], objectId='null', application=null, createdTime=Sat Mar 03 11:50:03 CST 2018, updatedTime=null, isPublished=null, isHidden=null, scheduledPublishTime=null, targeting=null, permalinkUrl=null}

La salida anterior podemos darnos cuenta del tipo de información que podremos obtener.

Obtener posts de otras cuentas

System.out.println("\tGetting cool posts\n");
ResponseList geeksFeed = facebook.getFeed("geeksJavaMexico");
for (Post post : geeksFeed) {
	System.out.println(post);
}

Lo anterior mostrará una salida como la siguiente:

PostJSONImpl{id='1135265379951652_1320846304726891', from=null, to=[], message='Hola Geeks! Queremos compartirles que entramos al concurso de kaspersky y quedamos seleccionados entre los mejores 25 proyectos deséenos suerte 🤓🇲🇽 https://kasperskystart.mx/', messageTags=[], picture=null, fullPicture=null, link=null, name='null', caption='null', description='null', source=null, properties=[], icon='null', actions=[], privacy=null, type='null', sharesCount=null, likes=PagableListImpl{count=null, paging=null, summary=null, []}, place=null, statusType='null', story='null', storyTags={}, withTags=[], comments=PagableListImpl{count=null, paging=null, summary=null, []}, attachments=[], objectId='null', application=null, createdTime=Wed Mar 14 12:42:33 CST 2018, updatedTime=null, isPublished=null, isHidden=null, scheduledPublishTime=null, targeting=null, permalinkUrl=null}

La salida anterior podemos darnos cuenta del tipo de información que podremos obtener.

Puntos importantes a considerar

 

Si tienes preguntas sobre el graph api de Facebook puedes ver su faq https://developers.facebook.com/docs/graph-api/faq,  es importante que consideres los endpoints que han sido marcados como deprecated por Facebook, un ejemplo claro son las búsquedas.

 

 

Consuming RESTful web services in Java with Jersey and FailSafe


One of the most common tasks is to consume RESTful web services in Java, in this post I will explain a design pattern using Jersey client.

Step 1 : Configuration

The project will include the dependencies described in the following pom.xml, as you can see there we have the following support:

  • Jackson : For serialization and deserialization
  • slf4j : For logging
  • failsafe: Api for retry logic

And at the end we can see we have the maven-compiler-plugin to define the Java version we are using.

Step 2 : Creating an abstract client

The next step will be to create an AbstractClient, you can use it in any rest client you want, lets see and analyze the code:


import java.io.IOException;

import java.util.Optional;

import java.util.concurrent.TimeUnit;

import java.util.logging.Logger;

import javax.ws.rs.client.Client;

import javax.ws.rs.client.ClientBuilder;

import javax.ws.rs.client.Entity;

import javax.ws.rs.client.WebTarget;

import javax.ws.rs.core.Response;

import javax.ws.rs.core.Response.Status;

import com.fasterxml.jackson.core.JsonParseException;

import com.fasterxml.jackson.core.type.TypeReference;

import com.fasterxml.jackson.databind.JsonMappingException;

import net.jodah.failsafe.Failsafe;

import net.jodah.failsafe.RetryPolicy;

/**

 * @author raidentrance

 *

 */

public abstract class AbstractClient {

	private String url;

	private String contextPath;

	private RetryPolicy defaultRetryPolicy;

	private static final Logger log = Logger.getLogger(AbstractClient.class.getName());

	public AbstractClient(String url, String contextPath) {

		this.url = url;

		this.contextPath = contextPath;

	}

	public AbstractClient(String url, String contextPath, int maxRetries, int delay, TimeUnit unit) {

		this.url = url;

		this.contextPath = contextPath;

		defaultRetryPolicy = new RetryPolicy().retryIf((Response resp) -> {

			if (resp != null) {

				return resp.getStatusInfo().getFamily() == Status.Family.SERVER_ERROR;

			} else {

				return false;

			}

		}).withDelay(delay, unit).withMaxRetries(maxRetries);

	}

	protected WebTarget createClient(String path) {

		String assembledPath = assembleEndpoint(path);

		Client client = ClientBuilder.newClient();

		return client.target(assembledPath);

	}

	private String assembleEndpoint(String path) {

		String endpoint = url.concat(contextPath).concat(path);

		log.info(String.format("Calling endpoint %s", endpoint));

		return endpoint;

	}

	/**

	 * Execute a GET http request over the endpoint received with the content

	 * type specified

	 *

	 * @param endpoint

	 *            Defines the endpoint that will be executed

	 * @param type

	 *            Defines the content type it can be JSON,XML, etc

	 * @return A response object with the entity response and the Http status

	 */

	protected Response get(String endpoint, String type) {

		WebTarget client = createClient(endpoint);

		Optional result = getDefaultRetryPolicy();

		if (result.isPresent()) {

			return Failsafe.with(result.get()).get((response) -> client.request(type).get());

		} else {

			return client.request(type).get();

		}

	}

	/**

	 * Execute a PUT request over the endpoint received with the content type

	 * specified and sending the object received in the body

	 *

	 * @param endpoint

	 *            Endpoint will be executed

	 * @param type

	 *            Defines the content type, it can be JSON, XML, etc

	 * @param entity

	 *            Object will be sent in the body of the request

	 * @return A response object with the entity response and the Http status

	 */

	protected Response put(String endpoint, String type, Object entity) {

		WebTarget client = createClient(endpoint);

		Optional result = getDefaultRetryPolicy();

		if (result.isPresent()) {

			return Failsafe.with(result.get()).get((response) -> client.request(type).put(Entity.entity(entity, type)));

		} else {

			return client.request(type).put(Entity.entity(entity, type));

		}

	}

	/**

	 * Execute a POST request over the endpoint received with the content type

	 * specified and sending the object received in the body

	 *

	 * @param endpoint

	 *            Endpoint will be executed

	 * @param type

	 *            Defines the content type, it can be JSON, XML, etc

	 * @param entity

	 *            Object will be sent in the body of the request

	 * @return A response object with the entity response and the Http status

	 */

	protected Response post(String endpoint, String type, Object entity) {

		WebTarget client = createClient(endpoint);

		Optional result = getDefaultRetryPolicy();

		if (result.isPresent()) {

			return Failsafe.with(result.get())

					.get((response) -> client.request(type).post(Entity.entity(entity, type)));

		} else {

			return client.request(type).put(Entity.entity(entity, type));

		}

	}

	/**

	 * If there is a default retry policy it will be returned

	 *

	 * @return

	 */

	public Optional getDefaultRetryPolicy() {

		return (defaultRetryPolicy != null) ? Optional.of(defaultRetryPolicy) : Optional.empty();

	}

	/**

	 * Modify the current default retry policy

	 *

	 * @param maxRetries

	 *            Number of times that it will retry until a successful request

	 * @param delay

	 *            The time that will wait until the next attempt

	 * @param unit

	 *            Unit of the time of the delay, it can be in seconds, minutes,

	 *            etc.

	 */

	public void setDefaultRetryPolicy(int maxRetries, int delay, TimeUnit unit) {

		defaultRetryPolicy = new RetryPolicy()

				.retryIf((Response resp) -> resp.getStatusInfo().getFamily() == Status.Family.SERVER_ERROR)

				.withDelay(delay, unit).withMaxRetries(maxRetries);

	}

	/**

	 * Get a Response and Parse to type T. if the response is not 200 throw

	 * Exception

	 *

	 * @param response

	 *            the HTTP response

	 * @param entityType

	 *            is a generic type that the function will return.

	 * @param

	 *            The Generic Type that the method is returing

	 * @return T

	 * @throws Exception

	 *             if a problem occurs

	 * @throws IOException

	 * @throws JsonMappingException

	 * @throws JsonParseException

	 */

	protected abstract  T parseResponse(Response response,TypeReference entityType) throws Exception;

}

Now lets analyze the class:

  • Constructors: There are two ways to construct our object
    • Only with the url and the context path
    • Including also the maxRetries, delay and TimeUnit
  • createClient(String path) : Will create a Jersey client used to consume the api’s.

  • assembleEndpoint(String path) : Based on the Endpoint will build the url we are going to consume.

  • Response get(String endpoint, String type) : Generic get method that receives two parameters an endpoint and a type.

  • Response put(String endpoint, String type, Object entity): Generic put method that receives two parameters an endpoint and a type.

  • Response post(String endpoint, String type, Object entity): Generic post method that receives two parameters an endpoint and a type.

  • Optional getDefaultRetryPolicy() : if the parameters maxRetries, delay and TimeUnit were assigned it will return the retry policy to use.

  • setDefaultRetryPolicy(int maxRetries, int delay, TimeUnit unit): You can use it if you want to change the default policy .

  • abstract T parseResponse(Response response,TypeReference entityType) throws Exception: Your implementation has to override the parseResponse method to define how it will translate the response.

Step 3 : Define a class to set all the endpoints

Once we have our abstract class, we have to define an endpoints class, in our case it will be ApplicationEndpoints, it will centralize all the endpoints used in the client.


/**

 * @author raidentrance

 *

 */

public class ApplicationEndpoints {

	private static final String TICKER = "/ticker";

	private ApplicationEndpoints() {

	}

	public static String getTickers() {

		return TICKER;

	}

	public static String getTickerByBook(String book) {

		return TICKER.concat("?book=").concat(book);

	}

}

In this case we are using the api of bitso to get prices of crypto currencies, you can see the documentation here and query the api in the url https://api.bitso.com/v3/ticker/

Step 4 : Defining the error model

Now we have to handle errors, to do it we will analyze the api we are querying, lets see the error model:


{

    "success": false,

    "error": {

        "code": "0301",

        "message": "Unknown OrderBook xrp_mxnq"

    }

}

Now we have to translate it to java classes.

ErrorCode.java


import java.io.Serializable;

/**

 * @author raidentrance

 *

 */

public class ErrorCode implements Serializable {

	private String code;

	private String message;

	private static final long serialVersionUID = 1735206115257033120L;

	public ErrorCode() {

	}

	public ErrorCode(String code, String message) {

		super();

		this.code = code;

		this.message = message;

	}

	public String getCode() {

		return code;

	}

	public void setCode(String code) {

		this.code = code;

	}

	public String getMessage() {

		return message;

	}

	public void setMessage(String message) {

		this.message = message;

	}

}

ErrorMessage.java


import java.io.Serializable;

/**

 * @author raidentrance

 *

 */

public class ErrorMessage implements Serializable {

	private boolean success;

	private ErrorCode error;

	private static final long serialVersionUID = -8921696489057035324L;

	public ErrorMessage() {

	}

	public ErrorMessage(boolean success, ErrorCode error) {

		this.success = success;

		this.error = error;

	}

	public boolean isSuccess() {

		return success;

	}

	public void setSuccess(boolean success) {

		this.success = success;

	}

	public ErrorCode getError() {

		return error;

	}

	public void setError(ErrorCode error) {

		this.error = error;

	}

}

Once we have both classes we are able to deserialize the errors to java objects, now we just need to create an exception to propagate the errors.


import com.raidentrance.rest.error.model.ErrorMessage;

/**

 * @author raidentrance

 *

 */

public class ServiceException extends Exception {

	private ErrorMessage errorMessage;

	private static final long serialVersionUID = -7898115956660992515L;

	public ServiceException(ErrorMessage errorMessage) {

		this.errorMessage = errorMessage;

	}

	public ErrorMessage getErrorMessage() {

		return errorMessage;

	}

}

The ServiceException will be thrown when an error happens and it will contain the error message we receive from the api.

Step 5 : Creating the model

We defined the model for the errors, but now we have to define the model for our api’s, in this case we will be reading tickers with the following structure:

Payload.java


import com.fasterxml.jackson.annotation.JsonProperty;

/**

 * @author raidentrance

 *

 */

public class Payload {

	@JsonProperty("high")

	private String high;

	@JsonProperty("last")

	private String last;

	@JsonProperty("created_at")

	private String createdAt;

	@JsonProperty("book")

	private String book;

	@JsonProperty("volume")

	private String volume;

	@JsonProperty("vwap")

	private String vwap;

	@JsonProperty("low")

	private String low;

	@JsonProperty("ask")

	private String ask;

	@JsonProperty("bid")

	private String bid;

	public String getHigh() {

		return high;

	}

	public void setHigh(String high) {

		this.high = high;

	}

	public String getLast() {

		return last;

	}

	public void setLast(String last) {

		this.last = last;

	}

	public String getCreatedAt() {

		return createdAt;

	}

	public void setCreatedAt(String createdAt) {

		this.createdAt = createdAt;

	}

	public String getBook() {

		return book;

	}

	public void setBook(String book) {

		this.book = book;

	}

	public String getVolume() {

		return volume;

	}

	public void setVolume(String volume) {

		this.volume = volume;

	}

	public String getVwap() {

		return vwap;

	}

	public void setVwap(String vwap) {

		this.vwap = vwap;

	}

	public String getLow() {

		return low;

	}

	public void setLow(String low) {

		this.low = low;

	}

	public String getAsk() {

		return ask;

	}

	public void setAsk(String ask) {

		this.ask = ask;

	}

	public String getBid() {

		return bid;

	}

	public void setBid(String bid) {

		this.bid = bid;

	}

	@Override

	public String toString() {

		return "Payload [high=" + high + ", last=" + last + ", createdAt=" + createdAt + ", book=" + book + ", volume="

				+ volume + ", vwap=" + vwap + ", low=" + low + ", ask=" + ask + ", bid=" + bid + "]";

	}

}

Ticker.java


import com.fasterxml.jackson.annotation.JsonProperty;

/**

 * @author raidentrance

 *

 */

public class Ticker {

	@JsonProperty("success")

	private boolean success;

	@JsonProperty("payload")

	private Payload payload;

	public boolean isSuccess() {

		return success;

	}

	public void setSuccess(boolean success) {

		this.success = success;

	}

	public Payload getPayload() {

		return payload;

	}

	public void setPayload(Payload payload) {

		this.payload = payload;

	}

	@Override

	public String toString() {

		return "Ticker [success=" + success + ", payload=" + payload + "]";

	}

}

TickerList.java


import java.util.List;

import com.fasterxml.jackson.annotation.JsonProperty;

/**

 * @author maagapi

 *

 */

public class TickerList {

	@JsonProperty("success")

	private boolean success;

	@JsonProperty("payload")

	private List payload;

	public boolean isSuccess() {

		return success;

	}

	public void setSuccess(boolean success) {

		this.success = success;

	}

	public List getPayload() {

		return payload;

	}

	public void setPayload(List payload) {

		this.payload = payload;

	}

	@Override

	public String toString() {

		return "TickerList [success=" + success + ", payload=" + payload + "]";

	}

}

Once we defined the model, we can call the api’s.

Step 6 : Creating the RestClient

The last step will be create the RestClient this class will be the responsible to join all the pieces:


import java.io.IOException;

import java.io.StringReader;

import java.util.concurrent.TimeUnit;

import javax.ws.rs.core.MediaType;

import javax.ws.rs.core.Response;

import javax.ws.rs.core.Response.Status;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.type.TypeReference;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.raidentrance.rest.commons.AbstractClient;

import com.raidentrance.rest.endpoints.ApplicationEndpoints;

import com.raidentrance.rest.error.exception.ServiceException;

import com.raidentrance.rest.error.model.ErrorCode;

import com.raidentrance.rest.error.model.ErrorMessage;

import com.raidentrance.rest.model.Ticker;

import com.raidentrance.rest.model.TickerList;

/**

 * @author raidentrance

 *

 */

public class RestClient extends AbstractClient {

	private static final Logger log = LoggerFactory.getLogger(RestClient.class);

	public RestClient(String url, String contextPath) {

		super(url, contextPath);

	}

	public RestClient(String url, String contextPath, int maxRetries, int delay, TimeUnit unit) {

		super(url, contextPath, maxRetries, delay, unit);

	}

	public TickerList getTickers() throws Exception {

		return parseResponse(get(ApplicationEndpoints.getTickers(), MediaType.APPLICATION_JSON),

				new TypeReference() {

				});

	}

	public Ticker getTickerByBook(String book) throws Exception {

		return parseResponse(get(ApplicationEndpoints.getTickerByBook(book), MediaType.APPLICATION_JSON),

				new TypeReference() {

				});

	}

	@Override

	protected  T parseResponse(Response response, TypeReference entityType) throws ServiceException {

		int status = response.getStatus();

		log.info("Status {}", status);

		if (response.getStatusInfo().getFamily() == Status.Family.SUCCESSFUL) {

			try {

				return new ObjectMapper().readValue(new StringReader(response.readEntity(String.class)), entityType);

			} catch (IOException e) {

				throw new ServiceException(

						new ErrorMessage(false, new ErrorCode("1000", "Request couldn't be processed")));

			}

		} else {

			throw new ServiceException(response.readEntity(ErrorMessage.class));

		}

	}

}

As you can see now just with one line of code we make an http request, handle the retries and parse the response.

Step 7 : Test it

In other posts we will show how to write unit tests for this kind of components, now lets just create a main method to test it:


public static void main(String[] args) throws Exception {

		RestClient client = new RestClient("https://api.bitso.com", "/v3", 3, 3, TimeUnit.SECONDS);

		TickerList tickers = client.getTickers();

		log.info("Getting tickers ");

		for (Payload payload : tickers.getPayload()) {

			log.info(payload.toString());

		}

		log.info("Getting ripple ticker");

		Ticker ripple = client.getTickerByBook("xrp_mxn");

		log.info(ripple.toString());

		log.info("Not existing ticker");

		Ticker alex = client.getTickerByBook("alex");

		log.info(alex.toString());

	}

If we execute the code we will see the following output (It can be different according with the ripple price :P):


mar 12, 2018 5:09:02 PM com.raidentrance.rest.commons.AbstractClient assembleEndpoint

INFORMACIÓN: Calling endpoint https://api.bitso.com/v3/ticker

[main] INFO com.raidentrance.rest.RestClient - Status 200

[main] INFO com.raidentrance.rest.RestClient - Getting tickers

[main] INFO com.raidentrance.rest.RestClient - Payload [high=187000.00, last=174233.33, createdAt=2018-03-12T23:09:02+00:00, book=btc_mxn, volume=244.45021807, vwap=175921.68443739, low=167000.00, ask=174216.10, bid=173560.15]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=13957.02, last=13388.86, createdAt=2018-03-12T23:09:02+00:00, book=eth_mxn, volume=340.00979171, vwap=13198.66244190, low=13000.00, ask=13388.86, bid=13052.63]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=0.00008704, last=0.00008600, createdAt=2018-03-12T23:09:02+00:00, book=xrp_btc, volume=44561.33425456, vwap=0.00008500, low=0.00008312, ask=0.00008678, bid=0.00008601]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=15.75, last=14.92, createdAt=2018-03-12T23:09:02+00:00, book=xrp_mxn, volume=688462.85426546, vwap=15.12242330, low=14.75, ask=15.08, bid=14.92]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=0.07688000, last=0.07550000, createdAt=2018-03-12T23:09:02+00:00, book=eth_btc, volume=56.85901332, vwap=0.07569318, low=0.07430000, ask=0.07641999, bid=0.07551000]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=0.11879867, last=0.11421000, createdAt=2018-03-12T23:09:02+00:00, book=bch_btc, volume=34.25655963, vwap=0.11580909, low=0.11336268, ask=0.11589999, bid=0.11430000]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=0.02011990, last=0.01965100, createdAt=2018-03-12T23:09:02+00:00, book=ltc_btc, volume=151.10013187, vwap=0.01966700, low=0.01942300, ask=0.01990000, bid=0.01965010]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=3700.00, last=3404.61, createdAt=2018-03-12T23:09:02+00:00, book=ltc_mxn, volume=2501.73845947, vwap=3466.53149080, low=3392.80, ask=3465.97, bid=3404.71]

[main] INFO com.raidentrance.rest.RestClient - Getting ripple ticker

mar 12, 2018 5:09:03 PM com.raidentrance.rest.commons.AbstractClient assembleEndpoint

INFORMACIÓN: Calling endpoint https://api.bitso.com/v3/ticker?book=xrp_mxn

[main] INFO com.raidentrance.rest.RestClient - Status 200

[main] INFO com.raidentrance.rest.RestClient - Ticker [success=true, payload=Payload [high=15.75, last=14.92, createdAt=2018-03-12T23:09:03+00:00, book=xrp_mxn, volume=688462.85426546, vwap=15.12242330, low=14.75, ask=15.08, bid=14.92]]

[main] INFO com.raidentrance.rest.RestClient - Not existing ticker

mar 12, 2018 5:09:03 PM com.raidentrance.rest.commons.AbstractClient assembleEndpoint

INFORMACIÓN: Calling endpoint https://api.bitso.com/v3/ticker?book=alex

[main] INFO com.raidentrance.rest.RestClient - Status 400

Exception in thread "main" com.raidentrance.rest.error.exception.ServiceException

	at com.raidentrance.rest.RestClient.parseResponse(RestClient.java:68)

	at com.raidentrance.rest.RestClient.getTickerByBook(RestClient.java:51)

	at com.raidentrance.rest.RestClient.main(RestClient.java:83)

As you can see we are testing the following cases:

  • Get the price of all the crypto currencies
  • Get the prices of one crypto currency
  • Ask for a price that doesn’t exist and throw an exception with the message.

You can find all the code of this post in the following repository https://github.com/raidentrance/rest-jersey-client.

If you get an SunCertPathBuilderException: unable to find valid certification path to requested target remember that you have to install the certificate to do https requests you can see a guide about how to do it here.

If you like our posts follow us in our social networks https://twitter.com/geeks_mx y https://www.facebook.com/geeksJavaMexico/.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contact: raidentrance@gmail.com

Crea web services REST utilizando Vert.x


Si escuchaste en algún lugar que Vert.x es un gran framework y quieres empezar a utilizarlo para tus aplicaciones, este post es la guía perfecta para eso.

Paso 1 Configuración

Veamos el archivo pom.xml.

La configuración anterior agrega la dependencia de Vertx al proyecto y define la versión 1.8 de java como la versión a utilizar en el proyecto(Vertx solo funciona con Java 1.8 o superior).

Paso 2 Creando el modelo de la aplicación

En este ejemplo mostraremos como buscar uno, buscar todos, crear y borrar un objeto User de una lista, el primer paso será crear la clase User.


import java.io.Serializable;

/**
 * @author raidentrance
 *
 */
public class User implements Serializable {
	private String username;
	private String password;

	private static final long serialVersionUID = -8672858398542565036L;

	public User() {
	}

	public User(String username, String password) {
		this.username = username;
		this.password = password;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}

La clase User contendrá los atributos username y password.

Paso 3 Creando un UserService

Una vez que se creó el modelo el siguiente paso será crear una clase llamada UserService, la cuál será la responsable de ejecutar la lógica de negocio de nuestros endpoints, en este ejemplo no utilizaremos una base de datos, solo tendremos algunos usuarios en memoria, con los siguientes métodos:

  • List findAll(): Devuelve todos los usuarios en la lista
  • Optional findByUsername(String username): Devuelve a un objeto usuario basado en el username

  • void create(User user): Agrega un usuario nuevo a la lista

UserService.java


import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import com.raidentrance.model.User;

/**
 * @author raidentrance
 *
 */
public class UserService {
	private List users = new ArrayList();

	public UserService() {
		users.add(new User("raidentrance", "superSecret"));
		users.add(new User("root", "superExtraSecret"));
		users.add(new User("dummy", "notSecret"));
	}

	public List findAll() {
		return users;
	}

	public Optional findByUsername(String username) {
		for (User user : users) {
			if (user.getUsername().equals(username)) {
				return Optional.of(user);
			}
		}
		return Optional.empty();
	}

	public void create(User user) {
		users.add(user);
	}
}

Hasta este punto no hemos utilizado para nada Vertx, el siguiente paso será exponer la clase UserService como una api REST.

Paso 4 Exponiendo las apis vía REST

El siguiente paso será exponer los métodos mencionados anteriormente como api’s REST:

UserResource.java


import java.util.Optional;

import com.raidentrance.model.User;
import com.raidentrance.services.UserService;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.json.Json;
import io.vertx.ext.web.Router;

/**
 * @author raidentrance
 *
 */
public class UserResource extends AbstractVerticle {

	private UserService service = new UserService();

	@Override
	public void start(Future fut) {
		Router router = Router.router(vertx);
		router.get("/users")
				.handler(routingContext -> routingContext.response()
						.putHeader("content-type", "application/json; charset=utf-8")
						.end(Json.encodePrettily(service.findAll())));

		router.get("/users/:username").handler(routingContext -> {
			Optional result = service.findByUsername(routingContext.request().getParam("username"));
			if (result.isPresent()) {
				routingContext.response().setStatusCode(200)
						.putHeader("content-type", "application/json; charset=utf-8").end(Json.encode(result.get()));
			} else {
				routingContext.response().setStatusCode(404);
			}
		});
		router.post("/users").handler(routingContext -> {
			User user = Json.decodeValue(routingContext.getBodyAsString(), User.class);
			service.create(user);
			routingContext.response().setStatusCode(201).putHeader("content-type", "application/json; charset=utf-8")
					.end(Json.encodePrettily(user));
		});
		vertx.createHttpServer().requestHandler(router::accept).listen(config().getInteger("http.port", 8080),
				result -> {
					if (result.succeeded()) {
						fut.complete();
					} else {
						fut.fail(result.cause());
					}
				});
	}

}

Como se puede ver se creará un objeto router basado en el objeto vertx, en el cuál se agregarán las siguientes rutas:

  • router.get(/users) : Devuelve todos los objetos User en la lista

  • router.get(/users/:username): Devuelve al usuario cuyo username sea pasado como parámetro en caso de no existir alguno devuelve un status http 404.

  • router.post(/users):Add a new user to the list

Al final se creará un servidor http y se iniciará en el puerto definido.

Paso 5 Ejecutando la aplicación

El último paso será iniciar nuestra aplicación, para esto crearemos un objeto Vertx y desplegaremos el Verticle creado.


import com.raidentrance.resources.UserResource;

import io.vertx.core.Vertx;

/**
 * @author raidentrance
 *
 */
public class VertxApplication {
	private VertxApplication() {
	}

	public static void main(String[] args) {
		Vertx vertx = Vertx.vertx();
		vertx.deployVerticle(UserResource.class.getName());
	}
}

Una vez que se ejecuta la clase VertxApplication, el siguiente paso será validar las apis utilizando algún cliente HTTP accediendo a las siguientes url’s:

[
   {
      username:"raidentrance",
      password:"superSecret"
   },
   {
      username:"root",
      password:"superExtraSecret"
   },
   {
      username:"dummy",
      password:"notSecret"
   }
]
{
     username: "raidentrance",
     password: "superSecret"
}
{
     username: "raidentrance",
     password: "superSecret"
}

Puedes encontrar el código completo en el siguiente enlace https://github.com/raidentrance/vertx-example.

Si te gusta el contenido y quieres enterarte cuando realicemos un post nuevo síguenos en nuestras redes sociales https://twitter.com/geeks_mx y https://www.facebook.com/geeksJavaMexico/.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Utiliza multiples bases de datos con Spring boot y Spring JDBC


Crear micro servicios que obtengan información de una base de datos es muy común, pero muchas veces la información que queremos obtener se encuentra en más de una base de datos, en este post explicaremos como escribir una aplicación REST spring boot que obtenga información de más de una base de datos.

Paso 1 Preparando las bases de datos

El primer paso será crear las dos bases de datos a las que conectaremos nuestra aplicación, en este ejemplo utilizaremos el motor de bases de datos MySQL, a continuación se presentan los scripts a utilizar para cada una:

  • Base de datos 1
create database database1;
use database1;
CREATE TABLE USER(
USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
USERNAME VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(100) NOT NULL
);
INSERT INTO USER (USERNAME,PASSWORD)VALUES('raidentrance','superSecret');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('john','smith');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('juan','hola123');
  • Base de datos 2
create database database2;
use database2;
CREATE TABLE USER_LEGACY(
USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
USERNAME VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(100) NOT NULL
);
INSERT INTO USER_LEGACY (USERNAME,PASSWORD)VALUES('rocky','Adrianna');
INSERT INTO USER_LEGACY (USERNAME,PASSWORD)VALUES('ivanDrago','golpesFuertes');
INSERT INTO USER_LEGACY (USERNAME,PASSWORD)VALUES('apolloCreed','theBest');

Con esto tendremos dos bases de datos, las cuales contienen tablas diferentes y datos diferentes.

Paso 2 Crear aplicación Spring boot

En este ejemplo tomaremos como base el proyecto Spring Boot + REST Jersey Parte 1 que explica como configurar un proyecto básico de spring boot y le agregaremos las siguientes dependencias:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

Como se puede ver haremos la conexión utilizando Spring JDBC e incluiremos el driver de mysql para conectarnos a las bases de datos.

Paso 3 Crear clase para representar a los usuarios

Como se puede ver, aunque son dos bases de datos diferentes y tablas diferentes ambas comparten las mismas columnas y tipos de datos, por esto solo crearemos una clase User para representar la tabla USER que se encuentra en la base de datos database1 y la tabla USER_LEGACY que se encuentra en la base de datos database2.


/**
 *
 * @author raidentrance
 *
 */
public class User {
	private Integer id;
	private String user;
	private String password;

	public User() {
	}

	public User(Integer id, String user, String password) {
		super();
		this.id = id;
		this.user = user;
		this.password = password;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getUser() {
		return user;
	}

	public void setUser(String user) {
		this.user = user;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}

Como se puede ver la clase cuenta con los atributos id, user y password, esto es suficiente para hacer el mapping en las dos tablas.

Paso 4 Agregar la configuración de las bases de datos

El siguiente paso será agregar al archivo application.properties los datos necesarios para conectarse a ambas bases de datos, para esto agregaremos las siguientes líneas:

#Settings for database conneection to database1
spring.datasource.url=jdbc:mysql://localhost:3306/database1
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

#Settings for database conneection to database2
legacy.datasource.url=jdbc:mysql://localhost:3306/database2
legacy.datasource.username=root
legacy.datasource.password=root
legacy.datasource.driver-class-name=com.mysql.jdbc.Driver

Como se puede ver se incluye la información de ambos datasources.

Paso 5 Agregar configuración para ambos datasources

Agregar las líneas al archivo properties no es suficiente, ahora crearemos una clase de configuración que contendrá los dos datasources y jdbcTemplates a utilizar.


import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * @author raidentrance
 *
 */
@Configuration
public class DatabaseConfig {
	@Bean(name = "dsSlave")
	@ConfigurationProperties(prefix = "legacy.datasource")
	public DataSource slaveDataSource() {
		return DataSourceBuilder.create().build();
	}

	@Bean(name = "dsMaster")
	@Primary
	@ConfigurationProperties(prefix = "spring.datasource")
	public DataSource masterDataSource() {
		return DataSourceBuilder.create().build();
	}

	@Bean(name = "jdbcSlave")
	@Autowired
	public JdbcTemplate slaveJdbcTemplate(@Qualifier("dsSlave") DataSource dsSlave) {
		return new JdbcTemplate(dsSlave);
	}

	@Bean(name = "jdbcMaster")
	@Autowired
	public JdbcTemplate masterJdbcTemplate(@Qualifier("dsMaster") DataSource dsMaster) {
		return new JdbcTemplate(dsMaster);
	}
}

En el código anterior se crean 4 objetos 2 de tipo DataSource y 2 de tipo JdbcTemplate :

  • DataSource
    • slaveDataSource : En la anotación @ConfigurationProperties(prefix = “legacy.datasource”) se define la configuración de la base de datos legacy, que en este caso es la database2, como se puede ver es posible asignarle un nombre al bean que se está generando, en este caso es dsSlave.
    • masterDataSource: Este método devolverá el datasource para la base de datos database1 con el nombre dsMaster.
    • slaveJdbcTemplate: Para utilizar Spring JDBC es necesario utilizar un objeto de este tipo, como se puede ver el método recibe un objeto de tipo datasource el cuál es inyectado gracias a la anotación @Autowired, en este caso existen 2 beans de este tipo, por esto es necesario incluir la anotación @Qualifier(“dsSlave”) para indicarle a Spring cuál de los dos datasources va a inyectar. Por último del mismo modo que en el anterior es posible definir un nombre al bean que se generará en este caso es jdbcSlave.
    • masterJdbcTemplate: Este método devolverá el objeto de tipo JdbcTemplate con referencia a el datasource dsMaster con el nombre de jdbcMaster.

Paso 6 Creación de los daos

Como tendremos dos tablas diferentes en dos bases de datos diferentes crearemos dos daos uno apuntando a la tabla user en la base de datos database1 y el otro apuntando a la tabla user_legacy en la base de datos database2.

UserDao.java


import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import com.raidentrance.model.api.User;

/**
 * @author raidentrance
 *
 */
@Component
public class UserDao {

	@Autowired
	@Qualifier("jdbcMaster")
	private JdbcTemplate jdbcTemplate;

	public List<User> findAll() {
		return jdbcTemplate.query("select * from user", new RowMapper<User>() {
			@Override
			public User mapRow(ResultSet rs, int arg1) throws SQLException {
				return new User(rs.getInt("USER_ID"), rs.getString("USERNAME"), rs.getString("PASSWORD"));
			}
		});
	}
}

En este DAO se inyecta la referencia al JDBC template utilizando @Autowired y un @Qualifier(“jdbcMaster”) esto es para determinar cuál de los dos jdbctemplates tiene que inyectar en esta referencia, en este caso es el que contiene el datasource que apunta a la base de datos database1.
UserLegacyDao.java

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import com.raidentrance.model.api.User;

/**
 * @author raidentrance
 *
 */
@Component
public class UserLegacyDao {
	@Autowired
	@Qualifier("jdbcSlave")
	private JdbcTemplate jdbcTemplate;

	public List<User> findAll() {
		return jdbcTemplate.query("select * from user_legacy", new RowMapper<User>() {
			@Override
			public User mapRow(ResultSet rs, int arg1) throws SQLException {
				return new User(rs.getInt("USER_ID"), rs.getString("USERNAME"), rs.getString("PASSWORD"));
			}
		});
	}
}

En este DAO se inyecta la referencia al JDBC template utilizando @Autowired y un @Qualifier(“jdbcSlave”) esto es para determinar cuál de los dos jdbctemplates tiene que inyectar en esta referencia, en este caso es el que contiene el datasource que apunta a la base de datos database2.

Paso 7 Creando un servicio común

Una vez que tenemos los dos DAOs en nuestra aplicación, el siguiente paso será crear un servicio común, en este se inyectarán ambos daos:


import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.raidentrance.dao.UserDao;
import com.raidentrance.dao.UserLegacyDao;
import com.raidentrance.model.api.User;

/**
 * @author raidentrance
 *
 */
@Service
public class UserService {
	@Autowired
	private UserDao userDao;

	@Autowired
	private UserLegacyDao userLegacyDao;

	public List<User> getUsers() {
		return userDao.findAll();
	}

	public List<User> getLegacyUsers() {
		return userLegacyDao.findAll();
	}

}

Como se puede ver en esta clase se inyectan ambos daos, ya no es necesario utilizar algún qualifier porque son beans diferentes.

Paso 8 Exponiendo la información vía REST

El último paso será exponer esta información en servicios REST, para esto crearemos la siguiente clase:


import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.raidentrance.service.UserService;

/**
 * @author raidentrance
 *
 */

@Component
@Path("/users")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {

	@Autowired
	private UserService userService;

	private static final Logger log = LoggerFactory.getLogger(UserResource.class);

	@GET
	public Response getUsers() {
		log.info("Getting user");
		return Response.ok(userService.getUsers()).build();
	}

	@GET
	@Path("/legacy")
	public Response getLegacyUsers() {
		log.info("Getting user");
		return Response.ok(userService.getLegacyUsers()).build();
	}
}

En el código anterior se puede ver que se exponen 2 endpoints uno es /users que devolverá a todos los usuarios que se encuentran en la base de datos database1, y el otro es /users/legacy que devuelve a todos los usuarios que se encuentran en la base de datos database2.

Paso 9 Ejecutando los endpoints

Para ejecutar la aplicación solo ejecutaremos la clase SprinBootSampleApplication que es la que contiene el main de nuestra aplicación e invocaremos los siguientes endpoints para ver sus salidas:

  • GET /users

Salida:

[
   {
      "id": 1,
      "user": "raidentrance",
      "password": "superSecret"
   },
   {
      "id": 2,
      "user": "john",
      "password": "smith"
   },
   {
      "id": 3,
      "user": "juan",
      "password": "hola123"
   }
]
  • GET /users/legacy

Salida

[
   {
      "id": 1,
      "user": "rocky",
      "password": "Adrianna"
   },
   {
      "id": 2,
      "user": "ivanDrago",
      "password": "golpesFuertes"
   },
   {
      "id": 3,
      "user": "apolloCreed",
      "password": "theBest"
   }
]

Puedes encontrar el código completo en el siguiente enlace https://github.com/raidentrance/spring-boot-example/tree/part11-multipledb.

Si te gusta el contenido y quieres enterarte cuando realicemos un post nuevo síguenos en nuestras redes sociales https://twitter.com/geeks_mx y https://www.facebook.com/geeksJavaMexico/.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com