Quantcast
Channel: Adictos al trabajo
Viewing all 990 articles
Browse latest View live

Introducción a StencilJS y demo de integración con Angular

$
0
0

En este tutorial vamos a hablar de StencilJS una tecnología que permite crear Web Components nativos cumpliendo 100% con el estándar actual, funcionando de forma autónoma o integrándose al 100% con otras tecnologías web como Vue, React, y como vamos a demostrar con Angular. Para mí StencilJS es la tecnología perfecta para crear verdaderas librerías de Web Components reutilizables universalmente.

Índice de contenidos


1. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Slimbook Pro 13.3″ (Intel Core i7, 32GB RAM)
  • Sistema Operativo: LUbuntu 16.04
  • Visual Studio Code 1.16.1
  • StencilJS next
  • @angular/cli 1.4.2
  • @angular 4.4.1

2. Introducción

Si me has seguido mínimamente por Twitter y en las charlas que he dado sabrás de mi obsesión por separar claramente el trabajo de los desarrolladores del de los arquitectos/diseñadores/UX a la hora de implementar una aplicación web:

Y es que hasta ahora la mejor solución para conseguir esto con Angular era hacer una librería de componentes reutilizables con Polymer, el “problema” de esta aproximación es que estas dos tecnologías casan perfectamente entre ellas pero no así con otras librerías de Web Components como Vue y React.

Digo hasta ahora porque el equipo de Ionic, famosos por el ecosistema de desarrollo de aplicaciones híbridas, nos ofrece StencilJS. Dirás, !venga va otro framework que se quiere comer el pastel y otro que hay que aprender! Pues te sorprenderás cuando sepas que esta tecnología no es ningún framework, ni tan siquiera una librería, es un compilador que genera Web Components 100% compatibles con el estándar, 100% compatibles con cualquier tecnología de las antes mencionadas y que gestiona de forma automática los polyfills que según el navegador que estemos utilizando necesite. ¡Cómo te quedas! :-O

Esto quiere decir que ahora sí podemos tener una librería de web components nativos que vamos a poder reutilizar sea cual sea (o no sea ninguno) el framework/librería que estemos utilizando, sin preocuparte por el navegador donde se ejecuten, haciendo uso de TypeScript con una sintaxis muy parecida a Angular y mezcla de React que resulta muy intuitiva, como veremos en el ejemplo.

Por ponerle una pega actualmente no tiene un catálogo de componentes ya desarrollados como el que cuenta Polymer, aunque en poco tiempo seguro que esto dejará de ser un problema y el equipo de Ionic ya ha anunciado que la versión 4 de Ionic tendrá todos los componentes actuales implementados con StencilJS.


3. Vamos al lío

Para nuestro primer componente con StencilJS vamos a implementar el componente “tnt-hello” que va a recibir un nombre para mostrar por pantalla y cuando se pulse sobre él va a mostrar una alerta mostrando el mismo nombre.

Un buen punto de partida es la documentación oficial

Esta documentación nos ofrece un proyecto “starter” para tener ya todo configurado que podemos utilizar ejecutando:

$> git clone https://github.com/ionic-team/stencil-starter.git stencil-lib

A continuación entramos dentro de la carpeta generada:

$> cd stencil-lib

Eliminamos el remote origin original, dado que no queremos subir nada a este repositorio:

$> git remote rm origin

Instalamos todas las dependencias necesarias:

$> yarn o npm install

Y directamente podemos ejecutar el proyecto de prueba con:

$> npm run start

En la URL http://localhost:3333 podremos ver algo parecido a esto:

Ya tenemos corriendo el proyecto de ejemplo que viene en el “starter”, y además con “live-reloading”, es el momento de crear nuestro componente.

Para ello podemos abrir el proyecto con nuestro editor de textos favorito, en mi caso Visual Studio Code donde si buscas en extensiones por la palabra “StencilJS” ya te salen dos muy útiles: un color syntax y un conjunto de snippets que nos ahorran escribir código. Dado que estamos trabajando con TypeScript, aconsejo incluir la extensión TSLint y Auto Imports.

Se agradece que el proyecto “starter” se mantenga simple y no cuente con muchos ficheros, uno de los más importantes es por supuesto el package.json que mantiene las dependencias y los scripts necesarios para la gestión de la configuración del proyecto y otro menos común, stencil.config.js que contiene información relativa a la forma de distribuir los componentes.

Los componentes tenemos que situarlos dentro de la carpeta “src/components”, aquí vamos a crear una nueva carpeta con el nombre de nuestro componente (“tnt-hello”) y dentro vamos a crear dos ficheros: el primero tnt-hello.scss para darle “estilo” al componente, con el siguiente contenido:

tnt-hello {
    color: blue;
}

y el segundo tnt-hello.tsx que contendrá la lógica y el contenido visual, con el siguiente contenido:

import { Component, Prop, Event, EventEmitter } from '@stencil/core';

@Component({
    tag: 'tnt-hello',
    styleUrl: 'tnt-hello.scss'
})
export class TntHello {

    @Prop() name: string;
    @Event() select: EventEmitter;

    onSelect() {
        this.select.emit(this.name);
    }

    render() {
        return (
           <h1 onClick={() => this.onSelect()}>Hello {this.name}</h1>
        );
    }
}

Como ves el componente se define con el decorador @Component con estas dos propiedades:

  • tag: exactamente igual al selector en el componente de Angular; define el nombre de la etiqueta.
  • styleUrl: donde indicamos la localización del fichero que alberga el estilo del componente, que puede ser scss o directamente css.

Además en el componente definimos la propiedad de entrada “name” con el decorador @Prop de tipo string y el evento de salida “select” con el decorador @Event de tipo EventEmitter.

Nota: Esto es muy parecido al @Input y @Output para hacer el data binding en Angular.

Quizá lo que más llame la atención es la forma de definir el contenido HTML del componente, esto es heredado de React y se basa en el Virtual DOM que le confiere mayor rendimiento. Es por ello que hay que definir un método llamado “render” que va devolver el HTML formateado entre paréntesis. Fíjate que no estamos devolviendo ni un string ni un template string es puro HTML.

En el contenido HTML estamos mostrando el texto “Hello” seguido del valor de la propiedad “name” entre etiquetas h1. Fíjate que la interpolación de la variable se hace con un solo {}, en contra del {{}} y que tenemos que hacer uso de this para acceder al valor de la propiedad.

Para manejar el evento click sobre el h1, lo definimos con la palabra “onClick” seguida de la llamada al manejador, en este caso el método onSelect que simplemente hace uso del EventEmitter “select” para a través del método “emit” lanzar fuera del componente el valor de la propiedad “name” sin preocuparse por quién o quiénes estén escuchando ese evento.

Es el momento de hacer uso de nuestro componente en el proyecto y probar su funcionamiento. Para ello editamos el fichero index.html y justo debajo de la etiqueta “my-name” colocamos nuestro componente pasándole valor en el atributo “name”, de esta forma:

<my-name first="Stencil" last="JS"></my-name>
<tnt-hello name="Rubén Aguilera"></tnt-hello>

Además para capturar el evento lanzado cuando pulsemos sobre el componente vamos a hacer uso del método addEventListener que nos proporciona JavaScript, de esta forma:

<script>
    document.querySelector('tnt-hello').addEventListener('select', function (event) {
      alert(event.detail);
    })
</script>

Este sería el resultado con los dos componentes cuando pinchamos en el nuestro:

Como ves esta parte funciona perfectamente y ya tenemos dos componentes listos para usar en producción 🙂

Ahora vamos a ver cómo de bien se integran nuestros componentes de StencilJS con mi framework favorito Angular. Para ello editamos el fichero stencil.config.js donde añadimos nuestro componente al array de “components” y eliminamos la referencia al router, quedando de este modo:

exports.config = {
  bundles: [
    { components: ['my-name', 'tnt-hello'] }
  ]
};

exports.devServer = {
  root: 'www',
  watchGlob: '**/**'
}

Con esta configuración estamos indicando que queremos generar un único bundle con los dos componentes, en caso de querer bundles independientes por cada uno de ellos, lo definiríamos del siguiente modo:

exports.config = {
  bundles: [
    { components: ['my-name'] },
    { components: ['tnt-hello'] }
  ]
};

exports.devServer = {
  root: 'www',
  watchGlob: '**/**'
}

Para generar los bundles deseados de producción solo tenemos que ejecutar:

$> npm run build

Esto nos va a crear la carpeta “build” dentro de “www” que va a contener todos los ficheros necesarios para la distribución en producción de nuestros componentes. Por tanto este contenido es el que tenemos que hacer llegar a los proyectos que quieran hacer uso de ellos. La forma correcta es publicando la librería en algún repositorio npm ya sea público a privado, para este caso vamos a copiar esta carpeta y la vamos a pegar dentro de la carpeta “src/assets” de cualquier proyecto con angular-cli que tengamos, si no tenemos ninguno podemos crearlo ejecutando:

$> ng new ng-app

Ahora editamos el fichero src/index.html del proyecto de Angular, añadimos la referencia al fichero app.js de la carpeta assets/build y hacemos uso del componente tnt-hello fuera del ámbito de Angular, de esta forma:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Autentia Angular University</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <script src="assets/build/app.js"></script>
</head>
<body>
  <tnt-hello name="Funciona fuera de Angular"></tnt-hello>
  <app-root>Loading...></app-root>
</body>
</html>

Para poder hacer uso del componente en Angular, al igual que ocurre cuando hacemos uso de componentes de Polymer, que Angular no entiende porque no tiene registrados, tenemos que editar el fichero app.module y añadir la propiedad “schemas” con el valor CUSTOM_ELEMENTS_SCHEMA:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})

Vamos a incluir el componente dentro del template del componente principal pasándole un texto y recibiendo el texto de vuelta a través del evento. Para ello vamos a editar primero el fichero app.component.ts, dentro del método ngOnInit establemos valor al atributo name y creamos el método manejador del evento select que saltará cuando pinchemos sobre el h1 del componente. El resultado sería este:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  name: string;

  constructor() { }

  ngOnInit() {
    this.name = 'Funciona dentro de Angular';
  }

  onSelect(event) {
    alert(event.detail);
  }

}

Ahora solo nos queda editar el fichero app.component.html para hacer uso del componente en el template quedando de esta forma:

<tnt-hello [name]="name" (select)="onSelect($event)"></tnt-hello>

Nota: Fíjate como hacemos uso del “data binding” de Angular, para las propiedades utilizamos el corchete para que se evalúe la variable entre dobles comillas y para los eventos hacemos uso de los paréntesis para declarar el manejador del evento “select” que simplemente mostrará la alerta. Es importante que como cualquier evento con Angular lo denotemos exactamente con $event sino la información no será transmitida.

Si ejecutas este ejemplo tendrás que ver que en pantalla se muestra el texto de las dos instancias del componente, una dentro del ámbito de Angular y la otra fuera y que solo al pinchar en la de dentro se muestra la alerta con el texto.


4. Conclusiones

Como has podido ver StencilJS cumple con lo prometido, facilidad en la creación de Web Components y al menos empíricamente hemos demostrado que con Angular se integra a la perfección; lo que nos permite poder dividir los proyectos y que los desarrolladores hagan aplicaciones con Angular sin preocuparse del estilo, simplemente añadiendo las etiquetas que los arquitectos, diseñadores y UX les han facilitado para hacerla vistosa, usable y funcional en un tiempo record, donde cada rol se dedica a lo suyo.

Recordad que esta técnica y otras muchas más las encontraréis en la guía práctica de Angular y también ofrecemos cursos in-house y online.

Cualquier duda o sugerencia en la zona de comentarios.

Saludos.


Securizar un API REST utilizando JSON Web Tokens

$
0
0

Índice de contenidos

1. Introducción

En este tutorial veremos cómo securizar un API REST empleando JSON Web Tokens (JWT). Para este tutorial utilizaremos un API muy simple donde activaremos Spring Security con la configuración adecuada e implementaremos las clases necesarias para el uso de JWT.

En anteriores tutoriales vimos con securizar un API REST utilizando Node.js y JWT, en esta ocasion utilizaremos Spring Boot ya que nos permite desarrollar rápidamente API REST con el mínimo código y por tanto con el menor número de errores 😉

Disponéis de un tutorial de Natalia Roales en el portal sobre proyectos con Spring Boot y os dejamos el código fuente de este tutorial en github para vuestra consulta.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Mac Book Pro 15″ (2,5 Ghz Intel Core i7, 16 GB DDR3)
  • Sistema Operativo: Mac OS Sierra 10.12.6
  • Entorno de desarrollo: Spring Tool Suite 3.9.0

3. JSON Web Token

JSON Web Token (JWT) es un estándar abierto (RFC 7519) que define un modo compacto y autónomo para transmitir de forma segura la información entre las partes como un objeto JSON. Esta información puede ser verificada y es confiable porque está firmada digitalmente. Los JWT se pueden firmar usando un secreto (con el algoritmo HMAC) o utilizando un par de claves públicas / privadas usando RSA.

3.1. Ventajas de los tokens frente a las cookies

Quizás la mayor ventaja de los tokens sobre las cookies es el hecho de que no tenga estado (stateless). El backend no necesita mantener un registro de los tokens. Cada token es compacto y auto contenido. Contiene todos los datos necesarios para comprobar su validez, así como la información del usuario para las diferentes peticiones.

El único trabajo del servidor consiste en firmar tokens al iniciar la sesión y verificar que los tokens intercambiados sean válidos. Esta característica permite la escalabilidad inmediata ya que las peticiones no dependen unas de otras. De esta forma se pueden tramitar en diferentes servidores de forma autónoma.

Las cookies funcionan bien con dominios y subdominios únicos, pero cuando se trata de administrar cookies en diferentes dominios, puede volverse complejo. El enfoque basado en tokens con Cross Origin Resource Sharing (CORS) habilitado hace trivial exponer las API a diferentes servicios y dominios.

Con un enfoque basado en cookies, simplemente se almacena el ID de sesión en una cookie. JWT por otro lado permiten almacenar cualquier tipo de metadatos, siempre y cuando sea un JSON válido.

Si os preguntáis por el rendimiento, cuando se utiliza la autenticación basada en cookies, el backend tiene que hacer una búsqueda habitualmente una base de datos para recuperar la información del usuario, esto seguramente supera el tiempo que pueda tomar la decodificación de un token. Además, puesto que se pueden almacenar datos adicionales dentro del JWT, por ejemplo, permisos de usuario, puede ahorrarse llamadas adicionales para la búsqueda de esta información.

Recordad que el token habitualmente va firmado pero no va cifrado. En la web de jwt.io disponéis de un depurador que permite consultar y comprobar la validez del mismo.

Después de tanta teoría vamos a lo interesante.

4. Estructura del proyecto

Nuestro proyecto a nivel de Maven está configurado para ser un proyecto Spring Boot de tipo web, que utiliza Spring Security, JPA y HSQLDB (podéis consultar el pom.xml). Veamos las partes más importantes del ejemplo, consta de un controlador para acceso al API REST, el acceso a la capa de datos por JPA y los beans de dominio, muy simple. Para esta demostración utilizaremos la base de datos en memoria HSQLDB (HyperSQL Database).

A nivel de controlador se disponen de métodos para crear un usuario, consultar todos o consultar uno concreto. Para evitar almacenar las password en plano, aplicamos una función de Hash basada en el cifrado Blowfish (BCrypt). Recordad que el uso de MD5 para almacenar las password no se recomienda, utilizad algoritmos más modernos.

UsuarioController.java
package com.autentia.demo.jwt.usuario;

	import java.util.List;

	import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
	import org.springframework.web.bind.annotation.GetMapping;
	import org.springframework.web.bind.annotation.PathVariable;
	import org.springframework.web.bind.annotation.PostMapping;
	import org.springframework.web.bind.annotation.RequestBody;
	import org.springframework.web.bind.annotation.RestController;

	@RestController
	public class UsuarioController {

		private UsuarioRepository usuarioRepository;

		private BCryptPasswordEncoder bCryptPasswordEncoder;

		public UsuarioController(UsuarioRepository usuarioRepository, BCryptPasswordEncoder bCryptPasswordEncoder) {
			this.usuarioRepository = usuarioRepository;
			this.bCryptPasswordEncoder = bCryptPasswordEncoder;
		}

		@PostMapping("/users/")
		public void saveUsuario(@RequestBody Usuario user) {
			user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
			usuarioRepository.save(user);
		}

		@GetMapping("/users/")
		public List<Usuario> getAllUsuarios() {
			return usuarioRepository.findAll();
		}

		@GetMapping("/users/{username}")
		public Usuario getUsuario(@PathVariable String username) {
			return usuarioRepository.findByUsername(username);
		}
	}

5. Spring Security

Gracias a Spring Security podemos incorporar mecanismos potentes para proteger nuestras aplicaciones utilizando una cantidad mínima de código.

En nuestro caso indicamos a Spring Security que proteja todas las URLs excepto la URL de login, así mismo, declaramos las implementaciones que utilizaremos para realizar la autenticación y autorización.

WebSecurity.java
package com.autentia.demo.jwt.security;

	import static com.autentia.demo.jwt.security.Constants.LOGIN_URL;

	import org.springframework.context.annotation.Bean;
	import org.springframework.context.annotation.Configuration;
	import org.springframework.http.HttpMethod;
	import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
	import org.springframework.security.config.annotation.web.builders.HttpSecurity;
	import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
	import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
	import org.springframework.security.config.http.SessionCreationPolicy;
	import org.springframework.security.core.userdetails.UserDetailsService;
	import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
	import org.springframework.web.cors.CorsConfiguration;
	import org.springframework.web.cors.CorsConfigurationSource;
	import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

	@Configuration
	@EnableWebSecurity
	public class WebSecurity extends WebSecurityConfigurerAdapter {

		private UserDetailsService userDetailsService;

		public WebSecurity(UserDetailsService userDetailsService) {
			this.userDetailsService = userDetailsService;
		}

		@Bean
		public BCryptPasswordEncoder bCryptPasswordEncoder() {
			return new BCryptPasswordEncoder();
		}

		@Override
		protected void configure(HttpSecurity httpSecurity) throws Exception {
			/*
			 * 1. Se desactiva el uso de cookies
			 * 2. Se activa la configuración CORS con los valores por defecto
			 * 3. Se desactiva el filtro CSRF
			 * 4. Se indica que el login no requiere autenticación
			 * 5. Se indica que el resto de URLs esten securizadas
			 */
			httpSecurity
				.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
				.cors().and()
				.csrf().disable()
				.authorizeRequests().antMatchers(HttpMethod.POST, LOGIN_URL).permitAll()
				.anyRequest().authenticated().and()
					.addFilter(new JWTAuthenticationFilter(authenticationManager()))
					.addFilter(new JWTAuthorizationFilter(authenticationManager()));
		}

		@Override
		public void configure(AuthenticationManagerBuilder auth) throws Exception {
			// Se define la clase que recupera los usuarios y el algoritmo para procesar las passwords
			auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
		}

		@Bean
		CorsConfigurationSource corsConfigurationSource() {
			final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
			source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
			return source;
		}
	}

Podemos observar que se ajusta la configuración para CORS y se desactiva el filtro de Cross-site request forgery (CSRF). Esto nos permite habilitar el API para cualquier dominio, esta es una de las grandes ventajas del uso de JWT.

6. Implementación

En el siguiente diagrama podéis observar el flujo habitual de una aplicación securizada. Si lo comparamos al flujo seguido en la autenticación vía cookies, es muy similar.

Por fin llegamos a la lógica de negocio a utilizar para autenticar y autorizar nuestras peticiones. Para simplificar este tutorial, se verificará únicamente que exista el usuario y password en nuestra base de datos. Se podría incorporar un modelo más complejo incorporando permisos y roles pero se aleja del objetivo de este tutorial. Si os interesa estos temas, podéis consultar su manejo en la documentación de Spring Security.

6.1. Autenticación

Haciendo uso de las clases proporcionadas por Spring Security, extendemos su comportamiento para reflejar nuestras necesidades. Se verifica que las credencias proporcionadas son válidas y se genera el JWT.

JWTAuthenticationFilter.java
package com.autentia.demo.jwt.security;

	import static com.autentia.demo.jwt.security.Constants.HEADER_AUTHORIZACION_KEY;
	import static com.autentia.demo.jwt.security.Constants.ISSUER_INFO;
	import static com.autentia.demo.jwt.security.Constants.SUPER_SECRET_KEY;
	import static com.autentia.demo.jwt.security.Constants.TOKEN_BEARER_PREFIX;
	import static com.autentia.demo.jwt.security.Constants.TOKEN_EXPIRATION_TIME;

	import java.io.IOException;
	import java.util.ArrayList;
	import java.util.Date;

	import javax.servlet.FilterChain;
	import javax.servlet.ServletException;
	import javax.servlet.http.HttpServletRequest;
	import javax.servlet.http.HttpServletResponse;

	import org.springframework.security.authentication.AuthenticationManager;
	import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
	import org.springframework.security.core.Authentication;
	import org.springframework.security.core.AuthenticationException;
	import org.springframework.security.core.userdetails.User;
	import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

	import com.autentia.demo.jwt.usuario.Usuario;
	import com.fasterxml.jackson.databind.ObjectMapper;

	import io.jsonwebtoken.Jwts;
	import io.jsonwebtoken.SignatureAlgorithm;

	public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

		private AuthenticationManager authenticationManager;

		public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
			this.authenticationManager = authenticationManager;
		}

		@Override
		public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
				throws AuthenticationException {
			try {
				Usuario credenciales = new ObjectMapper().readValue(request.getInputStream(), Usuario.class);

				return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
						credenciales.getUsername(), credenciales.getPassword(), new ArrayList<>()));
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}

		@Override
		protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
				Authentication auth) throws IOException, ServletException {

			String token = Jwts.builder().setIssuedAt(new Date()).setIssuer(ISSUER_INFO)
					.setSubject(((User)auth.getPrincipal()).getUsername())
					.setExpiration(new Date(System.currentTimeMillis() + TOKEN_EXPIRATION_TIME))
					.signWith(SignatureAlgorithm.HS512, SUPER_SECRET_KEY).compact();
			response.addHeader(HEADER_AUTHORIZACION_KEY, TOKEN_BEARER_PREFIX + " " + token);
		}
	}

No hay obligación de devolver el token en la cabecera ni con una clave concreta pero se recomienda seguir los estándares utilizados en la actualidad (RFC 2616, RFC 6750). Lo habitual es devolverlo en la cabecera HTTP utilizando la clave “Authorization” e indicando que el valor es un token “Bearer “ + token

Este token lo deberá conservar vuestro cliente web en su localstorage y remitirlo en las peticiones posteriores que se hagan al API.

6.2. Autorización

La clase responsable de la autorización verifica la cabecera en busca de un token, se verifica el token y se extrae la información del mismo para establecer la identidad del usuario dentro del contexto de seguridad de la aplicación. No se requieren accesos adicionales a BD ya que al estar firmado digitalmente si hay alguna alteración en el token se corrompe.

JWTAuthenticationFilter.java
package com.autentia.demo.jwt.security;

	import static com.autentia.demo.jwt.security.Constants.HEADER_AUTHORIZACION_KEY;
	import static com.autentia.demo.jwt.security.Constants.SUPER_SECRET_KEY;
	import static com.autentia.demo.jwt.security.Constants.TOKEN_BEARER_PREFIX;

	import java.io.IOException;
	import java.util.ArrayList;

	import javax.servlet.FilterChain;
	import javax.servlet.ServletException;
	import javax.servlet.http.HttpServletRequest;
	import javax.servlet.http.HttpServletResponse;

	import org.springframework.security.authentication.AuthenticationManager;
	import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
	import org.springframework.security.core.context.SecurityContextHolder;
	import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

	import io.jsonwebtoken.Jwts;

	public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

		public JWTAuthorizationFilter(AuthenticationManager authManager) {
			super(authManager);
		}

		@Override
		protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
				throws IOException, ServletException {
			String header = req.getHeader(HEADER_AUTHORIZACION_KEY);
			if (header == null || !header.startsWith(TOKEN_BEARER_PREFIX)) {
				chain.doFilter(req, res);
				return;
			}
			UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
			SecurityContextHolder.getContext().setAuthentication(authentication);
			chain.doFilter(req, res);
		}

		private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
			String token = request.getHeader(HEADER_AUTHORIZACION_KEY);
			if (token != null) {
				// Se procesa el token y se recupera el usuario.
				String user = Jwts.parser()
							.setSigningKey(SUPER_SECRET_KEY)
							.parseClaimsJws(token.replace(TOKEN_BEARER_PREFIX, ""))
							.getBody()
							.getSubject();

				if (user != null) {
					return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
				}
				return null;
			}
			return null;
		}
	}

7. Probando…probando

Para las pruebas seguiremos el flujo que vimos previamente, primero, se invoca al login para recuperar el token y posteriormente invocaremos las llamadas al API utilizando el token obtenido.

#Se lanza una petición de login
	curl -i -H "Content-Type: application/json" -X POST -d '{ "username": "admin", "password": "password"}' http://localhost:8080/login

	# Recuperamos los usuarios dados de alta
	curl -H "Authorization: Bearer xxx.yyy.zzz" http://localhost:8080/users/

	# Damos de alta un nuevo usuario
	curl -i -H 'Content-Type: application/json' -H 'Authorization: Bearer xxx.yyy.zzz' -X POST -d '{ "username": "daenerys", "password": "dracarys"}' http://localhost:8080/users/

Una vez arrancamos la aplicación y lanzamos los comandos obtendremos algo parecido a la imagen adjunta. Si intentamos invocar alguna URL sin el token obtendremos un código de error HTTP 403.

8. Conclusiones

La securización de API es un tema muy extenso y hemos visto una pequeña parte en este tutorial. Como habréis podido observar Spring nos facilita la incorporación de JWT a nuestras APIs gracias a su “magia”. En el caso que no dispongáis de esta posibilidad, os animo a consultar los frameworks y librerías existentes ya que cada vez está más extendido el uso de los tokens para diferentes lenguajes.

Espero que os haya servido

9. Referencias

Spring Cloud Feign: declarative REST client

$
0
0

0. Índice de contenidos.

1. Introducción.

Feign es una librería que forma parte del stack de Spring Cloud, desarrollada por Netflix, para generar clientes de servicios REST de forma declarativa.

Al estilo de los repositorios de Spring Data, lo único que debemos hacer es anotar una interfaz con las operaciones de mapeo de los servicios que queremos consumir, parametrizando apropiadamente la entrada y salida de los mismos, para que se correspondan con los verbos y los datos de las operaciones de los servicios que queremos consumir.

Desde el punto de vista del soporte que tenemos a día de hoy con Spring, Feign nos facilitaría el trabajo así como lo hace Spring Data, sin necesidad de “bajar” al nivel de RestTemplate, como Spring Data nos evita trabajar directamente con EntityManager o JdbcTemplate. Y, siguiendo con la comparación, igualmente la implementación se genera al vuelo en tiempo de arranque del contexto de Spring.

De entre sus características podemos encontrar las siguientes:

  • Es altamente configurable, pudiendo usarse diversos encoders y decoders para formatear la información que viaja en cada petición y respuesta.
  • Soporta las anotaciones de JAS-RS y Spring MVC para la declaración de los endPoints de los servicios REST.
  • Se integra perfectamente con el resto de componentes del stack de Spring Cloud:
    • balanceo de carga con Ribbon,
    • circuit breaker con Hystrix, permitiendo definir fallbacks a nivel de cliente,
    • registro de servicios en Eureka,

En este tutorial veremos un ejemplo de uso de la librería, examinando las posibilidades de customización para afrontar cuestiones transversales como son la propagación del contexto de seguridad o la gestión de mensajes/errores, en la invocación entre servicios.

2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.5 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS Sierra 10.12.5
  • Oracle Java: 1.8.0_25
  • Spring Cloud Dalston SR3

3. Ejemplo de consumo de servicios.

Vamos a hacer una prueba muy sencilla consumiendo un servicio fake externo expuesto en https://jsonplaceholder.typicode.com, como podría ser el servicio de posts que devuelve este tipo de información:

[{
	"userId": 1,
	"id": 1,
	"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
	"body": "quia et suscipit suscipit recusandae consequuntur expedita et cum reprehenderit molestiae ut ut quas totam nostrum rerum est autem sunt rem eveniet architecto"
}, {
	"userId": 1,
	"id": 2,
	"title": "qui est esse",
	"body": "est rerum tempore vitae sequi sint nihil reprehenderit dolor beatae ea dolores neque fugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis qui aperiam non debitis possimus qui neque nisi nulla"
}
...
]

Con este json de respuesta podríamos generar automáticamente un pojo para su mapeo en la web http://www.jsonschema2pojo.org/, aunque por su sencillez y haciendo uso de lombok, bastaría con crear una clase declarando las siguientes propiedades:

package com.sanchezjm.tuto.feign.dto;

import lombok.Data;

@Data
public class Post {


	private Integer userId;

	private Integer id;

	private String title;

	private String body;

}

Una vez hecho esto, tendríamos que crear la interfaz para consumir el servicio, que podría tener un código como el siguiente:

package com.sanchezjm.tuto.feign.clients;

import java.util.List;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.sanchezjm.tuto.feign.feign.dto.Post;

@FeignClient(name="posts", url="https://jsonplaceholder.typicode.com")
public interface PostClient {

    @RequestMapping(method = RequestMethod.GET, value = "/posts")
    List getAll();

}

Por supuesto que para externalizar la url del host podríamos hacer uso de propiedades y definirla con Expression Language de Spring.

@FeignClient(name="posts", url="${externalServer.url}")

Solo quedaría marcar la configuración para habilitar la generación de los clientes de feign (como haríamos con los repositorios de Spring Data) con la siguiente anotación que escaneará a partir del paquete en el que la ubiquemos de forma recursiva en busca de interfaces de clientes para generar los stubs.

@EnableFeignClients

Por último, sin que sirva de predecente, un test de integración para comprobar que podemos recuperar información del servicio:

package com.sanchezjm.tuto.feign;

import java.util.List;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.sanchezjm.tuto.feign.feign.clients.PostClient;
import com.sanchezjm.tuto.feign.feign.dto.Post;


@RunWith(SpringRunner.class)
@SpringBootTest
public class FeignClientsApplicationTests {

	@Autowired
	private PostClient postClient;

	@Test
	public void shouldLoadAllPosts() {
		final List posts = postClient.getAll();
		Assert.assertNotNull(posts);
		Assert.assertFalse(posts.isEmpty());
	}

}

Y en verde!, aunque lo realmente interesante es la integración de los clientes feign con el resto del ecosistema de Spring Cloud y la posibilidad de declarar nuestro cliente como consumidor de otro servicio dentro de la nube, para ello, solo tendríamos que indicar a nivel de cliente el identificador (spring.application.name) en términos de Spring Cloud del microservicio que tiene el endPoint que queremos consumir. En tiempo de despliegue el cliente de feign preguntará al servicio de registro cómo se ha registrado el servicio que queremos consumir y como hacer uso del mismo a través del gateway, de modo tal que para nosotros es totalmente transparente y no tenemos por qué conocer la ubicación física del resto de servicios que queremos consumir en la nube. Como digo, lo único que tenemos que hacer, en la declaración el cliente, es indicar el nombre del microservicio que queremos consumir:

@FeignClient("identity-service")

4. Propagación del contexto de seguridad.

Si estamos pensando en consumir un servicio dentro de nuestra propia nube tendremos que habilitar de alguna manera, la propagación del contexto de seguridad para que el microservicio al que invocamos disponga del mismo contexto de autenticación y autorización del usuario conectado.

Suponiendo que ya existe un filtro de Spring Security a nivel de servicio que recupera de una cabecera el usuario autenticado no tendríamos más que crear un interceptor de feign para propagar dicha cabecera.

package com.sanchezjm.tuto.feign.interceptor;

import org.springframework.security.core.context.SecurityContextHolder;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class SecurityFeignRequestInterceptor implements RequestInterceptor {

    private static final String AUTHENTICATION_HEADER = "my-security-header";

	@Override
    public void apply(RequestTemplate template) {
        propagateAuthorizationHeader(template);
	}

	private void propagateAuthorizationHeader(RequestTemplate template) {
		if (template.headers().containsKey(AUTHENTICATION_HEADER)) {
            log.trace("the authorization {} token has been already set", AUTHENTICATION_HEADER);
        } else {
        	log.trace("setting the authorization token {}", AUTHENTICATION_HEADER);
            template.header(AUTHENTICATION_HEADER, SecurityContextHolder.getContext().getAuthentication().getName());
        }
	}

}

También estaríamos presuponiendo que el contexto de seguridad se asigna a nivel de servicio, no en una capa superior, como podría ser el gateway. En tal caso, si trabajásemos con un token enriquecido en una capa superior bastaría con propagar dicho token.

Haciendo uso de hystrix, para que la propagación sea efectiva, debemos configurarlo para que propague el contexto de seguridad añadiendo la siguiente propiedad:

hystrix.shareSecurityContext=true

Además de la habilitación de hystrix para feign:

feign.hystrix.enabled=true

Al hacer uso de hystrix se lanza un hilo en segundo plano para controlar el timeout y poder lanzar un fallback, sino lo especificamos, en ese hilo no se propagará, por defecto, el contexto de seguridad.

Para configurarlo solo tenemos que declarar el bean en una clase anotada con un @Configuration:

@Bean
    public RequestInterceptor securityFeignRequestInterceptor() {
        return new SecurityFeignRequestInterceptor();
    }

5. Intercepción de mensajes/errores.

Lo normal es que los errores dentro de nuestra nube de servicios tengan una normalización en cuanto a formato y tipología, aunque si consumimos servicios externos quizás nos tengamos que adaptar a otros formatos de mensaje; sobrescribendo el comportamiento por defecto del framework que usemos, para por ejemplo, añadir un identificador único del error o permitir devolver una colección de errores que devuelvan información de validación de un recurso anotado con el soporte de la JSR-303.

Si damos por hecho que nuestros servicios pueden devolver errores con el siguiente formato, teniendo en cuenta que la tipología del error la delegamos en el estado http:

{
	"id": "FINDME_WITH_THIS",
	"items": [{
			"code": "ERROR_NO1",
			"description": "Desc NO1"
		},
		{
			"code": "ERROR_NO2",
			"description": "Desc NO2"
		}
	]
}

Si estamos pensando en disponer de una capa de clientes que consuman servicios que pueden devolver ese tipo de formato de salida, deberíamos pensar también en preparar un componente que parsee esa información de manera transversal.

Para cubrir este requisito basta con implementar un ErrorDecoder como el siguiente, asumiendo que el formato de mensajes podemos mapearlo contra el objeto MessageResource:

package com.sanchezjm.tuto.javassist;

import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.client.RestClientException;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
public class CustomFeignErrorDecoder implements ErrorDecoder {

    private ErrorDecoder delegate = new ErrorDecoder.Default();

    private ObjectMapper mapper = new ObjectMapper();

    @Override
    public Exception decode(String methodKey, Response response) {

        log.trace("An exception has been caught in {}, trying to parse the playload.", methodKey);

        if (response.body() == null) {
            log.error("Failed to parse the playload: Response has no body.");
            return delegate.decode(methodKey, response);
        }

        MessageResource messageResource;
        try {
            messageResource = mapper.readValue(response.body().asInputStream(), MessageResource.class);
        } catch (IOException e) {
            log.trace("Failed to parse the playload. The format of the message does not correspond with the predefined for the architecture.", e);
            return delegate.decode(methodKey, response);
        }

        final HttpStatus status = HttpStatus.valueOf(response.status());

        final String firstMessage =
            messageResource.getMessages().isEmpty() ? status.getReasonPhrase() : messageResource.getMessages().get(0).getMessage();

        log.trace("Throwing proper exception with this message \"{}\" ", firstMessage);

        if (status == HttpStatus.FORBIDDEN || status == HttpStatus.UNAUTHORIZED) {
            return new AccessDeniedException(firstMessage);
        }
        else if (status.is4xxClientError()) {
            return new BusinessException(status.getReasonPhrase(), messageResource);

        }
        else {
            return new RestClientException(firstMessage);
        }

    }
}

Se podría decir que este decoder tiene la lógica inversa al ErrorHandler que ha generado la respuesta de error.

Para que funcione solo tenemos que configurarlo en una clase anotada con un @Configuration:

@Bean
	public CustomFeignErrorDecoder customErrorDecoder(){
		return new CustomFeignErrorDecoder();
	}

Aunque lancemos una BusinessException propia, si tenemos configurado hystrix, las excepciones se encapsularán dentro de una HystrixRuntimeException que podríamos tratar en un errorHandler. Si no queremos que se encapsule podemos marcar nuestra excepción para que implemente ExceptionNotWrappedByHystrix. De una manera u otra, con este decoder podremos tratar la excepción, si para el cliente no se ha configurado un fallback de hystrix.

6. Referencias.

7. Conclusiones.

En el próximo hablaremos de su integración con hystrix, ribbon y, si estáis muy interesados, también con Sleuth.

Un saludo.

Jose

Cómo ahorrar un billón de dólares con tres líneas, o cómo evitar los NullPointerException en Java

$
0
0

En Java son bien conocidos las NullPointerException provocadas cuando accedemos a una referencia de un objeto que es null. A esto Tony Horae lo denominó su error del billón de dólares. En este tutorial veremos cómo podemos combatir este problema en Java.

Shadow of null

1. Introducción

Java es un lenguaje fuertemente tipado orientado a objetos, donde tendremos variables para referencias a estos objetos. Pero ¿qué pasa si una de estas referencias apunta a null e intentamos acceder a dicho objeto?

String s = null;
s.toUpperCase()

Estas dos líneas de código son perfectamente válidas y no darán ningún problema al compilarlas, pero cuando las ejecutemos la JVM lanzará la excepción NullPointerException ya que s no está referenciando a ningún objeto, y por lo tanto es imposible ejecutar el método toUpperCase().

A esta situación Tony Hoare la denominó su error del billón de dólares, y cierto es que lleva desde su invención en 1965 dándonos problemas.

No vamos a entrar en detalle sobre cuáles son los problemas pero básicamente podemos enumerar estos:

  • Empeora la legibilidad del código al tener que hacer las comprobaciones para verificar que las referencias no son null.
  • Rompe la filosofía de Java de ocultar a los desarrolladores los punteros.
  • Es un agujero en el Sistema de Tipos.
  • No tiene significado semántico en particular.
  • Es un mal modelo para representar la ausencia de valor para un lenguaje estrictamente tipado y orientado a objetos.

Podéis encontrar más sobre este problema en la presentación de SlideShare “The billion dollar mistake” de Álvaro García Loaisa.

De hecho tanto ha calado este problema que diferentes lenguajes no permiten esta situación, como por ejemplo Kotlin o Swift, obligando al desarrollador a tratar de forma explícita esta situación.

En Java el compilador no nos da ninguna ayuda per se, así que en este tutorial vamos a ver cómo podemos hacer uso de la JSR 305 para evitar, en tiempo de compilación las referencias de objetos a null.

El código de este tutorial en https://github.com/alejandropg/tutorial-java-jsr305

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15’‘ (2.5 GHz Intel i7, 16GB 1600 Mhz DDR3, 500GB Flash Storage).
  • AMD Radeon R9 M370X
  • Sistema Operativo: macOS Sierra 10.12.6
  • JVM 1.8.0_121 (Oracle Corporation 25.121-b13)
  • Maven 3.5.0

3. Cómo usar la JSR 305 en nuestro proyecto

La JSR 305 lo que hace es definir una serie de anotaciones que podemos poner en nuestro código para que otras herramientas las inspeccionen y nos ayuden a detectar posibles problemas.

Para poder usar las anotaciones de la JSR 305 tendemos que añadir a nuestro proyecto Maven la dependencia:

<dependency>
    <groupId>com.google.code.findbugs</groupId>
    <artifactId>jsr305</artifactId>
    <version>3.0.2</version>
</dependency>

Para el caso de las referencias nulas, en concreto las anotaciones que nos interesan son:

  • @CheckForNull indica que el valor podría ser nulo en circunstancias no conocidas a priori y por lo tanto debemos comprobar el valor antes de usarlo.
  • @Nonnull indica que el valor no puede ser nulo.
  • @Nullable indica que el valor será nulo en circunstancias conocidas a priori. Deberemos leer la documentación para saber cuáles son estas circunstancias.
  • @ParametersAreNonnullByDefault indica que por defecto todos los parámetros de los métodos se deben interpretar como @Nonnull
  • @ParametersAreNullableByDefault indica que por defecto todos los parámetros de los métodos se deben interpretar como @Nullable

Estas anotaciones se pueden usar a nivel de la declaración de un atributo o parámetro o un método. En el caso de usarlas a nivel de método se refieren al tipo de retorno del mismo.

En el caso de @ParametersAreNonnullByDefault y @ParametersAreNullableByDefault se puede usar también a nivel de paquete en el fichero package-info.java. Esto resulta especialmente útil ya que afectará a todos los métodos de todas las clases de ese paquete. Ojo porque esto no es recursivo, es decir no aplica a los sub paquetes, por lo que tendremos que poner este package-info.java en todos los niveles de la jerarquía de paquetes.

Este package-info.java tendrá la siguiente forma:

@ParametersAreNonnullByDefault
package com.autentia.tutorial.jsr305;

import javax.annotation.ParametersAreNonnullByDefault;

Estas son las tres líneas que mencionaba en el título de este tutorial y que nos pueden ayudar a solucionar muchos problemas en tiempo de edición y compilación. Es decir, mucho antes de la fase de ejecución o incluso antes de los tests.

4. Detectando los problemas en tiempo de edición con IntelliJ

IntelliJ IDEA es un IDE de desarrollo para Java que detecta automáticamente el uso de estas anotaciones y a través de distintas Inspections va a detectar los posibles problemas de referencias nulas en tiempo de edición del código. Esto es muy interesante ya que detectaremos los errores en el mismo momento en el que escribimos el código.

Así, si abrís con el IntelliJ el código de ejemplo de este tutorial (tenéis la URL en la introducción) veréis las siguientes cosas:

  • Service.java

En la siguiente imagen vemos como IntelliJ nos avisa de que el parámetro de entrada, que no debe ser null tal como está definido en el fichero package-info.java, está siendo llamado desde otro sitio con una referencia nula como argumento.

Illegal null parameter

Ahora nos avisa que, si parameter no puede ser null esa comprobación que hace el if es redundante.

Redundant condition

También se da cuenta de que estamos intentando devolver un null como valor de retorno del método.

Illegal return null

En la siguiente imagen se aprecian dos cosas, la primera que nos avisa de que s puede ser null y por lo tanto provocar un NullPointerException, y la segunda (aunque no se ve muy bien porque está medio tapado por el mensaje) es que estamos haciendo un return null y no se está quejando tal como lo hacía en el caso anterior. Esto es porque hemos anotado el método con @Nullable sobreescribiendo así el valor por defecto.

Possible null exception
  • Client.java

Detecta como intentamos pasar un argumento con una referencia nula a un método que no lo permite.

Invalid null argument

Nótese como el primer uso de trim() es válido, mientras que en segundo IntelliJ nos avisa que no es correcto. Esto se debe a que el método returnAlwaysNull() está anotado con @Nullable y por lo tanto estamos obligados a comprobar si el valor es nulo si no queremos tener problemas en tiempo de ejecución.

Possible null exception

5. Detectando los problemas en tiempo de compilación con Maven

El poder detectar los posibles errores mientras editamos el código es muy cómodo, pero es bastante débil en el sentido que depende de qué IDE usa cada miembro del equipo y de cómo lo tiene configurado.

Para evitar estos problemas “multi entorno”, lo que vamos a hacer es configurar nuestro proyecto de Maven para se hagan las comprobaciones de nulos durante la compilación. Para ello nos vamos a servir del Procesamiento de Anotaciones que es una característica que está disponible desde Java 5 aunque realmente el API está desde Java 6 (diciembre 2006). Esta característica nos va a permitir “enganchar” a la compilación un procesador de anotaciones que se encargará de hacer las comprobaciones pertinentes.

Para ello en nuestro pom.xml basta con añadir:

...
<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.7.0</version>
            <configuration>
                <annotationProcessors>
                    <annotationProcessor>org.checkerframework.checker.nullness.NullnessChecker</annotationProcessor>
                </annotationProcessors>
            </configuration>
        </plugin>
    </plugins>
</build>
...
<dependencies>
    ...
    <dependency>
        <groupId>org.checkerframework</groupId>
        <artifactId>checker</artifactId>
        <version>2.2.0</version>
        <scope>provided</scope>
    </dependency>
    ...
</dependencies>
...

En el ejemplo estamos usando The Checker Framework que se trata de un proyecto Free Software, con licencia GPL2, que nos proporciona distintas utilidades o “procesadores” para la prevención de bugs en tiempo de compilación. En el ejemplo estamos usando el NullnessChecker pero dispone de otros tantos para prevenir otras situaciones.

Podemos destacar como hemos puesto la dependencia como provided ya que realmente sólo la necesitamos durante el proceso de compilación y no queremos arrastrarla cuando preparemos el “distribuible” del proyecto.

Ahora al compilar en la línea de comandos detectaremos todos los errores:

$ mvn clean install

...

[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /Users/alex/src/sandbox/tutorial-java-jsr305/src/main/java/com/autentia/tutorial/jsr305/Client.java:[8,33] [argument.type.incompatible] incompatible types in argument.
  found   : null
  required: @Initialized @NonNull String
[ERROR] /Users/alex/src/sandbox/tutorial-java-jsr305/src/main/java/com/autentia/tutorial/jsr305/Client.java:[14,29] [dereference.of.nullable] dereference of possibly-null reference s2
[ERROR] /Users/alex/src/sandbox/tutorial-java-jsr305/src/main/java/com/autentia/tutorial/jsr305/Service.java:[15,16] [return.type.incompatible] incompatible types in return.
  found   : null
  required: @Initialized @NonNull String
[ERROR] /Users/alex/src/sandbox/tutorial-java-jsr305/src/main/java/com/autentia/tutorial/jsr305/Service.java:[21,9] [dereference.of.nullable] dereference of possibly-null reference s
[INFO] 4 errors
[INFO] -------------------------------------------------------------

...

6. Conclusiones

En determinados casos el uso de nulos en nuestro código puede ser beneficioso, pero por lo general debemos evitarlos.

Con este tutorial hemos visto cómo podemos configurar nuestro proyecto para añadir restricciones en tiempo de edición y compilación al uso de referencias nulas, de forma que conseguimos prevenir de forma temprana errores que sólo veríamos en tiempo de ejecución.

Además el método visto aquí es flexible en el sentido de que, en los casos que nos interese, podemos usar las anotaciones para especificar donde sí queremos usar nulos, de forma que no perdemos potencia y sí ganamos control.

Desde luego mi recomendación sería que, independientemente de si usáis este tutorial o no, huyáis de los nulos como de la peste.

7. Sobre el autor

Alejandro Pérez García (@alejandropgarci)

Ingeniero en Informática (especialidad de Ingeniería del Software) y Certified ScrumMaster

Socio fundador de Autentia Real Business Solutions S.L. – “Soporte a Desarrollo”

Socio fundador de ThE Audience Megaphone System, S.L. – TEAMS – “Todo el potencial de tus grupos de influencia a tu alcance”

Nomad – Despliegue y supervisión sencilla de aplicaciones

$
0
0

En este tutorial vamos a conocer Nomad y ver cómo puede facilitarnos los despliegues y el escalado de aplicaciones.

Índice de contenidos

1. Introducción

Cuando comienzas a desarrollar aplicaciones complejas orientadas a servicios, o incluso con microservicios, el número de artefactos a desplegar se vuelve inmanejable. Si además quieres tener un entorno de alta disponibilidad con varios nodos balanceados ejecutando tu aplicación esto es aún más tedioso. Tanto si quieres desplegar contenedores de Docker, como aplicaciones Java directamente, o apps de otro tipo como Apache Spark; Nomad es lo que necesitas.

Nomad es una pequeña pieza de software que se instala en los servidores que formarán parte de tu clúster. Se compone de dos partes: una servidor (gestiona los despliegues) y otra cliente (aloja los depliegues). Se recomienda tener 3 servidores y tantos clientes como se necesiten por cada zona.

Se declaran los trabajos, se planifican y muestran por pantalla los cambios que se aplicarán y se ejecuta el despliegue distribuido. Puedes ver el progreso en todo momento. Además te supervisarán los nodos y desplegará nuevas instancias en caso de caídas. Permite el uso combinado de una nube pública (tipo AWS o Azure) y una entorno privado, distribuyendo las aplicaciones como si de uno solo se tratara.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.2 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS X El Capitan 10.11.6
  • Entorno de desarrollo: Atom
  • Sistema operativa de las VM: CentOS/7
  • Nomad 0.5.0
  • Consul 0.7.0
  • Vagrant
  • Ansible
  • VirtualBox
  • Nginx

3. Qué hace y qué no hace

Nomad es una utilidad de “HashiCorp” que se instala en servidores y puede tener dos roles. Actuar como servidor para gestionar y supervisar los despliegues; o actuar como cliente para alojar estos despliegues.

Nomad no pretende ser una solución completa para orquestar una red de contenedores de Docker. Al menos no por sí solo.

Nomad no es un servidor de descubrimiento para resolver las ubicaciones de las aplicaciones desplegadas, pero para ello se integra muy bien con “Consul”, que es una herramienta también de “HashiCorp”.

Nomad no es un balanceador de carga ni un proxy para redirigir las peticiones a los diferentes nodos que tienen una aplicación. Aunque para ello se puede utilizar Nginx, Fabio, Traefik, HAProxy, etc. y automatizar su configuración desde los datos de consul utilizando consul-template.

Nomad no dispone de una gestión de secretos pero para ello se integra muy bien con “Vault”, que es una herramienta también de “HashiCorp”.

4. Instalación y arquitectura

Vamos a automatizar la instalación de nomad utilizando Vagrant (creación de máquinas virtuales) y Ansible (instalación y configuración de aplicaciones). Si no conocéis estas herramientas podéis ver un tutorial aquí.

Podéis clonaros el siguiente repositorio para seguir el tutorial: https://github.com/miyoda/nomad-consul-nginx

Como podréis ver en el fichero “inventory” vamos a crear 6 máquinas de las cuales 3 serán servidores y 3 clientes de nomad. Con ips desde 192.168.10.11 hasta 16.

[server]
cluster1 ansible_host=192.168.10.11 ansible_ssh_host=192.168.10.11 consul_bootstrap=true
cluster2 ansible_host=192.168.10.12 ansible_ssh_host=192.168.10.12
cluster3 ansible_host=192.168.10.13 ansible_ssh_host=192.168.10.13

[client]
cluster4 ansible_host=192.168.10.14 ansible_ssh_host=192.168.10.14
cluster5 ansible_host=192.168.10.15 ansible_ssh_host=192.168.10.15
cluster6 ansible_host=192.168.10.16 ansible_ssh_host=192.168.10.16

Si ejecutamos desde línea de comando “vagrant up” se crearán automáticamente las máquinas tal cual se define en el fichero “Vagrantfile”.

$ vagrant up
	Bringing machine 'cluster1' up with 'virtualbox' provider...
	Bringing machine 'cluster2' up with 'virtualbox' provider...
	Bringing machine 'cluster3' up with 'virtualbox' provider...
	Bringing machine 'cluster4' up with 'virtualbox' provider...
	Bringing machine 'cluster5' up with 'virtualbox' provider...
	Bringing machine 'cluster6' up with 'virtualbox' provider...
	......

Ya tendremos las máquinas creadas en nuestro VirtualBox


Además se ejecutarán los siguientes roles de ansible (en función de la maquina) como se define en el fichero “nomad.yml”:

- hosts: client
	  become: yes
	  roles:
	    - ansible-role-docker
	    - ansible-role-java

	- hosts: all
	  become: yes
	  roles:
	    - ansible-role-consul
	    - ansible-role-nomad

	- hosts: server
	  become: yes
	  roles:
	    - ansible-role-proxy

Los roles “ansible-role-docker” y “ansible-role-java” son para que los servidores que sean clientes nomad que ejecutarán las aplicaciones dispongan de un entorno de ejecución Docker y Java respectivamente. Si, por ejemplo, un nodo no tiene Java instalado y das la orden de ejecutar un Job de Java, el nodo en cuestión no será seleccionado por Nomad para desplegar la aplicación por falta de drivers.

El role “ansible-role-consul” se encarga de la instalación de Consul en todos los nodos. Consul es un servicio de autodescubrimiento que nos será útil para que Nomad pueda descubrir al resto de nodos y para poder gestionar/almacenar dónde está desplegado cada aplicación/artefacto.

El role “ansible-role-nomad” se encarga de la instalación de Nomad en todos los nodos. Tres de ellos lo usaran como servidor y otros tres como cliente.

El role “ansible-role-proxy” se encarga de la instalación de nginx en los nodos servidores. Este role podría ser instalado en otro servidor independiente. La función de nginx aquí es servir de balanceador para las aplicaciones utilizando Consul para saber a que servidor redirigir cada petición en función de donde la haya desplegado Nomad. Configura también un tarea de consul-template para que la configuración de nginx cambie automáticamente si se despliega en nuevos nodos o desaparece alguno.


El flujo de comunicaciones y despliegue del sistema es, como se ve en la imagen, el siguiente:

  • 1. Nomad despliega una aplicación en uno o varios de sus nodos.
  • 2. Nomad registra en Consul los nodos que tienen aplicación desplegada.
  • 3. Nginx se autoconfigura con los datos de Consul mediante el proceso de consul-template.
  • 4. Las peticiones entrarán a “nginx” (cualquiera de los 3 nodos servidores que lo tienen desplegado).
  • 5. Nginx redirige la petición a uno de los nodos de Nomad que tienen la aplicación solicitada en función de la u Url de entrada.

5. Uso

Una vez finalice la creación de los servidores, lo cual puede llevar unos minutos, ya podemos conectarnos a ellos. Para conectarnos a una de las máquinas ejecutar “vagrant ssh cluster1” o “vagrant ssh cluster2”, etc.

Consul

Podemos ejecutar comandos para ver el estado del servicio de Consul y sus nodos. Esto se puede ejecutar en cualquier nodo puesto que todos tienen Consul instalado.

$ consul members
	Node      Address             Status  Type    Build  Protocol  DC
	cluster1  192.168.10.11:8301  alive   server  0.7.0  2         europe
	cluster2  192.168.10.12:8301  alive   server  0.7.0  2         europe
	cluster3  192.168.10.13:8301  alive   server  0.7.0  2         europe
	cluster4  192.168.10.14:8301  alive   server  0.7.0  2         europe
	cluster5  192.168.10.15:8301  alive   server  0.7.0  2         europe
	cluster6  192.168.10.16:8301  alive   server  0.7.0  2         europe

	$ consul monitor
	...

	$ consul info
	...

Si todo ha ido bien se habrá desplegado Consul en todos los nodos, se habrá seleccionado un líder, y debería estar accesible su UI desde la url siguiente (de cualquiera de los servidores): http://192.168.10.11:8500/ui/

Nomad

Podemos ejecutar comandos para ver el estado del servicio de Nomad y sus nodos. Esto se puede ejecutar en cualquier nodo puesto que todos tienen nomad instalado; y si no es de tipo servidor se conectará a uno para obtener la información necesaria.

$ nomad status
	No running jobs

	$ nomad server-members
	Name             Address        Port  Status  Leader  Protocol  Build  Datacenter  Region
	cluster1.global  192.168.10.11  4648  alive   false   2         0.5.0  dc1         global
	cluster2.global  192.168.10.12  4648  alive   true    2         0.5.0  dc1         global
	cluster3.global  192.168.10.13  4648  alive   false   2         0.5.0  dc1         global

	$ nomad node-status
	ID        DC   Name      Class   Drain  Status
	cf4a08d8  dc1  cluster6    false  ready
	9f1d65ca  dc1  cluster5    false  ready
	11e9ba9d  dc1  cluster4    false  ready

Como vemos ahora mismo no tenemos ningún job lanzado, y en clúster tiene listos tres nodos servidores con uno de ellos asignado como líder, y otros tres nodos clientes.

Job de ejemplo

Una vez desplegado y verificada la configuración de Consul y Nomad es el momento de comenzar a lanzar ‘Jobs’! Los jobs son las configuraciones que se le pasan a Nomad para que despliegue. Están formados por ‘Tasks’ que representan las aplicaciones/artefactos a desplegar.

Vamos a comenzar creando y ejecutando nuestro primer job de ejemplo con los siguientes comandos:

$ nomad init
	Example job file written to example.nomad

	$ nomad run example.nomad
	==> Monitoring evaluation "26cfc69e"
	    Evaluation triggered by job "example"
	    Allocation "8ba85cef" created: node "171a583b", group "cache"
	    Evaluation status changed: "pending" -> "complete"
	==> Evaluation "26cfc69e" finished with status "complete"

Ahora podremos ver más información sobre el estado de este despliegue utilizando los siguientes comandos:

$ nomad status
	ID       Type     Priority  Status
	example  service  50        running

	$ nomad status example
	ID            = example
	Name          = example
	Submit Date   = 09/27/17 16:14:43 UTC
	Type          = service
	Priority      = 50
	Datacenters   = dc1
	Status        = running
	Periodic      = false
	Parameterized = false

	Summary
	Task Group  Queued  Starting  Running  Failed  Complete  Lost
	cache       0       0         1        0       0         0

	Latest Deployment
	ID          = 11c5cdc8
	Status      = successful
	Description = Deployment completed successfully

	Deployed
	Task Group  Desired  Placed  Healthy  Unhealthy
	cache       1        1       1        0

	Allocations
	ID        Node ID   Task Group  Version  Desired  Status   Created At
	8ba85cef  171a583b  cache       0        run      running  07/25/17 23:14:43 UTC

En la parte final del estado de una ‘task’ se puede ver sus ‘allocations’ que son las instancias de ejecución en un nodo concreto. en este caso en el nodo ‘171a583b’ el cual se puede identificar en el listado obtenido del comando ‘nomad node-status’. Puedes obtener más información sobre los logs de una ‘allocation’ concreta usando el comando:

$ nomad alloc-status 8ba85cef
	ID                  = 8ba85cef
	Eval ID             = 61b0b423
	Name                = example.cache[0]
	Node ID             = 171a583b
	Job ID              = example
	Job Version         = 0
	Client Status       = running
	Client Description  =
	Desired Status      = run
	Desired Description =
	Created At          = 09/27/17 16:14:43 UTC
	Deployment ID       = fa882a5b
	Deployment Health   = healthy

	Task "redis" is "running"
	Task Resources
	CPU    Memory           Disk     IOPS  Addresses
	2/500  6.3 MiB/256 MiB  300 MiB  0     db: 127.0.0.1:30329

	Recent Events:
	Time                   Type      Description
	07/25/17 23:14:53 UTC  Started     Task started by client
	07/25/17 23:14:43 UTC  Driver      Downloading image redis:3.2
	07/25/17 23:14:43 UTC  Task Setup  Building Task Directory
	07/25/17 23:14:43 UTC  Received    Task received by client
Job tipo Docker

En el directorio ‘jobs’ del repositorio tenemos entre otros un ejemplo de job de una app web sobre un docker llamado ‘hello-docker.nomad’. Tenemos el directorio ‘/vagrant/jobs’ en todas las máquinas virtuales con el contenido del directorio ‘jobs’ del proyecto. Vamos a ejecutar este job:

$ nomad run /vagrant/jobs/hello-docker.nomad
	==> Monitoring evaluation "38debb73"
	    Evaluation triggered by job "hello-docker"
	    Allocation "42c9d7c6" created: node "11e9ba9d", group "hello-docker"
	    Allocation "87e451bc" created: node "9f1d65ca", group "hello-docker"
	    Allocation "d284bf40" created: node "cf4a08d8", group "hello-docker"
	    Evaluation status changed: "pending" -> "complete"
	==> Evaluation "38debb73" finished with status "complete"

Esta aplicación está configurada en ese fichero para tener dos instancias. Por tanto debería estar desplegada en dos de los tres nodos clientes y accesible por tanto en dos de las siguientes urls:

http://192.168.10.14/ http://192.168.10.15/ http://192.168.10.16/
$ nomad status hello-docker
	ID          = hello-docker
	Name        = hello-docker
	Type        = service
	Priority    = 50
	Datacenters = dc1
	Status      = running
	Periodic    = false

	Summary
	Task Group  Queued  Starting  Running  Failed  Complete  Lost
	web         0       0         2        0       0         0

	Allocations
	ID        Eval ID   Node ID   Task Group  Desired  Status   Created At
	42c9d7c6  38debb73  11e9ba9d  web         run      running  09/26/17 13:21:45 UTC
	87e451bc  38debb73  9f1d65ca  web         run      running  09/26/17 13:21:45 UTC

Nginx como proxy balanceador nos redirigirá automáticamente a los dos nodos que la tengan desplegada accediendo a la siguiente url. La web nos dirá qué nodo es el que ha respondido y podéis observar que cada vez será uno de los dos donde está desplegado:

http://192.168.10.11/hello-docker

Nginx se ha configurado automáticamente con una tarea cron que ejecuta el proceso “consul-template”. Éste, basándose en la configuración de Consul y las plantillas del directorio ‘/usr/bin/consul-template’, configura los ficheros de ‘/etc/nginx’ y reinicia el servicio de nginx.

Job tipo Java

Ahora vamos a ejecutar un job que desplegará una aplicación Java directamente en los nodos clientes (sin Docker). Para ello usamos el ejemplo ‘hello-java.nomad’.

$ nomad run /vagrant/jobs/hello-java.nomad
	==> Monitoring evaluation "749505c3"
	    Evaluation triggered by job "java"
	    Allocation "d648fc4b" created: node "9f1d65ca", group "java"
	    Evaluation status changed: "pending" -> "complete"
	==> Evaluation "749505c3" finished with status "complete"

Esta aplicación debería estar desplegada en dos de los tres nodos clientes y accesible por tanto en dos de las siguientes urls:

http://192.168.10.14:8080/ http://192.168.10.15:8080/ http://192.168.10.16:8080/
$ nomad status hello-java
	ID          = hello-java
	Name        = hello-java
	Type        = service
	Priority    = 50
	Datacenters = dc1
	Status      = running
	Periodic    = false

	Summary
	Task Group  Queued  Starting  Running  Failed  Complete  Lost
	java        0       0         1        0       0         0

	Allocations
	ID        Eval ID   Node ID   Task Group  Desired  Status   Created At
	d648fc4b  749505c3  9f1d65ca  hello-java  run      running  09/26/17 13:38:05 UTC
	78119df7  14af1ff8  11e9ba9d  hello-java  run      running  09/26/17 13:42:50 UTC
Modificando un Job

Vamos a planificar el despliegue de una nueva versión de la aplicación Java. Para ello tenemos preparado otro fichero de job llamado ‘hello-java-v2.nomad’. Con el siguiente comando vemos que acciones se ejecutarían para realizar el despliegue de este nuevo fichero de configuración:

$ nomad plan hello-java-v2.nomad
	+/- Job: "hello-java"
	    Datacenters {
	  Datacenters: "dc1"
	}
	+/- Task Group: "hello-java" (2 create/destroy update)
	  +/- Task: "java" (forces create/destroy update)
	    + Artifact {
	      + GetterSource: "https://.../hello2.jar"
	      + RelativeDest: "local/"
	    }
	    - Artifact {
	      - GetterSource: "https://.../hello.jar"
	      - RelativeDest: "local/"
	    }

	Scheduler dry-run:
	- All tasks successfully allocated.

	Job Modify Index: 90
	To submit the job with version verification run:

	nomad run -check-index 90 hello-java-v2.nomad

	When running the job with the check-index flag, the job will only be run if the
	server side version matches the job modify index returned. If the index has
	changed, another user has modified the job and the plan's results are
	potentially invalid.

Ahora podemos confirmar la ejecución de estos cambios que conllevan cambiar el .jar que se ejecutará y Nomad se encargará de parar las versiones antiguas y desplegar las nuevas.

$ nomad run -check-index 90 hello-java-v2.nomad
	==> Monitoring evaluation "d77bfe4e"
	    Evaluation triggered by job "java"
	    Allocation "1914aac7" created: node "9f1d65ca", group "java"
	    Allocation "d5b08cff" created: node "11e9ba9d", group "java"
	    Evaluation status changed: "pending" -> "complete"
	==> Evaluation "d77bfe4e" finished with status "complete"

Veamos cómo va el despliegue… ¡todo correcto!

$ nomad status hello-java
	ID          = java
	Name        = java
	Type        = service
	Priority    = 50
	Datacenters = dc1
	Status      = running
	Periodic    = false

	Summary
	Task Group  Queued  Starting  Running  Failed  Complete  Lost
	java        0       2         0        0       2         0

	Allocations
	ID        Eval ID   Node ID   Task Group  Desired  Status    Created At
	1914aac7  d77bfe4e  9f1d65ca  java        run      running   09/26/17 13:48:58 UTC
	d5b08cff  d77bfe4e  11e9ba9d  java        run      running   09/26/17 13:48:58 UTC
	78119df7  14af1ff8  11e9ba9d  java        stop     complete  09/26/17 13:42:50 UTC
	d648fc4b  14af1ff8  9f1d65ca  java        stop     complete  09/26/17 13:38:05 UTC
Pruebas de estabilidad del clúster

¡Ahora es el momento de probar lo estable que es el sistema! Puedes probar a tirar la máquina virtual que actualmente es el líder de Consul y verás con el comando ‘consul monitor’ desde cualquiera de la otras máquinas que otro nodo adquiere la responsabilidad de líder.

Puedes probar a tirar al servidor Nomad que sea líder y verás con el comando ‘nomad server-members’ desde cualquier de las otras máquinas que otro nodo tipo server adquiere la responsabilidad de líder.

Puedes probar a tirar alguno de los nodos nomad cliente y observar que las task que tengan una allocation en este nodo crearán una nueva allocation en otro de los nodos clientes. Puedes verlo con el comando ‘nomad status hello-docker’ por ejemplo.

6. Nomad vs Otro software

No es fácil comparar Nomad con un software como Kubernetes. Kubernetes es una solución centrada más en los contendeores de Docker y que ofrece un sistema completo de orquestación con autodescubrimiento, balanceo de carga, gestión de volúmenes, networking, gestión de secretos, configuración, etc. En cambio Nomad es de propósito más general soportando aplicaciones virtualizadas, standalone y con contenedores de Docker. Nomad tiene una arquitectura mucho mas simple con un simple binario y requiere de otras piezas externas para añadir funcionalidades de coordinación o almacenamiento. Parecida es la comparación con otros sistemas como Docker Swarm.

Si lo comparamos con AWS ECS la principal diferencia es que ECS es para desplegar en los servidores de Amazon, mientras que Nomad es totalmente open source y permite el despliegue en cualquier nube privada o pública. Además ECS está centrado en contenedores de Docker mientras que Nomad tiene un propósito más general aunque soporta Docker también.

7. Conclusiones

Si necesitas una pieza de software que se encargue simplemente de gestionar y supervisar los despliegues distribuidos de tus aplicaciones o contenedores Docker; Nomad es una buena opción! Además, combinado con otras herramientas de ‘HashiCorp DevOps Infrastructure’ como Consul, Terraform y Vault puedes tener una solución bastante completa de infraestructura.

8. Referencias

Google Analytics II: iniciación

$
0
0

Índice de contenidos

1. Introducción

En este tutorial seguimos echando un vistazo general a los menús y funciones principales de Google Analytics. Ya vimos cómo localizar quién es nuestra audencia, de dónde viene y cómo se comporta. Ahora analizaremos cómo se traduce ese comportamiento o lo que es lo mismo, qué objetivos hemos cumplido o qué ventas hemos hecho. También aprenderemos a personalizar nuestros paneles e informes para ahorrar tiempo y a crear alertas y accesos directos. Por último, veremos qué es eso de la segmentación de audiencias. ¡Vamos allá!

2. Menú lateral: Conversions

Una conversión es un objetivo conseguido. Hay dos tipos comunes: conseguir un contacto y conseguir una venta.

El menú Conversiones recogerá todos los datos relativos a nuestros objetivos y se divide en varios submenús:

  • Goals/Objetivos:

    Como siempre, en Overview, tendremos una vista general de objetivos conseguidos diariamente, número total de objetivos cumplidos, número de usuarios por objetivo, porcentaje de conversión o usuarios que cumplen con un objetivo, tasa de abandono…

    Goal URLs nos dice dónde se ha generado la conversión. Un mismo objetivo puede tener varias páginas desde donde generar la conversión y en este menú nos desglosa qué URL es más efectiva.

    Reverse Goal Path informa la ruta que ha seguido el usuario para acabar formalizando una conversión y cuantas veces se ha seguido esa ruta. Se leen los datos de derecha a izquierda.

    Goal Inverse

    Funnel visualization es la misma información presentada de forma más visual y gráfica.

    Goal flow muestra el flujo que han seguido los usuarios y permite hacer un filtrado de los datos así como aislarlos y analizarlos por separado.


  • Ecommerce:

    Overview es el informe general de venta. No sólo veremos cuántas ventas diarias hacemos o la cuantía de dichas ventas, sino que también podremos ver qué productos hemos vendido, sus categorías y a través de qué medio.

    Product Performance muestra el informe específico con los datos relativos a cada producto, es decir, qué vende más. Cuánto se ha vendido, cuánto beneficio aportan, beneficios promedio, etc.

    Sales Performance nos informa en base a fechas de cuáles son los días que más ventas hemos hecho. Para analizar el porqué, debemos revisar nuestras campañas y promociones y ver si coinciden y por tanto, si han funcionado.

    Transactions organiza los datos en base al ID o número de ticket del producto. También aporta información sobre los gastos de envío y las tasas o impuestos que corresponden.

    Time to Purchase es el tiempo que el usuario tarda en hacer una compra, desde que entra y ve el producto hasta que lo adquiere.

  • Multi-Channel Funnels:

    En la vista general (‘Overview’), tendremos las conversiones en nuestra línea temporal. Pero el dato que nos interesa está en el visualizador de conversión de varios canales, es decir, a través de qué canal o mezcla de canales, se ha hecho la conversión y cuántas de esas conversiones han sido por búsqueda directa y cuántas por contribución (ayuda).

    Conversiones

    Hay que tener en cuenta que un usuario puede ver un producto/servicio un día y adquirirlo más tarde por otra vía. Por ejemplo, ve el artículo en nuestra página, no se decide y se va. Dos días después ve un banner en Facebook (remarketing) y ya accede a ejecutar la compra. Esto es lo que se representa en el diagrama de círculos.

    NOTA: El Remarketing es una funcionalidad que permite crear anuncios personalizados para los usuarios que visitaron previamente una web.

    Conversion Multicanal

    En ‘Assisted Conversions’, veremos más detalles de las conversiones, como el beneficio que generan o en qué medida uno u otro canal ha contribuido a finalizar una venta. Esto nos dirá si, aunque la compra se haga en nuestro site, los usuarios llegan siguiendo una ruta que parte de nuestras redes sociales o desde una búsqueda en Google y por tanto, podremos decidir qué canal reforzar.

    En Top Conversions Paths se muestran de forma muy clara las rutas que han seguido nuestros clientes.


    Ruta

    También nos indica el número de veces que se ha seguido una u otra ruta. Además, podemos visualizar estas rutas no sólo en función de la agrupación de canales, si no también en base a fuente/medio, sólo fuente o sólo medio, cambiando el filtro superior. De esta forma, veremos si la búsqueda orgánica es de Google, o qué red social se ha seguido.

    ‘Time Lag’ (lapso de tiempo) nos indica el tiempo que pasa desde la primera interacción hasta la última. Hay que prestar especial atención a los usuarios que han tardado más de 1 día o dos, ya que tendremos que recordarles que nuestros productos/servicios están ahí.

    En ‘Path Length’ (ruta de interacción) tenemos el número de pasos que ha seguido un usuario. Generalmente, nos deben encontrar con el menor número de interacciones.

  • Atribución:

    Es la herramienta de comparación de modelos. Con ella podemos forzar a los informes de conversiones a que únicamente computen los canales y atribuyan las conversiones según nuestro criterio:


    Atribución

3. Menú lateral: Customization

  • Paneles:

    Como hemos visto, Google Analytics tiene muchos datos susceptibles de ser analizados pero lo normal, es consultar algunos datos concretos de forma periódica que son los que más nos interesan. Estos datos varían en función del contenido de tu site y de tus objetivos.

    Para organizar, seleccionar y ver de forma rápida los informes que nos interesan tenemos el menú ‘Customization’ (‘Personalización’).

    Custom Menu

    En el submenú ‘Dashboards’ podemos crear tableros que engloben los informes (aquí los llama widgets) que queramos. Por tanto, lo primero es crear un tablero.

    Crear tablero

    La primera opción que tenemos es la de crear uno en blanco o seleccionar un panel predeterminado general. Empezar con ésta última y luego editar es una buena forma de empezar. También podemos importar los tableros que han hecho otros en la Solutions Gallery a través del botón ‘Import from gallery’.


    Paneles

    Muchos de estos paneles son específicos y podemos buscarlos filtrando por categoría, ranking, populares… o buscar directamente por autor. Una vez importado nos aparecerá en nuestro submenú de ‘Paneles’.

    Gallery

    A continuación se genera el tablero por defecto con una serie de widgets que podemos editar, eliminar y añadir otros nuevos. Así mismo, podremos personalizar la vista del panel entero en el menú superior de la derecha.

    Panel
  • Accesos directos (Saved Reports)

    La manera de crear accesos directos ha cambiado un poco en la última versión de Google Analytics. A los accesos directos que guardemos podremos acceder desde el menú ‘Customization’, submenú ‘Accesos directos’ o ‘Saved Reports’.

    Accesos directos

    Para guardar un informe, sólo debemos acceder a él y en el menú superior derecho pinchar en ‘Save’. Automáticamente se crea un enlace rápido en nuestra lista de accesos directos.

    Save Acceso Directo

    A diferencia de los paneles donde vemos los datos más necesarios, los accesos directos nos llevan directamente a los informes que nos interesan. Podemos hacer un panel con todos ellos y agruparlos o por ejemplo, exportarlos todos juntos. Son dos herramientas complementarias.

  • Alertas personalizadas:

    Las alertas automáticas ya no existen. Aquellas que estaban creadas se mantendrán pero ya no se podrán crear nuevas y llegado un punto desaparecerán.

    Por tanto, utilizaremos las alertas personalizadas ubicadas ahora en el menú ‘Customization’ (Personalización).

    A través del botón ‘Manage custom alerts’ accedemos al panel de creación de nuestras alertas.

    Manage

    Cuando creamos una alerta tenemos una serie de parámetros a determinar:

    • Nombre: lo primero es poner un nombre representativo.
    • Periodo: establecer si la alerta es diaria, semanal o mensual.
    • Correo: podemos marcar que se nos envíe un correo (a uno o varios usuarios) cuando se active la alerta.
    • Correspondencia: selección de filtros específicos sobre los que debe actuar la alerta. A qué afecta nuestra alerta.
    • Condiciones de alerta: indicamos el elemento que vamos a analizar (sesiones, rebote, permanencia…), la condición que debe cumplir para alertarnos y el valor de cambio. Seleccionar porcentajes y no valores absolutos permite crear alertas válidas aunque nuestros datos cambien. Por ejemplo, una subida de 20 visitas sobre 100 habituales es un dato relevante pero no sobre 1000. Si ponemos el 20% de incremento, la alerta nos valdrá para 100, 1000 ó 10000 visitas.
    • Alerts

4. Menú lateral: Real-Time

En este menú podemos ver qué ocurre en nuestro site en tiempo real, es decir, informes actualizados segundo a segundo. No es un informe muy fiable para tomar decisiones ya que está dando información segundo a segundo pero podemos analizar ciertos comportamientos cuando por ejemplo, se publicite nuestro site por primera vez en un medio de comunicación, queramos lanzar algo puntual y tengamos que elegir un medio, la web vaya lenta…

Al instante sabemos cuántos usuarios están dentro del site, desde qué lugar nos están viendo, qué dispositivo están utilizando, qué páginas están visualizando y qué eventos o conversiones se están produciendo.

Real Time

5. Segmentación

Un segmento es un subconjunto de datos a analizar. Por ejemplo, los usuarios de entre 40 y 50 años, las visitas orgánicas…

En casi todos los informes tenemos la opción de ‘Añadir segmento’.

Add Segment

Al hacer clic en ‘crear segmento’ accedemos a un panel donde podemos especificar todos los filtros que queramos para establecer nuestro segmento de estudio. Mientras no lo borremos se aplicará a todos los informes que consultemos.

Crear Segmento

Si copiamos un segmento, se abre la ventana anterior con los mismos parámetros del segmento copiado y podemos hacer modificaciones.

Copy Segment

Al guardar, creamos un nuevo segmento pero no eliminamos el anterior. De esta forma comparamos dos segmentos o más (hasta cuatro a la vez). También podríamos hacerlo, simplemente, añadiendo nuevos segmentos.

Dos segmentos

Otra opción que tenemos es la de ‘Compartir’. Esto quiere decir que podemos enviar la configuración de nuestros segmentos, no los informes privados que utilizan dichos segmentos.

En el panel donde creamos nuestros segmentos, podemos encontrar el listado con todos los que tenemos y clasificarlos como destacados. Es un lugar de acceso rápido a nuestra colección.

En este panel podemos, además, ‘Importar de la galería’, es decir, incorporar a nuestra colección segmentos hechos por otros o viceversa (ya los vimos en el punto 2) y subir los nuestros en ‘Compartir segmentos’.

importar de galería

En este otro panel tenemos una serie de filtros a la izquierda y todos los segmentos correspondientes a la derecha. Al hacer clic en importar se añadirán automáticamente a nuestra colección.

6. Conclusiones

Como hemos podido ver en estos dos tutoriales, analytics es una herramienta gigante que se repite a menudo. Por ello, en esta segunda parte, tener en cuenta la personalización, los accesos directos y las alertas nos facilitará el trabajo. Por otro lado, buscar resultados o compararlos a través de la segmentación también nos ahorrará tiempo y esfuerzo. Te invito a que juegues un poco y bucees en este mar de posibilidades para entender mejor qué pasa en tu site.

7. Referencias

Instalando un UI simple para Docker

$
0
0

Docker probablemente sea la tecnología más de moda en los últimos tiempos. La posibilidad de crear contenedores de un modo muy simple para ejecutar procesos complejos es sorprendente. Esto tiene mucho sentido a la hora de automatizar y simplificar los entornos DevOps y crear arquitectura de alto escalado sin la posibilidad de error por la intervención humana.

Os recomiendo que empecéis con tutoriales sencillos como el de Jorge Pacheco para entender los conceptos.

Os describo el equipo utilizado.


Por suerte o por desgracia, Docker cambia muy deprisa y estas pantallas pueden valer para actualizar el modo en el que se instalas en Mac y para descubrir un par de opciones gráficas que os ayuden a entender mejor los conceptos.

Lo primero que hacemos es ir a la página de Docker para descargarlo.


En Mac, te descargas de .dmg y a correr.


Arrastras la aplicación a la carpeta de aplicaciones.


Y ya estamos.


Lo arranca, tarda un poquito.


Y ya tenemos Docker instalado.


Lo suyo es abrir un terminal y empezar a jugar un poco con los comandos.


Ahora vamos al menú Kitematic (como primera opción, que vamos a ver también otro en este tutoríal) para ver la herramienta gráfica.


Se descarga el fichero.


Podemos logarnos a Docker Hub pero de momento pulso abajo a la derecha (Skip for Now, saltármelo por ahora).


Podemos ver las imágenes.


Buscamos Ubuntu y le damos a crear.


Podemos ver los parámetros de arranque.


Vamos a probar otra herramienta visual.

Os recomiendo, después de haber cacharreado un poco, dejarlo como al principio. Para ello, sobre el icono de Docker podéis ir a la opción preferencias y pulsar reset (obviamente si no tenéis nada de valor).


En reset, pulsamos a Reset to factory defaults (dejar como de fábrica).


Vamos a descargar una imagen Docker y crear un contenedor con la herramienta Portainer.io (mucho mejor que instalar software en local).

Podemos visitar su página web para leer el manual, pero vamos, bastante intuitiva.


Desde nuestro ordenador, donde hemos instalado Docker, ejecutamos el siguiente comando. Le he puesto el puerto 8000 (en la documentación hacer 9000:9000), el que más os guste (que no entre en conflicto con los actuales, ya que dará un error).

MacBook-Pro-Autentia$ docker run -d -p 8000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer


Nos pide que especifiquemos una contraseña para el usuario administrador. Luego nos pedirá logarmos con el usuario admin y esa misma contraseña.


Le decimos que arranque la instancia donde se está ejecutando la propia herramienta de administración (esto es cómodo y limpio).


Y ya podemos comprobar en el dashboard que tenemos una imagen y un contenedor (por eso os decía de hacer reset).


Podemos parar, pausar o borrar el contenedor.

Podemos también crear directamente contenedores desde aquí.


Ver la imágenes descargadas/creadas.


La configuración de red.


Y los volúmenes.


Incluso los eventos. Esto está muy bien para saber lo que pasa por debajo cuando utilizas la línea de comandos.


Vamos a arrancar un nuevo contenedor Ubuntu.


Y le decimos que queremos una consola interactiva: equivalente a

docker -t -i ubuntu

Una vez arrancada en la herramienta visual vamos a cambiar a la línea de comando (Terminal) para listar los contenedores y engancharnos a ubuntu (attach ubunturc).


Bueno, ya tenemos alguna herramienta para hacer las cosas un poquito más sencillas que escribiendo comandos y el entorno instalado en MacOs.

Supongo que esto evolucionará una barbaridad en poco tiempo porque junto con Vagrant, Ansible y Kubernetes esto da mucho juego combinado, y los IDES y GUIs los acabarán integrando.

Spring Cloud Hystrix: resilient services

$
0
0

0. Índice de contenidos.

1. Introducción.

Hystrix es una librería que forma parte del stack de Spring Cloud, desarrollada por Netflix, que facilita la implementación del patrón circuit breaker dentro de una arquitectura de servicios distribuidos.

La comunicación entre sistemas adolece de indisponibilidades debidas a las propias características de los mismos: microcortes en las comunicaciones, servicio no disponible temporalmente, lentitud en las respuestas por el excesivo uso de un servicio,… el patrón circuit breaker permite gestionar estos problemas derivados de las comunicaciones estableciendo mecanismos de control, ayudando a mejorar la resiliencia de los sistemas.

De entre las características de hystrix podemos resaltar las siguientes:

  • ejecución de llamadas a sistemas externos o internos en segundo plano,
  • posibilidad de establecer timeouts en las peticiones,
  • posibilidad de establecer semáforos (pools de hilos), para cada petición, de forma que si no hay hilos disponibles la petición será rechazada en lugar de encolada,
  • proporciona métodos para gestionar la propagación de errores en cascada,
  • recolección de métricas de peticiones exitosas, fallidas y timeouts,
  • gestiona las llamadas para que, en caso de que un sistema externo exceda una cuota de errores definida, no se realicen más peticiones al mismo,
  • permite la declaración de fallbacks, en caso de error en una petición se ejecutará la estrategia definida para el caso de fallo,
  • proporciona un dashboard que integra las métricas capturadas,

En este tutorial veremos un ejemplo práctico de uso de hystrix para la gestión de fallbacks, integrado con clientes de feign.

2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.5 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS Sierra 10.12.5
  • Oracle Java: 1.8.0_25
  • Spring Cloud Dalston SR3

3. Ejemplo de definición de fallback.

Vamos a ver un ejemplo de interfaz de servicio:

public interface HelloWorld {

	String sayHi(String name);

}

Un cliente de feign sin ninguna particularidad especial:

package com.sanchezjm.tuto.feign.dummy.remote;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import com.sanchezjm.tuto.feign.dummy.HelloWorld;

@FeignClient(name = "localapp")
public interface HelloWorldClient extends HelloWorld {

	@RequestMapping(value="/hello/{name}")
	String sayHi(@PathVariable("name") String name);

}

Otro cliente de feign con una estrategia de fallback definida:

package com.sanchezjm.tuto.feign.hystrix.dummy.local;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import com.sanchezjm.tuto.feign.dummy.HelloWorld;

@FeignClient(name = "localapp", fallback= HelloWorldFooClient.class)
public interface HelloWorldFallBackClient extends HelloWorld {

	@RequestMapping(value="/hello/{name}")
	String sayHi(@PathVariable("name") String name);

}

Apuntando a un cliente foo HelloWorldFooClient, que podría tener el siguiente código, para su ejecución por defecto en caso de error:

package com.sanchezjm.tuto.feign.hystrix.dummy.local;

import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;

@Component
public class HelloWorldFooClient implements HelloWorldFallBackClient {

	public String sayHi(String name){
		return "Bye "+ name + "!";
	}

}

Si hacemos uso del cliente HelloWorldClient y no puede comunicarse con el servicio o este devuelve un error, se lanzará una HystrixRuntimeException:

com.netflix.hystrix.exception.HystrixRuntimeException: HelloWorldClient#sayHi(String) failed and no fallback available.
	at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:819)
	at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804)
	at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140)
	at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
	at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
	at com.netflix.hystrix.AbstractCommand$DeprecatedOnFallbackHookApplication$1.onError(AbstractCommand.java:1472)
	at com.netflix.hystrix.AbstractCommand$FallbackHookApplication$1.onError(AbstractCommand.java:1397)
	at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
...

Si bien, si hacemos uso del cliente HelloWorldFallBackClient y se produce un error en las comunicaciones, se invocará a la lógica del cliente alternativo HelloWorldFooClient, devolviendo la cadena “Bye “+ name + “!”

4. Ejecución de un test de integración.

Vamos a ejecutar un primer test de integración para comprobar que efectivamente el comportamiento del primer cliente es ese y, para ello, lo primero será exponer el código del test:

package com.sanchezjm.tuto.feign;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.netflix.hystrix.exception.HystrixRuntimeException;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.sanchezjm.tuto.feign.dummy.HelloWorld;
import com.sanchezjm.tuto.feign.dummy.remote.HelloWorldClient;
import com.sanchezjm.tuto.feign.exception.BusinessException;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {FeignClientsApplication.class, FeignClientsIntegrationTest.Application.class}, webEnvironment = WebEnvironment.RANDOM_PORT, value = {
		"spring.application.name=hello-service", "feign.hystrix.enabled=true",
		"feign.okhttp.enabled=false" , "hystrix.shareSecurityContext=true"})
@DirtiesContext
public class FeignClientsIntegrationTest {

	@Value("${local.server.port}")
	private int port = 0;

	@Autowired
	protected HelloWorldClient helloWorldClient;

	@Configuration
	@EnableAutoConfiguration
	@RestController
	@EnableFeignClients(clients = { HelloWorldClient.class })
	@RibbonClient(name = "localapp", configuration = LocalRibbonClientConfiguration.class)
	protected static class Application implements HelloWorld {

		@Override
		@RequestMapping(value="/hello/{name}")
		public String sayHi(@PathVariable("name") String name) {
			if ("dummy".equals(name)){
				throw new IllegalArgumentException("Dummies not supported!");
			}
			if ("nil".equals(name)){
				throw new BusinessException("Nil not supported!");
			}
			return "Hi " + name + "!";
		}

		@RequestMapping(value="/not_found")
		public String notFound() {
			return "";
		}

	}

	@Configuration
	static class LocalRibbonClientConfiguration {

		@Value("${local.server.port}")
		private int port = 0;

		@Bean
		public ServerList ribbonServerList() {
			return new StaticServerList<>(new Server("localhost", this.port));
		}
	}

    @Test
	public void shouldSayHi(){
		Assert.assertEquals("Hi all!", helloWorldClient.sayHi("all"));
	}

	@Test
	public void shouldThrowIllegalArgumentException(){
		try {
			final String message = helloWorldClient.sayHi("dummy");
			Assert.fail("Must throws an Exception");
		}
		catch(HystrixRuntimeException e) {
			Assert.assertEquals("Dummies not supported!", e.getCause().getMessage());
		}
	}

}

Sus características:

  • levanta un contexto de Spring Web con una serie de propiedades que ya vimos para feign, por un puerto random,
  • dentro del propio test declaramos un controlador que implementa la misma interfaz que los clientes que ya hemos visto,
  • habilitamos el cliente de feign que usaremos primero
  • en la configuración del controlador habilitamos un cliente de ribbon con una política de balanceo con un único servidor local, que apunta al mismo puerto que el servicio publicado -el establecido con un random e inyectado con un @Value(“${local.server.port}”); con ello evitamos tener que levantar cualquier otro tipo de infraestructura en local para las pruebas,
  • la lógica del controlador simplemente comprueba el parámetro de entrada para lanzar una excepción de Runtime o de negocio, aunque esta es también unchecked o devolver el resultado concatenando el parámetro de entrada,
  • por último, un par de métodos de test que comprueban tanto el positivo como el negativo y, este último, comprueba que se lanza una HystrixRuntimeException, que encapsula el mensaje de la excepción que lo origina.

Partiendo del test anterior vamos a extender la propia clase de test sobrescribiendo el segundo de los métodos (aunque no cuadre el nombre con su nueva lógica) y haciendo uso del segundo de nuestros clientes, el que sí contiene la estrategia de fallback, lanzamos el test:

package com.sanchezjm.tuto.feign;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.sanchezjm.tuto.feign.hystrix.dummy.local.HelloWorldFallBackClient;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {FeignClientsApplication.class, HystrixFeignClientsIntegrationTest.Application.class}, webEnvironment = WebEnvironment.RANDOM_PORT, value = {
		"spring.application.name=identity-service", "feign.hystrix.enabled=true",
		"feign.okhttp.enabled=false" , "hystrix.shareSecurityContext=true"})
@DirtiesContext
@ComponentScan(basePackages="com.sanchezjm.tuto.hystrix.dummy.local")
public class HystrixFeignClientsIntegrationTest extends FeignClientsIntegrationTest{

	@Autowired
	protected HelloWorldFallBackClient helloWorldFallBackClient;

	@Test
	public void shouldThrowIllegalArgumentException(){
		Assert.assertEquals("Bye dummy!", helloWorldFallBackClient.sayHi("dummy"));
	}

}

Pese al nombre del método, ahora ya no se lanza la excepcíon puesto que hemos definido la estrategia de fallback; lo podemos comprobar en la salida por consola:

2017-09-13 19:15:21.688 ERROR 53471 --- [o-auto-1-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: Dummies not supported!] with root cause
...
2017-09-13 19:15:51.964 DEBUG 53477 --- [trix-localapp-1] com.netflix.hystrix.AbstractCommand      : Error executing HystrixCommand.run(). Proceeding to fallback logic ...

5. Configuración

Ejecutando el test con el nivel de logging a TRACE podemos ver todas las propiedades que se pueden establecer para configurar todas las estrategias de circuit braker, tanto a nivel del comando de hystrix (declaración de fallback en el cliente de feign) como a nivel global:

hystrix.command.HelloWorldFallBackClient#sayHi(String).circuitBreaker.enabled to use NEXT property: hystrix.command.default.circuitBreaker.enabled = true
hystrix.command.HelloWorldFallBackClient#sayHi(String).circuitBreaker.requestVolumeThreshold to use NEXT property: hystrix.command.default.circuitBreaker.requestVolumeThreshold = 20
hystrix.command.HelloWorldFallBackClient#sayHi(String).circuitBreaker.sleepWindowInMilliseconds to use NEXT property: hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds = 5000
hystrix.command.HelloWorldFallBackClient#sayHi(String).circuitBreaker.errorThresholdPercentage to use NEXT property: hystrix.command.default.circuitBreaker.errorThresholdPercentage = 50
hystrix.command.HelloWorldFallBackClient#sayHi(String).circuitBreaker.forceOpen to use NEXT property: hystrix.command.default.circuitBreaker.forceOpen = false
hystrix.command.HelloWorldFallBackClient#sayHi(String).circuitBreaker.forceClosed to use NEXT property: hystrix.command.default.circuitBreaker.forceClosed = false
hystrix.command.HelloWorldFallBackClient#sayHi(String).execution.isolation.strategy to use NEXT property: hystrix.command.default.execution.isolation.strategy = THREAD


hystrix.command.HelloWorldFallBackClient#sayHi(String).execution.isolation.thread.timeoutInMilliseconds to use NEXT property: hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 3000
hystrix.command.HelloWorldFallBackClient#sayHi(String).execution.timeout.enabled to use NEXT property: hystrix.command.default.execution.timeout.enabled = true
hystrix.command.HelloWorldFallBackClient#sayHi(String).execution.isolation.thread.interruptOnTimeout to use NEXT property: hystrix.command.default.execution.isolation.thread.interruptOnTimeout = true
hystrix.command.HelloWorldFallBackClient#sayHi(String).execution.isolation.thread.interruptOnFutureCancel to use NEXT property: hystrix.command.default.execution.isolation.thread.interruptOnFutureCancel = false
hystrix.command.HelloWorldFallBackClient#sayHi(String).execution.isolation.semaphore.maxConcurrentRequests to use NEXT property: hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests = 10

hystrix.command.HelloWorldFallBackClient#sayHi(String).fallback.isolation.semaphore.maxConcurrentRequests to use NEXT property: hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests = 10
hystrix.command.HelloWorldFallBackClient#sayHi(String).fallback.enabled to use NEXT property: hystrix.command.default.fallback.enabled = true
hystrix.command.HelloWorldFallBackClient#sayHi(String).metrics.rollingStats.timeInMilliseconds to use NEXT property: hystrix.command.default.metrics.rollingStats.timeInMilliseconds = 10000

hystrix.command.HelloWorldFallBackClient#sayHi(String).metrics.rollingStats.numBuckets to use NEXT property: hystrix.command.default.metrics.rollingStats.numBuckets = 10
hystrix.command.HelloWorldFallBackClient#sayHi(String).metrics.rollingPercentile.enabled to use NEXT property: hystrix.command.default.metrics.rollingPercentile.enabled = true
hystrix.command.HelloWorldFallBackClient#sayHi(String).metrics.rollingPercentile.timeInMilliseconds to use NEXT property: hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds = 60000
hystrix.command.HelloWorldFallBackClient#sayHi(String).metrics.rollingPercentile.numBuckets to use NEXT property: hystrix.command.default.metrics.rollingPercentile.numBuckets = 6
hystrix.command.HelloWorldFallBackClient#sayHi(String).metrics.rollingPercentile.bucketSize to use NEXT property: hystrix.command.default.metrics.rollingPercentile.bucketSize = 100
hystrix.command.HelloWorldFallBackClient#sayHi(String).metrics.healthSnapshot.intervalInMilliseconds to use NEXT property: hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds = 500
hystrix.command.HelloWorldFallBackClient#sayHi(String).requestCache.enabled to use NEXT property: hystrix.command.default.requestCache.enabled = true
hystrix.command.HelloWorldFallBackClient#sayHi(String).requestLog.enabled to use NEXT property: hystrix.command.default.requestLog.enabled = true


hystrix.threadpool.localapp.allowMaximumSizeToDivergeFromCoreSize to use NEXT property: hystrix.threadpool.default.allowMaximumSizeToDivergeFromCoreSize = false
hystrix.threadpool.localapp.coreSize to use NEXT property: hystrix.threadpool.default.coreSize = 10
hystrix.threadpool.localapp.maximumSize to use NEXT property: hystrix.threadpool.default.maximumSize = 10
hystrix.threadpool.localapp.keepAliveTimeMinutes to use NEXT property: hystrix.threadpool.default.keepAliveTimeMinutes = 1
hystrix.threadpool.localapp.maxQueueSize to use NEXT property: hystrix.threadpool.default.maxQueueSize = -1
hystrix.threadpool.localapp.queueSizeRejectionThreshold to use NEXT property: hystrix.threadpool.default.queueSizeRejectionThreshold = 5
hystrix.threadpool.localapp.metrics.rollingStats.timeInMilliseconds to use NEXT property: hystrix.threadpool.default.metrics.rollingStats.timeInMilliseconds = 10000
hystrix.threadpool.localapp.metrics.rollingStats.numBuckets to use NEXT property: hystrix.threadpool.default.metrics.rollingStats.numBuckets = 10

6. Referencias.

7. Conclusiones.

Continuamos examinando las posibilidades del stack de Spring Cloud.

Un saludo.

Jose


Testing funcional con SoapUI y Groovy

$
0
0

En este tutorial veremos cómo realizar testing funcional con SoapUI y Apache Groovy.

Índice de contenidos


1. Introducción

En este tutorial veremos cómo incorporar test funcionales a un API REST con la herramienta open source SoapUI utilizando scripts en Apache Groovy. SoapUI es una potente herramienta con muchas posibilidades, ya os hemos contado algunas de ellas en los diferentes tutoriales, pero hoy os mostraremos la potencia del scripting utilizando Groovy. Desarrollaremos un par de tests funcionales de un API en un escenario end to end.

Disponéis del código fuente en el siguiente enlace.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Mac Book Pro 15″ (2,5 Ghz Intel Core i7, 16 GB DDR3)
  • Sistema Operativo: Mac OS Sierra 10.12.6
  • SoapUI 5.3.0
  • Groovy 2.1.7
  • H2 versión 1.4.196

3. Definiciones

Dependiendo de con quién hables puede haber ciertos matices con respecto a lo que se entiende por test funcionales. Os hago este pequeño inciso porque muchas veces los test no tienen un objetivo claro y no aportan la seguridad que necesitamos. Para nuestro tutorial me basaré en la definición que se utiliza en la comunidad TDD: “Un test funcional es un subconjunto de los tests de aceptación. Es decir, comprueban alguna funcionalidad con valor de negocio.”


4. Groovy

Groovy es un lenguaje dinámico opcionalmente tipado que se ejecuta sobre la JVM, tiene características que están inspiradas en lenguajes como Python, Ruby y Smalltalk y utiliza una sintaxis similar a Java. Su sintaxis permite reducir la cantidad de código a desarrollar. A diferencia de otros lenguajes alternativos, está diseñado para ser un complemento, no un sustituto de Java. Y no… no es Java sin los puntos y coma. 😉


5. API REST a probar

En este tutorial utilizaremos un API REST para la gestión de usuarios securizada con JWT. Disponéis de los detalles de la misma en el siguiente enlace. En esta ocasión utilizaremos una base de datos H2 en modo servidor (estará escuchando en un puerto) para simular un servidor real de BD y poder acceder directamente por JDBC.

Nuestro testing funcional verificará que el API está debidamente securizada y que registra la información correctamente en la BD. Como hemos dicho anteriormente estas pruebas se centran en los requisitos de negocios.

Para comenzar, necesitaremos iniciar la base de datos en modo servidor. En la web de H2 podemos ver todos los detalles acerca de la misma, para nuestro ejemplo basta con arrancarla en modo servidor. A continuación, arrancaremos nuestra API con Spring Boot

# Se descarga H2, se descomprime y desde el directorio $H2_HOME/bin lanzamos el servidor en background
	cd $H2_HOME/bin
	java -cp h2*.jar org.h2.tools.Server &

	# Descargamos el proyecto demo y a continuacion arrancamos el API REST
	cd $DEMO_API_REST/
	mvn spring-boot:run

Por último, tendremos que copiar el jar de H2 en las librerías de SoapUI para que podamos acceder por JDBC a la BD. Consultad la ruta concreta, en mi caso está en $SOAPUI_HOME/Contents/java/app/lib/ . Ya podemos arrancar SoapUI.

#Copiamos el driver de la BD, dentro de las librerías de SoapUI para poder establecer la conexión JDBC
cp $H2_HOME/bin/h2-1.4.196.jar $SOAPUI_HOME/Contents/java/app/lib/

#Ya podemos arrancar SoapUI
Estructura del proyecto SoapUI

6. Scripting con SoapUI

Para realizar las pruebas generamos un proyecto de tipo REST y declaramos las URLs (recursos) que se utilizan en el API. A continuación, creamos el testsuite y los testcase que se van a utilizar. Como podéis observar en la siguiente imagen, los test están compuestos de llamadas y scripts. A continuación, podemos ver la estructura del proyecto.

Por ejemplo, en el testcase de login, lanzamos una llamada de login y validamos con el script la respuesta devuelta. El script verifica las cabeceras de la respuesta para poder afirmar que disponemos de un token (JWT) tras la autenticación. En el script ValidarLogin podéis observar cómo se accede al API Java de SoapUI para recuperar la repuesta HTTP e incorporar las validaciones correspondientes. ¡Todo ello en 4 líneas!

ValidarLogin
/*
	* Accede a la petición lanzada y revisa la respuesta.
	*
	* Para ello accedemos al API java que nos proporciona SoapUI
	* https://www.soapui.org/apidocs/index.html?overview-summary.html
	*/
	def httpResponseHeaders = context.testCase.testSteps["HTTP Login"].testRequest.response.responseHeaders
	def authorizationHeaders = httpResponseHeaders.get("Authorization")
	/*
	 * Se valida que existe la cabecera y que incluye un token.
	 * Cualquier array vacío, objeto a null en groovy equivale a false (esta característica se llama groovy truth).
	 * Si authorizationHeaders NO existe se genera una excepción con el literal
	 */
	assert authorizationHeaders : "No se recupera la cabecera Authorization"
	/*
	* Podemos acceder a cualquier objecto de un array por el índice
	*/
	assert authorizationHeaders[0].startsWith("Bearer") : "No se recibe un token válido"

En el test de alta de Usuario observamos cómo se realiza primeramente el login y se utiliza el token proporcionado por el login para realizar el alta de un usuario y para recuperar la información del mismo.

Recuperar JWT del Login
def httpResponseHeaders = context.testCase.testSteps["HTTP Login"].testRequest.response.responseHeaders
	def authorizationHeaders = httpResponseHeaders.get("Authorization")

	// Se valida que existe la cabecera y que incluye un token.
	assert authorizationHeaders : "No se recupera la cabecera Authorization"

	// Se modifica la petición de alta y se incorpora el token que nos ha devuelto el login
	def headers = testRunner.testCase.testSteps["HTTP Get Usuario"].getHttpRequest().getRequestHeaders()
	headers["Authorization"] = authorizationHeaders;
	testRunner.testCase.testSteps["HTTP Get Usuario"].getHttpRequest().setRequestHeaders(headers)

En el script ValidarListado podemos acceder al JSON devuelto en la consulta de un usuario y navegar por el resultado de una forma sencilla.

ValidarListado
import groovy.json.JsonSlurper
	/*
	 * Intentamos acceder al código de la respuesta HTTP
	 */
	def httpResponseHeaders = context.testCase.testSteps["HTTP Get Usuario"].testRequest.response.responseHeaders
	def httpStatus = httpResponseHeaders["#status#"]

	// Se valida que el servicio devuelve un código HTTP 200 -> OK.
	assert httpStatus == ['HTTP/1.1 200 '] : "No ha podido recuperar el listado de usuarios"

	// Se recupera los datos de la respuesta
	def responseBody = context.expand('${HTTP Get Usuario#Response}')
	def jsonResponse = new JsonSlurper().parseText(responseBody)

	// Se valida el alta
	assert jsonResponse.id : "No existe el atributo id"
	assert jsonResponse.username == "daenerys" : "El usuario no existe"

Para concluir con el script de limpieza restauramos el estado inicial eliminando el usuario dado de alta accediendo por JDBC directamente a la base de datos. ¡ Imaginad que tuviésemos que realizar este mismo script con Java !

Limpieza BD
import groovy.sql.Sql;
	import java.sql.*;

	//Se establece una conexión JDBC a la BD
	def con = Sql.newInstance("jdbc:h2:tcp://localhost/~/demo-jwt", "sa","","org.h2.Driver");

	// Se ejecuta el comando SQL
	con.execute("DELETE FROM USUARIO WHERE USERNAME = 'daenerys'")

	// Se cierra la conexión
	con.close()

7. Conclusiones

Aunque una parte de las validaciones desarrolladas se pueden realizar utilizando directamente los formularios de SoapUI (puede que haga falta la versión de pago), hemos visto que con Groovy podemos realizar dichas validaciones de una forma muy simple. Groovy permite acceder a todo el ecosistema Java e incorpora funcionalidades que simplifican el desarrollo de los scripts.

Los test funcionales son una pequeña parte de los test a realizar e intentan reproducir la interacción con el usuario. Tened en cuenta que son frágiles en la medida que cualquier cambio externo (cambio en la password del usuario, modificación en la base de datos…etc.) rompe dicho test pero son muy útiles para verificar que todas las piezas encajan. Además, con el plugin para Maven podéis incorporar estos test al ciclo de vida de Maven para que se ejecuten en el momento y entorno adecuado.

Espero que os sirva e incentive el uso de test en vuestros desarrollos.


8. Referencias

GIF animado con Photoshop

$
0
0

Aprende a crear de manera sencilla y rápida GIFs animados con Photoshop que le darán más vida a tus publicaciones.

Índice de contenidos


1. Introducción

A la hora de publicar en vuestras redes sociales, lo ideal es acompañar la publicación con algún recurso visual que la haga más atractiva. En este tutorial aprenderemos a generar de una manera muy sencilla GIFs animados utilizando el programa Photoshop (versión CS3 o superior).


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: portátil MacBook Pro 15′ (2,5 Ghz Intel i7, 16GB).
  • Sistema operativo: MacOS Sierra 10.12.
  • Adobe Photoshop CC Versión 2017.1.1

3. Medios necesarios

Para generar este tipo de GIFs necesitaremos una serie de imágenes estáticas, si queremos conseguir, por ejemplo, el efecto de un dibujo animado necesitaremos modificar ese dibujo y sacar una imagen de cada uno de los movimientos (es algo complejo que da para escribir otro tutorial, por lo que si lo queréis solo tenéis que pedirlo ;)).

En este caso, he utilizado estas imágenes libres y gratuitas de la página “Picjumbo” y la técnica de duotono que expliqué en mi último tutorial “Fotografía duotono con Photoshop”:


4. Línea de tiempo

Para empezar abrimos nuestro programa Photoshop con una de las imágenes y convertimos la imagen, que por defecto nos sale como fondo y bloqueada, en una capa normal pulsando encima con el botón derecho-capa a partir de fondo.

Posteriormente incluimos el resto de las imágenes en forma de capas (es importante que las imágenes que queramos incluir en el GIF tengan todas el mismo tamaño):


Una vez hecho esto nos vamos a la barra de herramientas Ventana-Línea de tiempo (Ventana-Animación para versiones anteriores de Photoshop) y en la parte inferior de la pantalla pulsamos en la flecha que sale al lado de “Crear línea de tiempo de vídeo” y la cambiamos por “Crear animación de cuadros”.

Una vez cambiado pulsamos encima de la frase y nos aparecerá lo siguiente:


5. Crear el GIF

Una vez llegados a este punto sólo necesitamos darle forma a nuestro GIF, para ello en primer lugar ocultamos todas las capas dejando visible la primera imagen de nuestro GIF.

Después, en la barra inferior pulsamos el icono de duplicar cuadro:


Pulsamos el icono hasta tener tantos cuadros como imágenes va a tener nuestro GIF, en este caso, cuatro.

Una vez hecho esto pulsamos en el cuadro número 2, que sería la segunda imagen del GIF y en la paleta capas hacemos visible la segunda imagen pulsando en el ojo.

Hacemos esto con el resto de cuadros hasta obtener lo siguiente:


El siguiente paso es decidir cuánto tiempo queremos que dure cada imagen. Para ello debajo de cada cuadro pinchamos en el recuadro de segundos y elegimos, en este caso 0,5.

Una vez hecho esto, solo tendríamos que pulsar debajo de los cuadros dónde pone “una vez” y seleccionamos “infinito” para que la animación se reproduzca en bucle.

Si queremos probar el resultado, pulsamos en el “play”.


6. Exportación

Para exportar el archivo en formato .gif nos vamos a Archivo-Exportar-Guardar para Web:


Para elegir la ubicación donde guardarlo pulsamos en “Guardar…” ya que si le damos a “Hecho” lo guarda en la última ubicación por defecto.

Y con ésto tendríamos el resultado:


7. Vídeo explicativo

Si aún te queda alguna duda, puedes ver el proceso paso a paso en el siguiente vídeo:

PWA y Services Worker en Angular

$
0
0

Índice de contenidos

  • 6. Conclusiones
  • 7. Referencias

  • 1. Introducción

    Actualmente está en auge las aplicaciones web progresivas (PWA), ¿pero qué son? En este tutorial explicaremos qué son las PWA, cuáles son los pasos a seguir para convertir tu aplicación angular en PWA y cuáles son las ventajas de esta forma de hacer aplicaciones webs. Ademas profundizaremos en los service workers y veremos cómo podemos configurarlos de forma manual.

    2. Entorno

    El tutorial está escrito usando el siguiente entorno:

    • Hardware: Portátil MacBook Pro 17′ (2,5 GHz Intel Core i7, 16GB DDR3).
    • Sistema Operativo: Mac OS Sierra 10.12.6
    • Entorno de desarrollo: visual studio code

    3. ¿Qué son las PWA?

    Son aplicaciones web que tienen las siguientes características:

    • Son progresivas, esto es que funcionan para cualquier usuario en cualquier navegador.
    • Son responsive, se adaptan a cualquier pantalla, ya sea móvil, tablet o pc.
    • Funcionan sin conexión.
    • Pueden ser instaladas en el móvil.
    • Se puede acceder directamente a ellas a través de una url.
    Desventajas
    • Solo funcionan en Chrome.
    • Su funcionamiento no puede llegar a ser tan fluido como las apps nativas.
    • Las aplicaciones PWA deben ser SPA
    • La aplicación debe tener una primera conexión para cargar los datos, después ya puede trabajar en offline, porque ya se habrá cargado la caché.

    4. PWA en Angular

    En angular el uso de PWA es realmente sencillo. Solo tenemos que instalar:

    @angular/service-worker:

    npm install @angular/service-worker --save

    Y generar el archivo de manifiesto (manifest.json)

    {
        "short_name": "BigFood",
        "name": "BigFood",
        "icons": [
    
          {
            "src":"/assets/icon512.png",
            "sizes": "512x512",
            "type": "image/png"
          }
        ],
        "start_url": "index.html",
        "background_color": "#FBFBFB",
        "theme_color": "#022E63",
        "display": "standalone",
        "orientation": "portrait"
      }

    El archivo de manifiesto debe tener los siguientes flags:

    • short_name:es el nombre que se mostrará en el home de tu móvil, junto al icono
    • icons: debe tener al menos uno con un tamaño de 512×512, este icono será el acceso a tu aplicación. También se usa cuando carga la aplicación, como icono de bienvenida.
    • start_url:aquí debemos poner la entrada a la aplicación, en nuestro caso index.html
    • theme_color:es el nombre que se mostrará en el home de tu móvil, junto al icono
    • background_color:es el color de fondo de la aplicación
    • display:define la preferencia de presentación, para PWA debe ponerse en standalone, así nos permite crear lanzador en nuestro home del teléfono. .
    • orientation:portrait (vertical) o landscape (horizontal).

    Para activar el service worker en Angular, hay que poner el flag service-worker a true en el fichero angular-cli.json.

    "apps": [
        {
          "root": "src",
          "outDir": "dist",
          "assets": [
            "assets",
            "favicon.ico",
            "manifest.json",
            "service-worker.js"
          ],
          "index": "index.html",
          "main": "main.ts",
          "polyfills": "polyfills.ts",
          "test": "test.ts",
          "tsconfig": "tsconfig.json",
          "testTsconfig": "tsconfig.json",
          "prefix": "app",
          "serviceWorker": true,
          "styles": [
            "styles.css"
          ],

    O directamente ejecutando, sobre el directorio raíz del proyecto

    ng set apps.0.serviceWorker=true

    Una vez realizada la build, angular-cli añade sw-register.xxxxx.bundle.js al index.html

    Se generan los ficheros:

    • sw-registres.xxxx.bundle.js que es el script de instalación del service worker.
    • worker-service-min.js
    • ngsw-manifiest.json

    4.1. El ngsw-manifest


    4.1.1. Contenido estático y externo

    Con static nos define las urls estáticas seguidas por el hash. Este fichero se genera al realizar la build.

    {
      "static": {
        "urls": {
          "/polyfills.dd172c3cbe11ef73974a.bundle.js": "7152d90bc05d5e4b0bcc88a6f5c963fcb864327a",
          "/main.b7bbd0fb1243d2926564.bundle.js": "51d6474180bb1c9981b23e9fc1d14b095fd92837",
          "/sw-register.6819cd2c5fa25470ecf2.bundle.js": "50d549edfbd188ed199d8d0b079afa593202e3ea",
          "/vendor.89bdadb1264db70e13e2.bundle.js": "ad57638b4892001e9f8065ce820b41048162ab2d",
          "/inline.8826fad66f99f9e4e1d3.bundle.js": "c5d716befeb16d6347415f64b0c6e06bba88a31e",
          "/styles.c75263eca8421e315975.bundle.css": "e5afde1ebe01d20cc2b2fc35fc6a75d4cd5e7ac3",
          "/assets/404.png": "9e8a41400f8c9eaa0a76e1c3cd6b76a393531d2b",
          "/assets/activity-icons-md.png": "e16b02a7a51f715161c25aeeb6be6f44e7def44e",
          "/assets/activity-icons-sm.png": "067f03197440f0b34a22c5dddfd404428c94eb44",
          "/assets/activity-icons.png": "d330226bd16b81810dba8ff3fb43bdcc796732e3",
          "/assets/fitness.png": "ee4b4e038ce4a7079e3605277d318fe631c6293e",
          "/assets/food.png": "5796b0c1ec43db7438fcad3d87df61b895c12978",
          "/assets/google.png": "9ca264564e0c561259061a451c8c9cb5534fae04",
          "/assets/hombre.png": "81711b04ebd73263bea8958d81704aa42b560fdf",
          "/assets/icon-white.png": "800cbf70225fef25dc8bb0623c9ddfb554000426",
          "/assets/icon.png": "34b1b50a5d7e1810fceec47d4e99f2039d74bec0",
          "/assets/icon144.png": "b4e5876b7a45dd4395e5fb03d6616bca0d6173ad",
          "/assets/icon192.png": "a3b3b97f897b402ba0ed19b550fb12233e5fe3a2",
          "/assets/icon3.png": "dcd02215ba3d10c0ad0ad0f0d3c801e61ce9f2da",
          "/assets/icon512.png": "4abce2802be8fb58d50c9c084556e6e3cb1ceb68",
          "/assets/icons-pack.jpg": "ce16b0d295350a7b1dc87297aee5f0b4db6e2d62",
          "/assets/mujer.png": "9201085c4f442418fa31735b14ef4ccd2cde2a13",
          "/assets/offline.png": "ab450a496257366c5bebd2cbd0d126b385bd11b1",
          "/favicon.ico": "eff98a58c41f6d64d5b9287e0e33a29d952639f0",
          "/manifest.json": "cad6d47e96d72e3fc265e1ca628fdb69f19033de",
          "/index.html": "46e652b9b25adfc15ca82ad200becb907397d295"
        },
        "_generatedFromWebpack": true
      }
    }

    Si tienes contenido externo, el archivo ngsw-manifest vendrá con esta sección:


    4.1.2 Routing

    Service worker solo dispone de los recursos almacenados en la caché, si deseamos añadir las rutas de nuestra aplicación tendríamos que instalar:

    npm install sw-precache

    Después creamos sw-precache-config.js en la carpeta src y configuramos:

    • staticFileGlobs: le decimos donde están los archivos estáticos
    • navigateFallback: si se intenta acceder a una ruta que no existe, se le redirige a la ruta que se informe aqui
    • root: donde esta los ficheros generados con la build
    • stripPrefix: elimina la cadena especificada del comienzo de las URL, de la ruta de acceso, en tiempo de ejecución.
    module.exports = {
      staticFileGlobs: [
        'dist/**.html',
        'dist/**.js',
        'dist/**.css',
        'dist/assets/**.png',
        'dist/vendor.**.bundle.js'
      ],
      root: 'dist',
      stripPrefix: 'dist/',
      navigateFallback: '/index.html',
      runtimeCaching: [
        {
          urlPattern: /^(https\:)(\/\/)([^\:\/]*)(\:\d{1,5})?(\/)([^\/\?\&\#\:]*)?\/?([^\/\?\&\#\:]*)?\/?([^\/\?\&\#\:]*)?\/?([^\/\?\&\#\:]*)?\/?([^\/\?\&\#\:]*)?\/?([^\/\?\&\#\:]*)?\/?([^\/\?\&\#\:]*)?\/?([^\/\?\&\#\:]*)?\/?(.*)$/,
          handler: 'cache-first',
        },
        {
          urlPattern: /\/food-list\//,
          handler: 'cache-first',
        },
        {
          urlPattern: /\/profile\//,
          handler: 'cache-first',
        },
        {
          urlPattern: /\/daily\//,
          handler: 'cache-first',
        },
        {
          urlPattern: /\/progress\//,
          handler: 'cache-first',
        },
    
      ]
    };

    4.2. Lo vemos en DevTools

    En devTools de Chrome podemos ver como se registra el service worker

    También vemos las cachés que da de alta

    Con esto finalizamos la configuración del service worker en Angular y hemos convertido nuestra app en PWA.

    Ahora, si deseamos realizar cambios en el service worker o personalizarlo a nuestro gusto, tendríamos que quitar toda esta configuración y hacerlo a mano. En los siguientes puntos, quitaremos el automático de Angular y os mostraremos la forma de configurar nuestro service worker de forma manual, por lo que conseguiremos un un mayor control de las peticiones y las cachés.


    5. Profundizando en los Services Worker

    Los services worker son secuencias de comandos que se ejecutan en segundo plano en el navegador. La función principal es la capacidad de interceptar y manejar solicitudes de red, incluida la administración de un caché. Donde el service worker se apoya para tener la información disponible en caso de:

    • Conexión baja: también conocida como Lie-fi, que hace referencia a ese estado donde la conexión es muy débil como para obtener información, pero sigue siendo más que no tener conexión, por tanto intenta conectar… conectando…así que esperas…, pero nunca conecta… 🙁
    • Sin conexión

    Con los service workers mejoramos la experiencia de usuario, haciendo que siempre tenga disponible la información. Si bien habrá veces que no sea información actualizada al segundo, pero en cuanto la aplicación disponga de conexión nuevamente, actualizará la página.

    La anterior app estaba hecha con el service worker de Angular, aquí como lo vamos a hacer de forma manual, creamos otra app. Para bajaros el código, ejecutad este comando:

    git clone https://github.com/Kiketic/marvelAngularServiceWorker.git

    5.1. Ciclo de vida

    El ciclo de vida de los service worker consta de instalación, activación, Idle (espera), fetch/message y terminado

    Mediante el siguiente el script llamamos a nuestro service worker para que comience con su instalación.

    if ('serviceWorker' in navigator) {
          navigator.serviceWorker.register('/sw.js').then(function (reg) {
    
            if (reg.installing) {
              console.log('Service worker installing');
            } else if (reg.waiting) {
              console.log('Service worker installed');
            } else if (reg.active) {
              console.log('Service worker active');
            }
    
          }).catch(function (error) {
            // registration failed
            console.log('Registration failed with ' + error);
          });
        }

    5.1.1. Instalación

    Al iniciar el serviceworker se debe registrar, para ello realiza la instalación, aquí es donde se puede almacenar los archivos estáticos en la caché.

    var staticCacheName = 'marvel-v3'
    self.addEventListener('install', function(event) {
      event.waitUntil(
        caches.open(staticCacheName).then(function(cache) {
          return cache.addAll([
            '/',
            '/assets/marvel.png',
            'index.html',
            'https://fonts.gstatic.com/s/roboto/v15/2UX7WLTfW3W8TclTUvlFyQ.woff',
            'https://fonts.gstatic.com/s/roboto/v15/d-6IYplOFocCacKzxwXSOD8E0i7KZn-EPnyo3HZu7kw.woff'
          ]);
        })
      );
    });

    5.1.2. Activación

    Después de la instalación, comenzará el paso de activación. Aquí podremos administrar los cachés anteriores. En la siguiente imagen podemos ver el proceso de activación, en él hemos programado un filtro para que coja las cachés antiguas y borre todas las caché que empiecen por “marvel-” y que no sean la versión actual.

    self.addEventListener('activate', event => {
       event.waitUntil(
          caches.keys().then( cacheNames => {
          return Promise.all(
            cacheNames.filter(cacheName => {
              return cacheName.startsWith('marvel-') && cacheName != staticCacheName;
            }).map(cacheName => {
              return caches.delete(cacheName);
            })
          );
        })
      )
    });

    Después de arrancar el service worker se ha instalado y activado


    5.1.3. Fetch

    Siguiendo al proceso de activación, está el proceso fetch, donde el service worker controlará todas las páginas que están a su alcance.

    self.addEventListener('fetch', event => {
      event.respondWith(
        caches.match(event.request).then(response => {
          return response || fetch(event.request);
        }).catch(function() {
          // If both fail, show a generic fallback:
          return caches.match('/offline.html');
        })
      );
    });

    En el proceso fetch hemos optado por programar offline-first, esto es que primero miramos la caché (2), y cargamos lo que tengamos (que hemos guardado en el proceso de instalación), después vamos a Internet a por nueva información(3), y la descargamos(4), finalmente pintamos en el navegador toda la información.

    En el caso de que fallen las dos peticiones, caché y network, podemos mostrar una página informando que no tenemos conexión, esto solo pasará cuando la caché esté vacía y no tengamos red.

    Una vez que se instale la caché ya tendrá una versión con los estáticos seleccionados en el proceso de instalación.

    Existen otras opciones, como por ejemplo ‘network & cache race’, que realiza la petición tanto a la red como a la caché. Si la caché tiene la información solicitada, esta siempre será la que se muestre en el navegador, porque será la que se obtenga antes, por tanto no será información actualizada.

    Hay otra versión, para mí la mejor, pero también más complicada, llamada caché y después network. Esta consiste en que primero el service worker mira en a la caché, recuperamos toda la información cacheada, después va a Internet y consigue toda la información actualizada, la muestra en la página y además actualiza la caché. Así que cada vez que hace el fetch, actualiza la caché, esto nos permite tener la información actualizada, incluso cuando tengamos cortes de conexión.

    Código a añadir en el index.html

    Código en el ServiceWorker:

    Aquí se le indica al SW que abra la caché, vaya a Internet y finalmente actualice la caché


    5.2. Disponibilidad en Navegadores

    En cuanto a los navegadores, los service worker aún no están estandarizados, pero parece ser que se está apostando por ello, ya solo falta Safari y Edge por completar su implementación.


    6. Conclusiones

    Los service worker son el futuro en cuanto a la plataforma web, pero las aplicaciones PWA aún están muy lejos de sustituir a las nativas. Aunque las PWA funcionen bien, en apps grandes, no llegan a la estabilidad y robustez que tienen las nativas.

    Solo desarrollaría una PWA cuando las condiciones económicas de la empresa o cliente no permitan invertir en dos aplicaciones, una web para los accesos desde portátil o PC y otra nativa para móviles y tablets.


    7. Referencias

    Mejora el SEO de tu aplicación Angular

    $
    0
    0

    En este tutorial vamos a hablar de cómo mejorar el SEO de cualquier aplicación de Angular 4 haciendo server rendering con Angular Universal.

    Índice de contenidos

    1. Entorno

    Este tutorial está escrito usando el siguiente entorno:

    • Hardware: Slimbook Pro 13.3″ (Intel Core i7, 32GB RAM)
    • Sistema Operativo: LUbuntu 16.04
    • Visual Studio Code 1.16.1
    • @angular/cli 1.4.2
    • @angular 4.4.1

    2. Introducción

    Una de las cosas que tenemos que tener muy en cuenta cuando desarrollamos una aplicación single page con la tecnología que sea es que no es muy “seo friendly”, dado que requiere de una carga inicial de la aplicación (el típico loading…) que hace que las arañas no puedan ver el contenido de la aplicación y no puedan indexar correctamente la página.

    Para resolver este problema muchas tecnologías optan por el “server rendering” o renderizado de páginas en el servidor, lo que quiere decir que al cliente que solicita la información se le sirve HTML estático. Hasta ahora en Angular no era algo trivial, y digo hasta ahora porque se ha creado el siguiente repositorio de angular-cli con la solución, tanto de server rendering como prerendering pudiendo hacer uso del lazy loading de módulos:

    https://github.com/angular/universal-starter/tree/master/cli

    Así que si estás a punto de empezar un proyecto con requerimiento de SEO te aconsejo que te bases directamente en este repositorio y si lo que quieres es adaptar un proyecto ya existente, sigue leyendo que a continuación te explico los pasos a seguir partiendo del repositorio anterior.

    3. Vamos al lío

    Así que ya tienes una aplicación con Angular en producción y alguien de negocio se ha acordado ahora del SEO porque pensaba que esto de Angular ya lo traía por defecto… bueno no te preocupes y sigue estos pasos.

    1. Vamos a instalar las dependencias necesarias:

    $> npm run install --save @angular/platform-server
    $> npm run install --save @nguniversal/express-engine
    $> npm run install --save @nguniversal/module-map-ngfactory-loader
    $> npm run install --save-dev cpy-cli

    Las tres primeras son necesarias para la implementación del servidor que va a renderizar las páginas, y la última la vamos a utilizar en la fase de build.

    1. Creamos el fichero server.js en la raíz del proyecto con este contenido:

    require('zone.js/dist/zone-node');
    require('reflect-metadata');
    const express = require('express');
    const fs = require('fs');
    
    const { platformServer, renderModuleFactory } = require('@angular/platform-server');
    const { ngExpressEngine } = require('@nguniversal/express-engine');
    // Import module map for lazy loading
    const { provideModuleMap } = require('@nguniversal/module-map-ngfactory-loader');
    
    // Import the AOT compiled factory for your AppServerModule.
    // This import will change with the hash of your built server bundle.
    const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require(`./dist-server/main.bundle`);
    
    const app = express();
    const port = 8000;
    const baseUrl = `http://localhost:${port}`;
    
    // Set the engine
    app.engine('html', ngExpressEngine({
      bootstrap: AppServerModuleNgFactory,
      providers: [
        provideModuleMap(LAZY_MODULE_MAP)
      ]
    }));
    
    app.set('view engine', 'html');
    
    app.set('views', './');
    app.use('/', express.static('./', {index: false}));
    
    app.get('*', (req, res) => {
      res.render('index', {
        req,
        res
      });
    });
    
    app.listen(port, () => {
    	console.log(`Listening at ${baseUrl}`);
    });

    Es el fichero que va a levantar nuestro servidor de NodeJS en el puerto que especifiquemos en la constante “port”. A destacar el uso LAZY_MODULE_MAP para soportar la carga lazy de módulos secundarios.

    2. Creamos el fichero src/app/app.server.module.ts con el siguiente contenido:

    import {NgModule} from '@angular/core';
    import {ServerModule} from '@angular/platform-server';
    import {ModuleMapLoaderModule} from '@nguniversal/module-map-ngfactory-loader';
    
    import {AppModule} from './app.module';
    import {AppComponent} from './app.component';
    
    @NgModule({
      imports: [
        // The AppServerModule should import your AppModule followed
        // by the ServerModule from @angular/platform-server.
        AppModule,
        ServerModule,
        ModuleMapLoaderModule,
      ],
      // Since the bootstrapped component is not inherited from your
      // imported AppModule, it needs to be repeated here.
      bootstrap: [AppComponent],
    })
    export class AppServerModule {}

    En este fichero estamos importando el módulo principal de nuestra aplicación (AppModule), el módulo para nuestro servidor (ServerModule) y el módulo que permite el “lazy loading” (ModuleMapLoaderModule). Además establecemos en la propiedad bootstrap cuál es nuestro componente principal (AppComponent)

    3. Creamos el fichero src/tsconfig.server.json con el siguiente contenido:

    {
        "extends": "../tsconfig.json",
        "compilerOptions": {
          "outDir": "../out-tsc/app",
          "baseUrl": "./",
          // Set the module format to "commonjs":
          "module": "commonjs",
          "types": []
        },
        "exclude": [
          "test.ts",
          "**/*.spec.ts"
        ],
        // Add "angularCompilerOptions" with the AppServerModule you wrote
        // set as the "entryModule".
        "angularCompilerOptions": {
          "entryModule": "app/app.server.module#AppServerModule"
        }
      }

    Fíjate como en las opciones de compilación de Angular le especificamos la ruta del módulo creado en el paso anterior.

    4. Creamos el fichero main.server.ts con el siguiente contenido:

    import { environment } from './environments/environment';
    import { enableProdMode } from '@angular/core';
    
    if (environment.production) {
      enableProdMode();
    }
    
    export {AppServerModule} from './app/app.server.module';

    Aquí indicamos el export a la clase AppServerModule que creamos anteriormente.

    5. Editamos el fichero app.module.ts para hacerlo compatible con Universal añadiendo la función .withServerTransition() y especificando un ID de aplicación, que puede ser el que se nos ocurra. En la práctica es dejar el fichero tal cual lo tengas y añadir al módulo BrowserModule lo siguiente:

    BrowserModule.withServerTransition({appId: 'angular-app'}),

    6. Añadimos una nueva app llamada “server” en .angular-cli añadiendo lo siguiente a la propiedad “apps”

    ...
    ,
        {
          "name": "server",
          "platform": "server",
          "root": "src",
          "outDir": "dist/dist-server",
          "assets": [
            "assets",
            "favicon.ico"
          ],
          "index": "index.html",
          "main": "main.server.ts",
          "test": "test.ts",
          "tsconfig": "tsconfig.server.json",
          "testTsconfig": "tsconfig.spec.json",
          "prefix": "app",
          "styles": [
            "styles.css"
          ],
          "scripts": [],
          "environmentSource": "environments/environment.ts",
          "environments": {
            "dev": "environments/environment.ts",
            "prod": "environments/environment.prod.ts"
          }
        }
    ...

    Este paso es muy importante y nos permite tener los dos tipos de aplicaciones en un mismo proyecto, el “normal” para desarrollar del modo común y el “server” para que nuestra aplicación se pueda servir desde el servidor en vez de renderizarse en cliente.

    Especificamos un nuevo directorio de salida y le indicamos que haga uso de los ficheros que hemos ido creando: main.server.ts y tsconfig.server.json

    7. Añadimos nuevos script al fichero package.json

    {
    ...
    "build:universal": "ng build --prod && ng build --prod --app 'server' --output-hashing=false && cpy ./server.js ./dist",
    "serve:universal": "npm run build:universal && cd dist && node server"
    ...
    }

    8. Probamos el resultado

    Hechas estas configuraciones es momento de arrancar nuestra aplicación para comprobar el resultado. Para ello ejecutamos:

    $> npm run serve:universal

    Terminado el proceso nos dirá que la aplicación está corriendo el puerto 8000 o el que hayamos especificado en la constante port del fichero server.js

    Nos conectamos a esta URL y el efecto más inmediato de que todo ha ido bien es que no vemos el típico “Loading…” (o el contenido que tengamos entre las etiquetas del selector principal en el index.html) cuando la aplicación carga, ni tan siquiera cuando forzamos un refresco de pantalla. Además si cargamos módulos secundarios con “lazy loading” tenemos que ver que solo se descargan cuando se solicitan.

    4. Conclusiones

    Como ves, uno de los puntos menos fuertes de las SPAs, el equipo de Angular lo ha solucionado de una forma elegante y casi transparente al desarrollador. Seguro que en futuras versiones de angular-cli pondrán el típico flag para ejecutar todos estos pasos de configuración de forma automática.

    Así que ahora cuando te planteen el tema del SEO con una aplicación de Angular ya no te tienes que echar a temblar y ya puedes decir, “por supuesto el framework lo soporta” 🙂

    Recordad que esta técnica y otras muchas más las encontraréis en la guía práctica de Angular y también ofrecemos cursos in-house y online.

    Cualquier duda o sugerencia en la zona de comentarios.

    Saludos.

    HTTP2 para Dummies

    $
    0
    0

    En este artículo desgranaremos los essentials que tienes que saber del nuevo protocolo de comunicación HTTP/2, además de recursos interesantes que te permitirán adaptarte a este nueva forma de intercambio de información entre cliente-servidor de una forma más sencilla.

    Índice de contenidos

    1. Introducción

    La creciente tendencia de la cantidad de usuarios que utilizan Internet, y la intención de mejorar el tiempo y rendimiento de los protocolos de intercambio de información entre cliente y servidor web, han llevado a la concepción, después de casi 20 años desde que saliera la versión 1.1 de HTTP, a la nueva versión del protocolo de aplicación: el famoso HTTP/2.

    2. ¿Qué es HTTP/2

    HTTP/2 es el nuevo protocolo binario de aplicación (OSI), el cual dispone de la misma semántica que su predecesor, la versión HTTP/1.1, lo que quiere decir que tanto los verbos, así como las cabeceras seguirán funcionando de manera transparente.

    3. Características principales

    Las principales características que hacen que HTTP/2 resulte interesante son:

    • Protocolo binario: A diferencia de su predecesor que estaba basado en texto, HTTP/2 utiliza directamente el lenguaje de las máquinas (binario) para intercambiar la información. Esto hace que sea mucho más sencillo de interpretar, menos propenso a errores, y más rápido.
    • Multiplexación: HTTP/2 utiliza identificadores únicos en las tramas binarias que se envían/reciben con el objetivo de poder aplicar multiplexación en la información y poder gestionar contenidos de distinto tipo con muchos menos recursos.
    • Una única conexión: HTTP/2 es capaz de gestionar el intercambio de información entre cliente-servidor utilizando una única conexión. Esto hace que sea especialmente útil para ahorrar recursos, y agilizar el proceso de intercambio de información.
    • Compresión de cabeceras: HTTP/2 introduce por primera vez la compresión de cabeceras de forma nativa, con el objetivo de intercambiar la menor cantidad de información posible, y poder reducir al máximo la latencia en lo que a cabeceras se refiere.
    • Servicio ‘server push’: HTTP/2 es capaz de realizar push a la cache de los navegadores a través de su conexión binaria de aquellos recursos que el navegador va a necesitar, antes si quiera de que éste haya podido interpretar el HTML de la página que le ha sido servida.

    4. ¿Cómo establece una conexión HTTP/2 nuestro navegador?

    Tenemos que tener en cuenta que, a pesar de que en la RFC del protocolo HTTP/2 no es obligatorio el uso de cifrado durante la conexión entre cliente-servidor, hay que decir que no existe a día de hoy ningún navegador que soporte HTTP/2 sin cifrado.

    Es por ello, por lo que en este artículo se explicará como se realiza la conexión HTTP/2 de la forma más habitual: Utilizando la extensión ALPN (Application-Layer Protocol Negotiation) de TLS (Transport Layer Security), la cual es ya el estándar recomendado de cifrado de la conexión soportado por prácticamente todos los navegadores.

    En este sentido, y si disponemos de un navegador que soporte HTTP/2, si accedemos a un site utilizando la conexión cifrada HTTPS, el navegador lanzará una petición “ClientHello” del protocolo TLS1.2, en el que se explicitará en la extensión ALPN que el navegador soporta HTTP2 (Protocolo h2)

    Paso 1/2: ClientHello del TLS1.2

    Seguidamente, el servidor responderá con el mensaje “ServerHello” del protocolo TLS1.2, en el cuál, aprovechándose de la misma extensión ALPN, se le confirmará al cliente, que el servidor utilizará HTTP/2 para establecer la comunicación (Protocolo h2.)

    Paso 2/2: ServerHello del TLS1.2

    A partir de aquí, la conexión HTTP/2 se habrá cerrado y el servidor comenzará a enviar tramas de datos binarios al navegador, que será interpretado de forma conveniente.

    5. ¿Qué navegadores soportan HTTP/2?

    Para conocer qué navegadores soportan HTTP/2, podemos utilizar el siguiente enlace: Navegadores que soportan HTTP2

    También podemos consultar directamente la tabla de navegadores en la siguiente imagen extraída de la web anterior:

    6. Plugins interesantes

    Como herramienta de ayuda a desarrolladores, existen plugins que permiten conocer de primera mano si nuestra conexión está utilizando el protocolo HTTP/2.

    En este sentido, el llamado “HTTP/2 and SPDY indicator” plugin, puede sernos de gran ayuda.

    7. HTTP/2 y Java

    Tenéis que saber que, puesto que HTTP/2 es un protocolo relativamente nuevo (2015), hasta la versión 9 de la JDK de Java no se dispone de soporte nativo para el protocolo ALPN.

    Eso quiere decir que, si queremos utilizar conexiones ALPN utilizando JDK 1.8 o inferior, es posible que nos tengamos que apoyar en librerías externas que arranquen con nuestra aplicación, dependiendo del contenedor de servlets o servidor de aplicaciones que utilicemos.

    En el siguiente enlace, podemos echar un vistazo rápido a aquellas implementaciones de servidores basadas en Java que actualmente ya soportan el protocolo HTTP/2: Implementaciones de servidores HTTP/2

    8. Conclusiones

    Con este artículo hemos podido desgranar las características más esenciales que debemos conocer de HTTP/2, pudiendo ser éste un punto de partida para facilitar nuestra labor de desarrollo con este nuevo protocolo y los nuevos contenedores de servlets / servidores de aplicaciones que soportan dicho protocolo.

    9. Referencias

    Aumenta el rendimiento de tus tests de integración con BBDD usando un pool de conexiones dbcp2 con BasicDataSource de Apache

    $
    0
    0

    Pasa tus tests de integración con base de datos a la velocidad de la luz con dbcp2 BasicDataSource. Si estás utilizando para conectar a la base de datos DriverManagerDataSource de Spring JDBC estás perdiendo tiempo y recursos de forma exponencial.

    Índice de contenidos

    1. Introducción

    Puede que “Han Solo” haya recorrido el “Corredor Kessel” en menos de 12 segundos, pero nadie será más rápido que tú ejecutando los tests de integración contra la base de datos si sigues los pasos de este tutorial.

    2. Entorno

    El tutorial está escrito usando el siguiente entorno:

    • Hardware: Portátil MacBook Pro Retina 15′ (2.3 Ghz Intel Core I7, 16GB DDR3).
    • Sistema Operativo: Mac OS Sierra 10.12.5
    • Entorno de desarrollo: Eclipse Oxygen
    • Apache Maven 3.3.0
    • Java 8
    • Spring 4

    3. DriverManagerDataSource de Spring JDBC

    Es muy posible que hasta ahora usaras una clasecita que tienen en Spring JDBC (DriverManagerDataSource.java), muy utilizada por verse en muchos ejemplos de Internet, para conectar con tu base de datos, y ejecutar tus tests de integración, donde pruebas todas tus querys de forma transaccional para que no dependan unos tests de otros. Seguramente tus contextos de tests de Spring tengan una pinta parecida a lo siguiente:

    applicationContext-test.xml
    <bean name="dataSource"
    		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
    		<property name="url"
    			value="jdbc:oracle:thin:@IP:PUERTO:NOMBRE_BBDD"></property>
    		<property name="username" value="Chewbacca"></property>
    		<property name="password" value="MUGRUUUUUUUHMA"></property>
    	</bean>

    Esta es una de las formas más usadas. El problema viene cuando empiezas a leer como funciona lo que estás usando. Os copio una imagen del javadoc.

    Básicamente dice que esta clase no utiliza un pool de conexiones, y que si quieres usar un pool puedes usar alternativas, de hecho te proponen la que te voy a contar en este tutorial.

    4. dbcp2 BasicDataSource de Apache

    ¿Y para que quiero yo un pool de conexiones en los tests?

    Pues para lo mismo que en producción. Los tests, lo que hacen, cada uno de ellos que ejecuta una consulta en BBDD es lo siguiente. Abro conexión, hago cosas, cierro conexión. Y hacen eso por cada test, de forma secuencial. Es decir, que por cada test, estás abriendo y cerrando conexión. Esta operación es una operación muy costosa para la BBDD, y bases de datos tan populares y usadas como Oracle pueden tardar segundos (1-2 segundos) en crear una conexión nueva, con el consiguiente uso de CPU. Todo ello para ejecutar una query, que muy probablemente dure 0.001 segundos.

    La solución es utilizar un pool de conexiones de tal forma que cuando un test termina, libera la conexión, que vuelve al pool, y el siguiente test la reusa, con lo que no gasta tiempo en abrir una conexión nueva. Mira lo fácil que es hacer el cambio.

    applicationContext-test.xml
    <bean name="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
    		destroy-method="close">
    		<property name="url"
    			value="jdbc:oracle:thin:@IP:PUERTO:NOMBRE_BBDD" />
    		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
    		<property name="username" value="OBIWAN" />
    		<property name="password" value="USETHEPOOLLUKE" />
    		<property name="removeAbandonedOnMaintenance" value="true" />
    		<property name="initialSize" value="1" />
    		<property name="maxTotal" value="15" />
    	</bean>

    Además te recomiendo que como los tests se ejecutan en orden secuencial, de 1 en 1, no te vuelvas loco creando conexiones a la BBDD de inicio. Con 1, para la mayoría de casos es suficiente, y te ahorras esos segundos de reutilizar otras conexiones que nunca vas a usar. Por eso pongo initialSize a 1.

    Para poder utilizar esa clase, solo tienes que añadir a tu pom.xml de maven la siguiente dependencia.

    applicationContext-test.xml
    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
    	<dependency>
    		<groupId>org.apache.commons</groupId>
    		<artifactId>commons-dbcp2</artifactId>
    		<version>2.1.1</version>
    		<scope>test</scope>
    	</dependency>

    5. Conclusiones

    Si haces TDD como nosotros en Autentia, vas a acabar con muchos tests. Algunos de esos serán de integración. En nuestro caso, en proyectos grandes podemos llegar a varios miles de tests de integración. Si por cada uno de ellos gastáramos 1 segundo en crear la conexión estaríamos horas esperando a que se terminaran, y no sería efectivo el pasarlos, ni la integración continua en Jenkins. Con este cambio conseguimos pasar esos miles de tests de integración en un par de minutos. Espero que te haya servido este tutorial, y que eleves el rendimiento de tus tests a la enésima potencia.

    Cómo montar un servidor HTTP/2 con Vert.x

    $
    0
    0

    Índice de contenidos

    1. Introducción

    En este tutorial aprenderás como poder crear un servidor HTTP/2 desde cero utilizando el framework Vert.x en pocos sencillos pasos, así como la solución de los problemas más comunes que tendremos que afrontar si queremos arrancar aplicaciones HTTP/2 en Java.

    2. Entorno

    El tutorial está escrito usando el siguiente entorno:

    • Hardware: Portátil MacBook Pro Retina 15′ (2.3 Ghz Intel Core I7, 16GB DDR3).
    • Sistema Operativo: Mac OS Sierra 10.12
    • Entorno de desarrollo: Eclipse Neon
    • Apache Maven 3.3.3
    • Java JDK 1.8.0_u45
    • Google Chrome 61.0.3163.100

    3. Creación del proyecto

    Creamos un proyecto Maven, a través del arquetipo “quick-start-maven”, y eliminamos todo aquello que le sobra (como es el fichero App.java y AppTest.java).

    En este tutorial, únicamente nos vamos a centrar en cómo montar un servidor HTTP2 con Vert.x.

    4. Importación de las dependencias necesarias

    Basándonos en la documentación oficial de Vert.x, añadimos en el fichero ‘pom.xml’ las dependencias necesarias para poder crear y configurar un servidor HTTP2.

    <dependencies>
    		<dependency>
    		  <groupId>io.vertx</groupId>
    		  <artifactId>vertx-core</artifactId>
    		  <version>3.4.2</version>
    		</dependency>
    	</dependencies>

    5. Creación e importación del certificado

    Aunque en la RFC del protocolo HTTP/2 no se explícita que sea obligatorio el uso de conexión cifrada, no existe prácticamente ningún navegador que soporte HTTP/2 sin haber cifrado la conexión, principalmente porque se aprovecha el TLS Handshake como medio para negociar la conexión HTTP/2

    Por tanto, para este tutorial vamos a generar un certificado auto-firmado, el cual utilizaremos más tarde en nuestro servidor HTTP/2.

    Para generar el certificado auto-firmado utilizaremos la herramienta Java KeyTool. Incluiremos directamente el JKS en el src/main/resources del proyecto

    keytool -genkey -keyalg RSA -alias vertx_http2 -keystore keystore_vertx.jks -storepass pass1234 -keysize 2048

    6. Creación y configuración del HTTP/2 Server

    En este apartado se mostrará como crear un servidor HTTP/2 con Vert.x. Hay que tener en cuenta, que para que Vert.x utilice el protocolo HTTP/2, hay que habilitar de forma explícita el uso de ALPN. Será aquí donde se configurará la ubicación del KeyStore generado en el paso anterior.

    VertxHttp2Server.java
    final Vertx vertx = Vertx.factory.vertx();
    
    	final HttpServerOptions options = new HttpServerOptions()
    				  .setLogActivity(true)
    			    .setUseAlpn(true)
    			    .setSsl(true)
    			    .setKeyStoreOptions(new JksOptions().setPath("keystore_vertx.jks").setPassword("pass1234"));
    
    	final HttpServer httpServer = vertx.createHttpServer(options);

    Con este fragmento de código, ya tendremos lista la base para nuestro servidor HTTP2. Vamos ahora a configurar un Handler para las request entrantes

    7. Creación del Handler para las Requests entrantes

    En este tutorial simplemente crearemos la clásica respuesta “Hello World!”, a través del objeto HttpResponse. Para ello, añadiremos el Handler correspondiente a la request de nuestro servidor HTTP2.

    httpServer.requestHandler(httpRequest -> {
    			System.out.println("HTTP Request recibida!");
    			httpRequest.response().end("Hello World!");
    	});

    A continuación pasaremos finalmente a asociar nuestro servidor HTTP2 con un puerto

    8. Configuración del puerto de escucha

    Finalmente, terminamos de codificar nuestro servidor HTTP2, configurando el puerto de escucha donde arrancará el servidor. En este caso, vamos a configurarlo en el puerto 8443.

    Añadiremos además un par de trazas de logging, para saber que el servidor ha arrancado correctamente

    httpServer.listen(8443, result -> {
    				if (result.succeeded()) {
    					System.out.println("Server is now listening!");
    				} else {
    					System.out.println("Failed to bind!");
    				}
    			});

    9. Arranque del servidor con la extension ALPN usando la JDK

    Es posible que cuando intentemos arrancar nuestra aplicación, nos aparezca el siguiente error

    Exception in thread "main" io.vertx.core.VertxException: ALPN not available for JDK SSL/TLS engine
    			at io.vertx.core.net.impl.SSLHelper.resolveEngineOptions(SSLHelper.java:89)
    			at io.vertx.core.net.impl.SSLHelper.(SSLHelper.java:169)
    			at io.vertx.core.http.impl.HttpServerImpl.(HttpServerImpl.java:158)
    			at io.vertx.core.impl.VertxImpl.createHttpServer(VertxImpl.java:268)
    			at io.drodriguezhdez.tutorials.http2.vertx.VertxHttp2Server.run(VertxHttp2Server.java:21)
    			at io.drodriguezhdez.tutorials.http2.vertx.VertxHttp2Server.main(VertxHttp2Server.java:12)

    Esto es debido a que tenemos que tener en cuenta que en este tutorial se ha utilizado la versión 1.8 de la JDK de Java, por lo que para poder arrancar nuestro servidor HTTP2 necesitamos vitaminar de alguna forma las clases de arranque de la JDK, ya que debemos recordar que la version 1.8 de JDK NO tiene soporte nativo para la extension ALPN

    Como en este tutorial vamos a utilizar TLS a través de la JDK, es necesario utilizar una de las librerías de ALPN de Jetty en tiempo de arranque. La elección de la versión de la librería dependerá de nuestra versión de JDK con la que vayamos a ejecutar la aplicación.

    En este caso, como la versión que estoy utilizando es la 1.8.0_u45, mi version de alpn-boot correspondiente es la 8.1.3.v20150130. Podéis consultar todas las correspondencias en la siguiente página: Table 15.1. ALPN vs. OpenJDK versions

    Una vez se disponga de la librería, es necesario añadir la siguiente propiedad a las VM properties de arranque

    -Xbootclasspath/p:<path/to/alpn-boot/library>/alpn-boot-<version>.jar

    Si todo funciona correctamente, al arrancar nuestra aplicación deberemos recibir el correspondiente mensaje de success por consola

    10. Probando el servidor usando Google Chrome

    Para poder probar si nuestro servidor está corriendo en HTTP/2, vamos a utilizar Google Chrome previa instalación de una extensión que nos va a permitir saber si la conexión con el servidor se está realizando en HTTP/2

    Podéis encontrar la extensión en el siguiente enlace: HTTP/2 and SPDY Indicator for Google Chrome

    Finalmente, accedemos a la URL donde está escuchando nuestro servidor HTTP/2, en este caso: https://localhost:8443 y comprobamos que el indicador nos dice que estamos en una conexión HTTP/2

    Y en nuestro log del servidor, podemos comprobar que efectivamente estamos recibiendo peticiones HTTP que estamos manejando sin ningún problema.

    11. Conclusiones

    En este tutorial hemos realizado un ejemplo de cómo implementar un servidor HTTP/2 utilizando Vertx, así como la resolución de los principales problemas de arranque utilizando versiones de Java JDK 1.8 o inferiores.

    12. Contacto y GitHub repository

    ¿Habéis tenido algún que otro problema con el tutorial? Déjame un comentario en este artículo o coméntamelo a través de mi cuenta de Twitter: @drodriguezhdez.

    Echa un vistazo al código del tutorial en mi repositorio de GitHub: Source code del Tutorial.

    13. Referencias


    Modularidad en Java 9 (1/2)

    $
    0
    0

    Los módulos son la principal novedad de Java 9. Un módulo agrupa código y recursos como los JARs tradicionales, pero añade además un descriptor que restringe el acceso a sus paquetes, y describe sus dependencias.

    El proyecto Jigsaw incluye la implementación del sistema de módulos (JSR 376 and JEP 261), y las mejoras (JEPs) relacionadas. Un puzzle Jigsaw es una imagen dividida en cientos de piezas. De igual modo, la plataforma Java y nuestras aplicaciones, pasan en Java 9 a estar compuestos por docenas de módulos.

    En este tutorial veremos

    • Las bases de la encapsulación,
    • los beneficios de la modularidad,
    • cómo escribir un descriptor de módulo,
    • y un ejemplo con varios módulos.

    Contenido

    Encapsulación

    ¿Qué es la encapsulación?

    Encapsular consiste en ocultar los detalles de implementación y proporcionar una interfaz más simple. Esta estrategia es una herramienta universal para reducir la complejidad. Tu coche por ejemplo, te abstrae los detalles mecánicos bajo su capó, y ofrece una interfaz más simple en forma de pedales y volante.

    encapsulación = detalles ocultos + interfaz simplificada

    En informática usamos la encapsulación para construir capas de software de complejidad progresiva. En el nivel más bajo, el ordenador almacena información en forma de ceros y unos. Es un sistema parecido al de las transmisiones Morse de hace siglos. La novedad es que los ordenadores son máquinas programables, donde la información representa no solo texto y números, sino programas que realizan cálculos complejos.

    Las instrucciones más cercanas a la máquina son tan simples como multiplicar dos números, o concatenar cadenas de texto. Sin embargo, al combinarlas creamos macroinstrucciones, cada vez más parecidas al lenguaje natural. La diferencia es notable:

    ScalaEnsamblador
    print("hola mundo")  
    section .data
     hello:     db 'Hello World',10
     helloLen:  equ $-hello
    section .text
    global _start
     _start:
      mov eax,4
      mov ebx,1
      mov ecx,hello
      mov edx,helloLen
      int 80h
      mov eax,1
      mov ebx,0
      int 80h

    En lenguajes de alto nivel las instrucciones se organizan en elementos de complejidad creciente:

    instrucciones > funciones > clases > librerías > frameworks > aplicaciones

    Cada uno de estos elementos tiene un nombre y propósito específico. Esta es una exigencia de nuestra memoria. Cuando algo crece demasiado necesitamos dividirlo y etiquetarlo para poder recordarlo. Usamos nombres como “capa de persistencia”, “cola de mensajes”, etc. Estas abstracciones proporcionan una visión de alto nivel que facilita el razonamiento. Además, los componentes resultantes son reemplazables, y sus posibles defectos están acotados. Es sentido común que el diseño modular es preferible al monolítico.

    En resumen

    • Un programa es complejo porque contiene muchas instrucciones simples.
    • La encapsulación nos permite agruparlas en componentes significativos y operar a alto nivel con ellos.

    Encapsulación en Java 8

    ¿Qué herramientas de encapsulación nos proporciona Java 8?

    • Paquetes
    • Clases, y clases anidadas.
    • Modificadores de acceso
    • JARs que agrupan paquetes relacionados
    • Patrones de diseño que exponen un interfaz y ocultan la implementación
    • Manejadores de dependencias de terceros, como
      • Maven, que gestiona dependencias en tiempo de compilación
      • OSGi, que gestiona dependencias en tiempo de ejecución

    Estas herramientas planteaban problemas:

    • Classpath Hell: el classpath (conjunto de clases cargadas) puede contener clases duplicadas, clases no disponibles, dependencias con diferentes versiones de una misma librería, o cargadores de clases anidados con comportamientos complicados.
    • Las clases cargadas carecen de información sobre su origen.
    • Una vez cargadas, todas las clases están disponibles por reflexión, y carecen de información sobre su origen.
    • El entorno de ejecución contiene la plataforma entera. En Java 8 existen profiles, pero sigue siendo una granularidad muy grande.

    Estos problemas están ligados a la implementación del compilador, el runtime, y la funcionalidad del lenguaje. Para solucionarlos era necesario cambiarlos, y eso ha hecho el proyecto Jigsaw.

    Modularidad en Java 9

    Los módulos de Java 9 mejoran así la plataforma:

    • Encapsulación fuerte. La encapsulación se cumple durante compilación y ejecución, incluso frente a intentos de reflexión.
    • Configuración fiable. El runtime comprueba la disponibilidad de las dependencias antes de lanzar la aplicación.
    • Creación de imágenes que empaqueta la aplicación con una plataforma Java hecha a medida. Esto implica
      • Menores requerimientos de memoria y disco (útil para microservicios y dispositivos pequeños)
      • Mayor seguridad, porque el código implicado es menor.
      • Optimización mejorada (dead-code elimination, constant folding, compression, etc.).
    • Servicios desacoplados sin escaneo del classpath (las implementaciones de un interfaz se indican explícitamente).
    • Carga rápida de tipos. El sistema sabe dónde está cada paquete sin tener que escanear el classpath.
    • Preserva las fronteras establecidas por la arquitectura.

    La encapsulación fuerte implica otros beneficios, como la posibilidad de realizar pruebas aisladas de un módulo, evitar la decadencia del código al introducir dependencias accidentales, y la reducción de dependencias cuando varios equipos trabajan en paralelo.

    ¿Qué es un módulo?

    En el diccionario, un módulo es una parte de algo más complejo. En Java, llamamos módulo a un artefacto que puede contener código, recursos, y metadatos. Los metadatos describen dependencias con otros módulos, y regulan el acceso a los paquetes del módulo.

    El conjunto de ficheros que forman un módulo se agrupa en uno de estos tres formatos

    • Formato explotado. Un directorio que contiene el código fuente, datos, y descriptor de módulo.
    • JAR. Ídem pero empaquetado en un JAR.
    • JMOD. Lo mismo que un JAR, pero además puede contener código nativo.

    Esto es un ejemplo de módulo en formato JAR:

    holamundo.jar
       ├── com
       │   └── ejemplo
       │       └── HolaMundo.class
       └── module-info.class

    Descriptor

    El descriptor de módulo es la meta-información sobre el módulo. Contiene lo siguiente:

    • Nombre del módulo
    • Paquetes expuestos
    • Dependencias con otros módulos
    • Servicios consumidos e implementados

    Los descriptores se escriben en un fichero module-info.java en la raíz del fichero JAR o directorio. Este fichero se compila junto al resto de ficheros Java. Observa que el nombre de fichero no es un identificador legal Java porque contiene un guión. Esto es a propósito para evitar que las herramientas lo confundan con un fichero Java. Es el mismo truco que se usa con package-info.java.

    Este es un fichero module-info.java de ejemplo:

    module ejemplo {
          requires java.util.logging;
          exports com.ejemplo;
      }
    • El módulo se llama ejemplo
    • Depende del paquete java.util.logging
    • Y expone el paquete com.ejemplo

    Cheatsheet

    Un módulo se define con las siguientes palabras clave:

    exports… to

    expone un paquete, opcionalmente a un módulo concreto

    import

    el típico import de Java. Lo normal es usar nombres completos de paquetes en vez de imports, pero si repites mucho un tipo, puede ser de utilidad.

    module

    Comienza la definición de un módulo.

    open

    Permite la reflexión en un módulo.

    opens

    Permite la reflexión en un paquete concreto, para alguno o todos los paquetes.

    provides…with

    Indica un servicio y su implementación.

    requires, static, transitive

    requires indica la dependencia con un módulo. Añade static para que sea requerido durante compilación y opcional durante la ejecución. Añade transitive para indicar dependencia con las dependencias del módulo requerido.

    // nombre del módulo. open permite la reflexión en todo el módulo
      open module com.ejemplo
      {
          // exporta un paquete para que otros módulos accedan a sus paquetes públicos
          exports com.apple;
    
          // indica una dependencia con el módulo com.orange
          requires com.orange;
    
          // indica una dependencia con com.banana. el 'static' hace que la dependencia
          // sea obligatoria durante compilación pero opcional durante ejecución
          requires static com.banana;
    
          // indica una dependencia al módulo com.berry y sus dependencias
          requires transitive com.berry;
    
          // permite reflexión en el módulo com.pear
          opens com.pear;
    
          // permite reflexión en el paquete com.lemon pero solo desde el módulo com.mango
          opens com.lemon to com.mango;
    
          // expone el tipo MyImplementation que implementa el servicio MyService
          provides com.service.MyService with com.consumer.MyImplementation
    
          // usa el servicio com.service.MyService
          uses com.service.MyService
      }

    Hola Mundo

    classpath

    Este es un hola mundo normal, compilado y ejecutado en el classpath.
    holamundo
    ├── Makefile
    └── src
        └── com
            └── ejemplo
                └── HolaMundo.java
    donde Makefile es
    compile:
    	javac `find src -name "*.java"` -d build
    run:
    	java -cp build com.ejemplo.HolaMundo
    clean:
    	rm -rf build

    modules

    Este es el mismo hola mundo compilado y ejecutado como módulo.
    holamundo
    ├── Makefile
    └── src
        └── holamundo
            ├── com
            │   └── ejemplo
            │       └── HolaMundo.java
            └── module-info.java
    Hay tres cambios que convierten el código en un módulo:
    • Añadimos un descriptor module-info.java en el directorio raíz del código fuente. Puede ser tan simple como module holamundo {}.
    • Movemos todo el código fuente a un directorio con el mismo nombre que dimos al módulo en el descriptor.
    • Compilamos indicando el directorio del código fuente del módulo:
    javac --module-source-path src `find src -name "*.java"` -d build
    Ya solo falta lanzarlo como módulo:
    java --module-path build --module holamundo/com.ejemplo.HolaMundo
    ¿Qué pasa si lo lanzamos una aplicación modular usando el classpath? se ejecuta igualmente. El fichero module-info.java es ignorado porque lleva un guión, y por tanto no cuenta como código java. Puedes probarlo con:
    java -cp build/Holamundo com.ejemplo.HolaMundo

    Para clonar el código de este ejemplo:

    git clone https://github.com/j4n0/holamundo-modules.git

    Descriptor de módulo

    module

    module define el nombre de un módulo.

    module ejemplo {}
    • El nombre de un módulo debe ser un identificador válido en Java o un nombre válido de paquete.
    • El nombre debe ser único. Si hay dos módulos con el mismo nombre en diferentes directorios, solo se usa uno de ellos. Es una buena idea usar nombres inversos de dominio para garantizar que el nombre es único.
    • El nombre no debe coincidir con el de una clase, interfaz, o paquete. No porque cause errores, sino porque sería confuso.

    O al menos, ese es el consejo de Oracle. Si es un módulo privado y usas ejemplo, en vez de com.ejemplo, estará más claro cuál es el módulo y cuál el paquete. Pero si es un API pública, te evitaras colisiones usando el nombre de dominio.

    requires

    requires indica una dependencia a un módulo.

    module ejemplo {
          requires java.logging;
      }

    Ten en cuenta que no están permitidas las dependencias cíclicas durante compilación por varios motivos:

    • Impediría la compilación. Un tipo solo puede compilarse si los tipos de los que dependen ya han sido compilados.
    • No sería un buen diseño. Dos módulos en un ciclo, son en la práctica equivalentes a un único módulo. Normalmente las dependencias de un sistema discurren en un único sentido, de componentes generales a más específicos y no al revés. Coloquialmente hablando, yo uso al martillo, pero el martillo no me usa a mí.

    static

    requires static indica una dependencia obligatoria durante compilación, pero opcional durante ejecución.

    module HelloTest {
          requires static HelloLogger;
      }

    Si el módulo HelloLogger no es accesible en tiempo de ejecución, los intentos de cargarlo devolverán nulo.

    Para modelar una dependencia opcional con reflexión

    try {
          Class clazz = Class.forName("com.example.logger.HelloLogger");
          System.out.println("Using reflection: HelloLogger is " + clazz.getConstructor().newInstance());
      } catch (ReflectiveOperationException e) {
          System.out.println("Using reflection: HelloLogger not loaded");
      }

    Para modelar una dependencia opcional con ServiceLoader

    Optional logger = ServiceLoader.load(HelloLogger.class).findFirst();

    Esto lleva más trabajo. Hay que poner una línea uses en el módulo que carga la instancia:

    module HelloTest
      {
          requires static HelloLogger;
          uses com.example.logger.HelloLogger;
      }

    Y una línea provides en el módulo que proporciona el tipo:

    module HelloLogger {
          exports com.example.logger;
          provides com.example.logger.HelloLogger with com.example.logger.HelloLogger;
      }

    transitive

    requires transitive indica dependencias con las dependencias de un módulo. Es decir, si A→B y B→C, entonces A→C.

    module A {
          requires transitive B;
      }

    Si quieres visualizar las dependencias que fueron requeridas transitivamente pero que no están disponibles, añade el flag -Xlint:exports a javac.

    exports

    exports indica que los tipos públicos de un paquete pueden usarse desde otros módulos.

    module Hello {
          exports com.example.app;
      }

    Decimos que un paquete es legible si está exportado, y que un tipo es accesible si es legible y además es public.

    exports to

    exports to indica que los tipos públicos de un paquete están disponibles pero solo para cierto(s) paquete. En inglés lo llaman “qualified export”.

    module HelloLogger {
          exports com.example.logger to com.example.junit;
      }

    Hay un problema con este enfoque. Si tengo el módulo independiente HelloLogger y añado el qualified export a com.example.junit, estoy acoplando ambos módulos. Los qualified exports están reservados para casos especiales. Por ejemplo, el paquete sun.* estaba pensado para uso privado, pero una vez en el classpath cualquiera podía usarlo. Para programas que dependen de él podemos hacerlo de nuevo accesible con un qualified export, aunque la mejor solución es sustituirlo por sus alternativas.

    Ejemplo

    Teniendo esto en cuenta, en JDK 9 el significado de public depende de si el tipo está exportado o no.

    ¿es en un paquete exportado?

    public es accesible

    sí pero solo para ciertos módulos

    public es accesible para esos módulos

    no

    public es legible pero no accesible

    Supongamos un módulo “ejemplo” con el siguiente contenido:

    // Clyde.java
      package com.ejemplo.bar;
      public class Clyde {}
    
      // Inky.java
      package com.ejemplo.foo;
      class Inky {}
    
      // Pinky.java
      package com.ejemplo.foo;
      public class Pinky {}
    
      // module-info.java
      module ejemplo {
          exports com.ejemplo.foo;
          exports com.ejemplo.bar to holamundo;
      }

    Si un módulo requiere este módulo ejemplo

    • Clyde es accesible sólo para el módulo holamundo
    • Inky es legible pero no accesible
    • Pinky es accesible

    Además, Pinky y Clyde pueden usarse con cualquier tipo del módulo al que pertenecen, de acuerdo con los modificadores de acceso.

    open

    open permite la reflexión en un módulo o en un paquete concreto.

    Llamamos deep reflection a la reflexión de tipos privados en Java. Por defecto, en el sistema de módulos sólo es posible hacer reflexión de métodos públicos pertenecientes a módulos exportados. Si el elemento es privado salta una excepción de tipo InaccessibleObjectException, y si es público pero no exportado salta una excepción de tipo IllegalAccessException.

    Para permitir deep reflection de todos los miembros de un módulo usa open.

    open module HelloLogger {
          exports com.example.logger;
      }

    Para permitir deep reflection de un paquete de un método usa opens.

    module HelloLogger {
          exports com.example.logger;
          opens com.example.logger;
          opens com.example.logger to junit;
      }

    Para permitir deep reflection de un paquete de un módulo de terceros añade el flag --add-opens al comando java. Por ejemplo, para que el paquete myframework realice deep reflection en el paquete java.lang del módulo java.base escribe:

    --add-opens java.base/java.lang=myframework

    Para realizar reflexión en módulos exportados

    try {
          Field f = HelloLogger.class.getDeclaredField("isEnabled");
          f.setAccessible(true);
      } catch (NoSuchFieldException e) {
          fail("Reflecting private field: " + e.toString());
      }
    
      try {
          Method m = HelloLogger.class.getDeclaredMethod("_debug", String.class);
          m.setAccessible(true);
          Object target = HelloLogger.class.getConstructor().newInstance();
          m.invoke(target, "");
      } catch (ReflectiveOperationException e) {
          fail("Reflecting private field: " + e.toString());
      }

    Para realizar reflexión en módulos no exportados

    Es lo mismo que con módulos exportados, pero tienes que añadir un --add-opens desde línea de comando y usar Class.forName("com.example.whatever") en vez de referirte al nombre del tipo.

    Servicios

    provides with

    provides with indica que el módulo proporciona un tipo que implementa el servicio.

    En Java 8, para implementar un servicio sin exponer la implementación podemos usar una factoría, o escanear el classpath para buscar el servicio. El sistema de módulos de Java 9 permite declarar los servicios explícitamente en los descriptores, lo cual es más rápido que un escaneo del classpath. Un servicio de Java 9 se compone de tres elementos: servicio, proveedor, y consumidor.

    Para declarar el tipo servicio puedes usar un interfaz, clase abstracta, o clase Java. Este tipo debería estar en un paquete accesible por consumidor y proveedor. En este ejemplo defino un interfaz público y expongo su paquete en el descriptor:

    package algorithms.sort;
      public interface Sortable {
          <T extends Comparable> void sort(T[] values);
      }
    module algorithms.sort {
          exports algorithms.sort;
      }

    Para registrar el proveedor hay que

    • Requerir el módulo que contiene la definición del servicio
    • Registrar la implementación del servicio usando la formula provides SERVICE with IMPLEMENTATION; Para los nombre de servicio e implementación tienes que usar el nombre completo, incluyendo el paquete.

    El siguiente ejemplo requiere el módulo algorithms.sort, donde está el interfaz que he definido antes, y registro la implementación algorithms.sort.BubbleSort. Observa que la implementación del servicio no necesita ser expuesta.

    module sortProvider {
          requires algorithms.sort;
          provides algorithms.sort with algorithms.sort.BubbleSort;
      }

    Para implementar el proveedor el tipo debe tener un constructor sin argumentos, y/o un método public static provider() que devuelva el tipo del servicio. Por ejemplo,

    class BubbleSort implements Sortable {
          public <T extends Comparable> void sort(T[] values) {
              Arrays.sort(values); // imagina que esto es un bubblesort
          }
          BubbleSort(){}
      }

    uses

    uses indica que el módulo usa un servicio dado.

    Para registrar el consumidor del servicio usa la palabra clave uses.

    module consumer {
          requires algorithms;
          uses algorithms.sort.Sortable;
      }

    Para cargar el servicio usa el API ServiceLoader. Hay dos modos de hacerlo: lookup o stream.

    // lookup
      ServiceLoader loader = ServiceLoader.load(Sortable.class);
      Sortable sortable = loader.iterator().next();
      sortable.sort(values);
    
      // stream
      Stream<ServiceLoader.Provider> providers = ServiceLoader.load(Sortable.class).stream();
      final Optional<ServiceLoader.Provider> sortable2 = providers.findFirst();
      sortable2.get().get().sort(values2);

    Cualquiera de las dos maneras lleva solo unas pocas líneas, pero serían demasiadas si tuvieras que usarlo a menudo. Otra manera más es devolver la lista de implementaciones desde el propio interfaz. Por ejemplo,

    public interface Sortable {
        <T extends Comparable> void sort(T[] values);
        static Iterable getSortables() {
            return ServiceLoader.load(Sortable.class);
        }
      }

    Instanciarlo el Provider del servicio es útil si queremos obtener información del tipo que lo implementa, antes de instanciar el servicio en sí.

    ¿Porqué deberíamos instanciar un servicio de JDK 9 en vez de usar una factoría? Los servicios son más rápidos de instanciar porque tienen un descriptor explícito con el que encontrarlos. Esto permite añadir implementaciones en forma de módulos sin escanear el classpath cada vez. Además, el API de ServiceLoader proporciona funciones de cacheo de servicios.

    Para listar los proveedores de un servicio puedes usar jlink:

    jlink --module-path MODULEPATH --add-modules NOMBRESDEMODULOS --suggest-providers SERVICIO

    En nuestro ejemplo:

    jlink --module-path Algorithms/target/classes:Hello/target/classes:HelloTest/target/classes:$JAVA_HOME/jmods --add-modules Algorithms,Hello,HelloTest --suggest-providers algorithms.sort.Sortable

    JDK 9

    Homebrew es una forma sencilla de instalar y actualizar Java:

    brew update
      brew tap caskroom/cask
      brew install brew-cask
      brew cask install java

    Para añadir al path los comandos del JDK edita .bashrc con el contenido siguiente:

    export JAVA_HOME=$(/usr/libexec/java_home)
      export PATH="$JAVA_HOME/bin:$PATH"

    Si alguna vez quieres alternar entre Java 8 y 9:

    # instala Java 8
      brew cask install java8
    
      # cambia a Java 8
      export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
      export PATH="$JAVA_HOME/bin:$PATH"
    
      # o cambia de vuelta a Java 9
      export JAVA_HOME=$(/usr/libexec/java_home -v 9)
      export PATH="$JAVA_HOME/bin:$PATH"

    javac

    ¿Qué nuevas opciones relativas a módulos hay para javac?

    --add-exports Hace legible un paquete no exportado en el descriptor de su módulo.
    --add-modules Módulos raíz a resolver.
    --limit-modules Limita los módulos observables.
    --module Compila solo el módulo indicado.
    --module-path Directorio donde están los módulos dependientes.
    --module-source-path Directorios de código fuente para los módulos.
    --module-version Versión de los módulos que estamos compilando.
    --processor-module-path Ruta a los módulos que contienen procesadores de anotaciones.
    --upgrade-module-path Módulos actualizables.

    java

    ¿Qué nuevas opciones relativas a módulos hay para java?

    --add-exports Hace legible un paquete no exportado en el descriptor de su módulo.
    --illegal-access Permite saltarse la encapsulación. Los valores posibles son permit, warn, debug, deny.
    --add-modules Añade módulos al modulepath.
    --add-opens Permite reflexión de un paquete a otro. El formato es
    –add-opens módulo/paquete=módulo_que_realiza_el_acceso
    --describe-module Describe el contenido del módulo.
    --list-modules Lista los módulos observables.
    --module-path Directorios conteniendo los módulos.
    --upgrade-module-path Actualiza módulos en la imagen.
    --validate-modules Valida los módulos del module path y termina.
    Al usar –add-module-path podemos hay nombres con significados especiales como ALL-DEFAULT, ALL-SYSTEM, ALL-MODULE-PATH. Curiosamente cuando hacemos java –help no muestra las opciones illegal-access, y add-opens. Si los comandos java o javac te quedan tan largo como tu brazo, puedes llevarte las opciones a un fichero de texto, por ejemplo argumentos.txt, y ejecutar java @argumentos.txt. Esta es una novedad en Java 9. Puedes incluso pasar varios ficheros, por ejemplo java @1.txt @2.txt.

    Ejemplos

    Multiproyecto Maven

    Voy a empezar con un multiproyecto Maven.

    Creo el proyecto

    Creo el proyecto padre e hijo

    mvn archetype:generate \
      -DarchetypeGroupId=org.codehaus.mojo.archetypes \
      -DarchetypeArtifactId=pom-root \
      -DarchetypeVersion=RELEASE \
      -DgroupId=com.example \
      -DartifactId=SimpleHello \
      -Dversion=1.0-SNAPSHOT \
      -DinteractiveMode=false
    
      cd SimpleHello
    
      mvn archetype:generate \
      -DarchetypeGroupId=org.apache.maven.archetypes \
      -DarchetypeArtifactId=maven-archetype-quickstart \
      -DarchetypeVersion=RELEASE \
      -DgroupId=com.example.app \
      -DartifactId=Hello \
      -Dversion=1.0-SNAPSHOT \
      -DinteractiveMode=false

    Al crear el segundo aparece un aviso:

    WARNING: Illegal reflective access by org.dom4j.io.SAXContentHandler
    (file:/Users/jano/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar) to method
    com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser$LocatorProxy.getEncoding()

    El comportamiento por defecto es permitir el acceso ilegal y mostrar un warning la primera vez, así que lo que vemos es normal. Este es el equivalente de pasar --illegal-access=permit a la JVM, que es un flag descrito en este mail.

    El siguiente problema que encontré fue que el Maven compila por defecto para Java 1.5. Esto se arregla fácil en el pom.xml del proyecto raíz:

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.9</source>
                    <target>1.9</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    mvn package compila, y ejecuta las pruebas. Renombro App.java y AppTest.java a Hello.java y HelloTest.java. Compruebo que mvn package sigue funcionando. Ahora voy a ejecutar la clase principal así que añado el plugin de ejecución al pom.xml del módulo hijo.

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <configuration>
                    <executable>java</executable>
                    <arguments>
                        <argument>-classpath</argument>
                        <classpath/>
                        <argument>com.example.app.Hello</argument>
                    </arguments>
                </configuration>
            </plugin>
        </plugins>
    </build>

    Ya tenemos un proyecto multimódulo en Maven donde podemos compilar, probar, y ejecutar.

    cd Hello
      mvn package exec:exec

    Este es mi layout por ahora:

    SimpleHello
          ├── Hello
          │   ├── pom.xml
          │   └── src
          │       ├── main
          │       │   └── java
          │       │       └── com
          │       │           └── example
          │       │               └── app
          │       │                   └── Hello.java
          │       └── test
          │           └── java
          │               └── com
          │                   └── example
          │                       └── app
          │                           └── HelloTest.java
          └── pom.xml

    El warning de antes

    Antes me salté unos detalles sobre el warning de acceso ilegal. Como experimento probé a pasar deny a la JVM de Maven de estas dos maneras pero no funcionó:

    • Crea un fichero .mvn/jvm.config que contenga --illegal-access=deny
    • Pasa la opción -DargLine=--illegal-access=deny al comando Maven

    La opción illegal-access solo funciona para paquetes que existían en el JDK 8, pero este sí existía así que debería haber funcionado. No sé que magia está haciendo Maven internamente para que esto falle (lanzando un nuevo proceso?).

    Las opciones en sí, funcionan. Puedes probarlas con este ejemplo:

    import java.lang.reflect.Method;
    class Untitled {
        public static void main(String[] args) throws Exception {
            Method method = Class.forName("com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser$LocatorProxy").getMethod("getEncoding", new Class[0]);
            method.setAccessible(true);
            System.out.println(method);
        }
    }
    Para denegar la reflexión:
    javac Untitled.java && java --illegal-access=deny Untitled
    Para permitir la reflexión desde el módulo unnamed (ALL-UNNAMED):
    javac Untitled.java && java --add-opens java.xml/com.sun.org.apache.xerces.internal.parsers=ALL-UNNAMED Untitled

    Conversión a módulos

    Para modularizar el proyecto he hecho esto:

    • Divido el proyecto y sus pruebas en proyectos diferentes.
    • Añado un module-info.java a cada uno para convertirlos en módulos.
    • Pongo el código y sus pruebas en paquetes diferentes (porque un mismo paquete no puede existir en diferentes módulos).
    .
      ├── Hello
      │   ├── pom.xml
      │   └── src
      │       └── Hello
      │           ├── com
      │           │   └── example
      │           │       └── app
      │           │           └── Hello.java
      │           └── module-info.java
      ├── HelloTest
      │   ├── pom.xml
      │   └── src
      │       └── HelloTest
      │           ├── com
      │           │   └── example
      │           │       └── junit
      │           │           └── HelloTest.java
      │           └── module-info.java
      └── pom.xml

    Los descriptores son los siguientes:

    module Hello {
          requires java.logging;
          exports com.example.app;
      }

    Para las pruebas necesitamos los módulos JUnit, Hello y sus dependencias, y tenemos que permitir el acceso de JUnit al código de ejemplo.

    module HelloTest {
          requires junit;
          requires transitive Hello;
          exports com.example.junit to junit;
      }

    JUnit carece de descriptor de módulo, por tanto es un módulo automático. Cuando ejecutamos mvn package aparece este warning:

    [WARNING] * Required filename-based automodules detected. Please don't publish this project to a public artifact repository!

    Se refiere a que hay módulos en el module path cuyo nombre ha sido inferido del nombre de fichero y puede que no sea el correcto. Durante una larga temporada tendremos que tratar con warnings, y plugins no actualizados para JDK 9. En este caso particular es un warning sin importancia.

    Ejecución con Maven

    Si ejecutamos mvn package exec:exec desde el módulo Hello, el plugin exec-maven-plugin ejecuta el módulo como un JAR normal. Eso ocurre porque por defecto usa classpath y no modulepath. Vamos a cambiarlo para que ejecute módulos.

    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.6.0</version>
        <executions>
            <execution>
                <goals>
                    <goal>exec</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <executable>${JAVA_HOME}/bin/java</executable>
            <arguments>
                <argument>--module-path</argument>
                <modulepath/>
                <argument>--module</argument>
                <argument>Hello/com.example.app.Hello</argument>
            </arguments>
        </configuration>
    </plugin>

    El comando para ejecutar las pruebas en el sistema de modularidad es un poco más largo. Hasta que Maven soporte mejor el sistema de modularidad este es posiblemente el mejor modo.

    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.6.0</version>
        <executions>
            <execution>
                <goals>
                    <goal>exec</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <executable>java</executable>
            <arguments>
                <argument>--module-path</argument>
                <modulepath/>
                <argument>--add-modules</argument>
                <argument>HelloTest</argument>
                <argument>--module</argument>
                <argument>junit/org.junit.runner.JUnitCore</argument>
                <argument>com.example.junit.HelloTest</argument>
            </arguments>
        </configuration>
    </plugin>

    Ejecución con Makefile

    Para ejecutar con make, he añadido estos Makefile a Hello y HelloTest

    run:
        javac `find src/Hello -name "*.java"` -d out/Hello
        java --module-path out/Hello --add-modules Hello com.example.app.Hello
      test:
        make -C ../HelloTest run
    run: ../Hello/out
        javac --module-path lib:../Hello/out `find src/HelloTest -name "*.java"` -d out/HelloTest
        java --module-path lib:../Hello/out:out/HelloTest --add-modules Hello,HelloTest -m junit/org.junit.runner.JUnitCore com.example.junit.HelloTest
      ../Hello/out:
        make -C ../Hello run

    La ejecución de pruebas requería Junit y Hamcrest así que los he descargado a un directorio lib. Podría haber hecho una referencia al repositorio de Maven pero no he querido liarme más.

    Idea

    Para cargarlo en Idea, arranca y sigue estos pasos

    • Import project
    • From external model > Maven, pulsa Next
    • Pulsa estas opciones:
      • Import Maven projects automatically
      • Create module groups for multi-module Maven projects
      • Next
    • Pulsa Next, Next, Finish.

    Dado que el código de pruebas está en un módulo Maven aparte, se importa correctamente y no hay problemas. Si hubiéramos dejado el layout original de Maven, Idea se quejaría de que los módulos tienen el mismo directorio raíz.

    requires

    Voy a pintar hola mundo con la clase Logger como excusa para importar un paquete.

    package com.example.app;
      import java.util.logging.Logger;
      public class Hello {
          private final static Logger LOGGER = Logger.getLogger(Hello.class.getName());
          public static void main(String[] args) {
              LOGGER.info("Hello World!");
          }
          public Hello(){}
      }

    Observa que Logger está en el módulo java.logging, no java.util.logging. Aunque la documentación recomienda usar el nombre de paquete principal, los módulos de Java se toman libertades para abreviar el nombre del módulo.

    module Hello {
          requires java.logging;
          exports com.example;
      }

    Añadir un servicio

    Para instanciar un servicio hacemos un lookup con la ServiceLoader API y a continuación instanciamos con un la clase, u obtenemos un stream de Providers. El stream de proveedores nos permite inspeccionar el servicio antes de instanciarlo.

    Aquí estoy instanciado el servicio a partir del primer elemento del iterador. Este es un ejemplo trivial, pero en un proyecto real debes controlar el caso en el que el iterador no devuelva elementos.

    public static void main(String[] args) {
          LOGGER.info("Hello World!");
          Integer[] values = {1,9,7,3};
    
          Class clazz;
          try {
              clazz = (Class) Class.forName("algorithms.sort.Sortable");
              ServiceLoader loader = ServiceLoader.load(clazz);
              Sortable sortable = loader.iterator().next();
              sortable.sort(values);
              System.out.println(Arrays.toString(values));
          } catch (ClassNotFoundException e) {
              e.printStackTrace();
          }
      }

    Código de ejemplo

    Casi todos los ejemplos en este tutorial están en esta aplicación: https://github.com/j4n0/SimpleHello

    .
      ├── Algorithms        Módulo que proporciona el servicio Sortable
      ├── Hello             Módulo cliente del servicio en Algorithms
      ├── HelloLogger       Módulo para ilustrar la reflexión
      ├── HelloTest         Módulo cliente del servicio en Algorithms
      ├── Makefile          Run make to compile, test, and run all modules
      ├── pom.xml           POM de la raíz del proyecto Maven
      └── showProviders.sh  Encuentra proveedores para servicios

    Puedes ejecutar este código

    • Con make: haciendo un make en el directorio raíz, o dentro del directorio de cualquiera de los módulos.
    • Con maven: haciendo un mvn install en el directorio raíz, y un mvn exec:exec dentro del directorio HelloTest.
    • Con Idea: ejecutando el módulo HelloTest. Sin embargo con Idea no me ha funcionado uno de los ejemplos con el ServiceLoader, imagino que por tema de configuración de Idea, no le dedique tiempo, se admiten sugerencias!

    Referencias

    Y ahora, si te gusta pulsar en enlaces y leer documentación, pulsa en estos y finalmente serás feliz:

    Youtube

    Autentia en Liferay Symposium 2017

    $
    0
    0

    Si algo nos caracteriza en Autentia, es el interés constante por todas las novedades tecnológicas relacionadas con el desarrollo de software y muy especialmente en los últimos tiempos por cómo estas novedades ayudan a las empresas en su camino hacia la transformación digital.

    Por este motivo, los pasados 25 y 26 de octubre asistimos al evento organizado por Liferay España en Madrid. El objetivo era conocer más de cerca cómo Liferay ha conseguido evolucionar desde su orígenes como gestor de portales y contenido, a una plataforma de desarrollo alineada con las tecnologías más actuales. Estas actualizaciones tocan las áreas principales de un desarrollo, desde el área de front-end, back-end e infraestructura.

    En primer lugar, cabe destacar la magnífica organización del evento por parte de @Liferay_es con una magnífica atención, una agenda interesante y unas charlas donde cabe destacar que, el foco no sólo se puso en resaltar las bondades del producto, sino también en explicar cuáles han sido las motivaciones que les han llevado a evolucionar como lo han hecho. Además, no se quedaron en el ámbito técnico sino que dieron el salto a aspectos más cercanos a Negocio. Durante las dos jornadas del evento se habló de temas bastante interesantes como,

    • La importancia de la experiencia de usuario en los procesos de transformación digital en las empresas, y cómo Liferay puede ayudar en esta tarea.
    • Entendiendo Liferay como una plataforma de desarrollo y no sólo como un gestor de portales y contenido.
    • La importancia de la introducción de OSGi como base de la plataforma para conseguir un objetivo importante, modularidad.
    • Cómo Liferay, al igual que otras herramientas están tendiendo a proporcionar toda su infraestructura en la nube.
    • Evolucionar el mundo de las Web APIs con Vulcan para poder crear consumidores diseñados para evolucionar eficientemente y sin necesidad de cambiar el código para cambiar el contenido de las aplicaciones.
    • Desarrollo móvil nativo e híbrido gracias a Liferay Screens además de dar soporte directo a Liferay en Xamarin.
    • Herramientas que mejoran la carga de recursos de cara a mejorar el rendimiento de los portales gracias a “Adaptative media”.
    • La inclusión de frameworks JavaScript actuales como Angular, React y Vue en la capa de front-end e incluso el poder usar varios de estos frameworks a la vez.

    Para todos aquellos que no conozcáis la plataforma y queráis comenzar con Liferay podéis seguir los siguientes tutoriales de nuestros compañeros Javier Sánchez y César Alberca:

    En resumen, fueron dos jornadas bastante productivas en las que hemos podido tener más de cerca al equipo de Liferay. Ahora que tenemos una idea más clara del potencial de la plataforma y solo queda probar estas nuevas características validando cómo ayudan a los equipos de desarrollo y a la transformación digital de las empresas.

    Prioriza tu trabajo eficazmente con la matriz ICE

    $
    0
    0

    ¿Cómo decides en qué acciones te enfocas primero? ¿basas tus decisiones del reparto diario en una lista alfabética o conforme se te va ocurriendo? ¿no llegas a todo y no consigues los resultados que quieres?

    Ya seas emprendedor, directivo, freelance o estés trabajando en una compañía te vas a encontrar a diario con multitud de ideas, oportunidades o tareas y muy poco tiempo y recursos para llevarlas a cabo. Emplear la intuición para decidir qué es prioritario puede estar bien… pero aquí traigo una técnica “neutral” y casi “científica” para ayudarte.

    Especialmente si estás arrancando tu propio proyecto, los recursos (entendiendo el tiempo como el principal y más valioso) son muy reducidos y la diferencia entre el éxito y el fracaso puede estar en la simple decisión sobre qué tarea hacemos antes y qué tarea dejaremos para después (y probablemente no haremos porque nos hayamos quedado sin tiempo).

    Hoy vamos a ver una técnica de priorización llamada ICE (de las siglas Impacto, Coste, Esfuerzo) donde filtrando cada una de las oportunidades/ideas/tareas conseguiremos un indicador que nos diferenciará entre aquello de mayor prioridad y lo que puede esperar.

    Construir una matriz ICE es muy sencillo. Montaremos una tabla como la siguiente:


    Ahora, para rellenar los campos de Impacto, Coste y Esfuerzo nos basaremos en las siguientes puntuaciones:


    Entenderemos el impacto como aquello que mayor beneficio nos aporta según nuestros objetivos. Por ejemplo, para un emprendedor el impacto puede ser ahorro de tiempo, ingresos, número de clientes, etc. para el técnico de mantenimiento de una empresa puede suponer el ahorro energético, tiempo, aumento de la seguridad, etc.

    El coste supondrá aquellos recursos económicos que tengamos que emplear para explotar una oportunidad o llevar a cabo una acción o tarea. Y el esfuerzo son los recursos, tanto en tiempo (especialmente) y/o en otros recursos necesarios para poder ejecutar.

    Es decir, si para la primera idea/tarea/oportunidad el impacto sobre nuestro objetivo o trabajo es alto colocaremos un 2. Si es bajo, un 0. Si el coste es alto, colocaremos un 0, si es bajo un 1. Si el esfuerzo es alto, un 0. Si es bajo, un 1.

    Por último, en prioridad, sumamos el valor de impacto, coste y esfuerzo de dicha fila.

    Una vez obtenida la suma, priorizaremos conforme a los siguientes valores:

    • 4 – Oportunidad extraordinaria: Ponte de inmediato con ella
    • 3 – Gran oportunidad: Ponte con ella en cuanto sea posible
    • 2 – Oportunidad: Ponte con ella cuando haya recursos disponibles
    • 1 – Pequeña oportunidad: Déjala para el futuro
    • 0 – No hay oportunidad: No debería hacerse…

    Veamos 3 ejemplos. Imaginemos que queremos priorizar entre:

    1. Desarrollar una funcionalidad en nuestro software para que el menú de navegación sea más elegante. Pensamos que así los usuarios verán nuestra herramienta “más amigable”. El desarrollo de la funcionalidad son 4 jornadas de trabajo.
    2. Un cliente nos pide que le adaptemos nuestro producto a una necesidad concreta. Nos costará 3 jornadas de trabajo y probablemente no le valdrá a otros clientes. Pero estamos empezando y queremos quedar bien. Al cliente le facturamos poco porque es pequeño.
    3. Tenemos que mejorar el aislamiento de una oficina de 600m² porque gastamos mucho dinero en climatizarla. Es necesario hacer una pequeña obra de 4 jornadas y adquirir el material. Tenemos presupuesto para ello.

    De esta forma sabemos que:

    1. Primero nos pondremos a reformar la oficina, porque para nosotros tiene un alto impacto debido al coste que supone la climatización de la misma. Al tener prioridad 2 es una “oportunidad” y como tenemos los recursos podemos ponernos.
    2. El desarrollo de la funcionalidad es una pequeña oportunidad… la dejaremos para el futuro.. y si no tenemos nada mejor que hacer (¿¿seguro??) nos pondremos con ella. (Ojo, si el menú fuese tan desastroso que es imposible navegar para los clientes el impacto sería alto y no bajo y cambiaría la prioridad).
    3. Adapta el producto… no es una oportunidad… ¿seguro que es nuestro cliente?

    ¡Prueba la técnica ICE y cuéntanos si te ha aportado claridad para organizar tu trabajo!

    Cómo usar JMX con HTTP a través de Jolokia

    $
    0
    0

    En este tutorial utilizaremos el agente VM de Jolokia para administrar la información de la JMX console a través de un protocolo mucho más estandar como es el protocolo HTTP/JSON.

    Índice de contenidos

    1. Introducción

    La administración de las aplicaciones empresariales a través de los protocolos RMI proporcionados por la JMX Console muchas veces hace que sea un quebradero de cabeza en cuanto a conectividad, aplicación específicas para dicho protocolo, etc.

    Es por ello, por lo que en este tutorial os traigo una forma mucho más sencilla de administrar la aplicación haciendo uso del agente VM Jolokia, que no es nada más ni nada menos que un puente HTTP/JSON para el acceso JMX remoto.

    2. Entorno

    El tutorial está escrito usando el siguiente entorno:

    • Hardware: Portátil MacBook Pro Retina 15′ (2.3 Ghz Intel Core I7, 16GB DDR3).
    • Sistema Operativo: Mac OS Sierra 10.12
    • Entorno de desarrollo: Eclipse Neon
    • Apache Maven 3.3.3
    • Java JDK 1.8.0

    3. Clonado de un proyecto ejemplo

    Como el objetivo de este tutorial es ver cómo podemos añadir Jolokia a nuestro proyecto, en esta ocasión vamos a apoyarnos en un proyecto ejemplo que dispongo en GitHub, y que está basado en Spring Boot.

    Este proyecto no es más que un “HelloWorld” hecho con Spring Boot: Spring Boot Helloworld en GitHub

    $> git clone https://github.com/drodriguezhdez/spring-boot-samples.git
    $> cd spring-boot-helloworld

    E importamos el proyecto en nuestro IDE favorito.

    4. Descarga del agente JVM de Jolokia

    Una vez disponemos del proyecto, es hora de descargar el agente JVM de Jolokia.

    En esta ocasión, podemos acceder a su página oficial de Downloads o directamente descargar la librería a través de este enlace jolokia-jvm-1.3.7-agent.jar

    Por comodidad, vamos a dejar la librería descargada en la ruta ‘src/main/resources’ del proyecto.

    5. Arranque de la aplicación con el agente JVM de Jolokia

    Para que el agente JVM de Jolokia entre en funcionamiento cuando arrancamos la aplicación, tenemos que añadir una nueva línea en los VM arguments indicando que vamos a añadir un nuevo ‘javaagent’.

    -javaagent:${workspace_loc:spring-boot-helloworld}/src/main/resources/jolokia-jvm-1.3.7-agent.jar

    Por defecto, Jolokia levantará el servidor HTTP en el puerto 8778.

    También podemos configurar el puerto en el que queremos que se arranque el servidor HTTP que nos permitirá lanzar peticiones a Jolokia.

    -javaagent:${workspace_loc:spring-boot-helloworld}/src/main/resources/jolokia-jvm-1.3.7-agent.jar=port=9999

    Una vez hayamos añadido dicha línea a nuestros VM arguments, arrancamos la aplicación y comprobamos que aparece en los logs la referencia a que Jolokia ha arrancado satisfactoriamente.

    Finalmente, comprobamos que Jolokia nos está respondiendo de forma satisfactoria a través de nuestro navegador.

    6. Uso del Jolokia REST API.

    A partir de aquí, ya disponemos de toda la información que nos ofrecería la JMX Console pero a través de un interfaz mucho más estandar, como es el interfaz HTTP Rest.

    Esto nos aporta la ventaja de que podemos utilizar multitud de clientes como por ejemplo el propio navegador, dispositivos móviles, aplicaciones personalizadas, o herramientas del mercado que entiendan HTTP Rest + JSON.

    Para este tutorial, vamos a mostrar un pequeño ejemplo de uso utilizando Jolokia para conocer los detalles de la memoria de nuestra aplicación.

    Para ello como primer ejemplo, así se obtendría la información completa del MBean ‘java:lang=Memory’

    http://localhost:8778/jolokia/read/java.lang:type=Memory

    O podríamos optar por conocer únicamente el atributo ‘HeapMemoryUsage’ de dicho MBean:

    http://localhost:8778/jolokia/read/java.lang:type=Memory/HeapMemoryUsage

    Como se puede observar, Jolokia no solo nos aporta el valor de lo que solicitamos, sino que además le añade información adicional como por ejemplo la petición que hemos realizado, el timestamp o el código HTTP de la respuesta.

    Podemos consultar la documentación completa del uso del Jolokia REST API en su página de referencia oficial: Jolokia Protocol

    7. Conclusiones

    En este tutorial hemos podido aprender cómo disponer de todo la funcionalidad que nos ofrece la JMX Console a través de un interfaz mucho más utilizado como es el HTTP Rest + JSON a través del agente de la JVM de Jolokia, evitando así los problemas derivados de utilizar la propia JConsole a través del protocolo RMI, los cuales no suelen tener conectividad en los entornos normales de producción.

    8. Contacto y GitHub repository

    ¿Habéis tenido algún que otro problema con el tutorial? Déjame un comentario en este artículo o coméntamelo a través de mi cuenta de Twitter: @drodriguezhdez.

    Echa un vistazo al código del tutorial en mi repositorio de GitHub: Source code del Tutorial.

    9. Referencias

    Websockets escalables con Spring y RabbitMQ

    $
    0
    0

    Índice de contenidos

    1. Introducción

    En ciertas ocasiones necesitamos implementar websockets para tener información actualizada en tiempo real con nuestro servidor en nuestras apps y webs. En otros tutoriales de este sitio hemos visto como implementar esto con spring, o directamente contra un ActiveMQ.

    En este tutorial vamos a ver cómo implementar la subscripción y envío de mensajes a un WebSocket controlado con Spring bajo el protocolo Stomp, para ver como se configura todo con anotaciones Spring Boot. Usaremos un ejemplo como el del tutorial oficial de spring. Como nuestro WebSocket será utilizado por miles de clientes y ahora nos va la moda de hacer microservicios, vamos a dar un paso más y ver cómo podemos escalar esto y poder enviar mensajes desde cualquier otro microservicio sin importar contra que servidor concreto tienen los clientes abierto el websocket. Para ello utilizaremos RabbitMQ como broker.

    2. Entorno

    El tutorial está escrito usando el siguiente entorno:

    • Hardware: Portátil MacBook Pro Retina 15′ (2.2 Ghz Intel Core I7, 16GB DDR3).
    • Sistema Operativo: Mac OS X El Capitan 10.11.6
    • Spring Boot 1.5.7
    • Stomp protocol
    • StompJS

    3. ¿Qué vamos a instalar/configurar?

    Vamos a implementar una aplicación JEE con Spring Boot que se pueda configurar en clúster para que sea escalable.

    Vamos a utilizar el protocolo Stomp para simplificar las comunicaciones y poder utilizar en cliente librerías como StompJS.

    Vamos a configurar un RabbitMQ para que se encargue de gestionar las subscripciones de nuestros clientes a través de websockets de tal manera que sea fácil enviar un mensaje a todos los clientes registrados en un topic sin saber contra cuál de los servidores del clúster tienen abierto el socket.

    Vamos a ver cómo podemos securizar la subscripción a los topics a través de los websockets.

    4. Creación y configuración básica del proyecto

    Introducimos en nuestro proyecto Spring Boot la dependencia para poder utilizar websockets:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>

    Configuramos nuestra aplicación para que admita nuevos websockets con STOMP:

    package hello;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.messaging.simp.config.MessageBrokerRegistry;
    import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
    import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
    import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
    
    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
    
        @Override
        public void configureMessageBroker(MessageBrokerRegistry config) {
            config.enableSimpleBroker("/topic");
            config.setApplicationDestinationPrefixes("/app");
        }
    
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            registry.addEndpoint("/gs-guide-websocket").withSockJS();
        }
    }

    Con esto aceptamos nuevos sockets bajo la url “/gs-guide-websocket”, podrán enviar mensajes a urls tipo “/app/*” y subscribirse a topics en urls tipo “/topic/*”.

    Creamos nuestro primer “controller” para recibir mensajes a través del websocket a la ruta “/app/hello” con un objeto tipo “HelloMessage” que será automáticamente parseado desde el json que se reciba. Y vamos a enviar un mensaje al topic “/topic/greetings” con un objeto tipo “Greeting” que será parseado a json para ser enviado a través del websocket.

    package hello;
    
    import org.springframework.messaging.handler.annotation.MessageMapping;
    import org.springframework.messaging.handler.annotation.SendTo;
    import org.springframework.stereotype.Controller;
    
    @Controller
    public class GreetingController {
    
        @MessageMapping("/hello")
        @SendTo("/topic/greetings")
        public Greeting greeting(HelloMessage message) throws Exception {
            Thread.sleep(1000); // simulated delay
            return new Greeting("Hello, " + message.getName() + "!");
        }
    
    }

    Con un cliente js tipo “StompJS” podemos tener un código sencillo de conexión desde un browser a un websocket, para hacer envío de mensajes y subscribirse a topics para recepción de mensajes.

    var stompClient = null;
    
    function connect() {
        var socket = new SockJS('/gs-guide-websocket');
        stompClient = Stomp.over(socket);
        stompClient.connect({}, function (frame) {
            setConnected(true);
            console.log('Connected: ' + frame);
            stompClient.subscribe('/topic/greetings', function (greeting) {
                showGreeting(JSON.parse(greeting.body).content);
            });
        });
    }
    
    function disconnect() {
        if (stompClient !== null) {
            stompClient.disconnect();
        }
        setConnected(false);
        console.log("Disconnected");
    }
    
    function sendName() {
        stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
    }

    5. Conectándolo a RabbitMQ

    Ya podemos crear websockets pero si escalamos la aplicación los mensajes que enviemos a un topic solo serán recibidos por los clientes que tengan abierto el websocket contra ese mismo servidor.

    Vamos a modificar la configuración de spring de websockets de la clase “WebSocketConfig” cambiando la linea ‘config.enableSimpleBroker(“/topic”)’ por:

    config.enableStompBrokerRelay("/queue", "/topic")
            .setUserDestinationBroadcast("/topic/unresolved.user.dest")
            .setUserRegistryBroadcast("/topic/registry.broadcast")
            .setRelayHost(springStompHost)
            .setRelayPort(springStompPort)
            .setClientLogin(springStompUsername)
            .setClientPasscode(springStompPassword)
            .setSystemLogin(springStompUsername)
            .setSystemPasscode(springStompPassword)

    Para que RabbitMQ soporte STOMP debemos habilitarle el plugin de “rabbitmq_web_stomp”:

    rabbitmq-plugins enable --offline rabbitmq_web_stomp

    Ahora es RabbitMQ el que mantiene el control de que websockets están conectados a que topics por lo que podemos enviar un mensaje a cualquier topic desde cualquier servidor con conexión a rabbitmq. Un ejemplo de envío usando “SimpMessagingTemplate”:

    @Autowired
    private SimpMessagingTemplate template;
    
      public void sendToTopicGreetings(Greeting greeting) {
          template.convertAndSend("/topic/greetings", greeting);
      }

    6. Securizando subscripciones

    Podemos añadir un interceptor para controlar las subscripciones a distintos topics y securizar éstas. Para ello modificamos la clase “WebSocketConfig”:

    @Override
     public void configureClientInboundChannel(ChannelRegistration registration) {
    	 registration.setInterceptors(new ChannelInterceptorAdapter() {
    		@Override
    		public Message preSend(Message message, MessageChannel channel) {
    			if (message.getHeaders().get("stompCommand") == StompCommand.SUBSCRIBE) {
    				//Añadir las condiciones deseadas de seguridad y lanzar excepción si no se pasan
    				//message.getHeaders().get("simpDestination") --> para ver el destino de la subscripción: "/topic/*"
    			}
    		}
    	 });
    }

    El cliente StompJS permite enviar headers para incluir la autenticación. Estos headers pueden obtenerse en el servidor del “message” con:

    Map<String, LinkedList<String>> nativeHeaders = (Map<String, LinkedList<String>>) message.getHeaders().get("nativeHeaders");
      LinkedList<String> authorizations = nativeHeaders == null ? null : nativeHeaders.get("Authorization");
      String authorization = authorizations == null || authorizations.size() == 0 ? null : authorizations.getFirst();

    7. Conclusiones

    Hemos visto cómo podemos utilizar WebSockets en nuestra app para tener comunicaciones en tiempo real en un entorno que es escalable y es fácilmente integrable como un microservicio nuevo en nuestro ecosistema, permitiendo a cualquier microservicio el envío de mensajes a los distintos topics de nuestros websockets utilizando RabbitMQ como broker.

    8. Referencias

    Viewing all 990 articles
    Browse latest View live