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

User Story Mapping con CardBoard

$
0
0

Últimamente me dedico a algo bastante interesante para distintas organizaciones: a partir de una definición de proyecto, convertirlo en historias de usuario para tratar de acotar el alcance y poder ejecutarlos de un modo tradicional o ágil.

El modelo es conceptualmente sencillo pero difícil de ejecutar.

Una organización, a través de sus métodos clásicos de definición de proyectos o mecanismos más nuevos para ellos, como Design Thinking, dedica semanas a conceptualizar una idea. Suele llevar semanas porque las personas no tienen una dedicación del 100%, no tienen demasiada práctica, se utilizan técnicas de UX para hacer prototipos rápidos y testear con usuarios (lo que lleva tiempo), cosa que todo me parece bien y normal.

Obviamente se puede mejorar con gente más dedicada, con asistencias externas experimentadas, con plantillas y con ejemplos claros, etc. pero es un camino a recorrer.

Ahora bien, si has tenido a un conjunto de personas hablando sobre un proyecto desde una perspectiva abierta durante mucho tiempo, ¿qué posibilidad hay de que el entendimiento del proyecto por todos los miembros sea distinto? Ya te digo que el 100% y, por tanto, el control de expectativas es imposible. Nadie tiene claro qué era o no entra en el proyecto o en una primera fase.

Aquí es donde entran en juego las historias de usuario para aterrizar las cosas.

Estoy poco a poco instrumentalizando un método para definir paso a paso historias de usuario y ando explorando herramientas que me ayuden. He encontrado CardBoard y me ha parecido bastante interesante. Ya os adelanto que para mi gusto, para que cumpliera del todo la función, echo de menos cosas pero es tan sencilla de usar que me parece super interesante para formaciones.

Podéis registraros en el siguiente enlace https://cardboardit.com/. Para paneles públicos es gratuita. Para tener paneles privados hay que pagar, que parece justo.


Después del login tenemos algún panel de ejemplo y acceso a otros públicos.


Voy a crear un nuevo panel con las historias que podéis encontrar en mi último libro. Por cierto, lo podéis descargar gratis en el siguiente enlace https://lk.autentia.com/rcanales.

Lo voy a llamar proyectos de futuro.


A mi me gusta descomponer los proyectos en 7 tipos de actores o roles diferentes, aunque no sea importante quién hace qué, sino que sea un recurso para descomponer un problema grande e 7 pequeños.

Los usuarios son anónimo, registrado, operador, supervisor, soporte, administrador y sistemas.


Se pueden colocar los paneles para tener distintos niveles de profundidad o ir completando el modelo y disponer de fotos de distintos momentos.


Existe la posibilidad de definir un journeys. Puede estar muy bien para ver historias dependientes de un flujo o para modelar el producto mínimo viable o conjuntos de historias por fases.


Podemos ver un mini mapa que puede ser muy útil para modelar dependencias con otros proyectos.


Y una de las cosas que más me gusta: podemos imprimir las tarjetas. Yo personalmente os recomiendo que uséis paneles físicos en las paredes y que las herramientas sean un complemento. Tener un espacio en común y discutir sobre algo tangible creará un nexo muy útil.


Estas tarjetas pueden imprimirse en pegatinas y pegarse sobre post-it para preparar o dinamizar sesiones.


Podemos clonar los paneles para usarlos para otros propósitos, como por ejemplo hacer una estimación relativa de historias por complejidad, muy útil para determinar desajustes de expectativas entre negocio y tecnología (lo que para uno en su mente es simple para otro es complejo).


Podemos indicar el tamaño de las historias y activarlo o desactivarlo.


Desde luego visualmente es extremadamente sencillo.

Obviamente esto se puede hacer un muchas herramientas y tenemos que seguir explorando cual es más adecuada para cada fase. Si quieres hacerlo todo en la misma, lo mismo te pierdes por el camino 😉

Os recuerdo que tenemos formaciones muy concretas y cada vez más prácticas y aterrizadas. https://www.autentia.com/servicios/formacion/curso-gestion-alcance-definicion-producto/


Introducción a la gestión de Servicios Web con Spring Cloud y Netflix OSS

$
0
0
  1. Introducción
  2. Entorno
  3. Spring Cloud Config
  4. Spring Cloud Netflix Eureka
  5. Spring Cloud Netflix Zuul
  6. Servicios Rest de ejemplo
  7. Conclusiones
  8. Repositorios
  9. Referencias

En este tutorial vamos a ver una introducción a Spring Cloud y Netflix OSS como herramientas para implementar algunos de los patrones más comunes utilizados en sistemas distribuidos.

1. Introducción

Ante modelos distribuidos es muy común toparnos con aplicaciones que frente a un creciente número de servicios afronten nuevos retos en lo referente a su administración y sincronización. En tal sentido, algunas de las dudas más comunes con las que nos podemos topar suelen ser:

  • ¿Cómo me puedo asegurar que todo mis servicios están configurados correctamente?
  • ¿Como puedo saber qué servicios se despliegan y dónde?
  • ¿Cómo puedo mantener actualiza la información del enrutamiento de todos los servicios?
  • ¿Cómo puedo prevenir las fallas por servicios caídos?
  • ¿Cómo puedo verificar que todos los servicios están en funcionamiento?
  • ¿Cómo puedo asegurarme cuales son los servicios expuestos externamente?

Para brindar una solución a estos escenarios Spring.io nos ofrece un conjunto de componentes que integrados con las herramientas de Netflix OSS nos permite desarrollar de una manera fácil y rápida aplicaciones que implementen algunos de los patrones más comúnmente usados en sistemas distribuidos.

Algunos de estos patrones suelen ser:

  • La configuración distribuida.
  • El registro y auto-reconocimiento de servicios.
  • Enrutamiento.
  • Llamadas servicio a servicio.
  • Balanceo de carga.
  • Control de ruptura de comunicación con los servicios.
  • Clusterización.
  • Mensajería distribuida.

Dado que son muchas las posibilidades que nos brindan Spring y Netflix, en este tutorial nos enfocaremos en un ejemplo con los siguientes componentes:

  • Spring Cloud Config: Para gestionar de manera centralizada la configuración de nuestros servicios utilizando un repositorio git.
  • Netflix Eureka: Para registrar los servicios en tiempo de ejecución en un repositorio compartido.
  • Netflix Zuul: Como servidor de enrutamiento y filtrado de peticiones externas. Zuul utiliza Ribbon para buscar servicios disponibles y enruta las solicitudes externa a una instancia apropiada de los servicios.

Para visualizar como se vinculan estos componentes podemos fijarnos en la siguiente imagen:

Para construir un ejemplo sencillo que involucre a todos estos componentes vamos a configurar un servidor con cada uno de ellos y un par de servicios “cliente” que comprueben su funcionamiento.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS Sierra 10.12.3
  • Maven – Versión: 3.3.9
  • Java – Versión: 1.8.0_112
  • Spring Boot – Versión: 1.5.1.RELEASE
  • Spring Cloud – Versión: 1.3.0.M1

3. Spring Cloud Config

Lo primero que vamos a configurar será nuestro servidor Spring Cloud Config desde el que nuestros servicios obtendrán un archivo de propiedades. Para crear nuestro servidor nos aprovecharemos de las bondades de maven para importar las dependencias necesarias y las de Spring Boot para tener un servidor autocontenido.

En este orden de ideas, lo primero que haremos será crear nuestro proyecto maven donde nuestro pom.xml debería tener la siguiente forma:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.autentia.spring.cloud</groupId>
 <artifactId>SpringCloudConfig-Server</artifactId>
 <version>0.0.1-SNAPSHOT</version>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.1.RELEASE</version>
 </parent>

 <properties>
  <java.version>1.8</java.version>
 </properties>

 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config</artifactId>
    <version>1.3.0.M1</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
   </dependencies>
 </dependencyManagement>

 <dependencies>
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-config-server</artifactId>
  </dependency>
 </dependencies>

<repositories>
 <repository>
  <id>spring-milestones</id>
  <name>Spring Milestones</name>
  <url>https://repo.spring.io/libs-milestone</url>
  <snapshots>
  <enabled>false</enabled>
  </snapshots>
 </repository>
</repositories>

</project>

Dado que nuestro Servidor de configuración es una aplicación Spring Boot necesitaremos un Main y en él incluiremos la anotación @EnableConfigServer que lo habilitará como servidor Spring Cloud Config.

package com.autentia.spring.cloud.config.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.config.server.EnableConfigServer;

@EnableAutoConfiguration
@EnableConfigServer
public class ApplicationConfigServer {

 public static void main(String[] args) {
 SpringApplication.run(ApplicationConfigServer.class, args);
 }

}

El último paso que debemos tener en cuenta es incluir un archivo de configuración llamado application.yml que ubicaremos en la carpeta /src/main/resources/ y en el que configuraremos el nombre de la aplicación, el puerto en el que se ejecutará y el repositorio git donde se almacenarán los archivos de configuración de nuestros servicios.

# Component Info
info:
 component: SpringConfig-Server

# HTTP Server
server:
 port: 8888

spring:
 # Spring Cloud Config Server Repository
 cloud:
  config:
   server:
    git:
     uri: https://github.com/jmangialomini/Spring.Cloud.Config.Server

 # Spring properties
 profiles:
  active: dev

Para probar nuestra aplicación vamos a la consola y en la carpeta de nuestro servidor Spring Config ejecutamos

mvn spring-boot:run

Una vez iniciado nuestro servidor podemos probar que todo esté funcionando correctamente con nuestro browser http://localhost:8888/health la cual nos debe reportar el estado actual de nuestro servidor.

{
 "status": "UP"
}

4. Spring Cloud Netflix Eureka

El segundo servidor que incorporaremos para el soporte de nuestros servicios será el de Eureka, con el incorporaremos la capacidad de descubrir automáticamente los servicios e instancias que vayamos incorporando en tiempo de ejecución. Para ello y al igual que el servidor anterior utilizaremos Maven y Spring Boot.

En tal sentido, nuestro pom.xml debería quedar de la siguiente manera:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.autentia.spring.cloud</groupId>
 <artifactId>SpringCloudNetflix-EurekaServer</artifactId>
 <version>0.0.1-SNAPSHOT</version>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.1.RELEASE</version>
 </parent>

 <properties>
  <java.version>1.8</java.version>
 </properties>

 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix</artifactId>
    <version>1.3.0.M1</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
  </dependencies>
 </dependencyManagement>

 <dependencies>
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-eureka-server</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
  </dependency>
 </dependencies>

 <repositories>
  <repository>
   <id>spring-milestones</id>
   <name>Spring Milestones</name>
   <url>https://repo.spring.io/libs-milestone</url>
   <snapshots>
   <enabled>false</enabled>
   </snapshots>
   </repository>
  </repositories>
</project>
Paso siguiente a la inclusión de las dependencias necesarias para habilitar nuestro servidor Eureka, debemos crear nuetro ApplicationEurekaServer.java e incluir la anotación @EnableEurekaServer .
package com.autentia.spring.cloud.netflix.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class ApplicationEurekaServer {

 public static void main(String[] args) throws Exception {
 SpringApplication.run(ApplicationEurekaServer.class, args);
 }

}

Para configurar nuestro servidor incluiremos un archivo application.yml dentro de la carpeta /src/main/resources/, y en el que configuraremos el nombre de la aplicación, el puerto donde se ejecutará y le indicaremos que no debe publicarse en otro servidor eureka con la propiedad registerWithEureka en false.

# HTTP Server
server:
 port: 8761

# Eureka Configuration Properties
eureka:
 client:
  registerWithEureka: false
   fetchRegistry: false
 server:
  waitTimeInMsWhenSyncEmpty: 0

Para probar nuestra aplicación vamos a la consola y en la carpeta de nuestro servidor Eureka ejecutamos:

mvn spring-boot:run

Una vez iniciado nuestro servidor podemos probar que todo esté funcionando correctamente con nuestro browser http://localhost:8761/health la cual nos debe reportar el estado actual de nuestro servidor.

{
 "description": "Spring Cloud Eureka Discovery Client",
 "status": "UP"
}

También podemos navegar la interfaz gráfica de Eureka (http://localhost:8761/) y validar los servicios que se vayan incorporando.

5. Spring Cloud Netflix Zuul

Como el último de los servidores de soporte para nuestros servicios web vamos a configurar los servicios de Zuul como puerta de enlace para los servicios que queramos publicar.

Para ello y al igual que en los anteriores utilizaremos Maven y Spring Boot donde el pom.xml debería lucir de la siguiente manera:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.autentia.spring.cloud</groupId>
 <artifactId>SpringCloudNetflix-ZuulServer</artifactId>
 <version>0.0.1-SNAPSHOT</version>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.1.RELEASE</version>
 </parent>

 <properties>
  <java.version>1.8</java.version>
 </properties>

 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix</artifactId>
    <version>1.3.0.M1</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
  </dependencies>
 </dependencyManagement>

 <dependencies>
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-eureka</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-zuul</artifactId>
  </dependency>

 <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
 </dependency>

 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>

 </dependencies>
  <repositories>
   <repository>
    <id>spring-milestones</id>
    <name>Spring Milestones</name>
    <url>https://repo.spring.io/libs-milestone</url>
    <snapshots>
    <enabled>false</enabled>
    </snapshots>
   </repository>
  </repositories>
</project>

Para habilitar el servicio Zuul en nuestra clase Main incluiremos la anotación @EnableZuulProxy.

package com.autentia.spring.cloud.netflix.zuul;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.stereotype.Controller;

@SpringBootApplication
@Controller
@EnableZuulProxy
public class ApplicationZuul {

 public static void main(String[] args) {
 new SpringApplicationBuilder(ApplicationZuul.class).web(true).run(args);
 }
}

Finalmente y al igual que los otros servidores incluiremos un application.yml para configurar nuestro servidor en el que debemos destacar el enrutado para las peticiones a nuestro servicio de público de ejemplo que identificamos con su Service Id:  public-restservice.

#Component Info
info:
 component: Zuul-Server

#Spring Application Name
spring:
 application:
  name: Zuul-Server

#Server Port
server:
 port: 8765

#Endpoints
endpoints:
 restart:
  enabled: true
 shutdown:
  enabled: true
 health:
  sensitive: false

#Zuul routes active
zuul:
 routes:
  public-restservice:
   path: /public/**
   serviceId: public-restservice

#Eureka Instance ID
eureka:
 instance:
  instanceId: ${spring.application.name}:${server.port}

#Ribbon Activation
ribbon:
 eureka:
  enabled: true

Para probar nuestra aplicación vamos a la consola y en la carpeta de nuestro servidor Zuul ejecutamos:

mvn spring-boot:run

Una vez iniciado nuestro servidor podemos probar que todo esté funcionando validando que se haya registrado contra nuestro servidor Eureka anteriormente iniciado.

6. Servicios Rest de prueba con Spring Boot

Finalmente y para poder probar cómo se sincroniza toda nuestra solución vamos a crear un par de servicios web que tomen un puerto aleatorio y se registren automáticamente contra nuestro servidor Eureka.

Al igual que con los servidores utilizaremos Maven y Spring Boot para facilitar nuestra implementación. Entre las dependencias que incluiremos para los servicios estarán las del Starter de Eureka como cliente y el Starter Web para habilitarlos como controladores REST.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.autentia.spring</groupId>
 <artifactId>SpringCloudNetflix-Public-RestService</artifactId>
 <version>0.0.1-SNAPSHOT</version>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.1.RELEASE</version>
 </parent>

 <properties>
  <java.version>1.8</java.version>
 </properties>

 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix</artifactId>
    <version>1.3.0.M1</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>

   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config</artifactId>
    <version>1.3.0.M1</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
  </dependencies>
 </dependencyManagement>

 <dependencies>
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-config</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-eureka</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
  </dependency>
 </dependencies>

 <repositories>
  <repository>
   <id>spring-milestones</id>
   <name>Spring Milestones</name>
   <url>https://repo.spring.io/libs-milestone</url>
   <snapshots>
   <enabled>false</enabled>
   </snapshots>
   </repository>
 </repositories>

</project>
Dado que es una aplicación Spring Boot necesitaremos un Main con la variante de incluir la anotación @EnableDiscoveryClient que permitirá que nuestros servicios se suscriban automáticamente en el servidor Eureka.  
package com.autentia.spring.cloud.netflix;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class ApplicationRestService {

 public static void main(String[] args) {
  SpringApplication.run(ApplicationRestService.class, args);
 }
}
Para habilitar un ejemplo para nuestros servicios incluiremos una clase que responda al mapeo /example:
package com.autentia.spring.cloud.netflix;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ServiceExample {

 @Value("{rest.service.cloud.config.example}")
 String valueExample = null;

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

@RequestMapping(value = "/example")
 public String example() {

 String result = "{Empty Value}";
 if(valueExample.equals(null)){

 log.error("PublicRestService - Called with errors property rest.service.cloud.config.example is empty");

 }else{
 log.info("PublicRestService - Called with this property: (rest.service.cloud.config.example:"+valueExample+")");
 result = valueExample;
 }
 return result;
 }
}

Finalmente y para diferenciar a nuestros servicios incluiremos un archivo de configuración para cada uno de ellos. Application.yml para el servicio público:

#Application Name
spring:
 application:
  name: Public-RestService

#Component Info
info:
 component: Public-RestService

#Port - If 0 get random port
server:
 port: 0

#Eureka Instance ID
eureka:
 instance:
  instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:{random.value}}}

#Service Registration Method
cloud:
 services:
  registrationMethod: route

#Disable HTTP Basic Authentication
security:
 basic:
  enabled: false
Y el Application.yml para el servicio privado:
#Application Name
spring:
 application:
  name: Private-RestService

#Component Info
info:
 component: Private-RestService

#Port - If 0 get random port
server:
 port: 0

#Eureka Instance ID
eureka:
 instance:
  instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:{random.value}}}

#Service Registration Method
cloud:
 services:
  registrationMethod: route

#Disable HTTP Basic Authentication
security:
 basic:
  enabled: false

Para probar nuestros servicios vamos a la consola y en la carpeta de cada uno de ellos ejecutamos:

mvn spring-boot:run

Una vez iniciados los servicios podremos comprobar que todo esté funcionando en nuestro servidor Eureka, en él podremos visualizar cuántas instancias de nuestros servicios se han registrado y bajo qué puerto podemos invocarlos.

Nota: Para hacer más rigurosa nuestra prueba podemos iniciar una segunda instancia de alguno de nuestros servicios y comprobar la cantidad de instancias que están disponibles del mismo servicio.


Finalmente y para probar que el servicio público sea accesible desde el exterior a través de nuestro servidor Zuul  podemos visitar http://localhost:8765/public/example y comprobar que nuestra configuración de servicios esté respondiendo correctamente.

7. Conclusiones

Hemos visto cómo se pueden usar los componentes de Spring Cloud y Netflix OSS para simplificar la gestión y sincronización de servicios web.

8. Repositorios

9. Referencias

Introducción a Vue.js

$
0
0

1. Introducción

En este tutorial veremos qué es y cómo funciona el framework vue.js, y cuáles son sus fortalezas y debilidades respecto a otros frameworks similares, como angular.js o react.

Índice de contenidos

2. Entorno

Cualquier editor de texto y cualquier navegador compatible con ES5 te valdrá para ejecutar el código que encontrarás en este tutorial.

3. ¿Qué es vue.js?

Vue.js es, en palabras de Evan You, su creador, un framework progresivo para crear interfaces de usuario.

Vue se basa en una implementación muy ligera del patrón view-model que nos ayudará a relacionar nuestra capa de presentación con nuestra capa de negocio de forma sencilla y eficiente.

A lo que se refiere You con ‘progresivo’, es a que es muy fácil añadir Vue.js a cualquier proyecto ya existente y poder aprovechar su funcionalidad casi sin complicaciones. Por el contrario, otros frameworks como Angular son mucho más opinionados y están más orientados a comenzar proyectos desde cero, y con una arquitectura predeterminada que viene dirigida por el framework.

You era un ingeniero front-end de google, y precisamente esto es lo que no le gustaba de Angular, aunque sí le parecía que el data-binding y las directivas de Angular eran una gran idea. Vue comenzó originariamente como un proyecto que aspiraba a reproducir este set mínimo de funcionalidades de Angular sin obligar al usuario a amoldarse a una determinada arquitectura. Comenzó a trabajar en Vue en 2013 y lo publicó en github en febrero de 2014.

Actualmente Vue ha tenido un gran impacto y hay varias compañías de renombre que utilizan Vue en su front-end, como Gitlab, Alibaba o Nintendo. Vue, seguramente debido a que Evan You es chino, ha tenido mucho impacto en compañías asiáticas.

Vue.js se caracteriza por ser uno de los frameworks javaScript de mayor rendimiento, gracias a una implementación muy ligera del llamado virtual dom. Aunque su núcleo se centra unicamente en la capa de vista y no provee herramientas de routing o control de estado, hay muchas herramientas del ecosistema de Vue para escarlarlo y crear SPA’s complejas. A sus fortalezas añade su adaptabilidad: se puede añadir a aplicaciones web ya existentes y aprovecharlo sin mayores problemas de instalación, simplemente cargándolo desde un <script>, pero también utilizarlo para crear aplicaciones complejas con un proceso de build que utilice commonJS o módulos ES6, hot module replacement y demás ventajas de bundlers como webpack o browserify durante el proceso de desarrollo.

4. Introducción a los view-models

El patrón view-model o Model-view-view-model consiste en añadir una capa de lógica entre nuestra capa de negocio y nuestra capa de UI para evitar que una tenga que comunicarse directamente con la otra: cuando la vista -la representación de una entidad en la capa de presentación-, o el modelo -su representación en la capa de negocio- cambien, el view-model se encargará de actualizar su contrapartida en la otra capa de forma automática.

esto facilita el testing y permite crear programas más modificables y reusables, a parte de evitar una gran cantidad de código ‘boilerplate’ que actualiza manualmente uno en función del otro.

Supongamos, por ejemplo, que en una página web de venta online de algún tipo de producto, tenemos una entidad ‘pedido’ tal que, en función de su estado, mostramos o no determinados bloques de html con determinado contenido. Cuando el usuario interactúa con la página, añadimos o modificamos campos de algún formulario que representa el estado final del pedido y, cuando el usuario quiere realizar la compra, enviamos ese formulario, que tendrá toda la información relevante, a algún controlador de nuestra página que procese la transacción.

Realizando este tipo de programas sin utilizar el patrón view-model es fácil que cometamos los siguientes errores:

  • Mezclemos lógica de negocio con lógica de presentación.
  • Almacenemos temporalmente información de negocio en el DOM.
  • Tengamos que gestionar manualmente cómo los cambios en una de las dos capas se reflejan en la otra.
  • Tengamos que preocuparnos constantemente de declarar y eliminar handlers que escuchen eventos.
  • Hagamos que lógica que debería ser pura, y por tanto fácil de probar en tests, dependa del estado del DOM, lo que dificulta la testabilidad y reusabilidad.

Las librerías de interfaz de usuario como vue.js que implementan este patrón, utilizan mecanismos que permiten, declarativa y automáticamente, comunicar cambios entre la capa de negocio y la capa de presentación. De este modo hay una fuente única de verdad, el modelo, y el DOM se ajusta a él en función del comportamiento de la aplicación.

Esto implica un cambio de paradigma respecto a la forma en la que se solía trabajar en javaScript con interfaces de usuario. Si antes era habitual utilizar jQuery constantemente para leer y manipular el DOM, ahora manipulamos únicamente modelos de datos en javaScript, y dejamos que sea el viewModel el que ajuste el DOM en consonancia.

5. la instancia de Vue

La forma más immediata de trabajar con Vue es instanciar Vue para un tag determinado de nuestra aplicación. Simplemente, crearemos una nueva instancia de Vue llamándolo como un constructor:

new Vue(params);

Es fácil ver cómo funciona la instanciación de Vue a través de este ejemplo en codePen.

En el ejemplo, params es un objeto javaScript con determinadas claves. Este objeto puede tener definidas muchas propiedades, aunque la fundamental es el que debe ser un string con un selector css indicando para qué elemento aplicará esta instancia de Vue que estamos creando.

El resto de propiedades de una instancia Vue indican sus datos, métodos que operan sobre estos datos, handlers de eventos que afecten a ese bloque html, o diversos hooks que podemos utilizar para realizar tareas en algún momento específico de su ciclo de vida.

Una vez hecho esto, podemos utilizar Vue para modificar y completar el html de la aplicación mediante templates y directivas:

Los ‘templates’ son expresiones escapadas entre corchetes en el contenido de nuestro html que Vue interpreta y renderiza de acuerdo a un modelo de datos. De este modo, podemos utilizar Vue en el front-end de manera similar a cualquier motor de templates de back-end, como Freemarker, Pug o Jade. En el caso de Vue, todo lo que se encuentra entre ‘{{‘ y ‘}}’ en determinados tags html se interpreta como javascript y su valor es modificado en función del valor de nuestros objetos.

Las directivas son atributos html que dirigen el comportamiento de la aplicación. Las directivas en Vue.js son muy similares a las de Angular 1. Por ejemplo, v-if añade o no un tag al DOM en función del valor de algún campo, o v-model enlaza controles de formularios con variables de nuestro modelo de datos.

Tanto los templates como las directivas están reactivamente enlazados con el modelo de datos: cuando uno cambia, el otro también lo hace. Esto es lo que se llama two way data binding, y hace que trabajar con formularios usando Vue sea mucho más cómodo que manejando handlers a mano.

Cuando una aplicación o un caso de uso empieza a tener un tamaño considerable, el two way databinding se considera una mala práctica, porque podemos tener múltiples elementos de la página modificando el modelo sin control. Por este motivo, cuando se utilizan componentes, esto cambia y el binding entre los componentes fluye unicamente de los padres a los hijos.

6. Propiedades de una instancia de Vue

Hay varias opciones que podemos especificar al crear una instancia de Vue. Un pequeño ejemplo más o menos completo sería como el que sigue:

new Vue({
     el : '#app',
     template : `<div v-bind:style="estilos" v-on:click="log()"`,
     data : { titulo : 'Rectángulo', largo : 100, alto : 40 },
     computed : {
         area : function(){ return this.largo * this.alto; } ,
         estilos : function(){
         return {
         width : this.largo + 'px',
         height : this.alto + 'px'
       }

      },
     methods : { log : function(){ console.log('Soy un rectángulo con un area de ' + this.area + ' pixels' ); } },
     created : function(){ console.log('se crea la instancia: todavía no se ha reemplazado "#app" por el template de esta instancia'); },
     mounted : function(){ console.log('ya se ha renderizado el template en el DOM'); }

});

Podéis ver ese mismo script funcionando en este codepen. La especificacíon completa de las propiedades de Vue se encuentra en la documentación de la API de Vue.

Como se vé en el ejemplo, aquí se está instanciando Vue con un objeto que tiene varias propiedades. Vamos a verlos por orden:

El

La propiedad el determina, como ya hemos dicho, el tag html sobre los que operará nuestra instancia Vue. Es un selector CSS como los que se utilizan en las hojas de estilo o jQuery.

Es importante notar que cada instancia de Vue apunta a un solo nodo html y sus hijos. Podemos utilizar como selector una clase o una etiqueta, pero la instancia solamente operará sobre el primer nodo que encuentre.

Template

Como template, se indica una cadena con html que reemplazará el html que hemos fijado como el. Nota que hay dos cosas distintas denominadas ‘template’: todo contenido entre {{}} y la propiedad template de la instancia. Son dos cosas distintas, aunque generalmente van juntas.

No es necesario usar la propiedad template para trabajar con Vue. Podríamos haber puesto ese mismo código directamente en el html dentro del div#app y hubiera funcionado de manera similar. A veces es deseable mantener el html en ficheros .html y no llevarlo a javaScript. Pero la opción template también tiene sus ventajas:

  • Si el html y el javaScript van a cambiar a la vez a menudo, los mantenemos en el mismo fichero.
  • Si el html que estamos escribiendo no es válido antes de que Vue lo parsee y lo modifique, evitamos que el navegador lo elimine antes de que Vue pueda operar sobre él.
  • Podemos usar javaScript para generar el template, lo que en según que casos puede ser cómodo y evitar duplicidades en el html.

Data

La propiedad data es un objeto con propiedades que podemos utilizar en nuestra instancia. En este caso, tanto la directiva v-bind como el método area() refieren estas propiedades. También se utiliza entre corchetes en el template. Básicamente, es el estado de nuestro objeto, el model del patrón view-model.

Cuando se crea la instancia, el motor de Vue lee todas las propiedades en data y les añade getters y setters, utilizando la ‘magia’ de Object.defineProperty para que sean reactivas y quedar a la escucha de sus cambios. La reactividad en Vue tiene ciertas limitaciones: si añadimos propiedades a un objeto dentro de data directamente con javascript, Vue no podrá escuchar estos cambios.Así que si uno de los campos de data es una lista de objetos y renderizamos html en función de estas listas, añadir un objeto a la lista o cambiar a mano el índice puede provocar que los cambios no se propaguen a la vista. De todos modos, en esos casos, el método $set nos tiene cubiertos.

Computed

Computed es un objeto que contiene una serie de métodos que devuelven valores calculados a partir de las propiedades del objeto. En nuestro ejemplo, valores derivados como el área, o el perímetro del rectángulo son los típicos casos en los que usaríamos computed.

Además tenemos un campo estilos que indica algunos estilos que se aplicarán al componente. Esto se consigue mediante el atributo v-bind que hay en el template. Más abajo veremos el tema de las directivas.

Methods

Methods es un objeto con funciones que podemos invocar, y cuyo contexto está enlazado con el propio objeto. Cuando queramos modificar las propiedades de nuestro objeto, o enviar eventos a otros componentes o instancias de la página, lo haremos desde alguno de estos métodos.

Nota que, moviendo area a methods y añadiendo () a su llamada en el método log, podríamos usar un método en vez de una computed property para indicar el área del rectángulo. La cosa quedaría como:

methods : {
         log : function(){
               console.log('Soy un rectángulo con un area de ' + this.area() + ' pixels' );
         },
         area : function(){
             return this.largo * this.alto;
         }

     }
     ...

Esto es una mala práctica y dará problemas de rendimiento, dado que el resultado de computed queda guardado en memoria y solamente cambia cuando cambia alguna de sus dependencias -en este caso, las propiedades alto y largo, mientras que las funciones en methods se invocan siempre.

Hooks

Created y mounted son funciones que se ejecutan en determinados momentos del ciclo de vida de la instancia. Created se invoca cuando se crea el objeto, mientras que mounted se invoca justo después de que el motor de Vue reemplaze el html que hubiera en el.

Hay otros hooks que podemos consumir, para realizar acciones cuando una instancia es destruida, es modificada… etc. etc. Generalmente mounted es el más utilizado y de alguna manera es similar a $( function(){} ) en jQuery: nos asegura que el código no se va a ejecutar hasta que Vue haya terminado de inicializar el html de la instancia.

La siguiente imagen es una representación esquemática bastante completa del ciclo de vida de una instancia -o un componente- y de sus hooks:

Vue lifecycle

Directivas

Habrás notado que en el html del template hay dos atributos: v-bind:style y v-on:click. Esto son directivas: atributos específicos de Vue.js que tienen asociada una determinada funcionalidad. En este caso, como es fácil ver, v-bind enlaza atributos -en este caso style- con valores de la instancia, y v-on añade un listener en un evento determinado, en este caso click. En el caso particular del atributo html style, como contiene css y la sintaxis de css es básicamente la de un json, podemos pasar un objeto como parámetro a v-bind: en este caso nuestra computed property ‘estilos’.

El string que se pasa a las directivas es interpretado como javascript, con el contexto de this asociado a la propia instancia.

Al igual que en angular.js, hay bastantes directivas predeterminadas para modificar el markup en función de nuestro modelo de datos. v-for, que genera listas, y v-if, que genera html condicionalmente, tal vez sean las más utilizadas, así que he creado codepens para ilustrarlas:

Una lista exhaustiva de todas las directivas se puede encontrar en la documentación.

7. Componentes

Los componentes son elementos html reusables y configurables, y nos permiten definir etiquetas que podemos usar en nuestro markup dotadas de una determinada api y comportamiento. Aunque su estructura no es idéntica a la de los custom elements, es posible convertir vue components a custom elements con facilidad mediante vue-custom-elements.

Aunque la Api más inmediata de Vue es su constructor, utilizando meras instancias de Vue no podemos montar con facilidad instancias dentro de otras ni reutilizar código para casos de uso similares. Para esto, deberemos usar componentes. Un componente es, básicamente, una instancia Vue que será utilizada dentro de otra, y que será referenciada mediante una etiqueta específica. De este modo, podemos extender el html para crear nuevas etiquetas con un comportamiento y unos datos dirigidos por Vue. Por ejemplo, si definimos un componente como <usuario>, referenciar esa etiqueta dentro de código html parseado por Vue generará html válido -en función de la propia lógica del componente-.

Los componentes son componibles, de tal modo que se pueden añadir componentes dentro de componentes, y tienen un orden jerárquico: los padres pueden modificar los datos de los hijos, pero no al revés. Los hijos únicamente pueden notificar eventos a los padres, pero no modificar su estado directamente. Esto puede parecer una molestia, pero definitivamente mejora el workflow y evita muchos bugs y condiciones de carrera, y asegura que cuando modifiquemos nuestros modelos haya un punto de entrada centralizado que gestiona estos cambios.

El uso de los componentes va más allá de este tutorial en concreto, pero he creado un codepen para ilustrar un caso de ejemplo muy básico.

8. Ecosistema

El core de Vue es adecuado para aplicaciones no muy grandes, y especialmente para aplicaciones web donde cada página se sirve por separado desde el back-end. Para aplicaciones SPA, sin embargo, necesitaremos alguna herramienta para gestionar las rutas y el estado de nuestra aplicación. Además, en el ecosistema de Vue hay muchas herramientas para utilizar webpack o browserify a la hora de ensamblar una aplicación basada en vue.js.

Precisamente una de las razones de ser de Vue es no atar a los desarrolladores a una herramienta o arquitectura en concreto, por lo que podemos utilizar casi cualquier librería de routing, de control de estado o relacionada con cualquier otro problema con Vue. De hecho, es bastante común usar Vue con redux, un motor de control de estado desarrollado originalmente para React.

Con todo, Vue tiene su propia librería de control de estado: Vuex, y su propio router oficial: vue-router.

Originalmente Vue tenía su propia librería oficial para hacer peticiones http, vue-resource, pero fue retirada por considerarse que, mientras que el router y el gestor de estado pueden estar acoplados al core de Vue, y por tanto tiene sentido tener herramientas específicas para Vue, ese no es el caso de un cliente http. La más popular entre los usuarios de Vue es Axios.

He encontrado muy útil el webpack-vue-loader, que permite codificar componentes con una sintaxis similar a la de los template elements, y dotar al componente de CSS contextual, que permite una privacidad de estilos similar a la que provee el shadow DOM sin necesidad de polyfills.

Por otro lado, hace poco los ingenieros de Alibaba y el equipo de Vue empezaron a colaborar en la herramienta weex, aún en desarrollo, cuyo objetivo es renderizar componentes vue de forma nativa en plataformas móviles, de forma similar a cómo funciona React native.

9. Conclusiones

Lo malo

Aunque Vue hace muchas cosas bien y puede ser la herramienta adecuada para muchos proyectos, también tiene sus contrapartidas.

Para proyectos con equipos muy grandes y mucha rotación de gente, el hecho de que Vue no tenga opiniones sobre la arquitectura puede llevar a la anarquía o al uso de anti patrones. En el mundo de la consultoría, donde los programadores rotan mucho y los desarrollos se delegan en terceros, esto hace que soluciones como Angular sean más apreciadas.

Vue es, además, un producto muy nuevo, y aunque tiene un ecosistema muy rico, algunas herramientas ligadas a él no son tan maduras como en otras alternativas. La herramienta de scaffolding por línea de comandos de Vue, vue-cli, tiene fama de no ser muy completa. La extensión de depuración de Vue, por otro lado, no provee toda la información que muchas veces sería deseable.

A veces, los mensajes de error de Vue no son todo lo informativos que uno desearía: eventualemente el motor no puede renderizar un componente por algún error de sintaxis y falla silenciosamente, o no se comporta como debería por errores en la declaración de un componente o una instancia que deberían producir errores más claros. No obstante, muchos problemas de este tipo que he tenido mientras he ido probando Vue se han ido resolviendo según han ido apareciendo nuevas versiones.

Por otro lado, Vue no tiene el apoyo de gigantes como Google o Facebook, que es un motivo que muchas compañías utilizan para decidirse por Angular o React, dado que temen que Vue sea abandonado por sus desarrolladores. Este es un problema serio para una gran compañía que está preparando un producto que debe mantenerse durante años.

Lo bueno

Vue tiene una gran documentación, lo que junto con la simplicidad de su diseño hace que aprenderlo y empezar a ser productivos con él sea muy sencillo. Es de los mejores trabajos de documentación de una herramienta de software que he leído en mi vida, y llevo unos cuantos.

Vue tiene de los mejores rendimientos entre frameworks js que hay en el mercado: esto es algo que en el front-end no es tan crítico como en el back-end, pero siempre es un punto a favor.

A pesar de no contar con el apoyo de compañías tecnológicas tan grandes como Google o Facebook, Vue es utilizado intensamente por Alibaba, y hay bastantes compañías importantes que dependen de Vue, lo que lleva a pensar que es difícil que su desarrollo sea abandonado. Su adopción en este último año ha crecido enormemente, y Evan You creó un patreon para financiar Vue que ha tenido bastante éxito y ha ayudado a contribuir al desarrollo de la herramienta.

Vue sea seguramente de las mejores soluciones posibles para refactorizar aplicaciones webs que llevan varios años en desarrollo en las que el front-end no está todo lo limpio que debiera, pero donde los recursos, las presiones de tiempo o la acumulación de deuda técnica impiden rehacer la aplicación de una vez como una SPA. Por otro lado, es una opción perfectamente viable para hacer grandes SPA’s, aunque en ese nicho hay muchas más alternativas.

10. Referencias

Introducción a Liferay 7

$
0
0

En este tutorial vamos a ver qué es Liferay 7, qué nos aporta y cómo podemos usarlo. ¡Vamos a ello!

1. Introducción

Antes de nada, ¿qué es Liferay Portal? Liferay Portal es, a grandes rasgos, un gestor unificado de información. Permite a empresas gestionar de forma eficiente ingentes cantidades de información. Da la posibilidad de crear intra y extranets, siendo los usuarios los que podrán dar forma a estos sitios. Además, permite publicar todo tipo de contenido a formato web, con una suite completa para su desarrollo, gestión y visionado.

Liferay Portal tiene dos vertientes, una versión open source (Liferay Portal Community Edition) y otra orientada a dar servicio a empresas que se quieran desentender de la parte de hosting, servidores y mantenimiento.

2. Características

Liferay se puede orientar a cuatro grandes áreas:

  • Intranets
  • Portales
  • Movilidad
  • Webs

Entre las características de Liferay se pueden destacar las siguientes:

  • Publicación en web
  • Repositorio centralizado de ficheros corporativos
  • Publicador de contenidos
  • Soporte de Single Sign On (Único punto de entrada)
  • Gestión completa de usuarios, grupos y organizaciones
  • Gestor de aplicaciones internas
  • Creación de aplicaciones web sencillas
  • Extensibilidad con módulos propios

Como podemos ver, Liferay ofrece soluciones a grandes problemas que sufren las mega corporaciones en cuanto a comunicación y organización.

Una vez dicho todo esto vamos a proceder a la instalación y configuración inicial.

2. Entorno

  • Hardware: Portátil MacBook Pro 17’ (2.66 Ghz Intel Core I7, 8GB DDR3).
  • Sistema Operativo: Mac OS Sierra 10.12.4
  • Entorno de desarrollo: iTerm2
  • Liferay 7.0 CE GA 3
  • Java JRE 1.8

3. Instalación

Para instalar Liferay Portal 7 CE nos dirigimos a la página de descargas. Y elegimos Liferay Portal 7 CE bundled con Tomcat. Ya que descarga un archivo comprimido será necesario descromprimirlo. Cuando éste se haya descomprimido lo moveremos al directorio de nuestra elección. Una vez movido el directorio abrimos nuestro terminal, ya que debemos iniciar la instancia de Liferay Portal.

Hacemos cd hasta la carpeta donde hemos puesto Liferay Portal, en nuestro caso Desktop.

cd ~/Desktop/liferay-ce-portal-7.0-ga3/tomcat-8.0.32/bin

Hay que tener en cuenta que los directorios se pueden llamar de forma distinta, ya que la versión en el momento en el que se escribió este tutorial puede ser distinta a la que os hayáis descargado vosotros.

Una vez en el directorio bin nos encontramos con una serie de ficheros que lanzarán nuestra instancia de Liferay:

  • startup.sh o startup.bat
  • shutdown.sh o shutdown.bat

(.sh para Unix y .bat para Windows)

Con lo que tendremos que escribir lo siguiente para lanzar la instancia. En nuestro caso, como estamos en macOS usaremos el fichero .sh:

./startup.sh

Si queremos parar el servicio pondremos lo siguiente:

./shutdown.sh

Tardará un par de minutos en terminar el proceso de construcción. Cuando éste termine se abrirá nuestro navegador predeterminado con Liferay Portal CE ejecutándose.

4. Configuración inicial

Si es la primera vez que habéis lanzado Liferay Portal, os pedirá una configuración inicial, la cual se muestra a continuación:

Ahora debemos configurar los datos de nuestra instancia y elegir la base de datos que usará por debajo Liferay Portal. Para este tutorial usaremos Hypersonic, aunque cabe destacar que no es apropiada para producción.

Una vez hecha la configuración inicial daremos a siguiente y aceptamos las condiciones de uso.

Nos pedirá que creemos una contraseña para nuestro usuario administrador

Además, por seguridad nos pedirá un recordatorio de contraseña

Una vez completados todos los pasos veremos la pantalla inicial de Liferay, donde podremos empezar a crear nuestra intra/extranet, aplicaciones web, portales y, ¡mucho más!

5. Conclusión

Liferay ha conseguido simplificar la instalación y configuración inicial de la aplicación gracias a tener tomcat/wildfly embebido. Por supuesto si queremos subir Liferay a nuestros servidores deberemos hacer una configuración más exhaustiva u optar por DXP (Enterprise Edition) de Liferay, donde ellos mismos se encargan de gestionar los servidores y el mantenimiento.

Esperamos que te haya picado la curiosidad para echarle un ojo a esta herramienta que bien podría servir a tu organización o te puede interesar para desarrollar extensiones, ya que cuenta con un mercado propio de plugins/módulos bastante cotizado.

Configurar Liferay 7 con PostgreSQL

$
0
0

En este tutorial se explica cómo emplear PostgreSQL al configurar Liferay Portal 7 CE.

Índice de contenidos

1. Introducción

En su tutorial Introducción a Liferay 7, César explicó qué es Liferay Portal, cómo se instala y cuál es su configuración por defecto. Durante el asistente de configuración, la base de datos preseleccionada es Hypersonic, pero Liferay recomienda cambiarla. En este tutorial, vamos a ver cómo hacerlo para emplear PostgreSQL.

2. Entorno

Este tutorial se ha desarrollado en el siguiente entorno:

  • Hardware: portátil MacBook Pro (Retina, 15′, mediados 2015).
  • Sistema operativo: macOS Sierra 10.12.3.
  • Java 1.8.0_112
  • PostgreSQL 9.6.2

3. Establecer PostgreSQL durante el asistente de configuración

Una vez descargado el paquete de Liferay Portal CE con Tomcat y extraído, procedemos a ejecutar la aplicación web a través de terminal con el comando

./startup.sh
; este ejecutable se encuentra en el directorio
liferay-ce-portal-7.0-ga3/tomcat-8.0.32/bin/
(salvando las diferencias por tema de versiones). Tendremos que esperar un poco —un par de minutos, incluso— para que automáticamente se nos abra en nuestro navegador una nueva pestaña a
localhost:8080
.

liferay_postgresql_asistente_defecto

Como vemos, Hypersonic es la base de datos por defecto, pero desde Liferay nos recomiendan no utilizarla en producción. Procedemos a cambiarla y elegimos PostgreSQL. Tendremos que cambiar la URL de JDBC para que apunte a una base de datos vacía que hayamos creado, ya que por defecto espera que exista y se llame

lportal
. Además, tendremos que rellenar el nombre de usuario y contraseña para poder realizar la conexión.

liferay_postgresql_asistente_postgresql

Pulsamos en

Finalizar Configuración
y, si la conexión a base de datos es correcta, la aplicación nos pedirá que reiniciemos. Para esto paramos el servicio con
./shutdown.sh
y lo volvemos a lanzar con
./startup.sh
. Se nos abrirá la aplicación, aceptaremos los términos de uso y estableceremos la contraseña del usuario administrador.

Si accedemos a la base de datos —por ejemplo con pgAdmin—, veremos que ya no está vacía y que se ha llenado con las tablas que Liferay Portal necesita.

liferay_postgresql_pgadmin

4. Volver a mostrar el asistente de configuración

Es posible que al lanzar la aplicación por primera vez, durante el asistente de configuración eligiéramos sin querer la base de datos por defecto —Hypersonic—. No pasa nada, podemos volver a ejecutar el asistente para enmendar nuestro desliz y elegir PostgreSQL. Para ello empezamos parando el servicio con

./shutdown.sh
. Luego abrimos el archivo
portal-setup-wizard.properties
, que se encuentra en el directorio raíz de nuestra carpeta de Liferay Portal extraída, y cambiamos la línea:
setup.wizard.enabled=false

a:

setup.wizard.enabled=true

Arrancamos la aplicación con

./startup.sh
y ya veremos el asistente, donde podremos elegir PostgreSQL como base de datos.

5. Error al conectar con base de datos

Es posible que, durante el asistente de configuración, nos diese error al intentar conectar con la base de datos: «Se ha producido un error inesperado al conectarse a la base de datos» («An unexpected error occurred while connecting to the database»).

liferay_postgresql_error_base_datos

Esto puede ser por múltiples razones: PostgreSQL no está ejecutándose, el puerto en el que escucha no es el que has indicado en la URL a JDBC, la base de datos no está creada o no has escrito bien su nombre en la URL de JDBC, etc.

Podemos empezar intentando acceder a la base de datos con pgAdmin. Si desde este programa la conexión es correcta, pasamos a ver si el fallo en Liferay Portal se debe a la conexión con base de datos. Para ello accedemos a los logs: vamos a la carpeta

liferay-ce-portal-7.0-ga3/tomcat-8.0.32/logs/
y abrimos el archivo
catalina.out
.

Si en este archivos vemos que nos ha saltado la excepción

org.postgresql.util.PSQLException: Connection to localhost:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections
, tendremos que revisar que PostgreSQL está escuchando en ese puerto, pues desde el pgAdmin no hubo problemas a la hora de conectarse a la base de datos. Una herramienta para comprobar rápidamente el puerto es
telnet
. Podemos ejecutar por terminal el comando
telnet localhost 5432
y ver si la salida es satisfactoria:
$ telnet localhost 5432
Trying ::1...
Connected to localhost.
Escape character is '^]'.

o si se están rechazando las conexiones:

$ telnet localhost 5432
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
telnet: connect to address 127.0.0.1: Connection refused
telnet: Unable to connect to remote host

En caso de error, vamos a asegurarnos de que el puerto es correcto. En nuestro caso, que instalamos PostgreSQL en macOS con el paquete EnterpriseDB, podemos ver el archivo de configuración

/Library/PostgreSQL/9.6/data/postgresql.conf
.

liferay_postgresql_postgresqlconf

En este archivo encontraremos, entre otras cosas, el puerto en el que está escuchando:

port = 5432                             # (change requires restart)

Si el puerto es distinto, simplemente tendremos que indicarlo en el asistente de Liferay Portal, en la URL a la JDBC. Por ejemplo, si el puerto es 5433 y la base de datos

lportal
, entonces será
jdbc:postgresql://localhost:5433/lportal
.

6. Conclusiones

Indicar en el asistente de Liferay Portal que deseamos emplear una base de datos distinta a Hypersonic es tarea sencilla, pues ya viene preparado para integrarse con varias de ellas, como PostgreSQL. El problema puede venir por despistes nuestros de mala configuración, pero si se identifica rápidamente su causa, entonces la solución no debería ser complicada.

7. Referencias

Primeros pasos con los módulos de Java 9 y Maven – proyecto Jigsaw, JSR 376

$
0
0

En este tutorial vamos a dar nuestros primeros pasos con los módulos de Java 9 y veremos cómo podemos combinarlos con Maven para conseguir lo mejor de los dos mundos: con los módulos de Java 9 gestionaremos la visibilidad entre las clases hasta un grano muy fino, mejorando la así la encapsulación; y con Maven especificaremos cómo queremos componer los artefactos de nuestra aplicación.

Índice de contenidos


Java 9 module dependencies example

1. Introducción

El proyecto Jigsaw (JSR 376 – Java Platform Module System), nace con los siguientes objetivos en mente:

  • Hacer que la plataforma Java SE, y la JDK, se puedan descomponer en trozos más pequeños para ser usadas en pequeños dispositivos.

  • Mejorar la seguridad y mantenimiento en general de las implementaciones de la plataforma Java SE, y en particular de la JDK.

  • Posibilitar la mejora de rendimiento de las aplicaciones.

  • Facilitar a los desarrolladores la construcción y mantenimiento de grandes librerías y aplicaciones, tanto para la plataforma Java SE y EE.

Todo esto quiere decir básicamente que vamos a poder romper nuestra aplicación en trozos, que llamaremos módulos, donde cada uno de estos módulos tiene perfectamente definidas sus dependencias con otros módulos. Y estas dependencias van a tener un impacto directo en la visibilidad de nuestras clases.

Alguien podría pensar que esto ya lo teníamos resuelto con los archivos JAR y la gestión de dependencias de Maven (o Gradle), pero nada más lejos. Efectivamente un JAR es una agrupación de clases y con Maven podemos definir las dependencias tanto a nivel de compilación como de ejecución que estos JAR tienen entre ellos, pero tiene un gran problema y es que cualquier clase public es visible en cualquier parte del sistema, y esto va en contra de los principios básicos del diseño de aplicaciones rompiendo con el bajo acoplamiento y la alta cohesión.

Esto sucede porque, por muchos JAR que tengamos, básicamente lo que hace Java es cargar todas las clases de todos los JARs de nuestra aplicación en el mismo class loader, con lo que todas las clases public son visibles por el resto de clases independientemente del JAR o del paquete en el que se encuentren definidas.

A nivel de Maven no mejora demasiado ya que podemos expresar dependencias entre JARs, por ejemplo A → B, pero una vez se establece esta dependencia, desde A podremos acceder a todas las clases public que se encuentren en B. Por esto, para mejorar el encapsulamiento es habitual encontrarse nombres de paquetes del estilo xxxx.internal.yyyy o xxxx.impl.yyyy donde ese internal y ese impl nos están indicando: ¡cuidado! ¡más allá hay dragones! Pero es simplemente eso, una advertencia, porque nada nos impide acceder a las clases que se encuentran tras esas “barreras”.

Incluso es bastante común encontrar librerías partidas en varios JARs (API e implementación) para conseguir que, por lo menos a nivel de compilación, no accedamos a las clases que no debemos (protección que luego no existirá en tiempo de ejecución). Por ejemplo para conseguir con Maven un esquema de dependencias similar al del dibujo inicial, podríamos hacer algo como:


Maven module dependencies example

Es decir, prácticamente cada JAR lo tenemos que partir en dos y especificar la dependencia en compilación con el API y en ejecución con la implementación. Además recordemos otra vez que esto sólo nos va a afectar mientras compilamos, pero que en runtime podremos acceder a cualquier clase, por ejemplo usando reflection, por lo que todo este trasiego de JARs tampoco nos garantiza realmente la encapsulación de nuestras clases.

Aquí tenéis un repositorio de GitHub con un ejemplo completo de todo lo que se habla en este tutorial.

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.4

  • Oracle Java 9-ea167

  • Maven 3.5.0

3. Definición de un módulo

Volvemos a poner aquí el gráfico que pusimos al principio del tutorial y que representa lo que queremos conseguir en este ejemplo.

Java 9 module dependencies example

Vemos que tenemos un módulo App que depende de un servicio de Logging y de un módulo de Dictionary. Las dependencias a estos dos módulos son las únicas directas que tiene App, sin embargo de forma transitive también será capaz de acceder al módulo Words. Y en runtime accederá al módulo ConsoleLogging que es la implementación del servicio. Lo bueno de esto es que realmente la aplicación no sabe quien es el proveedor del servicio, es decir estamos consiguiendo inversión de control (que no inyección de dependencias).

Para definir un módulo debemos añadir un fichero module-inform.java en el directorio raíz de nuestro código fuente (en el directorio donde veríamos nuestro paquete raíz com.). Por ejemplo, en un proyecto Maven sería en el directorio src/main/java.

  • Por convención el directorio raíz donde se encuentra el código del módulo debería tener el nombre del propio módulo, es decir src/main/com.autentia.logging/…​<estructura de paquetes>…​. Por ahora nos vamos a saltar esta convención para mantener el src/main/java/…​<estructura de paquetes>…​ y así simplificar la configuración de Maven.

Para nuestro caso más sencillo que es el módulo de Logging, este fichero tendría el siguiente aspecto:

module com.autentia.logging {

    exports com.autentia.logging;

}

Vemos cómo después de la palabra reservada module viene el ID del módulo, en nuestro caso es como.autentia.logging. Este ID tiene forma de nombre de paquete, pero es sólo una convención, ya que simplemente se trata de una cadena, por lo que podríamos poner lo que quisiéramos. Cuando desde otro módulo queramos hacer referencia a este, siempre lo haremos usando este ID.

Después entre llaves vemos la definición del módulos. En este caso sólo vemos la palabra reservada exports y a continuación el nombre del paquete que queremos exponer hacia afuera. Es decir, de este módulo sólo serán visibles desde otros módulos las clases públicas que se encuentren en el paquete como.autentia.logging. El resto de clases públicas que estén en el módulo pero en otros paquetes, quedarán ocultas tanto en tiempo de compilación como en tiempo de ejecución.

4. Dando más visibilidad a los modulos amigos

Hay ocasiones donde podemos tener varios paquetes que están relacionados. Entre estos paquetes podemos querer exportar más clases que las que exponemos al resto del mundo. Así en nuestro ejemplo vamos a suponer que el módulo Words quiere compartir con el módulo Dictionary ciertas clases que no serán visibles para el resto del mundo:

module com.autentia.words {

    exports com.autentia.words;

    exports com.autentia.words.friends to com.autentia.dictionary;

}

Vemos cómo tenemos un segundo exports con una cláusula to. Esto indica que el paquete que hay a la izquierda del to sólo será visible por el módulo cuyo ID hemos puesto a la derecha del to (importante recalcar que a la izquierda hemos puesto un nombre de paquete mientras que a la derecha hemos puesto un ID de un módulo). A la derecha del to podemos poner una lista de IDs de módulos, separados por comas “,”.

5. Dependencia transitiva de un módulo

Por defecto no hay dependencias transitivas, es decir, si nuestra App quiere usar el módulo Dictionary y este devuelve en su API pública clases del módulo Words, el módulo App estaría obligado a declarar también la dependencia con el módulo Words. Esto no parece ni práctico ni cómodo, ya que esta doble dependencia (el módulo Dictionary y el módulo Words) la tendríamos que declarar en todos los otros módulos que quieran usar el módulo Dictionary.

Para evitar esta redundancia de tener que declarar siempre la dependencia al módulo Words se puede evitar usando la palabra reservada transitive. Por ejemplo:

module com.autentia.dictionary {

    exports com.autentia.dictionary;

    requires transitive com.autentia.words;

}

Aquí, en la línea 5 estamos indicando que todo aquel que requiera como dependencia el módulo Dictionary, automáticamente de forma transitiva también tendrá disponibles las clases del módulo Words.

6. Definición de un servicio

En nuestro ejemplo ya hemos visto cómo tenemos un módulo de Logging donde vamos a definir un servicio, que es simplemente una Interface de Java. Este servicio (interfaz) será utilizado en otras partes del sistema, pero la gracia está en que no se sepa quién implementa dicho servicio (interfaz). De esta manera conseguimos que nuestro sistema sea más sencillo de mantener ya que podremos cambiar la implementación de dicho servicio sin necesidad de tocar ni una sola línea de código del resto del sistema.

Para ello en el módulo Logging ya vimos que simplemente se hacía export de un paquete. En este paquete está la interfaz que define el servicio (esta interfaz no tiene nada en especial, es la implementación de una interfaz de Java de toda la vida).

Ahora vamos a proporcionar una implementación donde los mensajes simplemente se escriben en la consola. Esto lo haremos en el módulo ConsoleLogging:

module com.autentia.logging.console {

    requires com.autentia.logging;

    provides com.autentia.logging.Logger with com.autentia.logging.console.ConsoleLogger;

}

Aquí en la línea 5 podemos ver cómo con la palabra reservada provides indicamos el nombre de la interfaz (que está definia en el módulo Logging), y con la palabra reservada with indicamos la clase, dentro del módulo ConsoleLogging, que va a implementar dicha interfaz.

7. Localización de un servicio

En el punto 3 hemos visto cómo definimos el servicio, y en el punto 6 hemos visto cómo indicar qué clase implementa dicho servicio. Ahora sólo nos falta localizar dicho servicio para poder usarlo. Así en el módulo App que representa nuestra aplicación declararemos:

module com.autentia.app {

    requires com.autentia.dictionary;
    requires com.autentia.logging;

    uses com.autentia.logging.Logger;

}

Aquí destacamos cómo en la línea 5 usamos la palabra reservada uses para indicar que en este módulo vamos a usar el servicio. Nótese especialmente que en este módulo App no tenemos ningún requires al módulo ConsoleLogging sino que simplemente lo tenemos al módulo Logging que es donde se define la interfaz. De esta forma el código de nuestra aplicación queda totalmente desacoplado de la implementación del servicio, ya que no hay ninguna dependencia, ni en compilación ni en ejecución, entre ambos módulos.

El código de nuestra aplicación quedará así:

public class App {

    private final Logger log = ServiceLoader.load(Logger.class).findFirst().get();
    private final Dictionary dictionary = new Dictionary();

    public static void main(String... args) {
        new App().execute();
    }

    private void execute() {
        final Word word1 = dictionary.getWord();
        final Word word2 = dictionary.getWord();
        final Word word3 = dictionary.getWord();

        log.error(word1.toString());
        log.info(word2.toString());
        log.debug(word3.toString());
    }

}

Donde vemos cómo en la línea 3 estamos localizando la implementación del servicio sin saber cuál es.

En el ejemplo se ve cómo localizamos la primera implementación del servicio con findFirst, pero aquí podríamos tener toda la lógica que quisiéramos, incluso no sería complicado pasar de un patrón de Service Locator a un contenedor de Dependency injection.

8. ¿Y qué pasa con el código antiguo? ¿cómo lo migramos al sistema de módulos?

Cuando intentemos cargar una clase que no se encuentra en ningún módulo, esta se va a buscar en el class path normal de Java (el class path de toda la vida). Si se encuentra en el class path, automáticamente esta clase se añade a un módulo especial llamado “módulo sin nombre” (unnamed module). De esta forma este unnamed module contendrá todas las clases que no estén definidas en ningún módulo explícitamente.

El módulo sin nombre puede acceder a todas las clases públicas que estén exportadas en el resto de módulos con nombre (módulos explícitos). Por el contrario los módulos con nombre no pueden acceder a ninguna clase que se encuentre en el módulo sin nombre, de hecho ni siquiera se puede especificar una dependencia hacia el unnamed module. Esta restricción está puesta a propósito para forzar a los desarrolladores a realizar una correcta configuración de los módulos.

Todo esto nos permite hacer una migración gradual de nuestras aplicaciones ya que pueden convivir perfectamente los nuevos módulos con JAR que todavía no tengan módulos definidos.

Teniendo en cuenta las restricciones antes mencionadas, esta migración la tendremos que hacer de abajo arriba. Es decir en un grafo de dependencias empezaríamos a migrar las hojas (JARs que no dependen de otros, en nuestro ejemplo serían Words, ConsoleLogging, …​), e iríamos subiendo en el grafo de dependencias (en nuestro ejemplo terminaríamos por el módulo App).

El problema que tiene esta migración de abajo arriba es que nosotros podemos tocar nuestro código, pero siempre vamos a depender de librarías de terceros que, si no están migradas al nuevo sistema de módulos, no podrán ser accedidas desde nuestros módulos. En estos casos lo que podemos hacer es tratar estos JAR como un “módulo automático” (automatic module), simplemente colocando el JAR en el module path en lugar del class path. De esta forma se genera un módulo de forma implícita, donde su nombre vendrá determinado por el nombre del archivo. Y así podremos usarlo como si se tratara de cualquier otro módulo con nombre.

9. Referencias

10. Conclusiones

Los módulos de Java 9 se presentan como una potente herramienta para la encapsulación. Hemos visto cómo podemos declarar las dependencias, hacer estas transitivas, definir implementar y usar servicios, …​

Sin embargo ¿qué pasa con Maven/Gradle? En el ejemplo completo de código podéis ver como ambos pueden convivir a día de hoy, existiendo una pequeña duplicidad de conceptos, ya que nos vemos obligados a definir las dependencias en dos puntos: los módulos de Java 9 y los módulos de Maven para su compilación. Veremos si en versiones posteriores de Maven se lee esta información directamente de los módulos de Java 9 ¿o acaso es hora de un nuevo sistema de empaquetamiento?

Y si podemos definir e inyectar servicios ¿qué pasa con Spring? ¿Acaso es su fin? ¿Habrá un enganche entre ambos? Desde luego habrá movimiento porque con las nuevas restricciones que impone el module path Spring no va a tener tan fácil hacer sus “automagias”.

En general hay muchas incertidumbres (por ejemplo, como hemos comentado antes, las migraciones de abajo arriba no van a ser nada sencillas porque implica que todo el mundo tiene que hacerla), y de hecho hay unos cuantos detractores, por ejemplo aquí tenéis un buen artículo Concerns Regarding Jigsaw(JSR-376, Java Platform Module System). Ojo que es largo, pero merece la pena para ver la de esquinas que todavía quedan por pulir.

Con todo esto lo que nos queda es estudiar, practicar y observar hacia dónde se mueven las cosas, a ver si finalmente se imponen los módulos de Java 9 o por el contrario se sigue optando por soluciones de terceros como OSGi, que por otro lado son mucho más potentes.

11. 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”

Definiendo productos ágiles con Craft.io

$
0
0

Estas semanas estoy probando aplicaciones para definir productos en base a historias de usuario.

Creo francamente que los técnicos muchas veces cometemos el error de querer utilizar una herramienta absolutamente para todo (anti patrón “martillo de oro”) y puede ser contraproducente tratar de meter a los usuarios de negocio por nuestro embudo.

Posiblemente sea muy buena idea trabajar con una herramienta en la fase de definición y con otra en la de construcción.

Y sí, si te lo preguntas, claro que me acuerdo del manifiesto ágil y tengo muy en cuenta que son más importantes las personas y sus interacciones que los procesos y herramientas. Claro que prefiero trabajar en la definición de proyectos con paneles físicos y pizarras. Lo único es que alguien luego (o en paralelo) tiene que ir recopilando la información.

Hoy voy a jugar un poco con Craft.io que podéis encontrar en https://craft.io/. Obviamente mi entendimiento del producto será limitado así que os recomiendo probarlo por vosotros mismos y llegar a vuestras conclusiones.


Me voy a crear una cuenta gratuita y voy a dar de alta un proyecto que voy a llamar “proyecto de futuro”.


Creo el nuevo proyecto:


La aplicación tiene 4 lengüetas: descubrir, definir, planificar y construir.

Curiosamente empieza por defecto por definir. Voy a seguir por aquí pero mirad antes de jugar con la herramienta el tutorial completo porque es mejor empezar por discover si quieres hacer un User History Mapping y que te cree todos las historias desde ese punto.

La estructura lógica es que tenemos algunos elementos esenciales como objetivos, personas, etc. Luego unos temas que tienen épicas e historias.


Si pinchamos sobre el + de Essentials vemos las opciones que nos propone.


Creamos el sumario del proyecto. Recordad que los proyectos hay que saber contarlos a las personas que no tienen contexto (que normalmente son los que aprueban las inversiones). Un glosario tampoco estaría mal.


Todo proyecto debería tener objetivo y diría que hasta KPIs para medir su éxito y saber si es mejor dejarlo a tiempo.


Podemos identificar los usuario-persona (o cliente-persona) como se dice ahora.


Y bueno, los que queráis más.


En los temas, me gusta agrupar empezar por distintos tipos de usuarios. Hacemos un problema grande descomponiéndolo en 7 más pequeños. Realmente da igual quién hace qué, por lo que no os molestéis demasiado en la integridad en una primera fase.


Para cada uno de los usuarios definimos las épicas agrupadoras y las historias


Cada una de las historias puede tener atributos como: importancia, categoría, valor, esfuerzo, puntos de historia, valoración de kano (esto me encanta porque lo cuento en todos los cursos).


A las historias se les puede incorporar el diseño por lo que tenemos junto a la definición una previsualización en distintas partes del sistema.


Podemos exportar la documentación que va quedando bastante aparente.


En la sección de plan podemos definir versiones e hitos. Interesante que podemos ver el porfolio de multiples proyectos.


Creamos una nueva versión con el Producto Mínimo Viable.


La llamamos PMV.


Y añadimos las fechas de inicio y de final.


Creamos un hito o milestone.


Vemos cómo se va quedando reflejado.


Creamos una segunda versión del producto donde meter las historias menos prioritarias.


Y visualizamos el modelo.


A las historias les podemos cambiar el atributo de versión una a una.


También desde RoadMap las podemos cambiar todas a la vez.


Podemos crear Sprints con sus fechas de inicio y final y añadir las historias.


Recordad que en un product backlog no todo son historias, podemos tener otros tipos de backlog items.


Podemos visualizarlo en columna para comprobar los contenidos completos asociados a cada versión.


Cada elemento que vamos creando podemos asociarlo a un rol y/o usuario.


De este modo podemos crear paneles Scrum donde ver todo el trabajo o por usuario concreto.


Los paneles pueden ser Scrum o Kanban. En estos últimos se puede definir el WIP.


Con toda la información guardada y actualizando en grado de avance podemos seguir fácilmente todos los items desde una pantalla de control.


Como podemos ver, esta herramienta tiene un metamodelo bastante útil porque en la mayoría de los casos no vamos a necesitar mucha más información a nivel de definición y seguimiento (estoy pensando en empresas muy grandes con un gran porfolio de proyectos y alta complejidad técnica por debajo).

Si os fijáis, desde el principio advertía que si quieres hacer un User Story Mapping tendrías que empezar en discover.

Podemos dar de alta una idea.


Sobre esta idea hacer el User Story Mapping.


Y decirle que me lo pase directamente a definición.


Le decimos a qué versión queremos que nos lo pase.


Y ya tenemos trasladados todos nuestros items a define. Rápido y sencillo. De todos modos parece razonable para un primer paso pero luego por experiencia parece que tenga más sentido seguir en define para darle más integridad al modelo.


La verdad es que me ha gustado mucho la herramienta. Obviamente está en el extremo opuesto de CardBoard https://www.adictosaltrabajo.com/tutoriales/user-story-mapping-con-cardboard/ donde teníamos un meta modelo muy reducido y mucha flexibilidad para pintar.

Todavía tengo que seguir probando algunas otras pero si os fijáis bien, lo importante es tener una metodología base para definir los proyectos. La herramienta luego veremos cómo nos da cobertura a esa metodología con la que nos sintamos cómodos.

Este modelo lo podéis encontrar en https://app.craft.io/share/7BC521D62305843009655840431

Si queréis aprender modelos de definición de proyectos y más sobre construcción de software de calidad podéis informaros sobre nuestros cursos. Tal vez te lo pueda contar en persona.

https://www.autentia.com/servicios/formacion/curso-gestion-alcance-definicion-producto/

Primeros pasos con React

$
0
0
Antes de empezar este tutorial es recomendable tener conocimientos intermedios de Javascript y de ES6.

1. Introducción

React es una librería para crear interfaces de usuarios. Fue creada por Facebook en 2011 y liberada como open source en 2013, cuenta con proyectos en producción por compañías como Netflix, Airbnb, Walmart, Facebook e Instagram.

Sus más notables propuestas son:
  • Propagación de datos unidireccional: Los datos son propagados de componentes padres a componentes hijos. La comunicación entre padres e hijos se hace mediante callbacks y los eventos son enviados de hijos a padres, siendo los componentes padre los que gestionan el estado y la lógica.
  • DOM Virtual: React genera una estructura en memoria semejante al DOM físico. Cuando detecta algún cambio compara entre el DOM virtual y el DOM físico y sólamente recarga aquello que haya cambiado. Ésta innovación hace obsoleto el tener que recargar la página entera cada vez que el estado es modificado.
  • JSX: JSX es una extensión de la sintaxis de Javascript comúnmente usada para codificar los componentes. Es semejante a html y se embebe en los ficheros .js, con lo que disponemos de todas las ventajas programáticas de Javascript.
  • Aplicaciones isomórficas: Esto quiere decir que las aplicaciones web se pueden renderizar tanto en el cliente como en el servidor. Si ésta se renderiza en el servidor se puede enviar al cliente desde el servidor html puro en aquellos casos que se pueda.
  • React Native: Facebook lanzó en 2015 React Native, el cual permite crear componentes usando herramientas de desarollo web y que genera aplicaciones nativas tanto para Android como para IOS.

Todo el código podrá verse en Github en este link.

2. Entorno

  • Hardware: MacBook Pro 17’ (2,66 GHz Intel Core i7, 8GB DDR3)
  • Sistema operativo: macOS Sierra 10.12.4
  • Entorno de desarrollo: VSCode
  • Nodejs 7.7.4

3. Instalación

Vamos a usar Create React App para crear una aplicación web sin necesidad de configurar nada.

Unicamente necesitaremos instalar en nuestra máquina NodeJS (Versión 7.7.4 o mayor). Si estás en windows es necesario reiniciar.

Una vez instalado NodeJS pasaremos a descargar la utilidad de create-react-app. Para ello abrimos terminal y seguimos estos comandos:

npm install -g create-react-app

create-react-app react-tutorial
cd react-tutorial/
npm start
Si todo ha ido bien veremos la siguiente pantalla: Create React App ejecutándose

Además veríamos que se abre nuestro navegador con la aplicación ya corriendo.

4. ¿Qué es lo que ha hecho create-react-app?

Create React App ha generado y dispuesto una estructura de ficheros de la siguiente forma:
react-tutorial/
  README.md
  node_modules/     // Librerías de nuestra aplicación
  package.json      // Información y dependencias de nuestro proyecto
  .gitignore        // Fichero donde se determina qué carpetas no deberán ser trackeadas con git
  public/
    favicon.ico
index.html          // Página donde se inyectarán los componentes de React
  src/              // Directorio de desarrollo que es el que usaremos nosotros
    App.css
    App.js
    App.test.js
    index.css
    index.js
    logo.svg

Además nos ayuda a generar un servidor local de desarrollo que nos muestra errores nos ofrece linting en nuestro editor y recarga la página automáticamente cuando un cambio es detectado. 😎

5. Componentes

Vamos a crear nuestro primer componente. Creamos un fichero nuevo llamado Hola.js en la carpeta src.

Nota: Por convención los componentes de React se escriben con la primera letra en mayúsculas.
import React, { Component } from 'react';

class Hola extends Component {
  render() {
    return (
      <h1>Hola mundo</h1>
    );
  }
}

export default Hola;

Para usar este componente vamos a ir al fichero App.js y pondremos lo siguiente:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

// Importamos nuestro componente
import Hola from './Hola';

class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <div className="App-intro">
          {/*Incluimos nuestro componente*/}
          <Hola/>
        </div>
      </div>
    );
  }
}

export default App;

Guardamos y veremos que la página se ha recargado automáticamente. Tendríamos que ver lo siguiente:

Vamos a hacer un repaso rápido del código que acabamos de escribir.

import React, { Component } from 'react';

Aquí importamos React. React tiene que estar en el contexto para que sepa que es un componente de React. Además, importamos de la librería de React el módulo { Component }. Esto se debe a que nuestra clase debe extender de Component.

class Hola extends Component {
    ...
}
Este es nuestro componente y debe extender de Component.
render() {
  return (
    <h1>Hola mundo</h1>
  );
}

Este es el único método de nuestra clase. Tiene que ser llamado obligatoriamente render() y este método es el que se encarga de renderizar el componente. Varios puntos a tener en cuenta. React necesita que retornemos un único elemento (Independientemente de cuantos hijos tenga), por ejemplo, esto nos daría un error:

render() {
  return (
    <h1>Hola mundo</h1>
    <h1>Adios mundo</h1>
  );
}
Mientras que esto estaría bien
render() {
  return (
    <div>
      <h1>Hola mundo</h1>
      <h1>Adios mundo</h1>
    </div>
  );
}

Y el otro punto importante es que hay ciertas palabras reservadas que no se pueden usar, ya que recordemos que estamos escribiendo html (JSX) en Javascript, y hay nombre que colisionan. Por ejemplo, class debe ser sustituida por className y for tiene que ser sustituida por htmlFor.

export default Hola;

Esta línea quiere decir que vamos a exportar nuestro componente para que éste pueda ser usado a lo largo de nuestra aplicación.

6. Props

Vamos a hacer que nuestro componente Hola sea dinámico.
import React, { Component } from 'react';

class Hola extends Component {
  render() {
    return (
      <h1>Hola {this.props.nombre}</h1>
    );
  }
}

export default Hola;

El objeto props es un objeto especial donde se determinan todas las propiedades que tiene un componente. Se usa para hacer que los componentes rendericen una cosa u otra. A este objeto props se le puede pasar desde strings hasta objetos e incluso funciones.

Para asignarle un prop a un componente vamos a App.js y cambiamos
<Hola/>
por
<Hola nombre="César"/>
Veremos que la página se recarga y deberíamos ver lo siguiente:

Es importante ver que para hacer uso de expresiones en jsx éstas tienen que estar entre llaves. Dentro de estas llaves podemos hacer virguerías como:

<h1>Hola {1 + 1}</h1>
O por ejemplo
<h1>{this.props.nombre === 'César' ? `Ave ${this.props.nombre}` : 'Tú no eres César'}</h1>

Que hace que si la propiedad que hemos pasado a nuestro componente es “César” nos saluda como es debido.

7. State

Vamos a ver una parte primordial de las aplicaciones hechas con React. El estado. Imaginemos que queremos hacer un contador que vaya contando de uno en uno. Primero hagamos un componente llamado Contador.js en la carpeta src.
import React, { Component } from 'react';

class Contador extends Component {
  constructor() {
    super();

    this.state = {
      contador: 0
    };
  }

  render() {
    return (
      <span>{this.state.contador}</span>
    );
  }
}

export default Contador;
Vamos a ir poco a poco explicando lo que hace cada parte.
constructor() {
  super();

  this.state = {
    contador: 0
  };
}

En el constructor de la clase llamamos a super() requisito obligatorio si tenemos un constructor. En la siguiente línea inicializamos state y le asignamos un objeto con una clave y un valor. Este estado puede ser cualquier cosa, desde más objetos hasta arrays o strings.

Si queremos más estados, sólamente los tenemos que separar por comas
this.state = {
    contador: 0,
    miArray: [1, 2, 3],
    miObjeto: {
        clave: 'valor'
    }
  };
render() {
  return (
    <span>{this.state.contador}</span>
  );
}

En el método ´render()´ pintamos el estado con this.state.contador. Ahora bien, el puntazo de React es que cuando modificamos el estado, todos aquellos componentes que dependen de ese estado se recargan automáticamente.

Para ello vamos a crear un botón de aumente el contador y un método de aumentar:

import React, { Component } from 'react';

class Contador extends Component {
  constructor(props) {
    super(props);

    this.state = {
      contador: 0
    };
  }

  aumentarContador = () => {
    // Importante no modificar directamente el estado, si no usar setState
    // y pasarle la clave del objeto a modificar y su nuevo valor
    this.setState({contador: this.state.contador + 1});
  }

  render() {
    return (
      {/* Como veíamos antes es necesario devolver un único elemento padre
      por eso usamos un div para agrupar lo demás elementos */}
      <div>
        <span>{this.state.contador}</span>
        {/* Al método onClick le asignamos un método.
        Importante poner la C de onClick en mayúsculas */}
        <button onClick={this.aumentarContador()}>+</button>
      </div>
    );
  }
}

export default Contador;

Y para usarlo vamos a App.js e importamos nuestro componente y lo incluimos como se muestra a continuación.

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

// Importamos nuestro componente
import Contador from './Contador';

class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <div className="App-intro">
          {/*Incluimos nuestro componente*/}
          <Contador/>
        </div>
      </div>
    );
  }
}

export default App;

Si todo ha ido bien veremos lo siguiente: Y podrás comprobar que si pulsas sobre el botón el contador va aumentando. 👍

8. Conclusión

React es una gran propuesta de Facebook para solventar el diseño y la programación de interfaces complejas. Además nos permite reutilizar componentes, abstrae la manipulación directa del DOM y nos da la posibilidad de separar modularmente los componentes.

9. Referencias


Crear proyectos, módulos y portlets de Liferay 7 con Blade CLI

$
0
0

En este tutorial se explica cómo crear proyectos, módulos y portlets para Liferay Portal 7 CE con Blade CLI para que utilicemos nuestro IDE favorito, sin depender de Liferay IDE.

Índice de contenidos

1. Introducción

Cuando leemos la documentación de Liferay, vemos que nos recomiendan emplear Liferay IDE para desarrollar. Este IDE es una extensión de Eclipse con plugins de Liferay Portal integrados, así que, si tu IDE favorito es Eclipse, sin duda es la mejor opción para ti. En mi caso, no me sentiría nada cómodo si tuviera que dejar de utilizar IntelliJ y, por tanto, no poder aprovechar sus cualidades. ¿Nos quedamos entonces sin las facilidades que proporciona Liferay IDE? Afortunadamente, no. Los de Liferay saben que cada uno programa con su IDE favorito y no quieren forzar a nadie a cambiar a Eclipse, así que han construido herramientas independientes del IDE que nos facilitan nuestra labor. Una de ellas es Blade CLI, y es la que emplearemos a lo largo de este tutorial para crear nuestro proyecto, nuestros módulos, desplegar nuestros portlets y arrancar nuestro servidor de Liferay local.

Durante el tutorial veremos que toda la estructura y código se genera automáticamente con Blade CLI, así que, mientras utilicemos la misma versión de esta herramienta, generaremos el mismo código. No obstante, lo he subido a GitHub por si a alguien le surge algún problema, para que pueda comparar.

2. Entorno

Este tutorial se ha desarrollado en el siguiente entorno:

  • Portátil MacBook Pro (Retina, 15′, mediados 2015)
  • MacOS Sierra 10.12.4
  • Java 1.8.0_131
  • IntelliJ IDEA Ultimate 2017.1.2
  • NetBeans 8.2
  • Eclipse Neon 4.6.3

3. Instalar Blade CLI

Blade CLI es una herramienta que facilita la labor de los desarrolladores de Liferay. La documentación oficial nos dice que Liferay IDE emplea Blade CLI por debajo, así que, para lo que pretendemos conseguir en este tutorial, nos basta Blade CLI para conseguir las ventajas que Liferay IDE nos daría.

Primero vamos a instalar Java Package Manager (JPM) para instalar con él Blade CLI. Si estamos en macOS o Linux, podemos abrir una terminal y ejecutar:

$ curl https://raw.githubusercontent.com/liferay/liferay-blade-cli/master/installers/global | sudo sh

Comprobamos que se ha instalado correctamente ejecutando los comandos

jpm
y
blade
.

Podemos ver la versión de Blade CLI con

blade version
. Para actualizarlo:
$ sudo jpm install -f https://releases.liferay.com/tools/blade-cli/latest/blade.jar

4. Crear un proyecto Liferay con Blade CLI

Una vez instalado Blade CLI, lo usaremos para crear nuestro proyecto Liferay. Para ello, primero nos desplazamos por terminal al directorio donde queremos crearlo. Ahí ejecutamos el siguiente comando:

$ blade init <nombre_proyecto>

Por ejemplo, si en el directorio workspaces ejecutamos

blade init tutorial-liferay-blade
, entonces se creará la carpeta workspaces/tutorial-liferay-blade, que será nuestro proyecto Gradle de Liferay:

De manera alternativa, podemos crear primero el directorio workspaces/tutorial-liferay-blade, entrar a él por terminal y entonces ejecutar

blade init
.

Una vez generado el proyecto, crearemos un portlet de ejemplo y lo desplegaremos en un servidor local, pero antes tenemos que instalar dicho servidor.

5. Levantar el servidor local

El portlet que generemos más adelante lo desplegaremos en una instancia local de Liferay Portal. Ésta será la que Liferay nos proporciona en su paquete de Liferay Portal + Tomcat (ver el tutorial Introducción a Liferay Portal 7 CE de César). Sin embargo, en lugar de descargarlo manualmente, vamos a dejar que Blade lo haga por nosotros.

Liferay recomienda que el paquete de Liferay Portal + Tomcat lo tengamos en una carpeta bundles en la raíz del proyecto que acabamos de crear pero, si no queremos que se encuentre ahí, entonces podremos indicarle la ruta en la que queremos que esté a través de la propiedad liferay.workspace.home.dir del archivo gradle.properties —que por defecto tomará el valor bundles si la dejamos comentada—. Por ejemplo:

liferay.workspace.home.dir=/Users/jsanchez/Liferay/liferay-ce-portal-7.0-ga3

Sea cual sea nuestra elección, ejecutaremos el siguiente comando desde la raíz del proyecto para que descargue y descomprima el paquete de Liferay Portal + Tomcat:

$ ./gradlew initBundle

La versión del paquete que descarga la podemos redefinir a través de la propiedad liferay.workspace.bundle.url del archivo gradle.properties. También es interesante saber que, al menos en macOS, el ZIP que descarga lo almacena en el directorio $HOME/.liferay/bundles (ejemplo: /Users/jsanchez/.liferay/bundles), por si acaso lo tenemos que borrar si hemos interrumpido la descarga y nos da error al volver a ejecutar

./gradlew initBundle
, o por si ya lo habíamos descargado manualmente, para ponerlo en ese directorio y que así no lo vuelva a descargar.

Una vez descargado el servidor, procedemos a arrancarlo. Esto podemos hacerlo de diferentes maneras:

  • Si el paquete de Liferay Portal + Tomcat lo tenemos en la carpeta bundles de nuestro proyecto, sirviéndonos de Blade CLI. Para ello, desde la raíz de nuestro proyecto, ejecutamos
    blade server start -b
    . La opción
    -b
    sirve para ejecutar este proceso en segundo plano; sin ella, la terminal nos iría mostrando los logs del proceso de arranque.
  • En cualquier caso, accediendo al directorio tomcat-8.0.32/bin (ejemplo: /Users/jsanchez/Liferay/liferay-ce-portal-7.0-ga3/tomcat-8.0.32/bin) y ejecutando
    ./startup.sh
    .

Recordad que el despliegue se toma su tiempo. Con

blade server start
(sin
-b
) podemos ir viendo el proceso de arranque, ya que vemos el volcado de los logs en terminal. Sabremos que ha concluido cuando veamos la siguiente traza de log:
dd-mmm-yyyy hh:mm:ss.milliseconds INFO [main] org.apache.catalina.startup.Catalina.start Server startup in XXX ms

En este punto ya podemos abrir http://localhost:8080/ en nuestro navegador para ver el asistente de Liferay Portal si lo hemos ejecutado por primera vez y configurarlo a nuestro gusto (es recomendable sustituir Hypersonic por una base de datos robusta).

Para parar el servidor, ejecutamos:

  • blade server stop
    desde la raíz de nuestro proyecto si el Tomcat lo tenemos en la carpeta bundles de nuestro proyecto.
  • ./shutdown.sh
    desde el directorio tomcat-8.0.32/bin, se encuentre donde se encuentre éste.
  • O bien hacemos ctrl + C en la terminal en la que arrancamos el servidor si lo hicimos sin la opción
    -b
    .

6. Añadir un módulo con un portlet al proyecto

Llegados a este punto, nuestro interés es crear portlets. Vamos a crear en nuestro proyecto un módulo que contenga el portlet de ejemplo. ¿Cómo lo creamos?, ¿con qué estructura? Blade CLI responderá a nuestras preguntas, pues va a ser él quien nos cree el módulo y el portlet, ya que nos ofrece la posibilidad de crear plantillas para empezar a desarrollar a partir de ellas. Para crear un módulo con un portlet MVC de Liferay, ejecutamos desde la carpeta modules el siguiente comando:

$ blade create -t mvc-portlet -p <nombre_paquete> -c <nombre_portlet> <nombre_módulo>

Por ejemplo:

$ blade create -t mvc-portlet -p tutoriales.liferay.blade.mymodule -c MyMvcPortlet mymodule

Esto nos creará el módulo mymodule dentro de la carpeta modules. El módulo contendrá una carpeta src con el código del portlet, un archivo bnd.bnd y el build.gradle con las dependencias necesarias. Yo recomiendo no actualizar la versión de las dependencias para evitar conflictos con las mismas; ya se encarga la gente de Liferay de actualizar Blade CLI para autogenerar el build.gradle adecuado.

Por último, quiero indicar que

mvc-portlet
no es la única plantilla que nos proporciona Blade CLI. Con
blade create -l
podemos obtener una lista de todas las que ofrece.

7. Desplegar el portlet en Liferay Portal

Tenemos un portlet de ejemplo que, aunque simplemente muestra un texto, nos gustaría ver en acción. Para ello, empezamos creando el JAR del módulo del portlet con el siguiente comando, ejecutado desde la carpeta del módulo (desde /tutorial-liferay-blade/modules/mymodule):

$ blade gw jar

De manera alternativa, podemos utilizar

./gradlew jar
, ya que cuando a Blade le damos el comando
gw <nombre_tarea>
, lo que hace es ejecutar la tarea de Gradle indicada, empleando para ello el Gradle Wrapper. Por cierto,
./gradlew jar
deberá ser lanzado desde la raíz del proyecto, ya que es ahí donde se encuentra el archivo gradlew.

Tras su ejecución, se habrá generado una carpeta build en la carpeta de nuestro módulo, y ésta contendrá otra llamada libs en donde se encontrará el JAR generado. Este JAR lo tendremos que desplegar en nuestro servidor local. Hasta que no lo hagamos, no podremos utilizarlo; comprobémoslo: accedemos a Liferay Portal, pulsamos sobre el botón + arriba a la derecha, buscamos nuestro módulo y vemos que no lo encuentra.

Para desplegarlo, nos aseguramos de que tenemos el servidor levantado y, entonces, nos vamos desde terminal al directorio del módulo y ejecutamos el siguiente comando:

$ blade deploy

Si tuviésemos diferentes módulos y quisiésemos desplegarlos todos a la vez, entonces podríamos ejecutar el anterior comando desde el directorio modules o desde la raíz de nuestro proyecto.

De manera alternativa, podemos utilizar

gradle deploy
desde el directorio del módulo para desplegar.

Si teníamos el servidor levantado, basta con recargar la página para poder añadir el portlet:

8. Abrir nuestro proyecto con un IDE

De momento, hemos usado únicamente Blade CLI para crear desde cero un proyecto y un módulo, para arrancar el servidor local y para desplegar en él el portlet de ejemplo. Ahora queremos modificar un poco el portlet y, por tanto, emplear un IDE para ello. Vamos a ver rápidamente cómo hacerlo en diferentes IDE.

8.1. IntelliJ IDEA

Ejecutamos IntelliJ y elegimos abrir un proyecto. Seleccionamos la carpeta de nuestro proyecto Liferay y, en la siguiente ventana, pulsamos OK:

Se nos abrirá una ventanita para elegir los módulos Gradle. Incluimos todos:

Al hacerlo, deberíamos tener añadidas automáticamente las bibliotecas que vimos que nuestro build.gradle incluye:

Además, si abrimos el panel Gradle, podremos ejecutar directamente desde ahí las tareas de construcción y despliegue que vimos en la sección 7. Desplegar el portlet en Liferay Portal, sin necesidad de utilizar la consola.

8.2. NetBeans

NetBeans no nos lo pone tan fácil como IntelliJ pues, si intentamos abrir un proyecto, no nos dejará seleccionar el proyecto Gradle que hemos creado. Debemos instalar antes el plugin Gradle Support. Para ello, iniciamos NetBeans y abrimos Tools / Plugins, buscamos el plugin Gradle Support y lo instalamos.

Reiniciamos NetBeans y ya, al elegir File/Open Project…, podremos abrir nuestro proyecto (de hecho, veremos el icono de Gradle como icono de carpeta).

En la vista de proyecto, veremos tanto el proyecto tutorial-liferay-blade, como el módulo mymodule.

Hacemos doble clic en mymodule y se nos abrirá ya el módulo que contiene nuestro portlet.

8.3. Eclipse

Si nuestro interés es desarrollar con Eclipse, entonces mejor hacerlo con Liferay IDE, pues es una extensión de Eclipse con plugins de Liferay Portal integrados. No obstante, vamos a describir aquí cómo abrir nuestro proyecto con un Eclipse limpio, al igual que hicimos con IntelliJ y NetBeans.

Primero abrimos el proyecto haciendo clic en File / Open Projects from File System….

Vemos que los archivos .gradle no los reconoce, así que procedemos a instalar un plugin para dar soporte a Gradle. Vamos a Help / Eclipse Marketplace…, instalamos el plugin Buildship Gradle Integration 2.0 y reiniciamos Eclipse.

Ahora hacemos clic derecho en el proyecto y elegimos Configure / Add Gradle Nature.

Vemos que nos ha separado el proyecto en tres: modules —que está vacío—, mymodule y tutorial-liferay-blade, y que ya reconoce los archivos .gradle.

Si hacemos clic derecho en tutorial-liferay-blade y elegimos Validate, veremos que obtenemos miles de errores y warnings. Todos ellos surgen de la carpeta bundles, aquella en la que metimos nuestro paquete de Liferay Portal + Tomcat, así que vamos a excluirla de la validación. Para ello, hacemos clic derecho en ella, pinchamos en Properties y, en el panel Resource, marcamos el atributo Derived.

Si validamos mymodule, nos da error el archivo view.jsp. Simplemente hace falta cambiar

<%@ include file="/init.jsp" %>
por
<%@ include file="./init.jsp" %>
.

9. Conclusiones

Si ante la recomendación de Liferay de emplear Liferay IDE se nos cayó el alma a los pies al ver que era Eclipse, estamos de enhorabuena. Con Blade CLI podemos obtener los beneficios de dicho IDE para la creación de proyectos Liferay pero sin vernos atados a él, pudiendo utilizar nuestro IDE favorito. Y todo ello sin perder simplicidad, pues el uso de Blade CLI y de las tareas de Gradle es sencillo: con un par de comandos tenemos todo montado.

10. Referencias

Podcast – Emociona en tus RRSS

$
0
0

Gracias a Roberto Canales me topaba con este podcast de La Máquina de Vender (LMDV), os dejo el enlace aquí para que podáis escucharlo.

Destacar que es un podcast dedicado al Neuromarketing pero en este caso contaron con un invitado de excepción, como es Juan Carlos Mejía LlanoAutor de los libros “Guía del Community Manager” y “Guía avanzada del Community Manager”.

Algo que me sorprendió tanto a mi como a la persona que entrevista a Juan Carlos es, ¿cómo un ingeniero acaba siendo un referente en el mundo del marketing digital? Juan Carlos da una respuesta con la que estoy muy de acuerdo. Las personas tienen que tener la capacidad de ir transformándose a la vez que lo hacen la tecnología y los negocios. Juan Carlos se adaptó a la revolución digital que asumieron muchas empresas y se subió al carro.

Con la llegada de las redes sociales y al haber trabajado en el mundo del marketing digital fue adquiriendo capacidades para adoptar decisiones a nivel social media.

El siguiente párrafo está dirigido a todos los que dicen que las RRSS son un invento temporal. A todos ellos decirles, como bien dice Juan Carlos, que esta nueva forma de comunicación responde una necesidad básica del ser humano, la comunicación y por eso no es una moda transitoria. Las redes sociales han llegado para quedarse.

Una buena conclusión que se puede sacar es que si las personas, los clientes, están en redes sociales, las empresas deben estar para poder llegar a ellos. Adoptar la mejor estrategia para tener los mejores resultados.

Pero no nos olvidemos que el mundo virtual debe ir acorde con el físico, se debe de saber manejar a las comunidades tanto offline como online.

¿Por qué RRSS aconseja Juan Carlos que empiece el CEO de una pyme?

Hay un orden que se debería seguir y depende sobre todo del tipo de pyme que sea (Por orden de importancia)

  • B2C: Facebook, Instagram, Twitter, Linkedin, Google+, Pinterest y con audiencia joven Snapchat. (aunque esta última red social está perdiendo importancia por la llegada de Instagram Stories)
  • B2B:Linkedin, Twitter, Instagram, Facebook, Google+, Pinterest

 Debes dedicar los esfuerzos necesarios a cada red social y adecuar la publicación de contenidos a tu estrategia y contenidos de los que dispongas.

Pero no nos podemos olvidar de otra red social como YouTube, tiene unas funcionalidades muy importantes. Es un repositorio de videos, por lo que debemos centrar muchos de los esfuerzos en publicar videos ya que se pueden publicar en las demás RRSS y se puede potenciar mucho más los esfuerzos.

 ¿Cuáles son los trucos para posicionar en Youtube?

1- El título que le pones al video es muy importante, debes insertar palabras clave para que te encuentren mejor en YouTube y Google.

2- La descripción debería ser amplia, donde también se deben utilizar palabras clave.

3- Aumentar el número de suscriptores hace que mejore la posición en YouTube.

No olvidarnos de Periscope, Facebook Live, Instagram stories y todas las RRSS que están aplicando el video en directo.

¿Cómo se puede explicar al CEO de una empresa, que es totalmente reacio a las RRSS, que realmente el social media vende?

Las RRSS no pueden tener como objetivo la venta, las RRSS nos dan la posibilidad de acercar la marca a la audiencia, conocimiento de marca.

¿Dónde se encuentra el futuro de las RRSS?¿Qué red social crees que se va a imponer?

Los videos que desaparecen en 24h puede considerarse una tendencia del futuro, como pasa con Snapchat y ahora con Instagram Stories.

El mundo virtual se está abriendo un hueco muy importante entre la gente, pero ¿qué hacen determinadas marcas para poder desvirtualizar a sus usuarios?

Muchas marcas hacen eventos, reunión de bloggers,etc. Para que las personas que se conocían en RRSS se conozcan físicamente, reunir a varias comunidades con el objetivo de que las redes virtuales se vuelvan físicas, crecer en la capacidad de comunicar.

¿Las RRSS se terminarán fundiendo con la realidad virtual?

La brecha existente entre los virtual y lo físico, desaparecerá, la tecnología de Realidad Virtual permitirán enriquecer la experiencia del usuario o sino como claro ejemplo Pokemon Go. Lo importante es la experiencia del usuario, por eso hay un potencial enorme para las marcas en cuanto a la Realidad Virtual.

Y por acabar el podcast, muchas pymes no se diferencian tanto de las grandes organizaciones en lo que se refiere al mundo digital, la brecha se disminuye considerablemente.

Os recomiendo a todos escuchar este podcast y escuchar el punto de vista de un gurú de las RRSS, podéis estar más o menos de acuerdo, pero sino lo escucháis no lo sabréis.

     

Prueba tus API REST con Rest Assured

$
0
0

En este tutorial vamos a ver cómo probar de manera muy sencilla el funcionamiento de cualquier API REST que se nos ponga por delante

Prueba tus API REST con Rest Assured

Indice de contenidos

  1. Introducción
  2. Entorno
  3. Nuestra primera prueba con Rest Assured
  4. Guardando el cuerpo de la respuesta
  5. Mostrar el contenido de request y response
  6. Conclusiones
  7. Referencias

1. Introducción

De un tiempo a esta parte, es cada vez más común que las aplicaciones expongan su funcionalidad mediante API REST, que pueda ser consumida por aplicaciones JavaScript, aplicaciones móviles, etc.

Y, al igual que usamos herramientas como Concordion y Selenium para asegurar que la funcionalidad que ofrece nuestra aplicación web es correcta, lo mismo debemos hacer con nuestros API. La ventaja es que para probar estas piezas disponemos desde hace tiempo de un buen conjunto de herramientas que nos permiten comprobar la funcionalidad que exponemos (HttpClient para hacer las invocaciones, Jackson para manipular JSON, etc.). Pero son utilidades pensadas para realizar peticiones y no expresamente para probar.

Hoy vamos a conocer Rest Assured una herramienta pensada específicamente para probar llamadas a API REST y, en general, cualquier pertición que se ejecute sobre el protocolo HTTP.

2. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Portatil MacBook Pro 17′ (2.8GHz Intel Core i7, 8GB DDR3)
  • Sistema operativo: MacOS Sierra 10.12.3
  • Entorno de desarrollo: IntelliJ IDEA 2017.1
  • JDK 1.8
  • Rest Assured 3.0.2

3. Nuestra primera prueba con Rest Assured

Para hacer nuestras pruebas, he decidido utilizar un API REST ya existente, concretamente “swapi”, que maneja información del universo de Star Wars. Podéis encontrar este API y su documentación en swapi.co

A continuación os pego el contenido del test, y posteriormente comentamos los aspectos más curiosos

package com.autentia.tutoriales;

  import io.restassured.RestAssured;
  import org.junit.Test;

  import static org.hamcrest.Matchers.*;

  public class SWApiTestWithRestAssured {

      @Test
      public void whenRequestingAResourceThenLinksToResourcesMustBeReturned() {

          String body = RestAssured
                  .given()
                      .baseUri("http://swapi.co/api")
                      .and()
                      .queryParam("format", "json")
                  .when()
                      .get("/")
                  .then()
                      .log().all()
                      .and().assertThat().statusCode(is(equalTo(200)))
                      .and()
                      .body("films", response -> notNullValue())
                      .body("vehicles", response -> notNullValue())
                      .body("people", response -> notNullValue())
                      .body("starships", response -> notNullValue())
                      .body("species", response -> notNullValue())
                      .body("planets", response -> notNullValue())
                      .and().extract().body().asString();
      }

  }

Lo primero que observamos es que la forma de describir la prueba con Rest Assured sigue el formato Gherkin (Given – When – Then), casi un estándar de la definición de escenarios de prueba.

Además, vemos también que podemos configurar para las peticiones tanto la URI a la que atacarán como los parámetros que se van a enviar en el “query string”. También podríamos, si quisiéramos, añadirle un cuerpo, subir un fichero con una petición “multipart”… Lo que se os ocurra. También podemos configurar el nivel de log que queremos para la response (volcar toda la petición, sólo las cabeceras, o el cuerpo, o…), y lo mismo podríamos hacer con la request. Esta es la salida que se mostraría en este caso, en que hemos solicitado que se vuelque la información de la respuesta

HTTP/1.1 200 OK
  Date: Thu, 30 Mar 2017 07:49:29 GMT
  Content-Type: application/json
  Transfer-Encoding: chunked
  Connection: keep-alive
  Set-Cookie: __cfduid=d4fc0a55b94153e4e73f5bb8d7137491e1490860169; expires=Fri, 30-Mar-18 07:49:29 GMT; path=/; domain=.swapi.co; HttpOnly
  X-Frame-Options: SAMEORIGIN
  Allow: GET, HEAD, OPTIONS
  Vary: Accept, Cookie
  Etag: W/"1f7a4766c9ebf66cdb1ddb85d5cc6f2f"
  Via: 1.1 vegur
  Server: cloudflare-nginx
  CF-RAY: 347978f890a70ded-MAD
  Content-Encoding: gzip

  {
      "people": "http://swapi.co/api/people/",
      "planets": "http://swapi.co/api/planets/",
      "films": "http://swapi.co/api/films/",
      "species": "http://swapi.co/api/species/",
      "vehicles": "http://swapi.co/api/vehicles/",
      "starships": "http://swapi.co/api/starships/"
  }

En este caso, se trata de una petición usando el verbo GET pero, evidentemente, el framework soporta peticiones usando prácticamente todos los verbos HTTP (soporta GET, POST, PUT, PATCH, DELETE, HEAD y OPTIONS). Y además, una vez realizada la petición, el mismo framework nos posibilita realizar aserciones sobre el contenido de la respuesta, usando los “matchers” de Hamcrest, que aportan bastante legibilidad a las pruebas, encargándose él de parsear la información, lo que nos facilita bastante la vida.

También es posible extraer el cuerpo de la respuesta si necesitamos hacer validaciones más complejas o si queremos guardarlo para usar algún elemento en futuras peticiones. En este ejemplo lo hemos almacenado en un String, pero podemos usar el objeto que queramos, incluso uno creado por nosotros (En el peor de los casos, será necesario crear un mapper específico

para nuestro objeto, pero seguirá siendo posible)

Para demostrar lo dicho en los párrafos anteriores, y más cosas interesantes de Rest Assured, vamos a seguir jugando con el framework…

4. Guardando el cuerpo de la respuesta

En el siguiente ejemplo, vamos a modificar el test anterior para guardar el cuerpo de la respuesta en un objeto propio, y posteriormente usarlo para invocar a otro endpoint

package com.autentia.tutoriales;

    import io.restassured.RestAssured;
    import org.junit.Test;

    import static org.hamcrest.Matchers.*;

    public class SWApiTestWithRestAssured {

        @Test
        public void whenRequestingAResourceThenLinksToResourcesMustBeReturned() {

            BaseApiResponse baseApiResponse = RestAssured
                .given()
                    .baseUri("http://swapi.co/api")
                    .and()
                    .queryParam("format", "json")
                    .log().all()
                .when()
                    .get("/")
                .then()
                    .statusCode(is(equalTo(200)))
                    .and()
                    .body("films", response -> notNullValue())
                    .body("vehicles", response -> notNullValue())
                    .body("people", response -> notNullValue())
                    .body("starships", response -> notNullValue())
                    .body("species", response -> notNullValue())
                    .body("planets", response -> notNullValue())
                    .and().extract().body().as(BaseApiResponse.class);

            RestAssured
                .given()
                    .queryParam("format", "json")
                    .log().all()
                .when()
                    .post(baseApiResponse.getFilms())
                .then()
                    .log().all()
                    .and()
                    .assertThat()
                        .statusCode(is(equalTo(405)));
        }

        private static class BaseApiResponse {
            private String films;
            private String vehicles;
            private String people;
            private String starships;
            private String species;
            private String planets;

            public String getFilms() {
                return films;
            }

            public String getVehicles() {
                return vehicles;
            }

            public String getPeople() {
                return people;
            }

            public String getStarships() {
                return starships;
            }

            public String getSpecies() {
                return species;
            }

            public String getPlanets() {
                return planets;
            }
        }
    }

Al ejecutar el test salta un error… RestAssured indica que no tiene ningún parser de JSON en el classpath, así que hay que añadir a mi pom.xml la dependencia de Jackson Databind

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <version>3.0.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.8.7</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

Si ahora ejecutamos de nuevo, todo va como la seda 🙂

5. Mostrar el contenido de request y response

Hemos visto también que es posible volcar al log el contenido de la request y la response. En las pruebas que hemos visto hasta ahora, lo hemos hecho añadiendo las llamadas al método “log()”, que nos permite configurar el nivel de detalle (de request y response por separado). Puedo indicar que se trace sólo el cuerpo (“log().body()”), el cuerpo y las cabeceras (“log().body().and().log().headers()”).

Pero existen otras formas de activar el vocado al log. Imaginemos que no nos interesa tener el detalle cuando todo va bien, pero que sí queremos que nos muestre la información de request/response en caso de que las validaciones solicitadas fallen. Para ver cómo hacer esto, vamos a simplificar un poco nuestro test

package com.autentia.tutoriales;

  import io.restassured.RestAssured;
  import org.junit.Test;

  import static io.restassured.config.LogConfig.logConfig;
  import static io.restassured.config.RestAssuredConfig.config;
  import static org.hamcrest.Matchers.*;

  public class SWApiTestWithRestAssured {

      @Test
      public void whenRequestingAResourceThenLinksToResourcesMustBeReturned() {

          RestAssured
              .given()
                  .queryParam("format", "json")
                  .config(config().logConfig(logConfig()
                          .enableLoggingOfRequestAndResponseIfValidationFails()))
              .when()
                  .get("http://swapi.co/api/")
              .then()
                  .assertThat().statusCode(is(equalTo(200)));
      }
  }

Podemos ver que, en la parte del “given” se ha añadido un nuevo elemento, “config()”, en le que hemos indicado que queremos habilitar las trazas sólo cuando fallen las validaciones. De esta manera, si se ejecuta el test tal cual está no mostrará traza, pero si se cambia el “statusCode” de 200 (success) a 404 (not found) y volvemos a ejecutar sí veremos el detalle completo de la request y la response.

Pero ¿qué pasa si en mi test hago varias llamadas y quiero el mismo comportamiento para cada una de ellas? ¿Tengo que configurar el log en cada petición que cree? La respuesta es “no”… RestAssured permite habilitar cierta configuración de manera global en lugar de hacerlo en la propia petición. Por ejemplo:

package com.autentia.tutoriales;

  import io.restassured.RestAssured;
  import org.junit.Test;

  import static org.hamcrest.Matchers.equalTo;
  import static org.hamcrest.Matchers.is;

  public class SWApiTestWithRestAssured {

      @Test
      public void whenRequestingAResourceThenLinksToResourcesMustBeReturned() {

          RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();

          RestAssured
              .given()
                  .queryParam("format", "json")
              .when()
                  .get("http://swapi.co/api/")
              .then()
                  .assertThat().statusCode(is(equalTo(200)));

          RestAssured
              .given()
                  .queryParam("format", "json")
              .when()
                  .post("http://swapi.co/api/films/")
              .then()
                  .assertThat().statusCode(is(equalTo(200)));
      }


  }

Si ejecutamos este test, no sacará traza para la primera llamada, pues se ejecuta correctamente, pero sí para la segunda, puesto que la aserción del statusCode fallará.

Sin embargo, no todo es bonito en el mundo del logging de Rest Assured… Por defecto, las trazas las vuelca directamente a la consola y no hay forma de decirle directamente que las vuelque en un sistema de traza estilo Log4J o similares. Sin embargo, existen formas de burlar esta limitación… Lo que sí permite el framework es indicar un PrintStream al que volcar las trazas de la prueba, y los PrintStream reciben como parámetro un OutputStream. Así que lo que vamos a hacer es implementarnos nuestro propio OutputStream, y decirle que imprima lo que reciba en el logger que le indiquemos:

package com.autentia.tutoriales;

  import org.slf4j.Logger;

  import java.io.IOException;
  import java.io.OutputStream;

  public class LoggerOutputStream extends OutputStream {

      StringBuilder unflushedContent = new StringBuilder();
      private final Logger logger;

      public LoggerOutputStream(Logger logger) {
          this.logger = logger;
      }

      @Override
      public void write(int b) throws IOException {
          this.unflushedContent.append((char)b);
      }

      @Override
      public void write(byte[] b) throws IOException {
          this.unflushedContent.append(new String(b));
      }

      @Override
      public void write(byte[] b, int off, int len) throws IOException {
          this.unflushedContent.append((new String(b)).substring(off, off + len));
      }

      @Override
      public void flush() throws IOException {
          this.logger.info(unflushedContent.toString());
          this.unflushedContent.setLength(0);
      }

      @Override
      public void close() throws IOException {
          this.flush();
      }
  }

Y ahora modificamos el código de la prueba para que emplee nuestro OutputStream

package com.autentia.tutoriales;

  import io.restassured.RestAssured;
  import org.junit.Test;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;

  import java.io.IOException;
  import java.io.PrintStream;

  import static io.restassured.config.LogConfig.logConfig;
  import static io.restassured.config.RestAssuredConfig.config;
  import static org.hamcrest.Matchers.equalTo;
  import static org.hamcrest.Matchers.is;

  public class SWApiTestWithRestAssured {

      private final Logger logger = LoggerFactory.getLogger(SWApiTestWithRestAssured.class);

      @Test
      public void whenRequestingAResourceThenLinksToResourcesMustBeReturned() throws IOException {

          try (LoggerOutputStream outputStream = new LoggerOutputStream(logger)) {
              RestAssured.config = config().logConfig(logConfig()
                  .defaultStream(new PrintStream(outputStream))
                  .enableLoggingOfRequestAndResponseIfValidationFails());

              RestAssured
                      .given()
                          .log().method().and().log().uri().and().log().params()
                          .queryParam("format", "json")
                      .when()
                          .get("http://swapi.co/api/")
                      .then()
                          .assertThat().statusCode(is(equalTo(200)));

              outputStream.flush();

              RestAssured
                      .given()
                          .queryParam("format", "json")
                      .when()
                          .post("http://swapi.co/api/films/")
                      .then()
                          .assertThat().statusCode(is(equalTo(200)));

              outputStream.flush();
          }

      }

  }

Con esto ya tendríamos la prueba mandando las trazas a nuestro logger. Aunque el constructor del objeto PrintStream nos permite indicarle que debe hacer “flush” automáticamente, en este caso es mejor no hacerlo y encargarnos nosotros de hacerlo a mano. Si activáramos el “autoflush” la traza de nuestra request (o response) quedaría muy “desligada”, y perderíamos legibilidad en la misma

6. Conclusiones

Cuando desarrollamos un nuevo software, las pruebas son una parte imprescindible del mismo. Son nuestra red de seguridad para asegurar que las cosas funcionan como deben y al igual que para el código de producción, es importante conocer y usar frameworks que nos permitan desarrollar las pruebas de una manera rápida y sencilla (y sin necesidad de reinventar la rueda). Además, puesto que las pruebas nos dicen qué hace nuestra aplicación, es importante también usar herramientas que ayuden a hacer el código de las mismas lo más legible posible

Rest Assured es una herramienta que nos proporciona todas estas características. Implementar una llamada es un proceso rápido y sencillo, igual que validar la respuesta obtenida. Además, el API que ofrece permite una verbosidad que hace que el código de la prueba se pueda leer casi como si fuera lenguaje natural, facilitando mucho la comprensión de lo que se está probando, lo que lo convierte en un framework a tener muy en cuenta.

7. Referencias

User Story Mapping con RealTimeBoard

$
0
0

Estoy escribiendo una serie de artículos probando distintas herramientas para definir productos digitales con User Story Mapping. Podéis ver algunos de ellos en este enlace.

Hoy voy a probar https://realtimeboard.com/ que permite disponer de una pizarra OnLine utilizable para múltiples propósitos. Obviamente esto solo cubre una parte del modelo.

Tiene un montón de plantillas predefinidas sobre las que nos podemos apoyar, como nuestro buscado User Story Mapping.


Una vez que nos registramos, que permite con Facebook, sería interesante ver el video demostración.


Creo un equipo y añado en los siguientes apartados miembros (si quieres) e información de tu rol y empleados (para marketing entiendo) que no de dar cosas gratis viven los negocios 😉


Tenemos un Wizard donde nos dice cómo queremos empezar.


Elegimos User History Mapping y ya tenemos un primer esqueleto.


Ahora, de un modo tremendamente intuitivo sólo tenemos que editar los elementos a placer.


Una de las cosas más interesantes que tiene es que desde las celdas de una hoja de cálculo puede crearte un conjunto de post-it.

Si vas a uno de mis últimos tutoriales sobre otra herramienta CardBoard puedes ver que ya parto de una lista de historias.


Las exporto de la herramienta origen a una hoja de cálculo y copio en el portapapeles.


Ahora simplemente pegando sobre realtimeboard ya tengo todas las entradas.


Solo tenemos que trabajar un poco el formato, cuestión de un rato.


Con tags podemos categorizar en cierto modo las historias, como por ejemplo en tamaño.


Sobre nuestra pizarra infinita también podemos añadir muchos otros elementos.


Tiene una galería para hacer wireframes.


También miles de iconos integrados y esqueletos para otras figuras.


Podemos pintar fácilmente gráficas.


O incluso otras plantillas más elaboradas como Customer Journey.


O mapas de empatía. Para nuestra metodología de definición de productos puede ser muy práctica.


La gracia es que también nos permite definir nuestros diseños como plantillas para poderlos reutilizar con facilidad.


Obviamente es un modelo de poca integridad (no hay un meta modelo subyacente a la arquitectura de la información) pero de grandes posibilidades. La gracia de esta herramienta es el trabajo simultáneo colaborativo.

Ya estoy pensando en el próximo libro y esta herramienta apunta maneras para simplificarme el trabajo. Mientras tanto puedes entretenerte con mi último libro publicado hace poco: https://lk.autentia.com/rcanales

Recuerda que podemos vernos en los cursos que ofrecemos sobre transformación digital y metodologías ágiles. https://www.autentia.com/servicios/formacion/

Creación de temas con Liferay 7

$
0
0

En este tutorial veremos la creación de temas personalizados con Liferay 7.

Índice

Liferay permite la opción de crear temas propios mediante dos opciones: el SDK de Liferay o con un generador de temas hecho con Yeoman y Gulp. Nosotros optaremos por la segunda opción, ya que nos da herramientas modernas de desarrollo web y mucha más rapidez a la hora de ejecutar tareas.

1. Introducción

Primero hay que distinguir entre lo que son temas y lo que son themelets. Los temas son el look and feel de un sitio o página. Los themelets son la apariencia y funcionalidad de los componentes que conforman nuestro sitio, pudiendo componer un tema mediante themelets, lo que haría de nuestra arquitectura mucho más modular, ya que los themelets se pueden distribuir en varios temas.

Los temas pueden heredar a su vez de otros temas, siendo lo más común que hereden de los temas base provistos por Liferay: styled y unstyled. Es recomendable heredar de uno de los dos, ya que si decidimos crear un tema desde cero podríamos romper la funcionalidad de Liferay.

Para la generación del tema han de ocurrir primero una serie de pasos:

  1. Transpilar Sass a CSS
  2. Generar el tema en formato .war
  3. Hacer deploy a Tomcat

De todo esto se encargará Gulp de forma muy simple como veremos en un apartado posterior. Vamos con la instalación.

2. Entorno

  • Hardware: Portátil MacBook Pro 17' (2.66 Ghz Intel Core I7, 8GB DDR3).
  • Sistema Operativo: Mac OS Sierra 10.12.4
  • Entorno de desarrollo: iTerm2 y VSCode
  • Liferay 7.0 CE GA 3
  • Java JRE 1.8.0.131
  • NodeJS 7.10.0
  • NVM 0.33.2
  • Yeoman 1.8.5
  • Gulp 3.9.1
  • Ruby 2.0.0p648
  • Sass 3.4.23
  • Compass 1.0.3

3. Instalación

Antes de nada es necesario haber seguido el tutorial de Javier para crear un workspace. Como habremos visto, desarrollar dentro del workspace nos ayuda mucho.

  1. Instalar NodeJS.
  2. Instalar Gulp y Yeoman globalmente:
    $ npm install -g yo gulp
  3. Instalar globalmente el generador de temas de Liferay:
    $ npm install -g generator-liferay-theme
  4. Instalar sass y compass:
    $ gem install sass compass

En algunos casos será necesaria la instalación de git.

Nota: Si estás en Windows necesitarás instalar Ruby y seguir una serie de pasos descritos aquí.

4. Generación del tema

Desde el directorio de nuestro liferay-workspace dentro de la carpeta themes introducimos el siguiente comando:

$ yo liferay-theme

Elegimos el nombre de nuestro tema. Éste es el nombre "público".

El id del tema tiene que ser todo en minúsculas, con caracteres comunes y los espacios sustituidos por guiones.

Elegiremos la versión de Liferay que usamos, en nuestro caso la 7.0:

Liferay - Yeoman versión de Liferay

Añadimos el directorio donde tenemos Tomcat y el puerto donde se lanzará Liferay:

Liferay - Yeoman generador tema Liferay servidor

Y si vamos a la carpeta de nuestro tema veremos que se han generado una serie de directorios y ficheros.

5. Estructura

build/                  // Directorio donde se genera la compilación del tema
dist/                   // Directorio donde se genera el .war
node_modules/           // Directorio de módulos de npm del proyecto
src/                    // Directorio de desarrollo del tema
.gitignore              // Fichero de git para ignorar ciertas carpetas o ficheros
.yo-rc.json             // Información de Yeoman
gulpfile.js             // Fichero de gulp donde se especifican tareas
liferay-theme.json      // Información de nuestro tema
package.json            // Información de nuestro paquete npm

6. Desarrollo del tema

El desarrollo lo haremos en la carpeta src. Para comprobar que nuestro tema funcione correctamente probaremos a poner al body un color de fondo. Para ello vamos a src/css/_custom.scss y escribimos lo siguiente:

/* These inject tags are used for dynamically creating imports for themelet styles, you can place them where ever you like in this file. */

/* inject:imports */
/* endinject */

/* This file allows you to override default styles in one central location for easier upgrade and maintenance. */
body {
  background-color: #cecece;
}

Como podemos observar, la extensión del fichero no es css, si no scss. Los temas de Liferay usan una tecnología muy extendida en frontend llamada Sass o Scss (ver aquí la diferencia), que es un lenguaje que se transpila a css y nos permite cosas muy interesantes como variables de css, mixins, funciones, loops y mucho más.

Una vez hecho esto, tenemos que hacer la transpilación y construcción del .war. Desde el terminal:

$ cd autentia-theme
$ gulp build

Comprobamos que tenemos lanzada la instancia de Liferay y desplegamos a Tomcat con:

$ gulp deploy

Esta tarea lo que hace es copiar el fichero .war de nuestro tema a la carpeta de liferay-workspace/bundles/deploy. Este directorio lo usa Liferay para aplicar en caliente los cambios.

Nota: gulp deploy hace gulp build por debajo.

7. Seleccionar nuestro tema

Por último hay que elegir el tema. Con lo que seguimos los siguientes pasos:

Vamos a aplicar el tema a la página de inicio de nuestro sitio. Para ello pulsamos sobre los tres puntos y pinchamos en configurar página:

Liferay - Configurar página

Pulsamos sobre definir un look and feel de nuestra página y seguidamente le damos a cambiar tema actual:

Liferay - Temas por defecto

Aquí se debería ya ver una miniatura con el nombre de nuestro tema:

Liferay - Elegir tema

Elegimos nuestro tema y le damos a guardar. Ahora podremos comprobar que se ha aplicado el tema después de recargar la página (si no se aplica, prueba a recargar sin caché cmd/ctrl + shit + r):

Liferay - Tema de Liferay aplicado

Si todo ha ido bien, veremos el color de fondo de la página en gris.

Prueba a cambiar el color otra vez y hacer gulp deploy, verás que tras hacer el deploy el tema se actualiza solo tras recargar la página. 🙌

8. Conclusión

Las tecnologías usadas por Liferay para la generación de temas son muy novedosas y han sido probadas exhaustivamente por una amplia comunidad de desarrolladores, con lo que es un ecosistema bastante maduro. Además, es un desarrollo rápido, cómodo y fácil.

9. Referencias

Especificaciones de Concordion en Markdown

$
0
0
Después de dar los primeros pasos con Concordion de la mano de nuestro compañero Daniel Rodríguez, vamos a profundizar en una de las novedades de la versión 2 de este framework de pruebas automáticas: el uso de Markdown para escribir nuestras especificaciones.

Especificaciones de Concordion en Markdown

Índice de contenidos

  1. Introducción
  2. Entorno
  3. Creando la especificación con Markdown
  4. Usando tablas para repetir escenarios de prueba
  5. Conclusiones
  6. Referencias

1. Introducción

Como ya nos explicó Daniel Rodríguez, en un tutorial anterior, Concordion es un framework pensado para la creación de tests automáticos de aceptación.

En sus primeras versiones, Concordion usaba únicamente HTML como lenguaje para las especificaciones, pero desde la versión 2.0 se ha añadido soporte para Markdown, un lenguaje de marcado que busca mejorar la legibilidad del documento “en plano”, así como facilitar su escritura, basándose en convenciones existentes previamente para dar “formato” al texto. El uso de estas convenciones es importante porque permite que, usando un intérprete, se pueda transformar este texto plano en HTML.

2. Entorno

Este tutorial está escrito usando el siguiente entorno:
  • Hardware: Portátil MacBook Pro 17′ (2.8GHz Intel Core i7, 8GB DDR3)
  • Sistema operativo: MacOS Sierra 10.12.3
  • Entorno de desarrollo: IntelliJ IDEA 2017.1, con los plugins “Markdown Support” y “Concordion Support”
  • JDK 1.8
  • Concordion 2.1.0

3. Creando la especificación en Markdown

Vamos a partir de un ejemplo sencillo, un servicio que, dado un nombre, devuelve el saludo correspondiente. Para ello, crearemos en nuestro IDE la especificación. Esto es especialmente sencillo en IDEA si empleamos el plugin “Concordion Support”.

Seleccionamos la carpeta de test, hacemos “click derecho” sobre ella y seleccionamos la opción “New > Concordion > Spec and fixture”. Esto nos creará no sólo la especificación, sino también su “fixture” (clase asociada que implementa la prueba). Además, podremos seleccionar en qué paquete queremos crear la especificación, con qué lenguaje, etc.

Una vez creada nuestra especificación, insertamos nuestros ejemplos:

# Pruebas de aceptación del servicio de saludos

    ## Primer ejemplo

    Cuando el usuario indica que su nombre es Marcos, entonces el saludo será Hola, Marcos

    ## Segundo ejemplo

    Cuando el usaurio indique que su nombre es Daniel, entonces el saludo será Hola, Daniel

Como podéis ver, la especificación queda muy limpia, sin el “ruido” de todas las etiquetas HTML, y casi sin necesitad de conocimientos o herramientas adicionales. El siguiente paso es instrumentalizar la especificación. En nuestro ejemplo, realizaremos dos operaciones: por un lado, tenemos que asignar valor a una variable; por otro, tenemos que usar esa variable para invocar a nuestro método de prueba y comprobar el resultado. Veamos la sintaxis necesaria para ambos casos…

# Pruebas de aceptación del servicio de saludos

    ## Primer ejemplo

    Cuando el usuario indica que su nombre es [Marcos](- "#name"), entonces el saludo será [Hola, Marcos](- "?=greetings(#name)")

    ## Segundo ejemplo

    Cuando el usaurio indique que su nombre es [Daniel](- "#name"), entonces el saludo será [Hola, Daniel](- "?=greetings(#name)")

Lo primero que vemos al revisar la especificación instrumentalizada es la forma en que indicamos a Concordion qué partes de todo lo escrito tiene que tener en cuenta:

  • Usaremos corchetes (
    []
    ) para indicar el texto que tiene que usar para sus operaciones, ya sea asignación de variables, comparaciones, etc.
  • A continuación se incluye la operación, con la notación
    (- "operation")
    .

En el primer caso,

(- "#name")
, estamos asignando valor a la variable “#name”.

En el segundo,

(- "?=greetings(#name)")
, estamos llamando al método “greetings()” de nuestro fixture, pasándole como parámetro la variable creada anteriormente, y estamos comprobando que el valor devuelto sea igual al texto entre corchetes (es muy importante hacerlo como está, sin ningún espacio entre los símbolos “?”, “=” y el nombre del método del fixture. En caso contrario obtendremos un error).

También es posible realizar la ejecución del método “greetings” y la aserción en dos pasos. En ese caso, el ejemplo quedaría parecido a esto:

Cuando el usaurio indique que su nombre es [Marcos](- "#name"), y [solicite un saludo](- "#greeting=greetings(#name)"), entonces el saludo será [Hola, Marcos](- "?=#greeting")

Ahora sólo nos queda implementar el método “greetings()” en nuestro fixture, y podemos ejecutar nuestras pruebas. Puesto que el objetivo de este tutorial es mostrar cómo hacer especificaciones en Concordion, voy a hacer un fixture muy sencillo, que devuelva directamente el texto que estamos esperando:

package com.autentia.tutoriales;

    import org.concordion.integration.junit4.ConcordionRunner;
    import org.junit.runner.RunWith;

    @RunWith(ConcordionRunner.class)
    public class Greeting {

        public String greetings(String name) {
            return String.format("Hola, %s", name);
        }
    }

Ahora, si ejecutamos la prueba, obtenemos nuestro resultado (en formato HTML):

4. Usando tablas para repetir escenarios de prueba

Cuando dimos nuestros primeros pasos con Concordion, una de las cosas que vimos fue la posibilidad de usar tablas para ejecutar de manera iterativa la misma prueba con distintos juegos de datos. Esto también podemos hacerlo con las especificaciones escritas en Markdown. El ejemplo que hemos usado se presta muy bien a usar una tabla, así que vamos a transformarlo para que así sea. Nuestro escenario queda como sigue

## Ejemplo con tablas

    | [greet][][Nombre][name] | [Saludo][greeting] |
    | ---------------| ---------------    |
    | Daniel         | Hola, Daniel       |
    | Marcos         | Hola, Marcos       |

    [name]: - "#name"
    [greet]: - "#greeting=greetings(#name)"
    [greeting]: - "?=#greeting"

Aquí vemos una cosa ligeramente distinta que en los ejemplos que habíamos visto hasta ahora, y es el uso de “etiquetas” para definir fuera de la tabla las acciones que vamos a realizar sobre cada elemento. Esto no es estrictamente necesario, pero mejora la legibilidad. Pero ¿cómo funciona? Es sencillo… La primera acción ([greet][]) es la acción que se va a repetir para cada registro de la tabla. Después, indicamos que debe ejecutar la acción “[name]” sobre cada elemento de la primera columna, y “[greeting]” sobre cada elemento de la segunda. Al pie de la tabla aparece definido lo que hace cada “alias”

En este ejemplo hemos hecho una tabla muy sencilla, pero ya podemos intuir que, si la prueba fuera un poco más compleja la tabla no será cómoda de usar ni será fácil saber qué hace en cada momento (demasiados “alias”, posiblemente demasiado separados de su definición si la tabla tiene demasiados registros). Además, es engorroso tener que “dibujar” la tabla con los separadores de columnas, los de las cabeceras y el cuerpo, etc.

Para evitar todos estos problemas, Markdown nos permite incluir elementos HTML dentro de la especificación (el objetivo final de Markdown es transformarse en HTML, así que es, por decirlo así, ahorrarnos un paso) encerrándolos en un “div”. De esta manera, el siguiente fragmento de especificación sería perfectamente válido, y tendríamos dos tablas perfectamente válidas funcionando, escritas en disintos lenguajes

## Ejemplo con tablas

      | [greet][][Nombre][name] | [Saludo][greeting] |
      | -              | ---------------    |
      | Daniel         | Hola, Daniel       |
      | Marcos         | Hola, Marcos       |

      [name]: - "#name"
      [greet]: - "#greeting=greetings(#name)"
      [greeting]: - "?=#greeting"

      ## Ejemplo con tablas HTML
      <div>
      <table concordion:execute="#greeting=greetings(#name)">
          <thead>
              <tr>
                  <th concordion:set="#name">Nombre</th>
                  <th concordion:assert-equals="#greeting">Saludo</th>
              </tr>
          </thead>
          <tbody>
              <tr>
                  <td>Daniel</td>
                  <td>Hola, Daniel</td>
              </tr>
              <tr>
                  <td>Marcos</td>
                  <td>Hola, Marcos</td>
              </tr>
          </tbody>
      </table>
      </div>

De hecho, si ejecutamos de nuevo nuestra prueba, vemos que el resultado es exactamente igual

5. Conclusiones

Una de los “stoppers” más habituales que podemos encontramos al intentar que personas no técnicas, pero con un conocimiento profundo del negocio, escriban los escenarios de prueba que se van a automatizar usando Concordion es el desconocimiento de HTML. Hasta hace poco, las alternativas de que disponíamos eran escasas: usar una herramienta WYSIWYG para “esconder” el HTML, pero el código generado puede ser muy engorroso para su instrumentalización posterior; o que nos pasaran los escenarios en un documento de texto (ya sea texto plano o escrito con algun procesador de textos más complejo) y hacer nosotros la transformación a lenguaje de marcado, lo que supone un doble trabajo.

Ahora, gracias a Markdown, disponemos de una nueva solución para acercar al usuario de negocio a la automatización de pruebas, pudiéndonos aprovechar de su conocimiento para definir buenos escenarios sin necesidad de trabajar de más

Como ya hemos visto, no es una solución perfecta, pero es una herramienta más a nuestra disposición para conseguir nuestro fin: asegurar la calidad de nuestro código, tanto desde un punto de vista técnico como funcional. Ahora queda en vuestra mano saber elegir la mejor opción en cada momento.

6. Referencias

Introducción a Canvas

$
0
0

En este tutorial vamos a comenzar a trabajar con canvas. Haremos nuestros primeros dibujos y alguna animación para ir entrando en el maravilloso mundo del Canvas.

Índice de contenidos


1. Introducción

Canvas (o lienzo, traducido al español), es un elemento añadido a HTML5 mediante el cual, se puede dibujar usando scripts (habitualmente JavaScript). Fué introducido por Apple para Mac OS X Dashboard y después implementado en Safari y Google Chrome. No está soportado en navegadores antiguos, pero si funciona en la mayoría de versiones más recientes de los navegadores. Canvas como tal, es solo un contendor de gráficos, un lienzo como su nombre indica, ya que la “magia” la haremos con JavaScript. El verdadero potencial de Canvas reside en la capacidad para actualizar contenidos en tiempo real, lo cual unido a la posibilidad de responder a eventos de usuario, proporciona un abanico de posibilidades para crear herramientas o juegos.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2 Ghz Intel Core I7, 8GB DDR3).
  • Sistema Operativo: Mac OS Sierra 10.12.3
  • Entorno: SublimeText

3. Configuración

Lo primero que tenemos que hacer, es “colocar” nuestro elemento canvas dentro del

en nuestro HTML.
<canvas id="lienzo" width="500" height="500"></canvas>

Si nos fijamos, con “width” y “height” podemos dar la anchura y altura que queramos. Ahora ya tenemos nuestro lienzo incrustado en el HTML, aunque solo es eso, un lienzo en blanco. Para poder dibujar en él lo llevamos a JavaScript con GetElementById. Antes de comenzar cualquier trazado hay que llamar a este metodo para que lo inicie:

function iniciar(){
  var elemento=document.getElementById('lienzo');
  lienzo=elemento.getContext('2d');
}
window.addEventListener('load', iniciar, false);

Ya tenemos todo listo para empezar a dibujar…


4. Primeros dibujos

Vamos a comenzar con algo tan sencillo como dibujar una línea.

function iniciar(){

  var elemento=document.getElementById('lienzo');
  lienzo=elemento.getContext('2d');

  lienzo.moveTo(0,0);
  lienzo.lineTo(500,500);
  lienzo.stroke();
}
window.addEventListener('load', iniciar, false);
En nuestro navegador veríamos algo como esto:

Si nos fijamos, en el método moveTo() , le pasamos por parámetro la posición “x” y la posición “y” desde donde vamos a empezar a dibujar. Este método es como decirle a un bolígrafo, donde debe posicionarse para dibujar. En el método lineTo() sin embargo, lo que le pasamos por parámetro, son la posición “x” y la posición “y” hasta donde queremos que llege la línea que vamos a dibujar. Finalmente con el método stroke() lo que hacemos es darle la orden para que empiece a dibujar

Ahora vamos a ver un ejemplo un poco más complicado:

function iniciar(){
  var elemento=document.getElementById('lienzo');
  lienzo=elemento.getContext('2d');

  lienzo.beginPath();
  lienzo.moveTo(0,0);
  lienzo.lineTo(500,500);
  lienzo.lineTo(0,300);
  lienzo.fill();
  lienzo.closePath();
}
window.addEventListener('load', iniciar, false);

Ahora lo que hemos hecho simplemente es rellenar el espacio que hay entre las líneas que hemos dibujado con el método fill(). Hemos usado también los métodos beginPath() y closePath(). Con esto lo que hacemos es indicar que vamos a iniciar un trazo y cuando hemos finaizado, closePath sería como decirle al bolígrafo que levante la punta del papel antes de seguir o no dibujando. si no usamos estos métodos corremos el riesgo de querer seguir dibujando líneas con stroke() y al no haber cerrado el trazo, estas sigan rellenando lo que hay dentro por el método fill() anterior.


4.1. Colores y cuadrados

Ahora vamos a ver algunos ejemplos para dibujar formas cuadradas y rectángulares. Vamos a empezar por un cuadrado sin relleno

function iniciar(){
  var elemento=document.getElementById('lienzo');
  lienzo=elemento.getContext('2d');

  lienzo.strokeRect(100,100,120,120);
}
window.addEventListener('load', iniciar, false);

Con el método “strokeRect”, lo que hacemos es dibujar el contorno de un cuadrado o un rectángulo, ya que lo que le pasamos por parámetro es: posición “x” y posición “y” donde vamos a empezar a dibujar, que es la esquina superior izquierda del cuadrado, los dos siguientes parámetros pertenecen a la longitud que tendrán las líneas del eje “x” y la longitud que tendrán las líneas del eje “y”. De esta forma, si en lugar de pintar un cuadrado, quisiéramos pintar un rectángulo, bastaría con darle distinta longitud a las líneas de un eje.

En el caso de que quisiéramos pintar un cuadrado sólido, es decir, con relleno usaríamos el método “fillRect()” en lugar de “strokeRect()”. Ahora vamos a darle un poco de color al asunto.

function iniciar(){

  var elemento=document.getElementById('lienzo');
  lienzo=elemento.getContext('2d');

  lienzo.fillStyle="#ff0000";

  lienzo.strokeStyle="#00ff00";

  lienzo.strokeRect(0,0,120,120);

  lienzo.fillRect(130,0,150,100);

  lienzo.fillRect(50,130,100,100);

  lienzo.clearRect(60,140,80,80);

}
window.addEventListener('load', iniciar, false);

En este caso, con el método “strokeStyle=”#00ff00″”, indicamos que queremos pintar de verde, tanto las líneas que dibujemos, como las figuras en las que dibujemos solo el contorno, mientras que con “fillStyle=”#00ff00″” indicamos que queremos pintar de color rojo cualquier relleno que hagamos. Con el método “clearRect()” lo que hacemos es dejar en blanco el área que indiquemos, de forma que en el ejemplo expuesto, dejamos en blanco el área de un rectángulo más pequeño dentro del rectángulo rojo más grande, creando un rectángulo con el contorno rojo ancho. Gracias a la combinación de “fill” y “clear”, se pueden conseguir diferentes efectos en los dibujos.

4.2. Círculos y arcos

Realmente lo que dibujamos en canvas son arcos: arc(x, y, radius, startAngle, endAngle, anticlockwise);

function iniciar(){
  var elemento=document.getElementById('lienzo');
  lienzo=elemento.getContext('2d');

  lienzo.beginPath();
  lienzo.arc(300,300,100,0,Math.PI, true);
  lienzo.stroke();
  lienzo.closePath();
}
window.addEventListener('load', iniciar, false);

Paso a explicar los parámetros que recibe el método “arc()” que es el que define que vamos a dibujar un arco:

  • (posX, posY) esto es donde se va a encontrar punto central del arco.
  • (radius) esto es la longitud que tendrá el radio de nuestro arco
  • (angle) esto indica desde que ángulo vamos a empezar a dibujar el arco
  • (radians) indicamos el ángulo que tendrá nuestro arco, expresado en radianes
  • (anticlockwise) si está en true, significa que vamos a dibujar el arco en sentido contrario a las agujas del reloj. Esto es opcional, si lo omitimos siempre estará en true
Para saber como indicar el ángulo que queremos en radianes sólo necesitamos hacer una pequeña ecuación
180 grados = π radianes

Para trabajar con grados:

(π/180)*grados

Por ejemplo, 90º:

(π/180)*90

Ahora ya, sabiendo esto, si lo que queremos es dibujar un círculo y ademas queremos rellenarlo, sería así:

function iniciar(){
  var elemento=document.getElementById('lienzo');
  lienzo=elemento.getContext('2d');

  lienzo.beginPath();
  lienzo.fillStyle="#4F99FF";
  lienzo.arc(300,300,100,0,Math.PI*2);
  lienzo.fill();
  lienzo.closePath();
}
window.addEventListener('load', iniciar, false);

Como podemos ver es bastante sencillo dibujar círculos. Ahora vamos a animar un poco la cosa.


5. Animaciones

Ahora que ya sabemos dibujar líneas, cuadros, círculos, etc… vamos a probar a animarlo un poco.

function iniciar(){
  var elemento=document.getElementById('lienzo');
  lienzo=elemento.getContext('2d');

  setInterval(function(){
    for (var i=300; i>70; i=i-25){
      lienzo.arc(250,250,i,0,Math.PI,true);

     if (i==250){
      lienzo.fillStyle= "#AB10FF";
      lienzo.fill();
     }else if (i==225){
      lienzo.fillStyle = "#171AFF";
      lienzo.fill();
     }else if (i==200){
      lienzo.fillStyle = "#38F7FF";
      lienzo.fill();
     }else if (i==175){
       lienzo.fillStyle = "#48FF27";
       lienzo.fill();
     }else if (i==150){
       lienzo.fillStyle = "#FFF432";
       lienzo.fill();
     }else if (i==125){
       lienzo.fillStyle = "#E89536";
       lienzo.fill();
     }else if (i==100){
       lienzo.fillStyle = "#FF0000";
       lienzo.fill();
     }else if (i==75){
       lienzo.fillStyle = "white";
       lienzo.fill();
     }

    lienzo.closePath();


    lienzo.beginPath();
    lienzo.moveTo(250,250);
    lienzo.closePath();

    }
  },1000);

   setInterval(function(){
    for (var i=300; i>70; i=i-25){
      lienzo.arc(250,250,i,0,Math.PI, true);

     if (i==250){
      lienzo.fillStyle= "#FF0000";
      lienzo.fill();
     }else if (i==225){
      lienzo.fillStyle = "#E89536";
      lienzo.fill();
     }else if (i==200){
      lienzo.fillStyle = "#FFF432";
      lienzo.fill();
     }else if (i==175){
       lienzo.fillStyle = "#48FF27";
       lienzo.fill();
     }else if (i==150){
       lienzo.fillStyle = "#38F7FF";
       lienzo.fill();
     }else if (i==125){
       lienzo.fillStyle = "#171AFF";
       lienzo.fill();
     }else if (i==100){
       lienzo.fillStyle = "#AB10FF";
       lienzo.fill();
     }else if (i==75){
       lienzo.fillStyle = "white";
       lienzo.fill();
     }

    lienzo.closePath();

    lienzo.beginPath();
    lienzo.moveTo(250,250);
    lienzo.closePath();

    }
  },2000);
}

window.addEventListener('load', iniciar, false);

Lo que hago aquí es dibujar un arco iris con un bucle for en el que vamos cambiando el radio del arco y rellenando de un color distinto el arco cada vez que cambiamos el radio, el último arco lo pintamos de blanco para conseguir el efecto de arco. Con el método “setInterval()” dibujo un arco iris cada segundo, mientras que en el segundo método “setInterval()”, lo que hago es dibujar un arco iris con los colores invertidos cada dos segundos. De esta forma conseguimos una animación. Aunque no funciona igual en todos los navegadores, donde mejor se puede apreciar es en Chrome.

6. Conclusiones

Por supuesto, esto que hemos visto es muy básico y es solo la punta del iceberg de una herramienta que nos dá muchas posibilidades a la hora de crear juegos o herramientas en web. Espero que haya servido por lo menos para empezar a entender un poco como funciona este elemento


7. Referencias


Cómo crear una oferta comercial irresistible para tu audiencia

$
0
0

Hace poco gracias a la recomendación de Roberto he descubierto a Frank Scipion. A mí hasta hace dos días este nombre no me decía mucho, pero la verdad es que el contenido que genera es de gran calidad, y aunque en muchos casos podemos llegar a restarle valor, merece la pena escucharlo y desde luego nunca perder de referencia sus comentarios.

Tanto si es el caso que nos presenta (empezar con un blog y monetizar productos digitales), como si eres un emprendedor o si te planteas lanzar un producto aunque no sea en el marco de un blog; sus comentarios sobre cómo conseguir vender productos digitales a través de un podcast son perfectamente aplicables en todos ellos y os recomiendo escucharlo de primera mano.

El podcast en concreto que me ha llamado la atención y del que os quiero hacer llegar un resumen se llama “Especial AA2: Crea una oferta irresistible para tu audiencia”. Podéis encontrarlo en Ivoox usando este enlace

imagen-podcast-ivoox


Si tuviese que extraer en unas breves lineas lo que considero más importante para aquellos que no tengáis más de 1 minuto que dedicarle, estas serían:

  • Antes de lanzarte a construir tu producto, asegúrate de tener tu oferta de venta, compárala con la competencia si puedes o mejor aún, pruébala con potenciales clientes para ver qué tal funciona. Esto es incluso “más barato que desarrollar en Agile”, porque verás si fallas incluso antes de haber empezado.
  • Ten perfectamente identificado tu producto y para quién está dirigido (de una forma muy específica, nada de generalismos, acota todo lo que puedas tu público objetivo) y establece una relación entre el producto y tu audiencia.
  • Tu audiencia debe ver de una forma clara qué le va a aportar, si no son capaces de indentificarlo fácilmente y ver que “les soluciona la vida”, tu capacidad de venta se verá muy limitada.
  • Enfoca tu oferta comercial claramente a tu público objetivo y delimita su función principal, que será la que más beneficios aporte a tus potenciales clientes.
  • Establece un precio justo, intentando posicionarte en la parte media-alta de los precios de tu nicho de mercado. El objetivo de una empresa pequeña es disponer de un producto premium, con un precio acorde y que te permita centrarte en un grupo reducido de clientes para darles una gran atención.

Y ahora, para quien le apetezca conocer un poco más, os dejo mis comentarios completos del podcast, no llevará más de 5 minutos leerlos y después si os parecen interesantes, escuchadlo de primera mano, son 43 minutos que merecen la pena.

Como decía al principio de la entrada, se trata de un podcast enfocado a conseguir comercializar con éxito diferentes productos digitales a través de un blog. El modelo es fácilmente adaptable a un emprendedor o a una startup y encaja perfectamente con productos de formación o difusión del conocimiento.


Lo primero que tenemos que tener en cuenta a la hora de pensar en nuestra oferta comercial son las siguientes 3 claves principales para vender un producto:

  1. Tener identificado tu producto: Delimita de una forma sencilla y concisa qué hace tu producto.
  2. Tener identificada tu audiencia: No sirven ejemplos generalizados, hay que centrarse en colectivos muy específicos: médicos, ciclistas…
  3. Hay que establecer una relación directa entre nuestra solución y la audiencia, alineando el producto a ella.

Teniendo en cuenta las claves anteriores, y pensando en crear una oferta específica para nuestros clientes, deberíamos además:

  1. Identificar los contenidos más relevantes relacionados con aquello que queremos vender.
  2. Ganar confianza entre nuestros potenciales clientes, referenciando contenido relacionado y hablando de él para simpatizar con ellos.

Frank propone además realizar las siguientes tareas o pasar por las siguientes fases durante la elaboración de la oferta:

  1. Ideación. Propone técnicas como la tormenta de ideas, que pongas en contexto temas sobre los que te preguntan tus círculos más próximos y sobre los que les aportas valor, que pienses temas que dominas sobre los que te plantearías dar un curso o una charla (lo que demostraría tu conocimiento y que te sientes cómodo con ellos). Otras opciones, son analizar qué grandes problemas has resuelto o a los que te has enfrentado, y que sean un valor añadido para tu propuesta, que revises cuáles de tus competencias (técnicas o no) puedes poner en valor para ofrecer a los demás y “tangibilizarlas”.
  2. Unificación. Se trata de juntar las ideas anteriores y valorarlas. Es importante emplear un sistema sencillo para poder valorarlas, y en este caso. el emplear un doble baremo de puntuación, de 1 a 10: ¿Cuánto dominas lo requerido para esa idea concreta? Y ¿Cuánto te gusta esa idea concreta?. Sumando ambas puntuaciones extraeremos un orden inicial.
  3. Priorización. Extrae las 3 ideas con mejor puntuación y valora cuál de ellas sería la mejor o la óptima para empezar.
imagen-top-3-beneficios

Una vez tenemos la priorización de aquellas funcionalidades más importantes de nuestros producto, y que a su vez son con las que nos sentimos más cómodos, podemos empezar a redactar nuestra oferta.


A la hora de realizar la redacción de los textos de la oferta nos ofrece unas recomendaciones específicas que se resumen en:

  1. Céntrate en “los novatos”. Si ya tienes dominio de uno o varios temas y/o conceptos, y refuerzas ese conocimiento con la lectura de libros o documentación relacionada, en el momento que hayas leído al menos dos de estos documentos, te posicionarás por encima del 90% de la gente que empieza a interesarse por ese tema. El esfuerzo para conseguir atacar a los profesionales o a los clientes más especializados te va generar fricciones y problemas que van a requerirte mucho tiempo para resolverlos. Los profesionales de tu sector, ya no buscarán novatos, se aburren con ellos, buscan retos diferentes.
  2. Valora que haya demanda suficiente. Si hay competidores, hay demanda.
  3. Expón claramente la transformación que tu solución va a propiciar en los clientes. El error más común es centrarse más en el propio formato de la oferta, que en los resultados que obtendrán los clientes. Hablando de contenido digital, el formato (video, pdf…) o la duración son superfluos, lo que busca el cliente es saber la solución a un problema específico, busca saber exactamente qué va a obtener al “terminar”. El 90% del contenido de la oferta debe orientarse a los beneficios que se van a obtener, y sólo el 10% al formato.

Por último, la estructura de la oferta que es muy importante ya que será el hilo argumental sobre el que se apoyará nuestro discurso y todos los textos que construyamos con las recomendaciones anteriores. Frank plantea la siguiente:

  1. El plato principal. El elemento principal o esencial de tu servicio o tu producto, con aquello que vas a resolver en la vida de tu cliente. Si el cliente no entiende qué obtiene de esto al terminar de hablar contigo, no verá el valor que le aporta y por lo tanto no identificará la necesidad de comprar tu producto. La gente no compra cantidades (X horas, X Mbs, X Libros…) ni formatos (audio, vídeo, streaming, PDF…), la gente compra resultados o soluciones a su problema concreto. Indica siempre de una forma clara y concisa estos beneficios: El 90% del texto de tu oferta debe estar orientado a este objetivo, y sólo el 10% a cómo vas a integrar la solución para tu cliente. Es imprescindible la “hiperespecialización”, la gente no busca soluciones multiuso tipo navaja suiza, busca una solución específica a un problema concreto como el abrebotellas, que haga aquello para lo que está pensada de una forma que ninguna otra es capaz.
  2. El precio. Antes incluso del precio, es importante hacerse esta pregunta: ¿Comprarías tú tu producto? Si tú mismo no comprarías tu propia solución, tienes un problema de valor añadido. Nunca vendas nada que no comprarías tú. No hay que regalar ni sacrificar el precio, lo ideal es establecer el precio justo para tu cliente. Cuanto mayor beneficio perciba el cliente, mayor será el precio que podrás establecer. No es recomendable posicionarse en “Low Cost”, esto no sirve para una startup o un negocio pequeño. Estudia bien la competencia y la horquilla de precios, e intenta posicionar tu precio en la gama media-alta (siempre acorde a lo que estás ofreciendo). Es mejor trabajar con pocos clientes a los que puedas dar un buen trato.
  3. Bonus, es decisivo en el proceso de compra On-Line. Un descuento o un bono tienen un impacto negativo, y están más relacionados con el “Low Cost”. Es mejor pensar en “bonus”, ya que éstos no reducen el precio de tu plato principal, sino que lo potencian más aún. Ofrece un buen bonus valorando económicamente cada uno de estos bonus para que sepas el impacto que tendrá para ti. No hay que dar demasiados y su valoración económica debe ser acorde al producto que se ofrece. La percepción que tenga el publico objetivo puede deteriorarse por exceso o por defecto, al ofrecer un bonus demasiado suculento o por la contra con poco valor para el cliente. La referencia base es que este bonus no debe exceder 10 veces el precio de venta de tu producto.
  4. El disparador, hay que establecer el por qué los clientes necesitan comprarnos algo a corto plazo. Si se van de tu página sin comprar no volverán. Comprar implica para el cliente el salir de su zona de confort, y para ello hay que empujarle usando los disparadores, a continuación se proponen varios:
    • Límite de tiempo. Es un incentivo claro para actuar en ese mismo momento. Ej. ¡Descuento durante 24-48h! O ¡Descuento del 20% comprando ahora!.
    • Límite en cantidad. Ej. Sólo los 100 primeros, etc.

  5. Hay que cumplir con los disparadores que comuniquemos, si no cumplimos romperemos las expectativas de nuestros posibles clientes y nuestra credibilidad. Tanto si lo hacemos en positivo como en negativo, es decir, si decimos que algo va a ser exclusivo para un colectivo o unas unidades determinadas, después no podemos decir, ¡Y ahora para todos!

    Hay que hacer una mezcla de bonus/disparadores balanceada para no bloquear mentalmente a los clientes, si ejercemos mucha presión sobre ellos perderemos la oportunidad.


    Siempre que hablamos de productos digitales, el parámetro esencial en el mundo digital es la velocidad y el “Time-To-Market” (TTM). Tener lista la solución y poder venderla lo antes posible es fundamental. Tenemos que pensar en productos que se puedan definir, construir y distribuir de forma rápida. Vender un servicio es un ejemplo atractivo, podemos tener nuestra página con la oferta en una tarde, pero los servicios no escalan, hay que buscar el elemento diferenciador que permita comercializar nuestro producto sin apoyarnos en servicios.

    tipos-producto-y-time-to-market

    Saca algo sencillo y mejóralo con las siguientes entregas. Tener algo que tu clientes puedan probar te ayudará a dar los siguientes pasos y evitarás llegar a puntos muertos. Piensa que si ya tienes una versión en el mercado puedes hacer cambios, o bien pedidos por tus clientes o que tú consideres de valor añadido, y mientras tanto sigues vendiendo la primera versión capturando ventas a la vez que mejoras tu producto.


    ¡El principal objetivo de nuestra oferta es DEMOSTRAR QUE PODEMOS VENDER!


    Insisto una vez más, escuchad el podcast, descargad antes los recursos de esta dirección http://lifestylealcuadrado.com/aa2, ya que son mencionados en varias ocasiones y tenerlos delante ayuda, pero no son estrictamente necesarios, por lo que si aprovecháis a escuchar el audio mientras hacéis ejercicio tampoco los echaréis de menos y siempre podéis verlos más tarde.

    Espero que os parezca tan interesante como me lo ha parecido a mi. 😉

API REST en Liferay 7 con JAX-RS

$
0
0

En este tutorial veremos cómo montar encima de Liferay 7 una API REST con JAX-RS y los servicios locales de Liferay.

Índice

1. Entorno

  • Hardware: Portátil MacBook Pro 17' (2.66 Ghz Intel Core I7, 8GB DDR3).
  • Sistema Operativo: Mac OS Sierra 10.12.4
  • Entorno de desarrollo: iTerm2 y IntelliJ IDEA 2017.1.3
  • Liferay 7.0 CE GA 3
  • Java JRE 1.8.0.131

2. Introducción

Una API de tipo REST nos aporta muchas ventajas, como simplicidad, fácil exploración y facilidad de uso para los clientes debido a su estandarización.

Muy a nuestro pesar, Liferay no expone directamente una API REST, pero sí unos Web Services en JSON (localhost:8080/api/jsonws) y otros en XML (http://localhost:8080/api/axis) (SOAP). Liferay nos da la posibilidad de crear nuestros propios Web Services usando blade y JAX-RS (Implementación de Apache CXF).

Si queréis ver el código fuente aquí tenéis el link al repositorio de Github

He aquí las posibilidades con las que contamos según los diferentes tipos de servicios:

  • Liferay JSON Web Services
    • Autorización
    • Autenticación
    • Configuración automática
    • Explorador de endpoints
    • Local
      • Cualquier lenguaje
      • Servicios propios
    • Remoto
  • Liferay Axis Web Services
    • Autorización
    • Autenticación
    • Configuración automática
    • Explorador de endpoints
    • Local
      • Cualquier lenguaje
      • Servicios propios
    • Remoto
  • Liferay Web Services propios
    • Autorización
    • Autenticación
    • Configuración automática
    • Local
      • Cualquier lenguaje
      • Servicios propios
    • Remoto

En cuanto a los JSON Web Services y a los de Axis podemos consumirlos dentro de un portlet o aplicación local. Si quisiésemos exponer algún servicio a clientes remotos tendríamos que crear Web Services propios que hagan llamadas a los Web Services de JSON o XML. La otra posibilidad es crear un Web Service que interactúe directamente con los localService de Liferay.

También tener en cuenta que, si desplegamos un Web Service propio, perderemos la autorización y autenticación que disponemos con los otros Web Services, es decir que por ejemplo tendremos que comprobar que el usuario X tiene permiso o no para acceder al recurso Y.

Referencias servicios:

3. Generación del módulo

Vamos a proceder a generar un Workspace de Liferay, lo que nos dará muchas facilidades a la hora de desplegar, crear y desarrollar módulos OSGi. Si quieres, puedes ver el tutorial sobre la creación de proyectos, módulos y portlets de Liferay 7 con Blade CLI, donde Javier explica más detalladamente Blade CLI y el Liferay Workspace. Comenzamos a crear el módulo.

Iniciamos con blade el Workspace en el directorio que queramos:

blade init liferay-workspace

Descargamos el bundle de Liferay:

blade gw initBundle

Una vez hecho esto pasamos a crear nuestro módulo. Usaremos una plantilla predefinida para evitarnos el tener que configurarlo todo desde cero. Para ello:

blade create -t rest -p <nombre_paquete> <nombre_proyecto>

Donde nuestro <nombre_paquete> será com.autentia.liferay.rest y nuestro proyecto <nombre_proyecto> será rest-users.

Blade infiere de este comando que nuestro endpoint a la hora de registrarlo en la instancia de Liferay será /rest-users. Si queremos cambiar este endpoint tendremos que modificar los dos archivos de configuración dentro de la carpeta $LIFERAY_WORKSPACE/modules/rest-users/src/main/resources/configuration. Tanto el archivo com.liferay.portal.remote.cxf.common.configuration.CXFEndpointPublisherConfiguration-cxf como com.liferay.portal.remote.rest.extender.configuration.RestExtenderConfiguration-rest. Y modificar el /contextPath a lo que queramos. En mi caso será /rest

Nota: Solamente deja modificar desde código la configuración la primera vez, así que si queremos volver a cambiar este valor, tendremos que hacerlo desde la interfaz de Liferay. (Control Panel / Configuration / System Settings / Foundation / REST Extender y CXF Endpoints)

Una vez hecho esto pasamos a iniciar Liferay:

blade server start

Desde otro terminal y en el directorio de nuestro módulo:

blade deploy

Si introducimos el siguiente comando, veremos el nombre de nuestro módulo en el terminal:

blade sh ss

Liferay REST SS Con Gogo shell

Como se puede observar, el módulo con id 486 es el módulo que acabamos de desplegar.

Nota blade sh hace que nos conectemos a través de Apache Felix a la Gogo Shell, que es la que nos da toda la información de los módulos y nos permite interactuar con ellos.

Ahora, si accedemos a http://localhost:8080/o/rest/greetings en nuestro navegador, podremos ver la respuesta It works!.

Nota: Por defecto todos los módulos OSGi que creamos colgarán del endpoint o (o de OSGi).

4. Arquitectura API REST

Según la arquitectura REST el enfoque cae en los recursos, que son sustantivos en plural (usuarios, roles, artículos, etc.) y sobre estos recursos, dependiendo del método http que usemos se tomarán unas acciones u otras. En el protocolo HTTP tenemos 4 verbos principales: GET, POST, PUT y DELETE. Con estos verbos podemos hacer operaciones CRUD sobre un recurso.

Haremos las peticiones con una extensión de Chrome llamada Postman para comprobar que estas se realizan correctamente.

5. JAX-RS

JAX-RS (Java API for RESTful Web Services) es una especificación de Java que da soporte a la creación de servicios web basados en la arquitectura Representational State Transfer (REST). Hace uso de anotaciones y tiene varias implementaciones, como: Jersey, Apache CXF, RESTeasy, RESTlet, etc.

Liferay usa la implementación de Apache CXF, aunque al ser una especificación al aprender una aprendemos todas. Actualmente JAX-RS va por la versión 2.0 que fue sacada en 2013.

En nuestra clase RestUsersApplication pondremos el siguiente código:

package com.autentia.liferay.rest.application;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

@ApplicationPath("/users") // Indica a JAX-RS el endpoint de nuestro recurso
@Component(immediate = true, service = Application.class)
public class RestUsersApplication extends Application {

    @Reference // Inyectamos nuestra clase recurso mediante una anotación de OSGi
    private RestUserResource restUserResource;

    // Registramos nuestro servicio en OSGi
    @Override
    public Set<Object> getSingletons() {
        super.getSingletons();
        return new HashSet<>(Collections.singletonList(restUserResource));
    }

}

Ahora nos creamos una clase RestUserResource que es donde estará la implementación con los métodos GET, POST, PUT y DELETE, aunque de momento probaremos con el método GET nada más. Además, aquí haremos las llamadas al servicio de Liferay para recoger los datos. Una posible mejora de este programa sería crear una clase de servicio para nuestro RestUserResource, aunque esto lo dejamos a manos del lector.

Dado que usamos el servicio UserLocalService debemos añadir este a gradle. Con lo que en el archivo build.gradle de nuestro módulo incluimos lo siguiente:

dependencies {
    compileOnly "com.liferay.portal:com.liferay.portal.kernel:2.0.0"
    compileOnly group: "javax.ws.rs", name: "javax.ws.rs-api", version: "2.0.1"
    compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0"
}

Nota: compileOnly es parte del plugin de gradle de Liferay, e indica que las dependencias serán recogidas en caliente de la instancia de Liferay, ya que tiene esas librerías expuestas por defecto. Si quisiésemos incluir nosotros una librería externa, debemos usar compileInclude, que hace que compile la librería y además añade la entrada al Manifest.mf. Más información aquí.

Un punto importante a tener en cuenta es que los servicios de Liferay devuelven entidades User mientras que nosotros queremos que devuelva una entidad creada por nosotros, ¿por qué es esto?

Esto es debido a que JAX-RS no tiene conocimientos sobre cómo parsear nuestro POJO a JSON o XML, con lo que hay que anotar nuestra entidad con la etiqueta @XmlRootElement.

Aquí tenemos un problema, ya que nosotros no podemos modificar directamente (y no deberíamos) la entidad User del kernel de Liferay, con lo que nos crearemos un POJO intermedio (RestUser) que mapeará aquellos campos que queremos de User a RestUser.

Por lo que cuando hacemos una llamada a userLocalService tenemos que convertir User a RestUser. En el caso de getUsers() uso un Stream de Java 8 para recorrer el ArrayList de User y por cada uno hago una instancia de RestUser y devuelvo por tanto una lista de RestUser.

He aquí el código de nuestro RestUser:

package com.autentia.liferay.rest.application;

import com.liferay.portal.kernel.model.User;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement // Permite parsear nuestra entidad a XML o JSON según el Accept de la petición
public class RestUser {

    private long id;
    private String firstname;
    private String lastname;

    // Importante tener un constructor público vacío para que CXF Apache pueda hacer las instancias correctamente
    public RestUser() {
    }

    RestUser(User user) {
        id = user.getUserId();
        firstname = user.getFirstName();
        lastname = user.getLastName();
    }

    // Los getters y setters deberán ser públicos
    public long getId() {
        return id;
    }

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

    public String getFirstname() {
        return firstname;
    }

    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    public String getLastname() {
        return lastname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

}

En versiones de JAX-RS anteriores había que crear una clase MessageBodyWriter que se encargaba de parsear nuestra entidad, pero en la nueva versión no hace falta, lo que nos quita mucho boilerplate.

6. GET

En nuestra recién creada clase RestUserResource incluimos el siguiente código:

package com.autentia.liferay.rest.application;

import com.liferay.portal.kernel.model.User;
import com.liferay.portal.kernel.service.UserLocalService;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

import javax.ws.rs.GET;
import java.util.List;
import java.util.stream.Collectors;

@Component(immediate = true, service = RestUserResource.class) // Nuestro recurso es a su vez un componente de OSGi
public class RestUserResource {

    // Instancia del logger de Liferay para poder loggear información
    private static final Log log = LogFactoryUtil.getLog(RestUserResource.class);

    @Reference // Referencia al servicio de Liferay para usuarios
    private UserLocalService userLocalService;

    @GET // Anotación de JAX-RS que hace que cuando nos conectemos a /users por GET ejecutará este método
    public List<RestUser> getUsers() {
        final List<User> users = userLocalService.getUsers(-1, -1);
        return users.stream().map(RestUser::new).collect(Collectors.toList());
    }

}

Hacemos blade deploy y probamos a hacer una petición desde Postman a http://localhost:8080/o/rest/users (cada vez que modifiquemos nuestro módulo haremos un blade deploy).

Liferay API REST - Petición GET Usuarios

Importante añadir en la sección Headers el campo Accept y poner como valor application/json ya que si no nos dará el siguiente error: No message body writer has been found for class java.util.ArrayList, ContentType: text/html (en Postman no dará error ya que Postman todas las peticiones que hace pone por defecto una cabecera de application/json).

Si cambiamos el valor del campo Accept a application/xml, veremos que devuelve la respuesta en formato XML. Esto es gracias a que tenemos anotado nuestro RestUser con XmlRootElement, de no ser así nos tocaría a nosotros programar la funcionalidad de parseo a XML o JSON.

Vamos a implementar un endpoint que devuelva un único usuario por id. Para ello en la clase RestUserResource añadimos el siguiente método:

@GET
@Path("{id}")
public RestUser getUser(@PathParam("id") long id) throws PortalException {
    return new RestUser(userLocalService.getUserById(id));
}

Nota: La anotación @PathParam recoge de la url el parámetro id. (ej: el 20164 de la url “http://localhost:8080/o/rest/users/20164” y lo mapea a nuestra variable.

Probamos ahora con el Postman:

Liferay API REST - Petición GET Usuario

Sin embargo, si introducimos el id de un usuario que no existe no recibimos un error 404, si no un error 500, lo que indica fallo interno del servidor. Esto es debido a que hacemos un throw en nuestro método.

Hay una forma mejor de manejar las excepciones con JAX-RS y es la siguiente:

@GET
@Path("{id}")
public RestUser getUser(@PathParam("id") long id) {
    try {
        return new RestUser(userLocalService.getUserById(id));
    } catch (PortalException e) {
        log.info(e);
        throw new NotFoundException(e);
    }
}

Nota: Dado que estamos en OSGi depurar a base de prueba y error no es especialmente óptimo, usamos log para ver por consola los errores.

De esta forma, si hacemos una petición sobre un usuario que no existe, recibiremos un error 404.

Aquí tienes un listado de las excepciones de JAX-RS:

Excepción Código de estado Descripción
BadRequestException 400 Mensaje malformado
NotAuthorizedException 401 Fallo en la autenticación
ForbiddenException 403 No hay suficientes permisos
NotFoundException 404 No se ha podido encontrar el recurso
NotAllowedException 405 Método de HTTP no válido
NotAcceptableException 406 Media Type no válido
NotSupportedException 415 Media Type de POST no válido
InternalServerErrorException 500 Error interno del servidor
ServiceUnavailableException 503 Servidor no disponible

Referencia manejo errores JAX-RS

7. POST

El código para crear un User (ojo cuidado, que tenemos que crear un User y no un RestUser) es el siguiente:

@POST
public RestUser postUser(RestUser restUser) {
    try {
        final User user = userLocalService.addUser(
                0,
                PortalUtil.getDefaultCompanyId(),
                true,
                null,
                null,
                true,
                null,
                restUser.getFirstname() + restUser.getLastname() + "@autentia.com",
                0,
                null,
                Locale.ENGLISH,
                restUser.getFirstname(),
                null,
                restUser.getLastname(),
                0,
                0,
                true,
                0,
                1,
                0,
                "",
                null,
                null,
                null,
                null,
                false,
                null
        );
        return new RestUser(user);
    } catch (PortalException e) {
        log.info(e);
        throw new InternalServerErrorException(e);
    }
}

Liferay API REST - addUser

Cosas a tener en cuenta:

  • emailAddress es un campo obligatorio y no puede estar duplicado
  • locale no puede ser nulo
  • birthdayDay empieza en 1, no como birthdayMonth que empieza en 0

Hacemos una petición POST sobre http://localhost:8080/o/rest/users de la siguiente forma:

Liferay API REST - Postman POST User

Por convención una vez se crea o modifica un recurso es conveniente devolver el recurso recién creado o modificado.

8. PUT

@PUT
@Path("{id}")
public RestUser putUser(@PathParam("id") long id, RestUser restUser) {
    try {
        final User user = userLocalService.getUserById(id);
        user.setFirstName(restUser.getFirstname());
        user.setLastName(restUser.getLastname());
        return new RestUser(userLocalService.updateUser(user));
    } catch (PortalException e) {
        log.info(e);
        throw new NotFoundException(e);
    }
}

Nota: Según el método PUT hemos de pasar el recurso entero en el body con todos los campos, aunque estos no sean los que queramos modificar. Si quieres otra opción puedes mirar el método PATCH.

Liferay API REST - Petición PUT Usuario

9. DELETE

@DELETE
@Path("{id}")
public RestUser deleteUser(@PathParam("id") long id) {
    try {
        return new RestUser(userLocalService.deleteUser(id));
    } catch (PortalException e) {
        log.info(e);
        throw new NotFoundException(e);
    }
}

Liferay API REST - Petición DELETE Usuario

10. Conclusión

La inclusión de la arquitectura OSGi en Liferay 7 ha posibilitado la creación de módulos como el que acabamos de crear con mucha facilidad. Además, hemos visto cómo montar una API REST con JAX-RS.

11. Referencias

JSDayES Madrid 2017 – Taller de Substack

$
0
0
JSDayES Madrid 2017 – Taller de Substack El pasado día 12 de mayo tuve el honor de acudir en el JSDayES de Madrid al taller que impartió James Halliday, más conocido en el mundillo de Javascript como Substack. Substack es conocido por varios proyectos open-source de NodeJS y Javascript que están a su cargo: Browserify, Minimist y uno de los documentos de referencia para trabajar con Streams en Javascript, el stream handbook, aparte de muchos otros proyectos open-source. El taller se titulaba ‘hands on modular web’, y la idea subyacente era usar diversos pequeños paquetes de npm, cada uno con una funcionalidad muy específica, para crear diversas aplicaciones con un nivel de complejidad relativamente grande. La verdad es que yo no conocía muchos de estos paquetes y me sorprendió cómo combinando yo-yo, budo y un poco de código, el bueno de James montó enseguida un mecanismo de dom diffing y renderizado inspirado en la arquitectura redux. No voy a entrar mucho en detalles sobre los aspectos técnicos del curso, más que nada porque si tenéis curiosidad podéis consultar el repositorio con todos los contenidos que Substack fue creando sobre la marcha en esta url, tan solo comentaré algunas cosas que me llamaron la atención:
  • Hay un formato de fecha, concretamente el ISO 8601, que ordena cada parte de la fecha de mayor a menor unidad de tiempo (año -> mes-> día -> hora -> minuto -> segundo). Es ampliamente usado en China y tiene bastantes promotores -entre ellos Substack- porque tienen la propiedad de conservar el orden lexicográfico cuando se formatean las fechas como cadenas de texto.
  • Substack con un terminal y una triste instancia de vim es siete veces más productivo que yo con un IDE.
  • Cuando se pasan template strings por parámetro a una función, no hace falta usar paréntesis para invocar la función. Puedes escribir cosas como ‘render${var} foo’ y funciona igual que ‘render(var + ‘foo’)’. No tenía ni idea 😀
  • Nunca había oído hablar de la arquitectura “Kappa“. James dedicó una parte de su charla a hablar del tema y demostró cómo replicarla con unos pocos módulos de npm.
  • Substack ha creado esta pequeña web donde se puede probar a hacer música simplemente definiendo funciones en javascript que representen ondas. En la sección de ‘help’ de esa web se explica un poco la idea. James tenía bastante soltura con el tema y durante el taller, con un poco de Javascript consiguió hacer una especie de techno que hubiera deleitado a los más hipsters de Autentia.
  En general, el taller me gustó mucho y me considero muy afortunado de haber podido asistir (cosa que le debo a mi empresa, Autentia, ya que era patrocinadora del evento).

Oficina de historias de Usuario: meme instrumental

$
0
0

INTRODUCCIÓN

‘Oficina de Historias de Usuario’. Este es el tema del que partirá mi ponencia del próximo 8 de junio en ItSMF, un foro en el que se discutirá sobre Tecnología, DevOps, Agilismo y Servicios. Os recomiendo asistir a este evento no sólo por los destacados conferenciantes que tendrán protagonismo en la jornada, sino también por las interesantes sinergias entre responsables de tecnología que tendrán lugar. Además, tengo un par de sorpresas bajo la manga para aquellos que se acerquen a mi charla. Si te interesa te pongo en antecedentes. ¿De qué trata esto de ‘Oficina de Historias de Usuario’?

EL RETO

Las organizaciones se han dado cuenta de que no pueden trabajar como lo hacían. Empieza a abandonarse las prácticas donde un proyecto se definía mal (por incapacidad o incluso voluntariamente para obtener más por menos de los proveedores), donde luego se dedicaban semanas a una negociación contractual a los proveedores más grandes (y cada vez más lejos) y posteriormente se descuidaba la ejecución (cual presentación de servicio) para descubrir meses después que no se cumplen las expectativas de funcionalidad o calidad: poco contacto con el usuario de negocio, poco acceso al código y pocas prácticas XP / DevOps.

Esto podría valer en tecnologías y negocios consolidados, pero “amigo” ahora han llegado las fintech, las startups, y los todopoderosos modelos de Spotify, Google, Amazon, etc. demostrando que sus sistemas dejan atrás a todos los que no cambian.

Entregar pronto, en ciclos de 2/3 semanas, hace que los equipos técnicos tengan que estar más alineados con las necesidades de negocio. También requiere que estén más cerca, físicamente incluso.

Pero cambiar es complicado y hay que ponérselo fácil a las personas y organizaciones.

Imaginad que se desea empezar a trabajar ágilmente, lo primero que tenemos que hacer es definir los proyectos más pequeños y empezar a construir lo antes posible para enseñárselo a los usuarios y obtener feedback temprano. Incluso para darnos cuenta que es una mala inversión y dejarla: falla rápido/falla barato.

Por tanto, hay que formar a las áreas de negocio y de tecnología para trabajar de otro modo. Es más, todos sabemos que el aprovechamiento de un curso es muy limitado, más aún cuando la gente tiene la mente en otro lugar, por la cantidad de tareas pendientes. En grupos pequeños o en áreas donde hay un héroe que se echa el problema a las espaldas (se forma y empuja) el problema es menor. Pero vamos a ponernos en un modo “normal” donde en las empresas hay de todo y tenemos que conseguir avanzar.

A ver si somos inteligentes facilitando el estado transitorio entendiendo donde estamos, empatizando con las personas y sus problemas y dándolas alternativas razonables para abordar el cambio.

EL ESTADO

Si empatizamos con un responsable de producto de una organización es una persona que ya tiene un modelo de trabajo, muchos productos en curso y su vida gobernada por una incesable agenda de reuniones. ¿ahora pretendemos que de un día para otro aprenda a?

  • Definir un proyecto en base a historias.
  • Dar cabida a otras áreas en la definición.
  • Poner al cliente en el centro de la necesidad.
  • Podar para hacer los proyectos más pequeños.
  • Concretar a un nivel al que no está acostumbrado.
  • Estar disponible para cuando la tecnología les requiera para dar una respuesta rápida.
  • Aprobar acciones sin (la falsa) estimación total del coste.

Parece bastante complicado que puedan dedicar tiempo, aprender y aceptar del modelo.

Pero ahora vamos a ponernos en la perspectiva de tecnología. En muchas organizaciones el rol de jefe de proyecto tecnología consiste en hacer de puente, a partir de un pliego escrito por negocio (en el que no han participado), como declaración de intenciones, con los proveedores que tienen casi todo el conocimiento y control de las plataformas. Se produce un ciclo: pliego mal definido, petición de oferta a proveedores, estimación cara, rebote a negocio para reducir ámbito, reducción del precio objetivo y asignación. Además, hay una tendencia a condicionar a negocio con lo que “se puede hacer”.

Tecnología propia no toca apenas código/sistemas y ha perdido habilidades técnicas, como todo lo que no se usa.

Ahora, si se desea que tecnología se integre con negocio y empiece a identificar y abordar chores (labores técnicas que se pueden anticipar), proponer alternativas menos costosas al mismo problema y empezar a construir de un modo temprano, sin que haya un pliego detallado sino un conjunto de historias a ejecutar por orden ¿tiene capacidad para poderlo hacer esta ejecución temprana o labores de investigación técnica?. Posiblemente no tenga ni los recursos (reducidos al mínimo internamente y sin un proveedor por no tener asignado el proyecto todavía) ni las capacidades.

Además, si te acostumbras a que los proveedores te hagan las tareas, hacerlas personalmente luego cuesta.

EL MODELO DE CAMBIO

Entonces ¿cómo hacemos para que la organización cambie de un modo gradual? Necesitamos que se adquiera el conocimiento y la práctica. Y sobre todo, necesitamos que nos den presupuesto para que alguien ayude a que esta transición no sea dolorosa.

Pues se me ha ocurrido un MEME instrumental que es fácil de vender a las organizaciones (dentro del modelo mental) y que puede ser una muy buena solución para resolver estos problemas: LA OFICINA DE HISTORIAS DE USUARIO.

Cada vez que se desee iniciar un nuevo proyecto estratégico, se le asigna una persona de la oficina de historias de usuario (perfil de coach agile) que está para encaminar el trabajo, es decir, no para solo “coachear”, formar, recopilar información o reportar sino para hacer el primer trabajo de campo y resolver el problema de folio en blanco: manchándose la manos en resolver el proyecto.

A: Montar el equipo multidisciplinar y definir las responsabilidades: Se constituye en equipo para un nuevo proyecto: Normalmente, con representantes de negocio (el PO), de operaciones, soporte, tecnología, un jefe de proyecto de toda la iniciativa y un responsable del proyecto desde tecnología. Posiblemente alguien más invitado en base al proyecto. Pero recordar, mucha gente es mucho gasto y muchas opiniones. El PO siempre tiene que tener un peso mayor. El jefe de proyecto luego tiene que ponerlo en marcha y tecnología es el único que puede estimar.

Podemos decir que aquí ya puede haber un problema, que la abuela fuma, ¿quién de toda esta gente tiene la responsabilidad de escribir las historias de usuario? Porque el product-owner es el responsable de priorizar y dar sentido al product-backlog pero ¿tiene que escribirlo todo? Te va a mandar cerca…

Además, sin práctica a la hora de hacerlo cuesta, se confunden historias con tareas y “da pereza”. Negocio percibe que no es su responsabilidad bajar a tanto detalle. También falta tiempo para detallar sistemáticamente.

Pues el representante de la oficina de historias (ROHO) se integra con ellos y hace el “primer trabajo sucio”. Después de dinamizar las reuniones se encargará de generar el esqueleto. Cuando ya hay una carcasa será relativamente fácil completar el trabajo (veremos cómo).

Una práctica que utilizamos es forzar a los miembros de la nueva iniciativa a imaginar que el sistema ya está construido y que nos explique como funcionaría. De este modo, nosotros vamos pintando en la pizarra un diagrama de procesos. De este modo, al no tener contexto profundo del negocio se ven obligados a explicarnos a muy alto nivel todos los pasos. Os sorprendería ver como cuando esto se hace, en la mente de todos los asistentes se fija este diagrama como referencia. ¡Por primera vez todos manejan la misma abstracción física!

B: Definir proyectos de futuro sin contaminarse pero avanzando. Pero además puede existir otro problema, en muchas organizaciones puede no desearse contaminar con el estado actual de una solución (as-is) el estado futuro al que se quiere ir (to-be) por lo que no se quiere que el equipo asignado al proyecto estudie la solución actual detalladamente para no ser continuista con el modelo. Aquí aparecen modelos como Design Thinking u otros.

Entonces, hasta que no se ha avanzado la conceptualización del nuevo proyecto no se estudia la solución actual. Esto crea un problema de eficiencia ¿tengo que esperar unas semanas para luego aterrizar la solución?

Pero ¿realmente son los proyectos tan sumamente disruptivos? Normalmente, y siguiendo la teoría de Kano (elementos imprescindibles, de valor linear o que sorprenden) diría que el 80 % de las nuevas funcionalidades pueden ser variaciones sobre lo que hay.

El ROHO puede hacer desde el primer día reingeniería del proyecto actual haciendo un User Story Mapping de la solución existente, de los componentes. Una iniciativa nueva claramente afectará a historias de usuario existentes y aportará algunas nuevas.

Sin afectar al proceso creativo del equipo asignado el ROHU se puede adelantar trabajo de entender la solución actual e ir trazando este mapa que casi ninguna organización tiene: la relación entre iniciativas – componentes (las épicas que lo componen) y sistemas base.

C: Unir el as-is y el to-be y modelar el sistema visualmente: llegado al punto donde ya se ha conceptualizado la solución nueva de un modo poco contaminado, hacer el mapping entre el as-is y el to-be ya estará adelantado. No tiene sentido pararse a hacer un mapa de todo el sistema y sus dependencias. El modelo irá creciendo y definiendo el nivel de abstracción a medida que se va participando en más proyectos.

Haciendo este trabajo en distintas iniciativas en paralelo con distintos representantes de oficia de historias de usuario, coordinados entre ellos, se podrá empezar a hacer un mapa de dependencias, progresivo, entre iniciativas y componentes (épicas/temas que lo componen).

D: Identificar los retos técnicos de un modo temprano: A medida que se trabaja en un proyecto y en fases muy tempranas aparecerán chores/spikes o labores técnicas (seguras o pruebas de concepto) que se pueden identificar para adelantar a la propia ejecución del proyecto. Por ejemplo: si estamos pensando capturar más información de un cliente puede ser interesante investigar almacenarla de un modo menos estructurado y se hace necesario una prueba de concepto que se puede iniciar casi el día 1.

Ahora lo complicado es convencer a la organización que sin tener el pliego detallado, sin tener un presupuesto total, sin tener asignado un proveedor, tiene que haber gente en el departamento de tecnología que empiece a hacer una prueba técnica, acotada en tiempo, para determinar si la solución teórica aporta el valor que se espera de ella.

También se tiene que procurar que estas pruebas técnicas aporten valor a toda la organización (gobierno y gestión del conocimiento), evitando los silos, o lo que es peor, que se le page a un proveedor por hacerlo y no se interiorice el conocimiento.

Obviamente esto pasa por tener unos conocimientos técnicos avanzados. Parece lógico que si la tecnología es un valor diferencial para la organización hay que potenciar a los departamentos de arquitectura propios y que dejen de ser “arquitectos power-point” para que sean los que rascan código y cuentan con los proveedores para aprovisionarse de nuevo conocimiento no para simplemente hacer de dispatchers de presupuestos y tareas.

Parece que en este país, con estructuras “gerenciales” el valor del técnico se ha tirado por tierra. Fijaros en los perfiles de los lideres de las empresas de hoy: Facebook, Amazon, Tesla .. y de quién se rodean: de los mejores profesionales técnicos. Si no les copias en esto no les podrá copiar en lo demás.

La oficina de Historias de Usuario puede dar visibilidad a estas iniciativas técnicas y al nivel de independencia de proveedores. El equipo de historias de usuario puede ayudar a bajar para evaluar las prácticas XP y DevOps que se usan por debajo: Integración continua, TDD, pruebas unitarias, pruebas automatizadas, métricas de calidad, etc. ¿qué sentido tiene entregar funcionalidad más atómicamente si no se puede probar y entregar más rápido? Muchas organizaciones han perdido el camino.

E: Detallar las historias inminentes y retrasar las demás a medida que avanza el proyecto Cuando está definido el User Story Mapping, hay que detallar las historias. Las historias tienen 3 Cs, la Card, la Confirmación y la Conversación (el orden está así a propósito)

Primero hay que aprender a trabajar con historias más pequeñas y descomponer en elementos menos: “épicas pueden contener historias no prioritarias”. Esto ayudará a hacer los proyectos más pequeños. Existen al menos 10 patrones de descomposición de historias que el equipo debe manejar, a medida que avanza su aprendizaje.

Imaginemos que de las primeras 5 o 10 primeras historias (priorizadas) el ROHU escribe el detalle. De tal modo que ya tienen todos los miembros del equipo un ejemplo. Por ejemplo la confirmación se puede ejecutar con esta plantilla.

Si forzamos a que los equipos definan casos de éxito, de fracaso y fallo operativo, será fácil no dejar fuera escenarios reales. Por ejemplo, un escenario de éxito, en una web de compras es que el usuario hace un pedido y puede pagar bien. Un escenario de fracaso es que ha superado un límite de compra o la tarjeta no pasa. Un escenario de fallo operativo es que se le cobra dos veces el mismo pedido. Esto da lugar a que, desde el principio, se planteen conversaciones interesantes y aparezcan y descarten (voluntariamente historias)

Y con diagramas de secuencia (yo uso DSS de UML no formales) y mockups se puede detallar la conversación como secuencias de baja fidelidad (mejor dicho, encontrar conversación a través de descubrir secuencias). De este modo pensamos desde la perspectiva del cliente final e integramos UX. Podemos convertir a los diseñadores en analista o conseguir que el PO baje de la abstracción al detalle: Decir, píntamelo es una buena práctica, pero si antes le enseñas como se puede pintar en algunos casos.

¿qué costaría, delante de un proyector, dirigido por el ROHO, empezar a escribir las siguientes historias en base a este patrón con los miembros del equipo de definición? Poco.

Primero se divide al equipo en dos y se ayuda a cada grupo a que asimilen la técnica. Luego, se descompone otra vez en dos. Se ayuda a los que le cueste más, que no todo el mundo tiene el mismo foco, tiempo ni capacidad. Después de unas cuantas horas todo el mundo tendrá la dinámica cogida y poco a poco se hará menos cuesta arriba el nuevo modelo. Si el ROHO se centra ya no en hacerlo todo (se hace colaborativamente) sino en matizar y ayudar a afinar, el trabajo es escalable.

F: Reducir el tamaño de lote, los silos y la comunicación por documento: No tiene ningún sentido dedicarse unas semanas a hacer un documento de historias para luego entregar otra vez un pliego entero y que tecnología te devuelva sus comentarios en bloque o te de una valoración conjunta. Tiene que co-crearse la definición detallada del proyecto como historias a medida que se está construyendo la solución.

Es decir, desde el principio del proyecto el equipo técnico trabaja en los Chores/Spikes, empieza a crear el entorno, trabaja en las primeras historias (detallando), se empieza de un modo temprano a montar el entorno DevOps y pruebas y a construir. En poco tiempo se lima el proceso.

Os puedo contar hasta como conseguir cuantificar el avance de la adopción de estas prácticas.

EL PROCESO DE DEFINIR LAS HISTORIAS

Una de las cosas más frustrantes que puede suceder a un equipo que se afronta a un nuevo modelo de trabajo es no saber donde está y que pasos tiene que dar.

Para hacerlo fácil podemos enseñarles un método concreto de hacer las cosas. Obviamente habrá muchos modos de descomponer una proyecto en historias, pero es casi preferible dar una única verdad, aunque sea temporalmente. Cuando la gente tenga ganas de estudiar y avancen empezarán a cuestionar el método o ver alternativas o variar pasos. En ese momento se podrán ampliar las alternativas y la formación.

A su vez, las historias tendrán un ciclo modelado en un Kanban: identificada, definida (3Cs), detallada por tecnología, valorada y ready. Nos vale este flujo u otro cualquiera. Recordad que menos es más. Demasiada consistencia en un principio es contra-producente.

Adicionalmente ES UN ATRASO TRABAJAR DIRECTAMENTE EN HERRAMIENTAS. En DOHO puede construir y ayudar a mantener paneles físicos. OS RECOMIENDO DURANTE LOS PRIMEROS MESES TRABAJAR LAS DINAMICAS SOBRE PANELES FÍSICOS. Si hace a negocio esclavo de las herramientas no conseguirás las adopciones.

Reporta, habla de los avances y discute sobre una pared y un panel.

Esta puede ser una labor de ROHU, incluso cuantificar el avance que supongo que veis sencillo, en cada una de las iniciativas.

CONCLUSIONES

La gente competente hace las cosas complejas sencillas. Cambiar una organización no es nada sencillo.

Los memes son palabras potentes que ayudan agrupar ideas complejas en conceptos sencillos y fácilmente extensibles. Ya me diréis que os parece el meme: OFICINA DE HISTORIAS DE USUARIO, y las prácticas que os cuento. Si os animáis a adoptarlo y si os es útil para transformar las organizaciones es que trabajéis.

Veréis que es un modo fácil para tener presencia en la organización entre negocio y tecnología y ayudarles a que racionalicen el modelo, a devolver valor al profesional técnico, tanto de conceptualización (UX+Desgin Thinking) como de modelado y facilitación (definir User Story Mapping y prácticas ágiles) y de desarrollo (Front-Back / DevOps). Las cosas bien hechas se hacen con pocos buenos profesionales no con muchos poco cualificados.

Introducción a la composición de Futures de Akka en Java

$
0
0

Akka es un toolkit y un runtime que nos permite desarrollar aplicaciones altamente concurrentes, distribuidas y resilientes, mediante el envío de mensajes entre actores. En este tutorial nos vamos a centrar en sus Futuros y su composición, y en como gracias a ellos podemos realizar de forma sencilla operaciones concurrentes.

Índice de contenidos



Akka Futures logo

1. Introducción

Ya tenemos un par de tutoriales sobre Akka y su sistema de actores:

así que en este tutorial voy a hablar un poquito de cómo funcionan los Futuros y cómo los podemos combinar o componer unos con otros.

Un Futuro no es más que la ejecución de un código que se realiza en paralelo (en otro hilo) y que, en “un futuro”, se resolverá y nos retornará un valor. Esto nos permite lanzar varias ejecuciones en paralelo (varios Futuros en paralelo) para que cada uno de ellos resuelva un valor, todos de forma simultánea.

La composición de futuros consiste en combinar varios futuros, o mejor dicho, el resultado de estos futuros, para acabar devolviendo un único valor que será el resultado de la combinación de todos los resultados anteriores.

Este tutorial intenta ser sobre todo práctico y mediante ejemplos de código vamos a ver que opciones tenemos para lanzar Futuros, cómo podemos combinarlos, sincronizarlos, …​

Todo el código del tutorial lo podéis encontrar en este repositorio de GitHub

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.4
  • Oracle Java 1.8.0_121
  • Akka 2.11

3. La aplicación

La aplicación va a ser muy sencilla, tan sencilla que aunque vamos a usar Akka, no vamos a usar Actores. Los Futuros son buenos cuando, dentro de un actor o en cualquier otra parte, queremos realizar un cálculo o proceso en paralelo. De hecho la propia documentación de Akka nos dice que si te pones a crear un pool de UntypedActors sólo por el hecho de realizar un cálculo en paralelo, la forma más sencilla y rápida de hacerlo es mediante Futuros.

public class App {

    public static void main(String[] args) throws InterruptedException {
        final App app = new App();
        app.execute();
    }

    private void execute() throws InterruptedException {
        final ActorSystem system = boot();

        new SimpleFuture(system).runExample();
        new MapFutures(system).runExample();
        new TraverseFutures(system).runExample();
        new SequenceFutures(system).runExample();
        new FoldFutures(system).runExample();
        new ReduceFutures(system).runExample();

        shutdown(system);
    }

    private ActorSystem boot() {
        return ActorSystem.create("actorSystem");
    }

    private void shutdown(ActorSystem system) throws InterruptedException {
        Thread.sleep(3);
        system.terminate();
    }

}

Aquí no hay nada que destacar, simplemente levantamos el sistema de actores y lanzamos los ejemplos.

4. Lanzando un solo Futuro

Este sería el ejemplo más sencillo de Futuro donde no hay ningún tipo de composición.

class SimpleFuture {

    private final LoggingAdapter log;
    private final ExecutionContext ec;

    SimpleFuture(ActorSystem system) {
        log = Logging.getLogger(system, getClass());
        ec = system.dispatcher();
    }

    void runExample() {
        log.info("*** Init");

        final Future<String> f1 = Futures.future(
                () -> "Hola" + " mundo del futuro!!!",
                ec
        );

        f1.onSuccess(new PrintResult<>(log), ec);

        log.info("*** End");
    }

}

En esté código cabe destacar la sintaxis para la creación de Futuros en la línea 14, donde usamos Futures.future pasando como primer parámetro una lambda que es la operación que queremos ejecutar en paralelo (en el ejemplo una concatenación de cadenas), y como segundo parámetro un ExecutionContext que es el que va a determinar el pool de hilos que se va a utilizar para poder ejecutar el Futuro. Este pool de hilos es habitual usar el propio Dispatcher del Actor, pero podemos usar el que queramos, como por ejemplo crear uno propio para los Futuros o como en este caso usar el del sistema.

También podemos destacar como en la línea 19 creamos un callback mediante f1.onSuccess. Con esto lo que estamos indicando es que cuando termine de ejecutarse el Futuro f1, si ha ido correctamente (es decir si no se ha lanzado ninguna excepción), se va a ejecutar el código que indiquemos aquí. En el ejemplo simplemente vamos a volcar el resultado en el log mediante la clase PrintResult, una clase de utilidad que nos hemos creado para tal efecto.

En todos los ejemplos he añadido bastantes trazas para ver el orden en el que se ejecutan las cosas y así ver de forma más clara cómo se comportan los Futuros y las distintas operaciones de composición. En este caso la salida sería:

[INFO] [04/08/2017 11:06:09.067] [main] [SimpleFuture] *** Init
[INFO] [04/08/2017 11:06:09.112] [main] [SimpleFuture] *** End
[INFO] [04/08/2017 11:06:09.112] [actorSystem-akka.actor.default-dispatcher-4] [SimpleFuture] ---> Hola mundo del futuro!!!

De esta salida podemos ver dos cosas:

  1. Como el código del ejemplo se ejecuta antes que el del Futuro. Lo vemos porque el *** End está antes que la salida del callback. Es decir, el método runExample termina antes que la ejecución del callback que habíamos definido en la línea 19.

  2. Podemos ver como el futuro se ejecuta en un hilo diferente. El método runExample se ejecuta en el hilo [main], mientras que el futuro de la línea 14 se ejecuta en el hilo [actorSystem-akka.actor.default-dispatcher-4].

5. Concatenación de Futuros con ‘map’

Los Futuros de Akka tienen varios métodos que nos permiten usarlos con un estilo más funcional y así combinarlos para conseguir la composición de Futuros. En este ejemplo vamos a ver el uso del método map que nos permite encadenar varios Futuros de forma que la salida de un futuro es la entrada del siguiente.

class MapFutures {

    private final LoggingAdapter log;
    private final ExecutionContext ec;
    private final Business business;

    MapFutures(ActorSystem system) {
        log = Logging.getLogger(system, getClass());
        ec = system.dispatcher();
        business = new Business(log);
    }

    void runExample() {
        log.info("*** Init");

        final Future<String> f1 = Futures.future(business::action1, ec);

        final Future<String> f2 = f1.map(new Mapper<String, String>() {
            @Override
            public String apply(String parameter) {
                return parameter + business.action2();
            }
        }, ec);

        final Future<String> f3 = f2.map(new Mapper<String, String>() {
            @Override
            public String apply(String parameter) {
                return parameter + business.action3();
            }
        }, ec);

        f3.onSuccess(new PrintResult<>(log),ec);

        log.info("*** End");
    }
}

Cabe destacar la línea 18 y 25 donde vemos el uso de map para crear un nuevo Futuro que se ejecutará cuando termine el futuro anterior (en el caso de la línea 18, f2 se empezará a ejecutar cuando termine f1). El método map tiene como parámetro de entrada un objeto de tipo Mapper. Esta clase usa Generics de forma que el primero de ellos representa el tipo del objeto que retorna el primer futuro (en la línea 18 sería el objeto que retorna f1), mientras que el segundo representa el tipo del objeto que retorna el futuro que estamos creando (en la línea 18 sería el objeto que va a retornar f2).

Aunque en el ejemplo simplemente estamos manipulando cadenas para hacer una concatenación y por tanto la entrada y la salida son ambas del mismo tipo String, hay que tener presente que podemos usar los Mappers para manipular los objetos e ir cambiándolos incluso de tipo, lo que lo hace muy conveniente para crear pipelines de manipulación de objetos.

El punto negativo del Mapper es que no es una interfaz funcional de Java, por lo que no podemos usar lambdas, quedando la sintaxis un poco engorrosa.

La salida de este ejemplo sería:

[INFO] [04/09/2017 09:06:20.868] [main] [MapFutures] *** Init
[INFO] [04/09/2017 09:06:20.925] [actorSystem-akka.actor.default-dispatcher-3] [MapFutures] action1 init
[INFO] [04/09/2017 09:06:20.927] [main] [MapFutures] *** End
[INFO] [04/09/2017 09:06:20.929] [actorSystem-akka.actor.default-dispatcher-3] [MapFutures] action1 end
[INFO] [04/09/2017 09:06:20.929] [actorSystem-akka.actor.default-dispatcher-4] [MapFutures] action2 init
[INFO] [04/09/2017 09:06:20.932] [actorSystem-akka.actor.default-dispatcher-4] [MapFutures] action2 end
[INFO] [04/09/2017 09:06:20.932] [actorSystem-akka.actor.default-dispatcher-4] [MapFutures] action3 init
[INFO] [04/09/2017 09:06:20.933] [actorSystem-akka.actor.default-dispatcher-4] [MapFutures] action3 end
[INFO] [04/09/2017 09:06:20.934] [actorSystem-akka.actor.default-dispatcher-4] [MapFutures] ---> Hola mundo del futuro!!!

Donde podemos ver como el hilo [main] es el primero que termina y mientras, en paralelo a este hilo principal, cada futuro se empieza a ejecutar sólo cuando termina el anterior, y el callback se ejecuta al final del todo mostrando como resultado la cadena que hemos ido componiendo a lo largo de los tres futuros f1, f2 y f3.

6. Usando ‘traverse’ para aplicar un Futuro a cada objeto de una colección

En este ejemplo vemos como podemos usar el método Futures.traverse para aplicar distintos futuros a cada uno de los elementos de una colección.

class TraverseFutures {

    private final LoggingAdapter log;
    private final ExecutionContext ec;

    TraverseFutures(ActorSystem system) {
        log = Logging.getLogger(system, getClass());
        ec = system.dispatcher();
    }

    void runExample() {
        log.info("*** Init");

        final List<String> listOfWords = asList("Hola", " mundo", " del futuro!!!");

        final Future<Iterable<String>> futureListOfString = Futures.traverse(
                listOfWords,
                word -> Futures.future(() -> {
                    log.info("init");
                    final String upper = word.toUpperCase();
                    log.info("end");
                    return upper;
                    }, ec),
                ec
        );

        futureListOfString.onSuccess(new PrintResult<>(log), ec);

        log.info("*** End");
    }

}

En la línea 16 podemos ver el uso del método traverse que toma como primer parámetro la colección de objetos, como segundo parámetro una lambda que crea un futuro para cada uno de los elementos de la colección (en el ejemplo para cada word), y como tercer parámetro, como siempre, el ExecutionContext.

Destacamos aquí como en este caso el resultado es un Futuro cuyo resultado será un Iterable<String>. Esto representa una colección con el resultado de haber aplicado el Futuro creado por la lambda a cada uno de los objetos de la colección inicial.

El orden de los objetos del resultado se mantiene en el mismo orden que en la colección inicial y no depende en ningún caso del orden de ejecución de los Futuros o de qué Futuro termina primero.

Así la salida de este ejemplo será:

[INFO] [04/09/2017 09:29:21.272] [main] [TraverseFutures] *** Init
[INFO] [04/09/2017 09:29:21.318] [actorSystem-akka.actor.default-dispatcher-2] [TraverseFutures] init
[INFO] [04/09/2017 09:29:21.319] [actorSystem-akka.actor.default-dispatcher-2] [TraverseFutures] end
[INFO] [04/09/2017 09:29:21.319] [actorSystem-akka.actor.default-dispatcher-2] [TraverseFutures] init
[INFO] [04/09/2017 09:29:21.319] [actorSystem-akka.actor.default-dispatcher-3] [TraverseFutures] init
[INFO] [04/09/2017 09:29:21.319] [actorSystem-akka.actor.default-dispatcher-3] [TraverseFutures] end
[INFO] [04/09/2017 09:29:21.319] [actorSystem-akka.actor.default-dispatcher-2] [TraverseFutures] end
[INFO] [04/09/2017 09:29:21.320] [main] [TraverseFutures] *** End
[INFO] [04/09/2017 09:29:21.320] [actorSystem-akka.actor.default-dispatcher-3] [TraverseFutures] ---> [HOLA,  MUNDO,  DEL FUTURO!!!]

Donde se aprecia como los futuros que se aplican a cada objeto de la colección de entrada se ejecutan en paralelo (líneas 4 y 5) y como el resultado es una colección que contiene cada una de las palabras de la entrada convertidas a mayúsculas.

7. Composición de un Futuro con una colección de otros Futuros gracias a ‘sequence’

En esta ocasión como entrada vamos a tener una colección de Futuros, y cuando todos ellos hayan terminado se usará otro Futuro para iterar sobre los resultados de cada uno de esos Futuros y así componer un nuevo resultado.

class SequenceFutures {

    private final LoggingAdapter log;
    private final ExecutionContext ec;
    private final Business business;

    SequenceFutures(ActorSystem system) {
        log = Logging.getLogger(system, getClass());
        ec = system.dispatcher();
        business = new Business(log);
    }

    void runExample() {
        log.info("*** Init");

        final Future<String> f1 = Futures.future(business::action1, ec);
        final Future<String> f2 = Futures.future(business::action2, ec);
        final Future<String> f3 = Futures.future(business::action3, ec);

        final Future<Iterable<String>> futureListOfString = Futures.sequence(asList(f1, f2, f3), ec);

        final Future<String> futureComposedString = futureListOfString.map(new Mapper<Iterable<String>, String>() {
            @Override
            public String apply(Iterable<String> futuresResults) {
                final StringBuilder builder = new StringBuilder();
                for (String s : futuresResults) {
                    builder.append(s);
                }
                return builder.toString();
            }
        }, ec);

        futureComposedString.onSuccess(new PrintResult<>(log), ec);

        log.info("*** End");
    }

}

En las líneas de la 16 a la 18 vemos como creamos tres futuros diferentes. En ese mismo momento estos tres futuros ya se empiezan a ejecutar en paralelo.

En la línea 20 usamos el método Futures.sequence para crear un nuevo futuro cuyo resultado es un Futuro de Iterable<String>, es decir, un Futuro cuyo resultado es una colección con los resultados de los futuros f1, f2 y f3, en el mismo orden en el que se han añadido al método Futures.sequence.

En la línea 22 usamos el método map para procesar el resultado del Futuro de la línea 20. Este método map es el que ya hemos visto antes. Así que igual que hacíamos antes, definimos un Mapper para procesar la entrada (un Iterable<String>) y producir un resultado (un String con la concatenación de todas las cadenas de la entrada).

Puede parecer un poco complicado pero para mí es uno de los ejemplos más útiles, ya que vemos como podemos lanzar varios Futuros que se ejecutan en paralelo y luego gracias a sequence podemos “sincronizar” estas ejecuciones en el sentido de que se llamará al Mapper una vez todos estos Futuros hayan terminado sus ejecuciones, y así poder operar sobre sus respectivos resultados.

Además en el ejemplo estamos trabajando con String pero ¿qué pasaría si trabajamos con Object? Pues que esto nos permitiría que los resultados de los Futuros fueran de cualquier tipo y al recuperarlos en el Mapper bastaría con hacerles un casting para convertirlos a su tipo específico. Esto sería posible porque, igual que con traverse, tenemos garantía de que se mantiene el orden de los resultados con respecto al orden en el que hemos añadido los futuros en el método sequence.

La salida de este ejemplo sería:

[INFO] [04/09/2017 10:15:35.184] [main] [SequenceFutures] *** Init
[INFO] [04/09/2017 10:15:35.229] [actorSystem-akka.actor.default-dispatcher-2] [SequenceFutures] action1 init
[INFO] [04/09/2017 10:15:35.229] [actorSystem-akka.actor.default-dispatcher-3] [SequenceFutures] action2 init
[INFO] [04/09/2017 10:15:35.229] [actorSystem-akka.actor.default-dispatcher-4] [SequenceFutures] action3 init
[INFO] [04/09/2017 10:15:35.231] [actorSystem-akka.actor.default-dispatcher-4] [SequenceFutures] action3 end
[INFO] [04/09/2017 10:15:35.232] [actorSystem-akka.actor.default-dispatcher-3] [SequenceFutures] action2 end
[INFO] [04/09/2017 10:15:35.232] [actorSystem-akka.actor.default-dispatcher-2] [SequenceFutures] action1 end
[INFO] [04/09/2017 10:15:35.235] [main] [SequenceFutures] *** End
[INFO] [04/09/2017 10:15:35.236] [actorSystem-akka.actor.default-dispatcher-2] [SequenceFutures] ---> Hola mundo del futuro!!!

Donde vemos como los tres Futuros se están ejecutando en paralelo.

8. Cómo aplicar una operación concreta a los resultados de una secuencia de Futuros con ‘fold’

En este ejemplo vamos a crear, igual que antes, un conjunto de futuros que se ejecutarán en paralelo. Luego gracias al método Futures.fold crearemos un nuevo futuro donde definiremos una operación que se va a realizar sobre un elemento inicial y luego sobre cada uno de los resultados de los Futuros anteriores.

class FoldFutures {

    private final LoggingAdapter log;
    private final ExecutionContext ec;
    private final Business business;

    FoldFutures(ActorSystem system) {
        log = Logging.getLogger(system, getClass());
        ec = system.dispatcher();
        business = new Business(log);
    }

    void runExample() {
        log.info("*** Init");

        final Future<String> f1 = Futures.future(business::action1, ec);
        final Future<String> f2 = Futures.future(business::action2, ec);
        final Future<String> f3 = Futures.future(business::action3, ec);

        final Future<String> futureString = Futures.fold(
                "",
                asList(f1, f2, f3),
                (previousResult, param) -> previousResult + param,
                ec
        );

        futureString.onSuccess(new PrintResult<>(log), ec);

        log.info("*** End");
    }

}

Vemos como en la línea 20 creamos el Futuro con el método Futures.fold donde el primer parámetro representa el elemento inicial (en el ejemplo la cadena vacía), el segundo parámetro es la colección de Futuros, el tercer parámetro es la lambda con la operación que se va ejecutar sobre cada elemento (en el ejemplo la concatenación del resultado anterior con el resultado de uno de los Futuros), y como siempre el último parámetro el ExecutionContext.

Destacamos como en la lambda tenemos a nuestra disposición dos parámetros, el primero previousResult es el resultado de haber aplicado la lambda anteriormente, y param es el resultado del Futuro que corresponda en cada momento. De esta forma vamos componiendo el resultado final a base de aplicar la lambda al elemento inicial y luego a cada resultado de forma consecutiva.

Si nos damos cuenta este método es prácticamente el mismo que Futures.sequence. La diferencia es que en sequence nosotros teníamos que iterar sobre la colección con los resultados, dándonos esto mucho control sobre lo que estamos haciendo y a la vez “afeando” un poco el código; mientras que con fold nosotros no tenemos control sobre cómo se itera sobre los resultados de los Futuros, y la lambda será llamada, como siempre, en el orden en el que hemos añadido los Futuros en el método fold.

La salida de este ejemplo sería:

[INFO] [04/09/2017 10:38:05.044] [main] [FoldFutures] *** Init
[INFO] [04/09/2017 10:38:05.101] [actorSystem-akka.actor.default-dispatcher-2] [FoldFutures] action1 init
[INFO] [04/09/2017 10:38:05.102] [actorSystem-akka.actor.default-dispatcher-3] [FoldFutures] action2 init
[INFO] [04/09/2017 10:38:05.103] [actorSystem-akka.actor.default-dispatcher-4] [FoldFutures] action3 init
[INFO] [04/09/2017 10:38:05.104] [actorSystem-akka.actor.default-dispatcher-4] [FoldFutures] action3 end
[INFO] [04/09/2017 10:38:05.105] [actorSystem-akka.actor.default-dispatcher-2] [FoldFutures] action1 end
[INFO] [04/09/2017 10:38:05.105] [actorSystem-akka.actor.default-dispatcher-3] [FoldFutures] action2 end
[INFO] [04/09/2017 10:38:05.113] [main] [FoldFutures] *** End
[INFO] [04/09/2017 10:38:05.113] [actorSystem-akka.actor.default-dispatcher-5] [FoldFutures] ---> Hola mundo del futuro!!!

Donde, igual que en el ejemplo anterior, vemos como todos los Futuros se ejecutan en paralelo.

9. ‘reduce’ es un ‘fold’ sin elemento inicial

Como dice el propio título, este ejemplo es exactamente igual que el anterior salvo que con el método Futures.reduce no tenemos elemento inicial (en el ejemplo anterior era la cadena vacía). De tal forma que la lambda se ejecuta directamente sobre el resultado del primer futuro, y luego en secuencia exactamente igual que con fold.

class ReduceFutures {

    private final LoggingAdapter log;
    private final ExecutionContext ec;
    private final Business business;

    ReduceFutures(ActorSystem system) {
        log = Logging.getLogger(system, getClass());
        ec = system.dispatcher();
        business = new Business(log);
    }

    void runExample() {
        log.info("*** Init");

        final Future<String> f1 = Futures.future(business::action1, ec);
        final Future<String> f2 = Futures.future(business::action2, ec);
        final Future<String> f3 = Futures.future(business::action3, ec);

        final Future<String> futureString = Futures.reduce(
                asList(f1, f2, f3),
                (previousResult, param) -> previousResult + param,
                ec
        );

        futureString.onSuccess(new PrintResult<>(log), ec);

        log.info("*** End");
    }

}

La salida sería:

[INFO] [04/09/2017 10:41:52.703] [main] [ReduceFutures] *** Init
[INFO] [04/09/2017 10:41:52.747] [actorSystem-akka.actor.default-dispatcher-2] [ReduceFutures] action1 init
[INFO] [04/09/2017 10:41:52.747] [actorSystem-akka.actor.default-dispatcher-4] [ReduceFutures] action2 init
[INFO] [04/09/2017 10:41:52.748] [actorSystem-akka.actor.default-dispatcher-3] [ReduceFutures] action3 init
[INFO] [04/09/2017 10:41:52.749] [actorSystem-akka.actor.default-dispatcher-3] [ReduceFutures] action3 end
[INFO] [04/09/2017 10:41:52.750] [actorSystem-akka.actor.default-dispatcher-4] [ReduceFutures] action2 end
[INFO] [04/09/2017 10:41:52.750] [actorSystem-akka.actor.default-dispatcher-2] [ReduceFutures] action1 end
[INFO] [04/09/2017 10:41:52.757] [main] [ReduceFutures] *** End
[INFO] [04/09/2017 10:41:52.757] [actorSystem-akka.actor.default-dispatcher-5] [ReduceFutures] ---> Hola mundo del futuro!!!

Donde vemos, igual que antes, como los Futuros se ejecutan en paralelo.

10. Conclusiones

Sé que el tema de la concurrencia, el multi-hilo, y en este caso concreto los Futuros, no es sencillo; por eso es importante que hagamos pequeños programas de ejemplo, tests, dejemos trazas e incluso hagamos sesiones de depuración, todo en un entorno de laboratorio controlado, para así coger soltura con las distintas construcciones y mecanismos que, en este caso Akka, pone a nuestro alcance. Sólo así conseguiremos sacarle el máximo provecho y podremos aprovechar toda esa potencia en entornos de producción.

Una vez cojáis esa soltura con los distintos métodos para la composición de Futuros veréis que se pueden llegar a hacer cosas realmente interesantes.

Y ya sabéis: Prohibidos Monos & Lagartos, que seamos nosotros los que controlamos a la “máquina” y no al contrario.

11. 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”

Viewing all 989 articles
Browse latest View live