Weblogs Código

Blog Bitix

Copiar datos de un tipo de objeto a otro con ModdelMapper

mayo 31, 2020 08:00

Las clases DTO son clases usadas como contenedores de datos sin ninguna lógica o con muy poca, se construyen con datos copiados de otras clases. Un uso de estas clases DTO es para evitar emplear el uso del patrón Open Session in View ya que aunque ofrece algunos beneficios también tiene algunos inconvenientes. La librería ModelMapper permite realizar los copiados de datos de un objeto origen a una nueva instancia destino de otra clase.

Java

En ocasiones es necesario copiar datos de un tipo de objeto a otro tipo, no es una operación complicada basta con llamar al método getter de la propiedad a copiar para obtener su valor del objeto origen y posteriormente llamar al setter para establecer el valor en objeto destino. Aún siendo una operación sencilla es tediosa y puede complicarse si se han de copiar listas de objetos y si esos objetos a copiar tienen referencias a otros objetos que también hay que copiar. Si además esta es una operación común en el código es conveniente utilizar una librería específica para este propósito, una de ellas es ModelMapper.

ModelMapper es una librería Java para copiar o mapear propiedades de un tipo de objeto a otro tipo de objeto, permitiendo copiar también los datos de las referencias a los objetos que contengan. Soporta diferentes convenciones, copiados explícitos, conversiones y proveedores para construir los objetos destino e integraciones con diferentes librerías, una de ellas jOOQ.

Un posible caso de uso es para evitar emplear el patrón Open Session in View ya que tiene varios inconvenientes. Con una librería como ModelMapper es posible hacer uso de simples objetos contenedores de datos en la vista copiando los datos de las entidades a los objetos DTO. O si para obtener los datos de la vista en vez de usar una librería como Hibernate se opta por una librería como jOOQ permitir copiar los datos de los registros de jOOQ a los mismos DTOs.

El siguiente ejemplo se compone de tres clases que tienen relaciones entre ellas, estas clases podrían ser las entidades si se persistiesen en base de datos con Hibernate.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package io.github.picodotdev.blogbitix.modelmapper.classes;

public class Order {

    private Customer customer;
    private Address billingAddress;

    public Order(Customer customer, Address billingAddress) {
        this.customer = customer;
        this.billingAddress = billingAddress;
    }

    public Customer getCustomer() {
        return customer;
    }

    public Address getBillingAddress() {
        return billingAddress;
    }
}
Order.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package io.github.picodotdev.blogbitix.modelmapper.classes;

public class Customer {

    private String firstName;
    private String lastName;

    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }
}
Customer.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package io.github.picodotdev.blogbitix.modelmapper.classes;

public class Address {

    private String street;
    private String city;

    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    public String getCity() {
        return city;
    }
}
Address.java

La clase DTO es simplemente una nueva clase POJO que contiene los datos de las clases anteriores, para evitar el patrón Open Session in View la vista recibiría una instancia de esta clase.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package io.github.picodotdev.blogbitix.modelmapper.classes;

public class OrderDTO {

    private String customerFirstName;
    private String customerLastName;
    private String billingAddressStreet;
    private String billingAddressCity;

    public String getCustomerFirstName() {
        return customerFirstName;
    }

    public void setCustomerFirstName(String customerFirstName) {
        this.customerFirstName = customerFirstName;
    }

    public String getCustomerLastName() {
        return customerLastName;
    }

    public void setCustomerLastName(String customerLastName) {
        this.customerLastName = customerLastName;
    }

    public String getBillingAddressStreet() {
        return billingAddressStreet;
    }

    public void setBillingAddressStreet(String billingAddressStreet) {
        this.billingAddressStreet = billingAddressStreet;
    }

    public String getBillingAddressCity() {
        return billingAddressCity;
    }

    public void setBillingAddressCity(String billingAddressCity) {
        this.billingAddressCity = billingAddressCity;
    }
}
OrderDTO.java

En esta aplicación de Spring Boot se construye una instancia de la clase ModelMapper y posteriormente con su configuración y convenciones por defecto realiza el copiado de datos de una instancia de la clase Order a una nueva instancia de la clase OrderDTO. En la salida del programa en la consola se muestran los valores de las propiedades de OrderDTO copiadas de la clase Order.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package io.github.picodotdev.blogbitix.modelmapper;

import io.github.picodotdev.blogbitix.modelmapper.classes.Address;
import io.github.picodotdev.blogbitix.modelmapper.classes.Customer;
import io.github.picodotdev.blogbitix.modelmapper.classes.Order;
import io.github.picodotdev.blogbitix.modelmapper.classes.OrderDTO;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Main implements CommandLineRunner {

	@Autowired
	private ModelMapper modelMapper;

	@Bean
	ModelMapper modelMapper() {
		return new ModelMapper();
	}

	@Override
	public void run(String... args) throws Exception {
		Customer customer = new Customer("Francisco", "Ibáñez");
		Address billigAddress = new Address("c\\ Rue del Percebe, 13", "Madrid");
		Order order = new Order(customer, billigAddress);

		OrderDTO orderDTO = modelMapper.map(order, OrderDTO.class);

		System.out.printf("Customer First Name: %s%n", orderDTO.getCustomerFirstName());
		System.out.printf("Customer Last Name: %s%n", orderDTO.getCustomerLastName());
		System.out.printf("Billing Address Street: %s%n", orderDTO.getBillingAddressStreet());
		System.out.printf("Billing Address City: %s%n", orderDTO.getBillingAddressCity());
	}

	public static void main(String[] args) {
		SpringApplication.run(Main.class, args);
	}
}
Main.java
1
2
3
4
Customer First Name: Francisco
Customer Last Name: Ibáñez
Billing Address Street: c\ Rue del Percebe, 13
Billing Address City: Madrid
System.out

El archivo de contrucción Gradle contiene la dependencia de ModelMapper.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
plugins {
    id 'java'
    id 'application'
 }

group = 'io.github.picodotdev.blogbitix.modelmapper'
version = '1.0'

java {
    sourceCompatibility = JavaVersion.VERSION_11
}

application {
    mainClass = 'io.github.picodotdev.blogbitix.modelmapper.Main'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation platform('org.springframework.boot:spring-boot-dependencies:2.3.0.RELEASE')

    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.modelmapper:modelmapper:2.3.7'
}
build.gradle
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run

» Leer más, comentarios, etc...

Bitácora de Javier Gutiérrez Chamorro (Guti)

Depurar aplicaciones DOS con Watcom C y OpenWatcom C++

mayo 31, 2020 07:25



Quizás por su dilatada historia, tal vez por ir en contra de los grandes, o por su elevado rendimiento, o porque era verdaderamente difícil de conseguir, Watcom C fue el entorno de desarrollo más deseado para mi. Uno de sus encantos es la posibilidad de realizar compilación cruzada o cross compile, es decir, que desde …

Depurar aplicaciones DOS con Watcom C y OpenWatcom C++ Leer más »



Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

» Leer más, comentarios, etc...

Blog Bitix

El patrón Open Session in View, qué es, ventajas, problemas y alternativas

mayo 29, 2020 08:30

En patrón Open Session in View mantiene abierta durante toda la petición a un servidor la conexión a la base de datos. Esto tiene la ventaja de que en cualquier momento es posible recuperar datos de la base de datos, incluso desde las vistas pero tiene inconvenientes ya que las conexiones a la base de datos son un recurso escaso. Si además durante la petición se hacen peticiones a otros servicios que añaden tiempo de procesamiento la aplicación es posible que tenga problemas de escalabilidad con muchos usuarios y peticiones durante un corto periodo de tiempo.

Java

La librería Hibernate proporciona persistencia del modelo de objetos del lenguaje Java al modelo de las bases de datos relacionales de una forma sin que el programador necesite lanzar las consultas SQL directamente por lo general. Con Hibernate los objetos de las relaciones se cargan de los datos de las filas de la base de datos cuando son solicitadas si la relación es lazy o al obtener el objeto en el modo eager.

El modo lazy tiene la ventaja de que los datos de las relaciones solo se cargan si se necesitan pero tiene el inconveniente de producir más SQLs a la base de datos. El modelo eager carga los datos con menos SQLs pero carga más datos de los necesarios si no se necesitan.

Para que el modo lazy funcione se ha de mantener la conexión a la base de datos abierta para cargar los datos cuando se soliciten. Mantener la sesión y conexión de base de datos abierta es lo que define el patrón Open Session in View. Sin embargo, mantener la conexión abierta durante toda la petición incluida la parte de generación de la vista tiene inconvenientes, incluso llegando a considerar el patrón Open Session in View un antipatrón que no se de debe usar.

Qué es y como funciona

En este diagrama se aprecia su funcionamiento. La primera acción en una petición es abrir una sesión para obtener datos de la base de datos, lo que se traduce en apropiarse de una conexión a la base de datos. El flujo del programa procesa la petición invocando la lógica de la aplicación y empleando los diferentes servicios en las diferentes capas formadas por el controlador, servicio y DAO para el acceso a la base de datos. El último paso es generar el resultado que es devuelto al cliente, puede ser contenido HTML o un resultado en formato JSON si es un servicio REST. En este punto se accede de nuevo a la base de datos para recuperar las relaciones lazy de los objetos que fueron devueltas por el servicio, esto es habitual en el caso de emplear un ORM como Hibernate o JPA.

Diagrama del patrón open session in view

Diagrama del patrón open session in view Fuente: vladmihalcea.com

Las ventajas

Con el patrón Open Session in View durante toda la petición se mantiene la conexión a la base de datos abierta de modo que al solicitar las relaciones de una entidad las excepciones LazyInitializationException de Hibernate no se producen en las relaciones cargadas en modo lazy. Sin mantener la conexión abierta todos los datos que se necesiten han de cargarse con antelación de lo contrario al acceder a las relaciones de un objeto provocará esa excepción LazyInitializationException. El modo lazy permite solicitar los datos según se necesiten sin necesidad de hacerlo con antelación.

En Spring hay una variable de configuración con la que se aciva o desactiva un filtro que implement el patrón Open Session in View.

1
spring.jpa.open-in-view=false
SpringJpaOpenSessionInView.properties

Los problemas, por que se considera una mala práctica

El patrón Open Session in View tiene varios problemas. Uno de ellos es que al mantener la sesión abierta durante toda la petición y permitir en todo momento acceso a la base de datos no se es consciente de las consultas que se lanzan más usando Hibernate que hace precisamente esto más fácil. El resultado es que hay que tener especial cuidado en no generar el problema 1+N donde se ejecuta una consulta para recuperar una lista de objetos y N para cargar una relación de cada uno de los objetos de la lista anteriores recuperados.

Estos problemas tienen solución en cierta medida con la anotación @BatchSize para recuperar listas de objetos en lotes y FetchMode.SUBSELECT para lanzar una consulta adicional que recupere los objetos de las relaciones. Su inconveniente es que es poco flexible ya que su uso con anotaciones afectan a todas las consultas.

El segundo problema es que la vista es capaz de generar consultas a la base de datos las cuales pueden producir excepciones y las vistas no suelen estar preparadas para manejar excepciones.

Además, las conexiones a la base de datos son un recurso escaso, más incluso que los threads de modo que mantener abierta la conexión durante más tiempo limita la escalabilidad de una aplicación.

Establecer las consultas en modo FetchType.EAGER para recuperar las relaciones cuanto antes aún no conociendo si se usarán los datos no es una solución ya que tampoco puede cambiarse a nivel de consulta. Por estas razones las asociaciones suelen configurarse en modo FetchType.LAZY.

La alternativa

La alternativa al patrón Open Session in View es usar objetos DTO para proporcionar a la vista todos los datos que necesite sin que esta al usar esos datos lance consultas. Esto obliga al controlador del patrón modelo-vista-controlador o MVC a conocer y recuperar de antemano los datos que necesite la vista.

Hibernate es una gran librería por la funcionalidad que ofrece al abstraer el modelo relacional del modelo orientado a objetos del lenguaje relacionar, tanto que es capaz de lanzar las consultas adecuadas a la base de datos relacional tanto en la lectura como en la escritura según las operaciones realizadas en los objetos.

Por otro lado Hibernate en la correspondencia que hace entre el modelo relacional y las entidades de objetos se cargan todos los datos de la entidad aunque muchos no se necesiten en la vista, lo que lo hace algo ineficiente en el acceso de lectura a la base de datos.

En el modelo DTO usando la lógica que recupera los datos ha de estar sincronizada con la lógica de la vista. Por ejemplo, si un dato en la vista solo es necesario dada cierta condición esa misma condición ha de estar en el código que del la vista, o en la vista ser suficiente la presencia del dato para mostrarlo.

Cada vista necesitará unos datos específicos de modo que serán necesarias consultas específicas para recuperar cada uno de los datos. Para el acceso en modo lectura y recuperar algunas de las consultas en vez de usar Hibernate se puede usar la librería jOOQ que proporciona una API en el lenguaje Java para la construcciones de consultas con comprobación de tipos proporcionado por el compilador.

Conclusión

En muchas aplicaciones usar el patrón Open Session in View con Hibernate no supone un gran problema y simplifica el código. Para aquellas aplicaciones que necesitan escalabilidad y soportar un gran número de usuarios concurrentes o hagan operaciones que impliquen operaciones de red se aconseja usar DTO en las vistas ya sean mapeando las entidades Hibernate recuperadas por el controlador a esos DTO con una librería específica para el propósito como ModdelMapper y recuperar únicamente los datos que necesita la vista usando librerías como jOOQ que ofrecen mayor control sobre las columnas de la base de datos datos recuperadas para reducir los datos recuperados de la base de datos a únicamente lo necesario.

» Leer más, comentarios, etc...

Picando Código

Se acerca RuboCop 1.0

mayo 27, 2020 03:00

RuboCop es la herramienta de facto para analizar y formatear código en Ruby. Con miras de publicar la versión 1.0, Bozhidar Batsov -autor de Rubocop-, realizó una encuesta a la comunidad Ruby. RuboCop viene con un montón de valores por defecto, y la idea de la encuesta era medir lo que se viene usando para entender qué valores por defecto valía la pena cambiar basados en su uso y la frustración o satisfacción de la comunidad.

En los últimos años se había evitado cambiar valores por defecto en un esfuerzo por contener la fricción durante actualizaciones de RuboCop. Pero con la publicación de una versión 1.0, llega la oportunidad de revisar esos valores. Con 722 respuestas, un número que hasta el autor admite es bastante decepcionante dada la cantidad de usuarios de la herramienta, finalmente se publicaron los resultados y cómo afectaría a RuboCop 1.0.

Algunos de los resultados destacados:

Comillas simples vs comillas dobles en Strings literales

Personalmente uso comillas simples, y probablemente sea por costumbre de usar RuboCop y que me señala que tengo usar comillas simples cuando no estoy interpolando valores en un String. Hace unos años me acuerdo haber estudiado con Daniel Chains el tema respecto a la performance de simples contra dobles y vimos que no cambiaba nada. Pero como dice el autor del post original, lo único que importa es usar una o la otra de forma consistente. El resultado:

  • Comillas simples – 58%
  • Comillas dobles – 39%
  • Otro (por ejemplo “que no les importa”) – 3%

Así que es algo que no va a cambiar por defecto, por más que sea uno de los temas en los cuales la comunidad parece ser más quejosa.

Longitud máxima de caracteres por línea

Acá estoy con la mayoría también. Si bien se puede configurar un Rubocop específico por proyecto, el valor por defecto de 80 caracteres me resultado demasiado corto.

  • 120 caracteres – 48%
  • 100 caracteres – 21%
  • 80 caracteres – 18%
  • Otro (ejemplo 150, sin límite) – 13%

A pesar de cambiar el valor por defecto de RuboCop para líneas más largas, se agregó una nota en la Ruby Style Guide de por qué sigue siendo buena idea mantener líneas cortas.

Comas finales en colecciones

# sin comas:
{ uno: 1, dos: 2 }

# con comas:
{ uno: 1, dos: 2, }

El valor por defecto de no exigir comas al final ganó en los resultados, por lo que se mantiene igual:

  • No uso comas al final de la colección – 64%
  • Uso comas al final sólo en colecciones multi-línea- 27%
  • Uso comas al final en todos los casos- 1,5%
  • Otro – 7,5%

and y or

“La pregunta si and y or son útiles o deberían ser evitados completamente ha cautivado por mucho tiempo a la comunidad Ruby”. Acá también estoy con la mayoría, los usuarios de RuboCop dijeron:

  • No los uso para nada – 68%
  • Los uso todo el tiempo – 4%
  • Los uso para control de flujo (ejemplo do_something and return) – 29%

A pesar de los resultados, decidieron ser más permisivos y permitirlos para control de flujo, más que nada porque es un estilo bastante común en Ruby On Rails.

Doble Negación (!!)

Resultados:

  • Solamente lo uso cuando necesito devolver un booleano – 53%
  • No lo uso para nada – 39%
  • Otro – 8%

Así que la doble negación va a estar permitida en el contexto de return.

Espacios dentro de los hash

# con espacios
{ hulk: "Bruce Banner" }

# sin espacios
{hulk: "Bruce Banner"}

En esta también sigo a la mayoría, 71% votaron que usan los espacios y 29% que no los usan.

Paréntesis para argumentos de métodos de Kernel (puts, system, exit)

# sin paréntesis
puts "Hola, mundo!"
exit -1

# con paréntesis
puts("Hola, mundo!")
exit(-1)

El 80% respondió que no usa paréntesis para estos métodos  y un 17% que sí usa paréntesis.

¿Encuentras los cops de métricas (como CyclomaticComplexity) útiles?

  • Un poco útiles – 43%
  • Muy útiles – 29%
  • Inútiles – 19%
  • Otro – 9%

Los autores lo tomaron como un área donde se puede mejorar.

Nivel general de felicidad con los valores por defecto de RuboCop

  • Muy feliz – 15%
  • Feliz – 47%
  • OK – 23%
  • No feliz – 8%
  • Muy infeliz – 5%

Los resultados se ven como “no geniales, pero tampoco terribles”. Personalmente lo veo bastante bien, más de la mitad de los usuarios está al menos feliz. Con la versión 0.84 de RuboCop se hicieron cambios buscando mejorar estos resultados, y se van a venir más cambios

Feedback General

Al final de la encuesta había una sección abierta para más comentarios. Las respuestas resumidas:

  • El valor de varios cops es subjetivo y estaría bueno que RuboCop tuviera algún preset con cops realmente esenciales (como cops para Linting y los que se corresponden con las convenciones e idiomas más fuertes).
  • El valor de los cops de métricas es cuestionable. Hubo muchas sugerencias para relajar ciertos valores por defecto.
  • Mucha gente parece odiar los frozen string literals.

El equipo planea trabajar en el primer punto y proveer un set más chico de cops esenciales, junto al actual set de cops por defecto en el futuro.

Una nota aparte de Bozhidar Batsov, cuando vio mucha gente quejarse de los cops de métricas, agregó una pregunta extra para saber si la gente sabía que pueden deshabilitar departamentos enteros de cops a través de .rubocop.yml. El 40% de los usuarios no lo sabía, y se puede hacer con:

Metrics:
  Enabled: true

También si no me equivoco, podemos empezar un archivo rubocop.yml deshabilitando TODOS los cops, de manera de ir definiendo de a uno aquellos que nos interesan en el proyecto.

El trabajo con esta encuesta y los cambios que ya vimos en RuboCop 0.84 muestran que el equipo de desarrollo está escuchando a la comunidad y ajustándose a las preferencias de la mayoría. La versión 1.0 está cerca, y avisan que es poco probable que haya más cambios de último momento, pero siempre existirá una versión 2.0 y 3.0.

RuboCop es una excelente herramienta y creo que gran parte de la negatividad que se le atribuye viene de no definir conenciones a nivel de un equipo o quedarse con las convenciones por defecto que no se adaptan necesariamente a las preferencias de un grupo de usuarios. Pero es bastante fácil de personalizar, y lo importante al final del día es ser consistente, más allá de si usamos ' o " para definir Strings.

Pueden leer más en RuboCop Defaults Survey Results.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 405

mayo 27, 2020 07:01

Enlaces interesantesEl código de estado HTTP 405 es utilizado por los servidores para indicar a los clientes que el recurso solicitado existe, pero el método empleado para tener acceso al mismo no es correcto. Esto podemos verlo si, por ejemplo, hacemos una petición de tipo POST a un endpoint que sólo soporta PUT, o cualquier otra combinación incorrecta.

Además del error 405, como parte de la respuesta, el servidor debe añadir un encabezado Allow con una lista de métodos soportados:
GET http://192.168.1.33:55678/api/example HTTP/1.1

HTTP/1.1 405 Method Not Allowed
Allow: POST, PUT
Y ahora, ahí van los enlaces recopilados durante la semana pasada que, como no podía ser de otra forma, viene cargado de novedades presentadas en el Build 2020. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Picando Código

Cities: Skylines y DLCs a partir de USD 1 en el Humble Cities: Skylines Bundle

mayo 26, 2020 09:51

Hace un tiempo escribí sobre Cities: Skylines, el juego simulador de ciudades que estábamos esperando. Si todavía no lo han probado, pueden adquirir el juego original junto al DLC Deep Focus Radio por apenas 1 unidad de dinero (1 dólar si están en América, 1 libra si están en el Reino Unido, 1 euro si están en Europa, etc). El juego y sus DLCs están disponibles en Steam para GNU/Linux, Mac y Windows.


Humble Cities: Skylines Bundle

Pagando el mínimo obtenemos claves Steam para el juego original y el DLC Deep Focus Radio. Pagando más del promedio (variable) el paquete incluye estos DLCs:

  • Cities: Skylines – Concerts: mini-expansión para planear y presentar conciertos.
  • Cities: Skylines – Content Creator Pack: High-Tech Buildings: paquete de 15 edificios arqui-tecnológicos.
  • Cities: Skylines – Snowfall: mod para agregar nieve, lluvia y niebla al sistema de clima del juego con los desafíos que conlleva.
  • Cities: Skylines – Natural Disasters: Éste suena bastante interesante, es un catálogo de catástrofes naturales para un desafío mayor, además de sistemas de advertencia, rutas de emergencia, y demás. También incluye un editor de escenarios. Los desastres naturales incluyen terremotos, tormentas eléctricas, tsunamis, incendios forestales, tornados, pozos y meteoros. No menciona nada de Kaijus, pero no pierdo las esperanzas…

Pagando USD 25, £ 14.50 o € 16.50, obtenemos los siguientes DLC:

  • Cities: Skylines – Mass Transit: expansión con varios sistemas de tránsito para el agua, montañas y el aire, ferris, monorieles, dirigibles y más.
  • Cities: Skylines – Green Cities: expansión para construir ciudades verdes con edificios ecológicos, tiendas orgánicas, coches eléctricos y demás.
  • Cities: Skylines – Industries: expansión para las zonas industriales con más recursos, servicios y fábricas.
  • Cities: Skylines – Campus: expansión con nuevos tipos de áreas para estudiantes.
  • Cities: Skylines – Content Creator Pack: Art Deco: expansión con edificios nuevos: 6 residenciales, 6 comerciales y 3 únicos. Los ingresos de este paquete se comparten con el usuario que creó los mods!
  • Cities: Skylines – Content Creator Pack: European Suburbia: Otro pack de contenidos con edificios residenciales con estilo europeo.

Además de elegir cuánto pagar, parte de lo recaudado se divide entre charity: water y otra beneficencia que elijamos. Al pagar podemos elegir cuánto del dinero va para Paradox Interactive (los desarrolladores del juego), cuánto a beneficencia y cuánto a Humble Bundle. ¡No se lo pierdan! La oferta dura sólo 2 semanas. Creo que me voy a construir una ciudad nueva en Cities: Skyline…

Humble Cities: Skylines Bundle

» Leer más, comentarios, etc...

proyectos Ágiles

Master en Agile – MMA 2020

mayo 25, 2020 05:28

En octubre de 2020 se iniciará el Barcelona la 7ª edición del Postgrado en Métodos Ágiles (PMA) y otra del Máster en Transformación Agile (MMA) en La Salle (Universitat Ramon Llull), el primero a nivel mundial sobre Agile.

MMA-banner

El Máster incluye el Certified Scrum Master (CSM) de la Scrum Alliance y la certificación Kanban Systems Design de la Lean Kanban University.

SAI_BadgeSizes_DigitalBadging_CSM

Certified Kanban Training

Esta es una oportunidad única para aprender de profesionales de primer nivel, con varios años de experiencia específica en Agile, aplicando principios y métodos ágiles en contextos diversos, especializándose en aspectos concretos,  investigando sobre nuevas técnicas y ponentes en conferencias nacionales e incluso internacionales.

PMA – Postgrado en métodos Ágiles

El PMA incluye el Certified Scrum Master (CSM) de la Scrum Alliance.

Asignaturas Temas Profesores
Fundamentos & Inception

Principios y métodos más conocidos (Scrum, Lean, Kanban y XP). Facilitadores e impedimentos. Inception y conceptualización ágil de proyecto, priorización ágil, historias de usuario,  elaboración de Product Backlog, técnicas de priorización.

Silvia Sistaré
Agustín Yagüe
Scrum y Kanban Estimación y planificación ágil, framework de Scrum, retrospectivas, Kanban, métricas ágiles, herramientas ágiles físicas, radiadores de información. Tiago Garcez
Teodora Bozheva
Personas y equipos Gestión de personas, gestión de conflictos, motivación e incentivos, facilitación compartida, contratación ágil, visual thinking. Steven Wallace

 

Manuel Lopez

Silvia Sistaré

Virginia Armas

Gestión de producto ágil

Design Thinking.

Lean Startup & Agile Product Management

Ángel Díaz-Maroto

Gabriel Prat

Ingeniería ágil 

User eXperience y prototipado en Agile.

ALM ágil, eXtreme Programing, Software Craftsmanship, testing ágil.

BDD y TDD. Desarrollo guiado por pruebas (de aceptación y unitarias).

Cómo trabajar con código heredado y reducir la deuda técnica.

Marc Pifarré

 

Álvaro García

Pablo Gómez

 

Rubén Bernárdez

Trabajo Final de Postgrado Durante el Postgrado se elaborará un caso práctico de introducción de los contenidos en un equipo ágil. Para ellos los alumnos se organizarán en equipos multidisciplinares utilizando Scrum.  

MMA – Master en Métodos Ágiles

Incluye todas las asignaturas del Postgrado (PMA), el CSM, la certificación Kanban Systems Design de la Lean Kanban University y, adicionalmente, las siguientes asignaturas especializadas en Business Agility, agilidad organizacional y transformación:

Asignaturas Temas Profesores
Enterprise Learning & personal efficiency

Agile Kaizen, Comunidades de Práctica, Open Spaces, Talent development, gamification.

Productividad y aprendizaje personal en Agile (eficiencia).

Steven Wallace
Manuel Lopez
Jordi Molla
Lean Thinking & Agile Management

Lean

Escalado con Kanban.

Visual Management Framework (VMF- Agile para cualquier área – Fuera de tecnología).

Agile-Lean Management

Teodora Bozheva

Xavier Quesada

Xavier Albaladejo

Coaching y Cultura

Coaching de equipos, creación de equipos de alto rendimiento, liderazgo.

Tipos de cultura empresarial, gestión del cambio organizativo.

Joserra Díaz

Jasmina Nikolic
Jaume Gurt

Transformación Continua

Enterprise continuous improvement.

Despliegue de Agile en organizaciones. Contratos ágiles.

Enterprise software development & DevOps.

Ángel Medinilla
Xavier Albaladejo

Álvaro García

Scaling Agile 

Escalado (LESS, Spotify, Nexus, SAFe), desescalado y auto-organización empresarial (reinventing organizations, sociocracy 3.0, liberating structures, …), equipos distribuidos.

Impact Mapping, Product Portfolio Management, Roadmapping, Budgeting for Agile

Adrian Perreau
Fernando Palomo

Mattijas Larsson

Trabajo Final de Máster Durante el Máster se elaborará un caso práctico de introducción y aplicación de Agile en una empresa, incluyendo la parte de transformación organizativa, de métodos y de cultura. Para ellos los alumnos se organizarán en equipos multidisciplinares utilizando Scrum.  

Algunos comentarios de los alumnos en ediciones anteriores: “Cuando se acaba la clase, estamos ya esperando a que llegue la semana que viene para ver un nuevo tema con el siguiente profesor”. “Muy práctico”. “La calidad y diversidad de puntos de vista entre los profesores le aporta mucho valor”.  Más detalles en: Mejora de la situación laboral los alumnos del PMA trans un año.

 
Además de los conocimientos concretos que se reciben, uno de los principales cambios que han experimentado los alumnos es mayor perspectiva en los problemas, cómo abordar la complejidad en las empresas, en su trabajo y en las relaciones entre personas y en equipos. Han ganado discurso y aplomo para defender de manera más objetiva propuestas de cambio y mejora.
 

El Máster tendrá una duración de un año académico y se realizará viernes tarde y sábado por la mañana.

 
Para más detalles, consultar la página oficial del Máster.
 

–> Ver también cuál ha sido la Mejora de la situación laboral de los alumnos tras un año y los Principales aspectos valorados por los alumnos.

» Leer más, comentarios, etc...

Picando Código

Nintendo Switch: SNK Gals’ Fighters

mayo 24, 2020 04:38

SNK Gals' Fighters

SNK Gal’s Fighter es un juego de peleas de SNK, una empresa con una reputación más que conocida cuando se trata de juegos de luchas. Salió originalmente en el año 2000 para la console Neo Geo Pocket Color. Ahora podemos revivir el principio del milenio con una versión para Nintendo Switch publicada en el eShop de Nintendo. Tenemos para elegir de entre 11 luchadoras de distintos títulos de SNK como King Of Fighters, Samurai Shodown y Art Of Fighting. Además de las 11 luchadoras originales, podemos desbloquear 3 personajes escondidos.

El modo de juego normal es el clásico de ir luchando contra distintas oponentes hasta llegar a la jefa final: “Miss X”. Los personajes cuentan con poderes típicos de sus apariciones anteriores, pero también algunos movimientos nuevos. A simple vista parece un juego súper simple, pero es bastante complejo para ser un juego de peleas con dos botones. Cada personaje tiene varios poderes y las peleas son entretenidas a pesar de las limitaciones del hardware.

Si bien podemos atacar con movimientos especiales, incluso tenemos la barrita que se va llenando hasta 3 niveles como para atacar con “súper poderes”, encontré que lo más eficiente es atacar con combos. Las luchadoras controladas por el CPU bloquean casi todos los poderes especiales, así que la mejor estrategia me resultó ir probando y descubriendo combos para enganchar varios golpes de una, y de repente meter un movimiento especial en el medio. Tenemos disponible un modo de entrenamiento para poder practicar los poderes especiales y combos. Algo que no he probado todavía es jugar contra alguien más, pero se puede luchar contra amigos en los modos sobremesa o portátil del Switch.

La versión de Switch tiene varias características nuevas que generalmente encontramos en otros juegos emulados. En la pantalla, podemos elegir de entre varios skins del Neo Geo Pocket Color, hacer zoom para variar entre ver la consola dentro de nuestra consola (o pantalla) o simplemente aumentar el tamaño para ver sólo el gameplay. También incluye un filtro para ver el gameplay como lo veríamos en la consola original. Otra característica es la posibilidad de rebobinar, algo que me resulta un poco “tramposo” en este tipo de juegos, pero de última está bueno que esté. Por último, incluye el manual original del juego en formato digital para ver en nuestro Switch.

[See image gallery at picandocodigo.net] [See image gallery at picandocodigo.net] El Neo Geo Pocket Color salió unos meses después que el Game Boy Color. Nunca había jugado a ninguno de sus juegos y por alguna razón esperaba algo similar a los juegos de Game Boy Color. Pero me sorprendió ver lo bueno y dinámicos que son. La pantalla no tenía una capacidad técnica muy espectacular, pero el uso creativo dentro de las limitaciones gráficas genera resultados bastante buenos. Quedé sorprendido con lo rápido y fluido que es y la complejidad que puede llegar a tener un juego de peleas a pesar de las limitaciones de procesamieto y hardware.

YouTube Video

Ojalá SNK decida publicar más juegos de Neo Geo Pocket Color en el futuro. Hay títulos bastante interesantes, en particular me gustaría ver SNK vs. Capcom: Match of the Millennium, un King of Fighters o Fatal Fury.

SNK Gal’s Fighter está disponible ahora en el Nintendo eShop:
https://www.nintendo.com/games/detail/snk-gals-fighters-switch/

Y sí -como yo- se quedaron con ganas de más juegos de lucha de SNK, en la eShop hay varios títulos de Neo Geo como King Of Fighters (94, 95, 96, 97… etc), The Last Blade, Art Of Fighting, Fatal Fury y Samurai Shodown. Un catálogo impresionante

» Leer más, comentarios, etc...

Blog Bitix

El problema de seguridad tabnabbing y phising en los enlaces en nuevas pestañas a páginas externas y cómo solucionarlo

mayo 24, 2020 08:00

A medida que las personas dependen en mayor medida para operar en internet como compras, acceso a cuentas bancarias o trámites administrativos la seguridad de las aplicaciones web es más crítica. Una parte de la seguridad es responsabilidad del usuario pero otra parte importante es responsabilidad del sitio web. Un potencial problema de seguridad está en los simples y aparentemente inocentes enlaces abiertos en nuevas páginas si al mismo tiempo es posible insertar contenido en la página que otros usuarios obtengan. El resultado es una vulnerabilidad de tabnabbing y phising.

HTML

Es bueno conocer los problemas de seguridad más comunes en las aplicaciones web. Aún siendo la lista de los 10 problemas de seguridad más importantes muy conocidos aún siguen siendo de los más importantes por seguir habiendo aplicaciones vulnerables a ellos y por su gravedad para la seguridad de los datos así como para explotar una aplicación.

Aún así no son los únicos importantes, algunos ni siquiera requieren complejas técnicas para explotarlos. Uno de ellos son los enlaces externos que se abren en páginas en blanco. El problema de seguridad reside en que el modo de funcionamiento por defecto de estos enlaces se permite el acceso a la ventana origen desde la página abierta. Esto hace que la página abierta potencialmente sea capaz de tomar el control de la ventana origen y modificar su contenido, por ejemplo cargando una página maliciosa para hacer un peligroso ataque de suplantación de identidad o phising que simule una página legítima con la intención de robar las contraseñas de algún servicio importante de un usuario.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>Page</title>
</head>
<body>
    <p><a href="external.html" target="_blank">Open page</a></p>
    <p><a href="external.html" target="_blank" rel="noopener noreferrer">Open page (secure)</a></p>
</body>
</html>
page.html

Página con enlaces a otras páginas abiertas en una ueva pestaña

Página con enlaces a otras páginas abiertas en una ueva pestaña

El problema es que los navegadores cuando se abre un enlace en una página en blanco o nueva, el navegador hace accesible a la ventana abierta el objeto Window de la página que lo abre. Y teniendo acceso al objeto Window una página maliciosa cargada tiene la posibilidad de cargar una nueva página en la página original o acceder a las cookies entre ellas las que permiten mantener la sesión en el servidor. Por ejemplo, con la variable window.location es posible cargar una página de autenticación falsa que le pida al usuario introducir sus datos y realmente realice el robo de la contraseña.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>Unknown</title>
    <script type="text/javascript">
        window.opener.location = 'https://www.google.com';
        document.write('You have heen tabnabbed!');
    </script>
</head>
<body>
</body>
</html>
external.html

Es un problema peligroso porque se aprovecha de los navegadores basados en pestañas, en este caso en una página fuera de la atención del usuario se carga un contenido nuevo, el usuario inadvertido al volver a esa pestaña puede pensar que el contenido cambiado de esa pestaña es legítimo sin ser consciente de que no lo es, sin embargo, ser víctima de este peligro de seguridad conocido como tabnabbing combinado con phising. Por ello se recomienda y es importante comprobar que el dominio de la página mostrado por el navegador en la barra de direcciones se corresponda con el contenido, que la página utilice un protocolo seguro no es suficiente si el ataque es de phising.

Este ataque es realmente sencillo pero ha de complementarse con una forma de ataque XSS no tanto por permitir insertar código JavaScript pero si por permitir insertar contenido inseguro sin control de forma que otros usuarios tangan posibilidad de abrirlos y ser potenciales victimas en este caso enlaces que abran una página maliciosa.

El enlace que abre una página en una nueva pestaña es vulnerable a tabnabbing. Al ir al enlace se abre una pestaña, el usuario pierde el foco de la página original y en ella la página abierta carga una nueva página produciéndose el tabnabbing.

Problema de tabnnabing en enlaces que abren páginas en uneva pestaña

Problema de tabnnabing en enlaces que abren páginas en uneva pestaña

Problema de tabnnabing en enlaces que abren páginas en uneva pestaña

La solución más sencilla es añadir el atributo rel="noopener noreferrer” a los enlaces que se abran en una nueva página, esto informa al navegador para que no proporcione a la página abierta el acceso a la variable window.opener, como se muestra en el segundo enlace del ejemplo de código page.html, si la página abierta hace uso de ella se produce un error de JavaScript.

La variable window.opener es nula en el enlace seguro

La variable window.opener es nula en el enlace seguro

Otras medidas recomendables son codificar los datos para evitar ataques XSS y filtrar el contenido enviado por los usuarios o devuelto por la página sobre todo si proviene de fuentes externas a la aplicación ya sea de formularios introducidos por el usuario, parámetros, cabeceras u otras aplicaciones.

Referencia:

» Leer más, comentarios, etc...

Blog Bitix

La concurrencia en la plataforma Java con Project Loom

mayo 23, 2020 01:30

Desde la publicación de Java 8 junto con el nuevo calendario de publicación las mejoras en la plataforma Java y en el lenguaje han sido constantes y significativas. Las mejoras continúan en cada nueva versión y hay muchas otras en preparación para ser publicadas cuando estén listas. Una de ellas muy prometedoras es una nueva implementación de los threads mucho más ligera que han existido desde la primera versión. Estos harán innecesarios en la mayoría de los casos los más complicados modelos programación asíncrona, la programación reactiva, la programación mediante callbacks y las construcciones async/await.

Java

Los threads han existido en Java desde la primera versión siendo uno de sus componentes esenciales. Los threads representan una unidad de trabajo concurrente como una abstracción de los recursos computacionales disponibles y que ocultan la complejidad de gestionar esos recursos.

Ya se usen de forma directa o dentro de un framework como JAX-RS la concurrencia en Java significan threads. En realidad, la plataforma Java entera, desde la máquina virtual al lenguaje y librerías incluidos depuradores y profilers está construida alrededor de los threads como componente esencial de ejecutar un programa.

En general la plataforma Java se basa en:

  • Las APIs son síncronas y describen operaciones E/S de inicio y espera a sus resultados como una secuencia ordenada de sentencias por threads que se bloquean.
  • Las operaciones de memoria con efectos colaterales son ordenadas secuencialmente por las acciones del thread.
  • Las excepciones proporcionan información útil indicando la operación fallida en el contexto del thread actual.
  • Los depuradores siguen el orden de ejecución aunque se realice procesado de E/S.

Los problemas de los threads y sus alternativas

En la implementación de Linux los threads no se diferencian de los procesos. Los threads son costosos de crear y pesados aún empleando pools de threads por lo que el sistema operativo solo es capaz de mantener unos pocos miles activos. Esto afecta especialmente en las aplicaciones Java en el lado de servidor ya que para procesar cada petición se le asigna un thread de modo que el número de peticiones simultáneas se ve limitado por el número de threads que soporta el sistema operativo. En aplicaciones con un número elevado de usuarios y peticiones la escalabilidad se ve limitada.

Por este motivo ha surgido la programación asíncrona, la programación reactiva, la programación mediante callbacks y las construcciones async/await y frameworks basándose en estos principios como Vert.x o Spring Reactive o librerías como RxJava. El resultado es una proliferación de APIs asíncronas desde NIO en el JDK a los servlets asíncronos a las librerías denominadas reactivas para no bloquear los threads. Sin embargo, estas formas de programación tienen un costo mayor que el tradicional y simple modelo secuencial. Son más difíciles de programar, más difíciles de mantener e implican cambios importantes en el modelo de programación. Por otro lado es más difícil depurarlos ya que no se mantiene en una única pila de llamadas toda la tarea.

Estos estilos de programación no han sido inventados porque sean más fáciles de entender, son más difíciles también de depurar y de hacer profile. Son muy intrusivos y hacen la integración con el código síncrono virtualmente imposible simplemente porque la implementación de los threads es simplemente inadecuada en Java tanto en carga del sistema como rendimiento. La programación asíncrona es contraria al modelo original diseñado en la programación de la plataforma Java en varios aspectos con un alto coste de mantenibilidad y observabilidad. Pero lo hacen por una buena razón, para conseguir la escalabilidad y el rendimiento haciendo buen uso de los costosos recursos hardware.

La nueva implementación de los threads

El proyecto Loom persigue crear unos threads que eliminen los costes de los hilos tradicionales del sistema operativo. Serán mucho más ligeros, con ellos Java será capaz de mantener varios órdenes de magnitud superior, millones de threads en vez de solo unos pocos miles. Estos threads virtuales o fibras de la plataforma Java son también simplemente threads pero que crearlos y bloquearlos es mucho más barato. Son gestionados por el entorno de ejecución de Java y no son una representación uno a uno de un envoltorio de los threads del sistema operativo, en vez de eso están implementados en el espacio de usuario del JDK.

Los hilos de los sistemas operativos son pesados porque deben soportar todos los lenguajes y tipo de cargas de forma genérica. Un thread requiere la habilidad de suspender y reactivar su ejecución de la computación. Esto requiere preservar su estado, lo que incluye su puntero de instrucciones así como todo los datos locales de computación que son almacenados en la pila. Dado que el sistema operativo no conoce cómo implementa el lenguaje su pila debe reservar una suficientemente grande.

Loom añade la habilidad de controlar la ejecución, suspensión y reactivación manteniendo su estado no como un recurso del sistema operativo sino como un objeto Java conocido por la máquina virtual bajo el control directo del entorno de ejecución. El conocimiento de las estructuras internas del lenguaje hace que mantener su estado sea más pequeño en comparación con el que mantiene el sistema operativo. Cuando un thread invoca una operación bloqueante se traspasa el control a otro thread con un coste mucho menor que el realizado por el sistema operativo.

El proyecto Loom modificará muchas de las clases de forma interna para implementar los threads con los thread virtuales. Las librerías y aplicaciones que hagan uso de estas clases se beneficiarán de estas mejoras sin necesidad de realizar ninguna modificación.

Estos párrafos son varios extractos del magnífico artículo State of Loom.

Programmers are forced to choose between modeling a unit of domain concurrency directly as a thread and wasting considerable throughput that their hardware can support, or using other ways to implement concurrency on a very fine-grained level but relinquishing the strengths of the Java platform. Both choices have a considerable financial cost, either in hardware or in development and maintenance effort.

We can do better.

Project Loom intends to eliminate the frustrating tradeoff between efficiently running concurrent programs and efficiently writing, maintaining and observing them. It leans into the strengths of the platform rather than fight them, and also into the strengths of the efficient components of asynchronous programming. It lets you write programs in a familiar style, using familiar APIs, and in harmony with the platform and its tools — but also with the hardware — to reach a balance of write-time and runtime costs that, we hope, will be widely appealing. It does so without changing the language, and with only minor changes to the core library APIs. A simple, synchronous web server will be able to handle many more requests without requiring more hardware.

Whereas the OS can support up to a few thousand active threads, the Java runtime can support millions of virtual threads. Every unit of concurrency in the application domain can be represented by its own thread, making programming concurrent applications easier. Forget about thread-pools, just spawn a new thread, one per task. You’ve already spawned a new virtual thread to handle an incoming HTTP request, but now, in the course of handling the request, you want to simultaneously query a database and issue outgoing requests to three other services? No problem — spawn more threads. You need to wait for something to happen without wasting precious resources? Forget about callbacks or reactive stream chaining — just block. Write straightforward, boring code. All the benefits threads give us — control flow, exception context, debugging flow, profiling organization — are preserved by virtual threads; only the runtime cost in footprint and performance is gone. There is no loss in flexibility compared to asynchronous programming because, as we’ll see, we have not ceded fine-grained control over scheduling.

However, the existence of threads that are so lightweight compared to the threads we’re used to does require some mental adjustment. First, we no longer need to avoid blocking, because blocking a (virtual) thread is not costly. We can use all the familiar synchronous APIs without paying a high price in throughput. Second, creating these threads is cheap. Every task, within reason, can have its own thread entirely to itself; there is never a need to pool them. If we don’t pool them, how do we limit concurrent access to some service? Instead of breaking the task down and running the service-call subtask in a separate, constrained pool, we just let the entire task run start-to-finish, in its own thread, and use a semaphore in the service-call code to limit concurrency — this is how it should be done.

Using virtual threads well does not require learning new concepts so much as it demands we unlearn old habits developed over the years to cope with the high cost of threads and that we’ve come to automatically associate with threads merely because we’ve only had the one implementation.

La API de threads

La forma de programación con los nuevos threads es muy parecida a la tradicional que ha existido siempre. Se parece tanto a los threads de siempre que incluso ni siquiera cambia la clase que los representa, que sigue siendo Thread, las diferencias de implementación son internas a la clase y en la JVM. En estos ejemplos se ejecutan tareas de dos formas diferentes y en la tercera se envían tareas para su ejecución y posteriormente se espera a obtener el resultado.

1
2
Thread thread = Thread.startVirtualThread(() -> System.out.println("Hello"));
thread.join();
threads-api-1.java
1
2
3
4
5
6
Thread thread1 = Thread.builder().virtual().task(() -> System.out.println("Hello")).build();
Thread thread2 = Thread.builder()
                      .virtual()
                      .name("bob")
                      .task(() -> System.out.println("I'm Bob!"))
                      .start();
threads-api-2.java
1
2
3
4
5
ThreadFactory tf = Thread.builder().virtual().factory();
ExecutorService e = Executors.newUnboundedExecutor(tf);
Future<Result> f = e.submit(() -> { ... return result; }); // spawns a new virtual thread
...
Result y = f.get(); // joins the virtual thread
threads-api-3.java

Conclusión

Esta nueva implementación de los threads es una mejora significativa sobre la implementación original basada en el sistema operativo. Una vez esté disponible en una versión del JDK muchas aplicaciones se beneficiarán de forma transparente de sus mejoras simplemente por usar un JDK más reciente. Como es principio en la plataforma Java estos cambios están implementados de forma que sean compatibles hacia atrás para que no haya que realizar ningún cambio o muy pocos en las aplicaciones o librerías para beneficiarse de ellos.

El modelo secuencial de los threads más simple que la programación reactiva, asíncrona, callbacks o las construcciones async/await tiene ventajas en la creación del software en su mantenibilidad, legibilidad y es beneficioso desde el punto de vista económico.

Loom es un nuevo ejemplo de que Java no adopta las nuevas tendencias de forma inmediata sino que espera a ver como se desarrollan, y después de evaluar todas las posibilidades opta por una que en este caso es mejor que la programación reactiva o asíncrona que otros lenguajes para permitirlas han tenido que realizar modificaciones comprometiendo la compatibilidad en el futuro del código fuente o desaconsejando el uso de funcionalidades para eliminarlas en el futuro. Esto mismo lo mencionaba en 10 razones para seguir usando Java.

Este artículo es simplemente un resumen de otros dos magníficos artículos State of Loom que explica todo esto en mayor profundidad. Muy recomendables su lectura junto a otros relacionados con Loom.

Y otros artículos sobre Loom.

» Leer más, comentarios, etc...

Picando Código

Reclaiming Work – Documental sobre cooperativas de plataformas de delivery

mayo 19, 2020 02:00

Reclaiming Work (“Reclamando el trabajo”) es un documental producido por Black Brown Films sobre la “gig economy” y los ciclistas que hacen deliveries. Habla un poco desde el punto de vista de los trabajadores mismos sobre lo que está mal de la gig economy (como escribe Wendy Liu en “Abolish Silicon Valley”: la expresión más pura de software como medio para extender el poder del capital sobre los trabajadores) y una alternativa más sana: cooperativismo.

Los trabajadors de estas empresas no tienen derechos laborales, seguro médico, vacaciones pagas, y demás. El sistema no es sustentable. Creo que Cory Doctorow lo expresa bastante bien: “Las plataformas de Delivery como Grubhub y Deliveroo son parasitarias, basureros prendidos fuego sangrando dinero que destripan los márgenes de los restaurantes y somete a los trabajadores a trabajo brutal y precario, sin obtener ganancias, pero para convencer a idiotas de comprar acciones”. Les recomiendo visitar este último enlace en Twitter, donde me enteré de este documental. Doctorow escribe más al respecto en este otro hilo de Twitter.

Una solución a la injusticia e inviabilidad de estos negocios son las plataformas cooperativas. Los trabajadores son los dueños de los medios de producción, en este caso la plataforma y el servicio, asegurándose condiciones laborales dignas y una repartición justa de los ingresos. En el documental veremos testimonios de trabajadores de La Pájara Ciclomensajería de Madrid y otras cooperativas en Francia. Para conocer más, podemos también visitar el sitio CoopCycle, una federación de cooperativas de entrega por bicicleta.

En palabras de los trabajadores:

Tenemos que usar las últimas herramientas que fueron creadas por el capitalismo y extenderlas para implementar socialismo.

Estamos tramando su desaparición con las mismas plataformas que construyero para ellos mismos.

Si te gusta la literatura cyberpunk, éstos días hay que mirar al mundo real para encontrarla:

YouTube Video

 

» Leer más, comentarios, etc...

Variable not found

Componentes con cuerpo en Blazor

mayo 19, 2020 06:05

BlazorNormalmente, en demos y ejemplos de Blazor solemos ver el uso componentes sin cuerpo, muchas veces escritos en forma de tags autocerrados, como los siguientes:
<Article Title="A new hope" DatePublished="@DateTime.Now"></Article>
<Article Title="A new hope" DatePublished="@DateTime.Now" />
El código de este componente, en Article.razor, podría ser el siguiente:
<h1>@Title</h1>
<p>Post content goes here...</p>
@code {
[Parameter]
public string Title { get; set; }
[Parameter]
public DateTime DatePublished { get; set; }
}
En este punto, es lógico pensar que nuestro artículo tendrá su texto, por lo que deberíamos poder incluirle un cuerpo. Una posibilidad sería incluir en el componente una propiedad Body y enviarle el cuerpo en el momento de su instanciación, como en el ejemplo que vemos a continuación:
<Article Title="A new hope" DatePublished="@DateTime.Now" Body="This is the post content..."></Article>
Pero, como podréis imaginar, esto es bastante limitado. Aparte de la legibilidad nula que tendría a nivel de código si el texto es medianamente extenso, no podríamos utilizar marcado HTML ni otros componentes Blazor en su interior, por lo que no es una solución práctica. Lo que en realidad nos interesa es poder hacer lo siguiente:
<Article Title="A new hope" DatePublished="@DateTime.Now">
<p>This is the post content</p>
<p>Blah, blah, blah...</p>
</Article>
Sin embargo, si probáis a ejecutar el código anterior obtendréis un error parecido al siguiente:
InvalidOperationException: Object of type 'MyBlazorProject.Pages.Article' does not have a property matching the name 'ChildContent'.
El mensaje de error no nos indica por qué ocurre, aunque apunta en la dirección para poder solucionarlo. Como no podría ser de otra forma, Blazor permite la creación de componentes con cuerpo.

La propiedad ChildContent

Cuando Blazor parsea un componente con cuerpo, intenta introducirlo en una propiedad de tipo RenderFragment que, por convención, se denomina ChildContent. Como podréis adivinar, cuando no la encuentra se lanza la excepción que veíamos anteriormente.

Reescribamos nuestro componente para incluirle esta propiedad:
<h1>@Title</h1>
<div class="content">
@ChildContent
</div>

@code {
[Parameter]
public string Title { get; set; }
[Parameter]
public DateTime DatePublished { get; set; }

[Parameter]
public RenderFragment ChildContent { get; set; }
}
Fijaos especialmente en dos cosas:
  • Primero, atendiendo a la convención, hemos añadido la propiedad ChildContent de tipo RenderFragment. En esta propiedad se introducirá el contenido especificado en el cuerpo del componente en el momento de utilizarlo.
     
  • Segundo, algo más arriba, justo debajo del título del post, hemos insertado @ChildContent en la zona de marcado HTML donde queremos que sea renderizado el cuerpo del componente.
De esta forma, ya podemos suministrar a <Article> el cuerpo con toda la riqueza que Blazor nos permite, incluyendo tanto tags HTML como construcciones Razor:
<Article Title="A new hope" DatePublished="@DateTime.Now">
<p>This is the post content</p>
<p>Today is @DateTime.Now</p>
<p>Blah, blah, blah...</p>
</Article>
Fácil, ¿verdad?

Aquí podríamos dar por terminado el post, pero todavía nos quedan cosas interesantes por descubrir...

¿Se pueden tener otras propiedades de tipo RenderFragment en el componente?

Pues sí, y de hecho puede resultar bastante útil. Imaginad que queremos añadir a nuestro componente <Article> la posibilidad de utilizar en su cuerpo tags como <Summary> y <Body> para especificar el resumen y el contenido del artículo de la siguiente forma:
<Article Title="A new hope" DatePublished="@DateTime.Now">
<Summary>
This article talks about a new hope.
</Summary>
<Body>
<p>This is the post content</p>
<p>Today is @DateTime.Now</p>
<p>Blah, blah, blah...</p>
</Body>
</Article>
Este sería un caso de uso de múltiples RenderFragment. Observad que aquí ya no nos interesa acceder al cuerpo completo del componente mediante la propiedad ChildContent, sino que queremos obtener por separado el sumario y el cuerpo.

Para ello, la implementación del componente podría ser la siguiente:
<h1>@Title</h1>
<div class="summary">
@Summary
</div>
<div class="content">
@Body
</div>

@code {
[Parameter]
public string Title { get; set; }
[Parameter]
public DateTime DatePublished { get; set; }

[Parameter]
public RenderFragment Summary { get; set; }
[Parameter]
public RenderFragment Body { get; set; }
}

¿Y podemos enviarle datos a un RenderFragment?

Pues sí, y conceptualmente es algo muy parecido a lo que hacemos cuando en MVC enviamos a una vista tipada los datos que necesita para maquetarse. En el caso Blazor, podemos definir un fragmento como RenderFragment<T>, siendo T el tipo de datos que le suministraremos en el momento de renderizarlo.

Así, cuando un RenderFragment<T> es renderizado, es necesario enviarle una instancia de T. Es decir, ya no incluiremos el fragmento en el marcado utilizando una expresión como @Fragmento (como hacíamos antes con @Summary o @Body), sino @Fragmento(obj), siendo obj la instancia de T que actuará como contexto de renderizado del fragmento.

Por ejemplo, continuando con el escenario anterior, podríamos definir nuestro componente <Article> como aparece algo más abajo, donde indicamos que tanto Summary como Body son fragmentos tipados que recibirán una instancia de Article, el componente que los contiene. O en otras palabras, Summary y Body son fragmentos que se renderizarán usando como contexto un objeto de tipo Article:
[Parameter]
public RenderFragment<Article> Body { get; set; }
[Parameter]
public RenderFragment<Article> Summary { get; set; }
Ya en el marcado HTML, en el momento de renderizar estos fragmentos debemos suministrarles la instancia que actuará como contexto. Dado que en este caso estamos en el interior de la clase Article, podemos utilizar this:
<h1>@Title</h1>
<div class="summary">
@Summary(this)
</div>
<div class="content">
@Body(this)
</div>

@code {
[Parameter]
public string Title { get; set; }
[Parameter]
public DateTime DatePublished { get; set; }

[Parameter]
public RenderFragment<Article> Body { get; set; }
[Parameter]
public RenderFragment<Article> Summary { get; set; }
}
¿Y cómo podemos utilizar el contexto a la hora de definir el contenido de los fragmentos? Pues sencillo. Por defecto, en el cuerpo de estos fragmentos tendremos definida una variable llamada context que nos dará acceso al contexto:
<Article Title="A new hope" DatePublished="@DateTime.Now">
<Summary>
This article was posted on @context.DatePublished.ToShortDateString()
and talks about @context.Title.
</Summary>
<Body>
<p>This is the post content</p>
<p>Today is @DateTime.Now</p>
<p>Blah, blah, blah...</p>
</Body>
</Article>
Si el nombre context no nos convence demasiado podemos utilizar la propiedad Context de los RenderFragment para indicar el nombre de la variable con la que podremos acceder desde ellos a sus datos de contexto. Es decir, con  Context indicaremos el nombre que daremos localmente a dicha información para poder referirnos a ella.

Quizás suene un poco confuso, pero una vez lo pillamos es simple. En el siguiente bloque de código utilizamos un componente <Article> en cuyo fragmento <Summary> especificamos que el contexto estará disponible localmente bajo el nombre article (que, como sabemos, es de tipo Article). Por esa razón, podemos acceder a él mediante @article:
<Article Title="A new hope" DatePublished="@DateTime.Now">
<Summary Context="article">
This article was posted on @article.DatePublished.ToShortDateString()
and talks about @article.Title.
</Summary>
<Body>
<p>This is the post content</p>
<p>Today is @DateTime.Now</p>
<p>Blah, blah, blah...</p>
</Body>
</Article>
Si lo pensáis un poco, veréis que este patrón es el mismo que se utiliza en el componente de routing de la aplicación que encontraréis en el archivo App.razor de todos los proyectos Blazor. En él, el fragmento <Found>, que es un RenderFragment<RouteData>, establece que su contexto estará disponible en la variable routeData, que es luego utilizada para proporcionar a <RouteView> el valor del atributo RouteData:
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
...
</Router>
Espero que os resulte útil en vuestros desarrollos :)

Publicado en Variable not found.

» Leer más, comentarios, etc...

Fixed Buffer

Como manejar las trazas de una aplicación .Net Core con Seq y Serilog

mayo 19, 2020 06:00

Tiempo de lectura: 8 minutos
Imagen ornamental para la entrada "Como manejar las trazas de una aplicación .Net Core con Seq y Serilog"

Esta semana he estado preparando una entrada para CampusMVP hablando de Serilog y lo útil que es instrumentar el código utilizando Serilog como herramienta para este fin.

En esa entrada se plantea la importancia de añadir un buen sistema de trazas que sea fácilmente configurable según las necesidades de cada momento. Precisamente de esa entrada y de un proyecto en el que estuve hace unos meses viene la idea de esta entrada.

¿Cómo poder crear trazas estructuradas y consultarlas en vivo?

Una de las principales necesidades cuando estamos revisando las trazas de una aplicación, es aporten suficiente información y que sean fáciles de consultar. Para esto es vital que definamos unas buenas trazas a lo largo del código y que sus niveles estén bien definidos.

Por poner un caso concreto, si estas trazando una excepción utilizar el nivel ‘Debug’ seguramente no sea la mejor opción.

Por otro lado, el hecho de utilizar trazas estructuradas nos facilita enormemente el poder ver que datos se estaban utilizando en el momento concreto y el poder analizar los datos de una manera más eficiente.

Si has leído mi entrada en CampusMVP, ya sabes que entre los múltiples Sinks que ofrece Serilog hay varios que nos permiten tener información en tiempo real. Abrir y cerrar un fichero para ver los cambios no es una manera muy eficiente de revisar logs en tiempo real…

Aquí es donde entra en juego Seq.

¿Qué es Seq?

Seq es un sistema de ingesta de logs que permite hacer analítica de manera muy sencilla. Simplemente va a exponer un endpoint donde las aplicaciones van a publicar sus trazas. Además, ofrece de serie una interfaz donde vamos a poder ver todas las trazas y hacer consultas sobre ellas:

La imagen muestra la interfaz de Seq con una consulta cualquiera. Las trazas que se muestran son las que cumplen la consulta.

Aunque es una herramienta de pago, tiene un nivel gratuito que se puede utilizar incluso de manera comercial en producción (y esa es la razón de hablar en esta entrada).

Para poder utilizarlo hay que crear un servidor Seq ya sea instalando el software en un equipo o mediante Docker.

Si acostumbras a utilizar Docker en tus desarrollos y tienes dudas sobre donde o cómo hacer las pruebas, no te pierdas sobre cómo ejecutar pruebas de código dentro de contendores Docker.

Utilizando Seq en una aplicación .Net Core con Serilog

Vale, ahora que ya tenemos Seq en nuestro equipo, sea utilizando el instalador o mediante Docker, es hora de configurar Serilog en una aplicación .Net Core para enviar las trazas a Seq.

Importante: En este punto se asume que has leído la entrada sobre como instrumentar el código utilizando Serilog y sabes cómo funciona Serilog. Si tienes dudas sobre como funciona échale un ojo a esa entrada primero.

Para poder enviar las trazas de Serilog a Seq, basta con añadir el Sink de Seq utilizando el paquete NuGet ‘Serilog.Sinks.Seq‘. Una vez hecho eso, simplemente vamos a configurar el Sink para que funcione correctamente. La única configuración obligatoria que necesitamos de momento es el endpoint de ingesta de Seq, que por defecto es ‘http://localhost:5341’

var logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.Seq("http://localhost:5341")
                .CreateLogger();

Si sobre ese logger que hemos creado, escribimos mensajes como por ejemplo:

logger.Verbose("Mensaje Verbose");
logger.Debug("Mensaje Debug");
logger.Information("Mensaje Information");
logger.Warning("Mensaje Warning");
logger.Error("Mensaje Error");
logger.Fatal("Mensaje Fatal");

Vamos a poder ir a la web de administración built-in de Seq y encontrarnos algo como esto:

La imagen muestra el panel de eventos de Seq donde se pueden ver las trazas: Mensaje Fatal, Mensaje Error, Mensaje Warning, Mensaje Information y Mensaje Debug

Como era de esperar ‘Mensaje Verbose’ no aparece ya que el nivel mínimo que debe tener una traza para enviarla a Seq según la configuración que hemos dado al logger es ‘Debug’

Al igual que ocurría con el resto de sinks y enrichers de Serilog, el sink de Seq puede utilizar el fichero de appsettings.json de .Net Core o app.congif/web.config de .Net para configurarse:

{
  "Serilog": {
    "WriteTo": [
      { "Name": "Seq", "Args": { "serverUrl": "http://localhost:5341" } }
    ]
  }
}

¿Cómo gestiona Seq las trazas estructuradas desde Serilog en .Net Core?

Una de las grandes ventajas de utilizar Serilog en .Net Core y enviar las trazas a Seq, es que estas no se limitan a mensajes de texto, sino que pueden contener información mucho más compleja.

Imaginemos que tenemos una clase como pudiera ser:

public class Pedido
{
    public int IdPedido { get; set; }
    public string Dirección { get; set; }
}

Si no utilizásemos trazas estructuradas y quisiésemos registrar en el log la información del pedido que ha producido un error, tendríamos que hacer algo como este:

logger.Error($"Se ha producido un error registrando el pedido. Id: {pedido.IdPedido}, Direccion: {pedido.Dirección}");

Si además añadiésemos un nuevo campo a la clase, tendríamos que revisar todas las trazas donde se usa para añadirlo, o no estaríamos registrando ese nuevo campo.

En cambio, utilizando trazas estructuradas podemos hacer algo como esto:

logger.Error("Se ha producido un error registrando el {@Pedido}.",pedido);

De este modo además de que queda todo mucho más claro y legible, todas las propiedades se van a registrar independientemente de que la clase cambie.

Para que Seq añada el contenido del objeto a la traza, hay que colocar una arroba (@) delante del objeto entre paréntesis

A este punto, es importante señalas que quien está creando las trazas estructuradas de nuestra aplicación .Net Core es Serilog y no Seq. Es por esto que si comprobamos la salida de consola o de fichero podemos encontrar algo como esto:

[20:52:52 ERR] Se ha producido un error registrando el pedido. Id: 2134, Direccion: www.fixedbuffer.com
[20:52:52 ERR] Se ha producido un error registrando el {"IdPedido": 2134, "Dirección": "www.fixedbuffer.com", "$type": "Pedido"}.

¿Y qué ventaja nos aporta Seq entonces? Pues que es muy cómodo poder consultar esos datos:

La imagen muestra desplegada una traza generada desde .Net Core con Serilog  y enviada a Seq, donde se lee: "Se ha producido un error registrando el Pedido", y junto al mensaje un json con los datos del pedido.

Securizando la ingesta de trazas enviadas a Seq desde .Net Core con Serilog

Si bien es cierto que con lo que hemos hecho hasta ahora ya tenemos listo Serilog para enviar trazas a Seq desde una aplicación .Net Core, el escenario es un tanto inseguro… Cualquiera que conozca el endpoint de ingesta puede mandar datos.

Para poder solucionar esta situación, Seq nos ofrece la posibilidad de crear tokens que deberán utilizar las aplicaciones para enviar las trazas, ya sea desde Serilog o desde cualquier otro logger de .Net Core que soporte trabajar con Seq.

Para conseguir ello basta con ir al menú ‘settings’ de la parte superior derecha y después pulsar sobre ‘Add API Key’

La imagen señala los botones "settings" de la parte superior derecha y "ADD API KEY" de la parte inferior izquierda

Eso nos lanzará una nueva ventana donde vamos a poder configurar diferentes aspectos de la key como su nombre, sus permisos, el nivel mínimo de trazas que va a registrar o diferentes filtros y propiedades.

La imagen muestra la interfaz de creación de una key para una aplicación .Net Core con Serilog y Seq

Una vez configurado, basta con pulsar sobre ‘Save Changes’ para guardar los datos y crear una key que se nos mostrará por pantalla.

Importante: La key solo se mostrará una vez y no será posible recuperarla después, asi que toma nota de ella y ponla a buen recaudo

Una vez tenemos la key, basta dársela a Serilog a través del parámetro ‘apiKey’, ya sea utilizando código o un fichero de configuración.

Visualización mediante paneles

Una de las cosas que más me gusta de Seq es la posibilidad que ofrece de monitorizar el estado de las aplicaciones sin ningún esfuerzo. Creas tu aplicación .Net Core, añades Serilog, configuras el sink de Seq, y a partir de ese momento puedes crear diferentes paneles donde visualizar la información relevante.

La imagen muestra el panel de gráficas por defecto que ofrece Seq

Para esto Seq ofrece una ventana de paneles donde es posible crear paneles propios aplicando consultas sobre la información que se registra.

Extensión de la funcionalidad

Aunque no quiero entrar muy a fondo en lo extensible que es Seq, si es muy útil conocer que se puede extender a través de más de 500 paquetes NuGet que añaden diferentes funcionalidades como por ejemplo el envío de alertas por correo electrónico, o incluso por Slack o Microsoft Teams.

Para poder añadir estos paquetes que dotan de funcionalidad extra a Seq, bastan con ir al menú de ‘settings’, pero esta vez al submenú ‘Apps’.

La imagen señala el botón "settings" de la parte superior derecha y el submenu APPS de la parte izquierda

Desde esta ventana vamos a poder buscar los diferentes paquetes y podremos instalarlos con un simple click.

Evidentemente luego hay que configurarlos, y que sea sencillo o no dependerá de cómo y para qué sea el paquete.

Conclusión

Para no extender mucho la entrada y que se haga eterna, hemos revisado por encima la principal funcionalidad que ofrece enviar las trazas de .Net Core a Seq con Serilog. La funcionalidad que he planteado es la que a mí me parece más interesante, pero eso no significa que sea la única.

Seq ofrece una gran funcionalidad extra que facilitan mucho la vida a la hora de encontrar problemas sobre sistemas en producción, pero nada es gratis.

Primero, el software, aunque ofrece un modo gratuito que se puede utilizar en producción, si necesitas una gestión de usuarios o un soporte avanzado, no te queda otra que pasar por caja…

Después, está el coste computacional extra de enviar las trazas por una conexión de red, que, aunque es muy bajo, no es despreciable y hay que tenerlo en cuenta.

Por último, el hecho de que ante un fallo catastrófico pueden quedarse trazas pendientes de enviar y no registrarse en Seq. Si que es cierto que Serilog y otros sistemas de logging ofrecen mecanismos para guardar las trazas temporalmente en un fichero de modo que evitemos esos casos, es trabaja extra por nuestra parte el configurarlos. Además, el sistema que tiene que gestionar ese trabajo extra así que hay que plantearse si es necesario cuando escribes código de alto rendimiento en .Net Core.

Con todo, personalmente creo que Seq es una gran herramienta a tener en el cinturón, aunque solo sea durante la fase de desarrollo. Al fin y al cabo, se puede poner en marcha Seq desde un Docker y poner y quitar el sink son un par de líneas de código.

¿Y tú? ¿Crees que Seq puede ayudarte en tu día a día?

**La entrada Como manejar las trazas de una aplicación .Net Core con Seq y Serilog se publicó primero en Fixed Buffer.**

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 404

mayo 18, 2020 06:40

Enlaces interesantesEl popular código de estado HTTP 404 (Not found) indica que la conexión con el servidor ha podido ser establecida, pero el recurso solicitado no existe o bien el servidor prefiere hacer ver que no existe, quizás por razones de seguridad. Por defecto, este resultado puede ser cacheado en el cliente o elementos intermedios, salvo que se indique lo contrario.

Sin embargo, atendiendo a la especificación del protocolo HTTP, no siempre se utiliza correctamente. Por definición, la respuesta 404 no dispone de mecanismos para indicar si el recurso existió alguna vez o si el error es permanente o transitorio, mientras que el código HTTP 410 (Gone) sí permite expresar que el recurso existió, pero ya no está disponible y no volverá a estarlo, por lo que los clientes podrían eliminar enlaces o referencias al mismo con tranquilidad.

Y ahora, vamos a por los enlaces recopilados durante la semana pasada que, como es habitual, espero que os resulten altamente interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

    Web / HTML / CSS / Javascript

    Visual Studio / Complementos / Herramientas

    Xamarin

    Otros

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Arragonán

    Ideas para mejorar tus Historias de Usuario

    mayo 18, 2020 12:00

    Me parecía un valor seguro hacerme con Fifty Quick Ideas to Improve your User Stories de Gojko Adzic y David Evans. Venía siguiendo los artículos del blog de Gojko Adzic (de hecho algunas de esas ideas ya están desarrolladas ahí) a raíz de leer Specification By Example y además tenía buenas referencias del libro. Desde luego que no me defraudó.

    Tal y cómo está escrito existe una idea por capítulo que contiene la descripción, los beneficios clave y algunas recetas sobre cómo llevarla a cabo; teniendo en algunos casos referencias a otros libros y artículos donde profundizar más sobre esa idea. El formato está genial para revisitar de vez en cuando alguna de las ideas y llevarlas al día a día.

    Creo que quienes más partido le pueden sacar a este libro son las personas con roles tipo product manager/owner o que tengan influencia sobre cómo se gestiona el producto y los stakeholders en una organización. Pero es muy interesante para cualquiera que se dedique a la creación de productos de software con cierto recorrido profesional e interés en profundizar sobre la gestión de producto.

    Y hablo de cierto recorrido profesional porque no es un libro introductorio donde expliquen qué son las historias de usuario, si es mejor una u otra plantilla y cosas así.

    Mientras lo leía estuve marcando lo que me parecieron los principales puntos de cada idea y al terminarlo, como ejercicio de repaso, decidí empezar a traducir esos extractos. Me pareció que sería interesante compartirlo, así que pedí permiso a Gojko para hacerlo.

    Estas ideas están agrupadas en cinco bloques:


    Creando historias

    Cuenta historias, no las escribas

    Usar historias de usuario implica un modelo completamente diferente, es definir requisitos de forma colaborativa.

    No te preocupes demasiado por el formato de historia

    Experimenta con el formato:

    • Nombra las historias rápido, añadiendo los detalles después.
    • Evita caer en escribir soluciones obvias.
    • Piensa en más de un stakeholder interesado en el ítem, esto abre opciones de partir la historia.
    • Utiliza imágenes en vez de palabras.
    • Haz una pregunta.

    Describe un cambio de comportamiento

    Las iniciativas que aportan valor producen cambios observables en la forma de trabajar de alguien. Capturar ese cambio de comportamiento hace una historia medible desde un punto de vista de negocio y eso simpre abre una buena discusión.

    Describe un cambio del sistema

    La descripción de la historia debería aclarar exactamente cómo cambia el sistema o las reglas de negocio del momento actual y en el que la historia esté implementada. También necesitamos saber cuánto difiere del comportamiento actual. Empieza la discusión añadiendo otra cláusula a la plantilla de las historias (“Where as…”, “Instead of…”), debería aclarar lo más posible el contraste de lo que hay con lo que es necesario.

    Enfoca las historias como experimentos que sobreviven

    Las preguntas clave para el tamaño de la historia no deberían ser sobre la duración de la iteración, sino acerca de cuánto quieren invertir los stakeholders de negocio en saber si el cambio propuesto retornará lo que asumieron invertir. Enfocando las historias como experimentos podemos cambiar el enfoque desde la complejidad técnica hacia los outcomes (o resultados esperados) y el aprendizaje.

    Diseña experimentos en torno a suposiciones y conviértelos en historias de usuario.

    Cuidado con los roles genéricos

    Evita “As an user…” como la peste. Una descripción clara y precisa de roles de usuario ayuda a identificar necesidades y eliminar complejidad innecesaria. Esto también ayuda a mejorar la gestión de los proyectos y la estrategia de planificación.

    Evalúa la zona de control y la esfera de influencia

    Hay tres áreas de sistemas diferentes:

    • La zona de control incluye todas las cosas en un sistema que podemos cambiar nosotros mismos.
    • La esfera de influencia incluye las actividades con las que podemos impactar, pero sobre las que no podemos ejercitar control total.
    • Los elementos externos incluyen elementos sobre los que no podemos influir.

    La necesidad del usuario de una historia idealmente debería estar en la esfera de influencia del equipo, mientras que el entregable en su zona de control.

    Pon una fecha de “best before” en las historias

    Para evitar presiones innecesarias y emergencias auto inflingidas, comprueba si hay una fecha límite cuando una nueva historia de usuario es propuesta. Escribe la fecha de “best before” en esas historias y haz visualmente obvio que esas son especiales y tienen que gestionarse por separado.

    No intentes poner fecha de “best before” en todas las historias, de lo contrario todo se convertirá en una emergencia.


    Planificando con historias

    Establece un deadline para abordar los principales riesgos

    Tener un plan explícito para manejar los grandes riesgos permite a los stakeholders y al equipo a lograr un equilibrio entre los beneficios de negocio a corto plazo y la sostenibilidad a largo plazo.

    Usa backlogs jerárquicos

    Organizar el backlog en varios niveles permite a los equipos reducir significativamente el número total de items, proveyendo al mismo tiempo de una perspectiva general.

    Una manera de implementar esta idea es usar un tablero visual como múltiples carriles horizontales para representar los diferentes niveles. Si usas un tablero de planificación, puedes hacerlo mediante un código de colores o usando diferentes tamaños de tarjetas. Alternativamente puedes representar la misma información en un customer journey, un user story map o un impact map.

    Agrupa las historias por impacto

    Un impact map es una visualización (tipo mind map) de cómo el entregable esperado conecta con las metas de negocio y las hipótesis subyacentes en cuatro niveles: Goal/Actor/Impact/Deliverable.

    Organizar un grupo de historias como un impact map facilita varios niveles de discusiones sobre toma de decisiones y priorización.

    Crea un user story map

    Los user story map conectan los entregables de software con customer journeys y flujos de negocio, mostrando cómo las historias individuales contribuyen a la perspectiva general proveyendo de una buena representación visual de los planes de release.

    Ayuda a los equipos a tener un visión más clara de por qué están construyendo un software y cómo encaja en la perspectiva general.

    Los stakeholders a veces ven que pueden habilitar ciertas acciones eligiendo opciones con entregables más simples y posponiendo historias más complejas para releases posteriores.

    Cambia el comportamiento usando el funnel CREATE

    Las siglas CREATE significan Cue, Reaction, Evaluation, Ability, Timing y Executing.

    • Cue: La posibilidad de una acción cruza por la mente de la persona.
    • Reaction: la persona de forma automática e intuitiva reacciona a la idea en una fracción de segundo, generando una respuesta emocional.
    • Evaluation: la persona piensa sobre la acción conscientemente, evaluando costes y beneficios.
    • Ability: la persona evalúa si la acción es factible dado el contexto actual.
    • Timing: la persona juzga si debe actuar ahora o más tarde.
    • Executing.

    Las buenas historias de usuario generalmente tienen como objetivo lograr un cambio de comportamiento, así que se debería poder vincular una historia con una parte del funnel CREATE para una persona relevante.

    Muestra las preocupaciones globales al inicio de una milestone

    Los decisores clave probablemente pueden participar en una discusión aparte sobre las preocupaciones globales una vez por milestone. Esto hace que sea más fácil llegar a un acuerdo sobre los objetivos globales y hacer una lista de las preocupaciones transversales más importantes.

    Prioriza según las etapas de crecimiento

    Modelos de crecimiento para productos éxito:

    • Empathy: descubrir cómo resolver un problema real por el que gente pague.
    • Stickiness: crear el producto adecuado para mantener a los usuarios.
    • Virality: hacer crecer la base de usuarios de forma orgánica y/o artificial.
    • Revenue: establecer un modelo de negocio sostenible y escalable con los márgenes correctos en un ecosistema saludable.
    • Scale: crecimiento del negocio.

    Lograr que los stakeholders se pongan de acuerdo sobre la etapa actual de crecimiento ayuda con la priorización del ahora/ahora no.

    Prioriza usando la alineación de propósito

    El modelo requiere que los stakeholders respondan 2 preguntas por item:

    • ¿Es de crítico? (¿Puede el negocio funcionar sin él?)
    • ¿Es diferenciador en el mercado? (¿Trae clientes, proporciona una ventaja competitiva o algo similar?)

    Una vez que se han respondido estas preguntas, los items pueden terminar en una de estas cuatro categorías:

    • Parity si es de misión crítica.
    • Partner si es diferenciador en el mercado.
    • Differentiating si responde a ambas preguntas.
    • Who cares si no tiene ninguno.

    Haz una mapa de stakeholders

    • ¿Quién recibirá impacto por el proyecto?
    • ¿Quién es responsable del proyecto?
    • ¿Quién tendrá autoridad para tomar decisiones sobre el proyecto?
    • ¿Quién puede apoyar el proyecto?
    • ¿Quién puede dificultar el proyecto?
    • ¿Quién ha estado involucrado en este tipo de proyectos en el pasado?

    Es particularmente útil cuando la clave para lograr los outcomes deseados involucra cambiar el comportamiento de otras personas, especialmente en situaciones con mucha carga política.

    Pon nombre a tus milestones

    Elegir buenos nombres para las milestones mejora la participación de los stakeholder en el proceso de priorización de las historias y ayuda a la planificación con los backlogs jerárquicos.

    No confundas las milestones con épicas. Las verdaderas épicas son simplemente historias grandes que satisfacen todos los demás criterios de INVEST (Independent, Negotiable, Valuable, Estimable, Small, Testable), pero resultan demasiado grandes para ser implementadas en una iteración.

    Enfoca las milestones en un número limitado de segmentos de usuario

    Seleccionar un número limitado de segmentos para cada milestone evita que los stakeholders inventen constantemente nuevos roles de usuario.

    Obliga a las personas a considerar seriamente qué segmentos de usuario se beneficiarán de la historia, si lo hubiera y ayuda a evitar historias genéricas.


    Discutiendo historias

    Usa baja tecnología para tener conversaciones sobre la historia

    Las buenas historias de usuario son el recordatorio de una conversación.

    No usar herramientas técnicas durante la conversación permite a los equipos focalizarse en el asunto sin tener que preocuparse de cosas como el alineamiento del texto, formato de negritas o cuál es la sintaxis correcta. Es importante recordar los resultados de una conversación, pero volcarlo en ese tipo de herramientas puede ser pospuesto.

    Al provocar menos distracciones, las discusiones sobre pizarras son más rápidas y productivas que alrededor de herramientas técnicas.

    Imagina la demostración

    Cuando el equipo discute una historia, en el momento de entender el criterio de aceptación o de explorar los ejemplos clave, comienza respondiendo la pregunta “¿Cómo haremos la demostración de esta historia?”

    Fomenta y refuerza el compromiso del equipo con la idea de que la demo es la prueba de sus esfuerzos, la medida real de progreso, el momento de la verdad, la ceremonia de cierre de la iteración. Haz que sea un gran momento. Incluso trae comida. Que se sienta que es el momento de celebración que debería ser.

    Diverge y converge para las discusiones de las historias

    Nuestra forma preferida de facilitar las discusiones de historias para equipos de 10 a 20 personas es dividir el equipo en varios grupos más pequeños, hacer que los grupos capten su comprensión de una historia usando ejemplos y luego juntar a los grupos para comparar resultados.

    Focalízate en las diferencias entre los grupos:

    • En el formato o estructura de la información.
    • En los outcomes de ejemplos similares.
    • En los tachones y signos de interrogación.

    Involucra a todos los roles en la discusión

    Un error común es delegar la tarea de análisis de una historia en una sola persona.

    Crea pequeñas conversaciones que involucren al menos a una persona representando a los three-amigos; con roles de desarrollo, testing y análisis. Sin encasillarse con el número tres, que sean representados todos los roles relevantes que es necesario que contribuyan.

    Asegúrate de involucrar a personas que realmente vayan a trabajar en la entrega de la historia.

    Mide el alineamiento usando ejercicios de feedback

    Según la sabiduría popular hay que detener la discusión de una historia cuando no hay más preguntas. Esto puede ser engañoso cuando desarrolladores y testers carecen de experiencia en el dominio del negocio; simplemente pueden no saber qué pregunta adicional hacer.

    El ejercicio se basa en que alguien presenta un caso extremo complicado basado en la conversación que se ha tenido. En vez de discutir sobre esa condición, todo el mundo escribe cuál cree que es el resultado esperado. Después, todos lo enseñan a la vez y se comprueba si están alineados o hay que seguir discutiendo sobre la historia.

    Los ejercicios de feedback proporcionan un práctico mecanismo de cierre para talleres y discusiones de historias. Es lo más cercano que hemos encontrado a medir de forma objetiva el entendimiento compartido.

    Juega al abogado del diablo

    Un gran beneficio de esta técnica es descubrir pronto las malas ideas, para tirar o refinar las historias que podrían introducir complejidad innecesaria en el software.

    No se trata de ser negativo, se trata de buscar grietas para ver si una historia es sólida o no. Dos buenas formas de prevenir problemas personales son:

    • Elige a una persona para que desempeñe el rol inicialmente, luego que rote ese rol historia por historia para evitar que una sola persona focalice las reacciones negativas.
    • Haz que todo el grupo proponga ideas sobre por qué una historia puede ser incorrecta y haz turnos para presentarlas.

    Divide la responsabilidad para definir historias

    Hacer que los stakeholders de negocio diseñen soluciones no es la intención original de las historias de usuario, pero muchos equipos acaban cayendo en esta trampa. Este puede ser un experimento para ayudar a arreglar esas situaciones:

    • Los stakeholders de negocio especifican sólo lo “In order to..” u “As a…” de la historia.
    • El equipo propone varias opciones para el “I want…”
    • Ambos evalúan conjuntamente las opciones y los stakeholders deciden cuál de ellas deberías ser implementada.

    El mayor beneficio de esta aproximación es que fuerza a mantener una conversaciones para decidir cuál es la solución.

    Divide las discusiones de negocio de las técnicas

    Permite a los equipos tener discusiones sobre el negocio más cortas y usar el tiempo de los usuarios de negocio más eficientemente. También hace que los equipos consideren un conjunto de historias de usuario cuando están pensando en cambios en el diseño.

    Una discusión focalizada previene que los equipos vayan a los detalles de implementación demasiado pronto, haciendo que se invierta más tiempo en entender lo que realmente necesita hacerse.

    Investiga el valor a múltiples niveles

    Las buenas historias influencian en una cosa que luego llevan a otra para mejorar de algún modo el trabajo de alguien.

    Piensa en los dos niveles más típicos: el valor para el usuario y el valor para la organización que vende u opera el software. Si puedes encontrar una historia que contenga ambas cosas, entonces es una situación de win-win.

    Discute métricas de escala variable con QUPER

    Muchos aspectos de los sistemas de software no están relacionados con la presencia o ausencia de una funcionalidad en particular, sino por una combinación de ellas que proveen valor en una escala variable (estos aspectos se conocen como requisitos no-funcionales o atributos de calidad).

    El modelo QUality PERformance expone y visualiza dos tipos de información, la necesidad del negocio y las soluciones de arquitectura propuestas: breakpoints y barriers.

    Breakpoints son los umbrales (que vienen determinados por necesidades que se esperan de los usuarios y la competencia) de utilidad para un aspecto particular de un sistema. QUPER asume que la relación de coste-beneficio de las soluciones potenciales es similar a una curva en S. Los puntos donde la relación coste-beneficio cambia bruscamente se llaman barriers y están relacionados con potenciales soluciones arquitectónicas.

    Al enumerar claramente las opciones de costes y analizar los cambios en las funcionalidades, el modelo QUPER también ayuda a mostrar suposiciones ocultas sobre el crecimiento futuro.


    Partiendo historias

    Empieza con los outputs

    La reescritura de un sistema legacy es posiblemente una de las situaciones más difíciles para partir los entregables en pequeñas piezas con historias de usuario.

    A menudo tienen metas ambiciosas, como acelerar la capacidad de entrega en el futuro, desbloquear oportunidades de negocio, adoptar una arquitectura que permita crecer… Esto suele verse como un trabajo para una espada, no para un bisturí.

    El beneficio clave de arrancar con los outputs es que facilita crear un plan de entrega incremental más refinado, porque permite dar a los usuarios funcionalidades antes y permite tener discusiones más fructíferas.

    Olvida el walking skeleton, ponle muletas

    Un walking skeleton es la implementación de una parte del sistema que realiza una pequeña función de extremo a extremo. No requiere utilizar la arquitectura de software final, pero debe enlazar los principales componentes arquitectónicos. La arquitectura y la funcionalidad deben evolucionar en paralelo.

    La idea central del esqueleto con muletas es lanzar una interfaz de usuario pronto y planear hacer despliegues iterativos de todo lo que se encuentra tras la interfaz de usuario.

    Limita el segmento de clientes

    Hay situaciones en las que las discusiones sobre si algo es requerido u opcional conducen a un callejón sin salida y es particularmente difícil partir el trabajo en trozos pequeños que aporten valor. Un buen truco es evitar la discusión de partir los entregables y centrarse en limitar el segmento de clientes objetivo. No le des a todos el 2% de lo que necesitan, dales al 2% de los usuarios todo lo que necesiten.

    El mayor beneficio de esta aproximación es que un subconjunto de usuarios empieza a usar el software pronto. Y aunque no sean necesariamente el mercado objetivo, proveen feedback del mundo real.

    Divide por ejemplos de utilidad

    Una situación difícil es partir una historia cuando hay una gran tarea técnica que hacer, como cambiar una base de datos o un gran cambio de diseño. Esto a veces lleva a divisiones que son demasiado finas para ir a producción de forma independiente o a esas monstruosidades a veces conocidas como historias técnicas.

    En vez de partir por los entregables técnicos y luego agrupar el valor que hay en ellos, prueba lo contrario: parte de los entregables de valor de negocio y luego agrupa las necesidades técnicas.

    Esto evita caer en la trampa técnica del gran riesgo de hacer una gran migración al final. Este enfoque convierte los entregables en un flujo de pequeños cambios, cada uno lo suficientemente valioso como para que pueda pasar a producción y ser utilizado por alguien.

    Divide por capacidad

    Puedes abrir discusiones para conseguir historias más pequeñas y conseguir feedback antes si ves la capacidad como una dimensión que puede ser entregada progresivamente.

    Hay muchos tipos distintos acerca de la capacidad que pueden entregarse iterativamente, trata de considerar varias dimensiones al partir historias. Algunas ideas por dónde empezar:

    • Tamaño de ficheros.
    • Duración de las sesiones.
    • Número total de usuarios.
    • Pico de uso (en usuarios concurrentes o volumen de datos).
    • Número de elementos para un usuario (como ficheros o recursos).

    Comienza con un dummy, luego hazlo dinámico

    Hay entornos donde la posibilidad de acceder a datos de referencia involucra más tiempo de espera que de trabajo efectivo (necesidad de autorizaciones, falta de documentación, peculiaridades de sistemas legacy…).

    Partir las historias para poder trabajar con datos hard-coded al inicio mejora la predictibilidad para los planes a corto plazo y permite no bloquear el desarrollo de otras funcionalidades.

    Elegir correctamente los campos para dejar hard-coded es clave para que esta técnica funcione bien. Los candidatos ideales son los que aumentan significativamente el esfuerzo o la incertidumbre si se cargan dinámicamente, pero cambian con poca frecuencia.

    Simplifica los outputs

    Especialmente en sistemas enterprise complejos puede reducir el riesgo significativamente para los planes a corto plazo, ya que a menudo las historias que involucran sistemas externos o legacy se bloquean o quedan incompletas. Partir una historia en una que usa un canal de salida y otra que lo traslada los datos a un sistema legacy divide el riesgo.

    Por ejemplo, si una historia involucra exportar a PDF, Excel y fichero dividido por tabs, divídelo en una historia por cada uno de los formatos de salida.

    Divide el aprendizaje de la ganancia

    El desarrollo de software a veces involucra tratar con lo desconocido (sistemas de terceros, nuevos estándares, evaluar el riesgo de cambios de infraestructura…).

    Las historias de aprendizaje ayudan a los stakeholders a planear mejor. Mientras que las de ganancia ayudan a dar valor a los usuarios finales.

    Los criterios de aceptación de las historias de aprendizaje se tienen que trabajar con los stakeholders para identificar qué tipo de información necesitan para dar el trabajo como hecho o no. Decidiendo previamente cuánto tiempo quieren invertir en obtener esa información.

    Planificar historias de aprendizaje con duración limitada evita que la investigación se convierta en un trabajo vago e incontrolado que introduce variabilidad. También evita largos análisis iniciales.

    Extrae la utilidad básica

    En situaciones donde un proceso de negocio debe implementarse en su totalidad para que sea útil, una buena opción para dividir una historia es simplificar la interacción del usuario al mínimo. En lugar de usabilidad, brinde a los usuarios una utilidad básica.

    Esta técnica funciona especialmente bien para partir las historias que son críticas de entregar en el tiempo en una que se mantendrá crítica y otra que se puede gestionar sin un deadline.

    Cuando todo lo demás falla, trocea la hamburguesa

    User Story Hamburger es una técnica de facilitación que puede ayudar a los equipos a pensar en cómo partir de una forma orientada a valor cuando están atascados en cómo es el flujo técnico de una historia grande y en casos de uso de todo o nada.

    Cómo crear una hamburguesa:

    1. Lista los componentes técnicos involucrados de forma vertical.
    2. Define atributos de calidad para cada uno de los componentes.
    3. Lista las opciones de los diferentes niveles de calidad posibles para cada componente de forma horizontal.
    4. Elimina las opciones que no son satisfactorias para cumplir con un nivel de servicio útil.
    5. Elimina las opciones que cuestan aproximadamente el mismo esfuerzo o más que las de mayores niveles de calidad.
    6. Elige una rebanada.

    Al Mapear opciones, esta técnica facilita discusiones sobre completar las necesidades de un subgrupo de usuarios más rápidamente o para desplegar sólo una parte de un caso de uso que pueda proveer valor igualmente.


    Gestionando la entrega iterativa

    No pongas todo en historias

    Hay montones de cosas que cualquier equipo de software necesita hacer que no encajan conceptualmente como historias de usuario. Preparar máquinas, entornos de test, actualizar librerías, incrementar la velocidad de despliegue…

    Si el equipo tiene un presupuesto de tiempo para poder dedicarlo a trabajar en otros incidentes, puede acumular holgura o slack para hacer frente a interrupciones inesperadas. De este modo la planificación a corto y largo plazo se va volviendo más precisa y el equipo se va haciendo más productivo.

    Los equipos junto con los stakeholders deben decidir sobre el tiempo reservado y revisarlo periódicamente.

    Presupuesta el tiempo en lugar de estimarlo

    Evita perder tiempo en análisis innecesarios relacionados con el alcance y establece un compromiso para entregar valor de negocio.

    La mejor manera de decidir un presupuesto de tiempo, tanto en términos de tiempo como de dinero, es observar el beneficio de negocio y estimar su valor para los stakeholders.

    Cuando no se encuentre un buen modelo de valor prueba los dos enfoques siguientes:

    • Pregunta por los extremos: ¿cuándo lo usarías si estuviera listo? ¿qué es lo mínimo con lo que aún recibirías valor?
    • Presupuesta de forma incremental: en casos de alta incertidumbre puedes presupuestar los aprendizajes (prototipos, tests con usuarios, procesos semi manuales…) hasta llegar a la solución final.

    Evita usar números en el tamaño de las historias

    Los equipos poco experimentados a menudo usan el tamaño numérico de las historias para la planificación a largo plazo y la gestión de capacidad, lo cual es perjudicial y engañoso.

    El uso de un método de dimensionamiento más simple ayuda a los equipos a pasar por la planificación de la historia más rápido y a tomar la decisión más importante en función su tamaño (si debe desglosarse más o menos).

    Una buena idea es seleccionar varias historias representativas como referencia y comparar las nuevas historias con ellas.

    Estima la capacidad en función de una media móvil del número de historias acumuladas

    El número total de historias de tamaño similar es más simple y más fácil de calcular que un agregado de números arbitrarios como los puntos de historia. Y el resultado también será más preciso.

    Tal y como el producto madura o el equipo crece o se encoge, la media móvil sólo comparará la capacidad con eventos recientes, no con la historia completa.

    Usa la media móvil solo dentro del equipo para la planificación de la capacidad de iteración. Nadie fuera del equipo necesita saber esta media.

    Estima la capacidad basada en el tiempo de análisis

    Usar el tiempo que lleva el análisis de una historia como indicador de su complejidad es una alternativa a medir tamaños de historia o contar número de historias.

    Marca un timebox para evitar que un solo elemento complejo tome demasiado tiempo de análisis.

    Elige impactos en lugar de priorizar historias

    En lugar de priorizar historias, intenta elegir los impactos más importantes en los clientes o usuarios. Los impactos son mucho más fáciles de discutir y comparar que las historias.

    Idealmente selecciona un impacto para trabajar cada vez.

    Nunca digas “no”, di “no ahora”

    Las organizaciones con una buena gestión de producto saben cuándo decir que no, pero cuando no hay una visión sólida de los productos los equipos suelen acabar con problemas al aceptar todo.

    Cuando alguien propone un cambio, pídele que revise si la propuesta se ajusta al objetivo de negocio actual. De lo contrario, ofrece dejar de trabajar en el objetivo actual y priorizar algún otro impacto o posponer el cambio propuesto para más adelante.

    La trampa típica es que cada idea se declara lo suficientemente crítica como para cambiar el objetivo actual, para evitarlo:

    • Proporciona un canal de baja fricción para que los stakeholders cambien de dirección a intervalos regulares.
    • Proporciona un canal de alta fricción para evitar que se provoquen cambios crítico entre esos intervalos.

    Divide las mejoras de UX del trabajo de revisión de consistencia

    Las mejoras de UX no necesitan pequeñas historias de usuarios, necesitan trabajar en el alto nivel sobre impactos y cambios de comportamiento.

    Después de la priorización de alto nivel utilizando cosas como la selección de impactos clave, comienza con la investigación de UX antes de decidir sobre las historias de usuario concretas.

    Para el trabajo en marcha, involucra a los diseñadores en hacer pruebas exploratorias para detectar inconsistencias. Más tarde evita esos problemas futuros creando un checklist de revisión de UX.

    Haz que los usuarios finales participen en grandes cambios en la interfaz de usuario

    El gentle deployment introduce cambios significativos en el producto y evita sorpresas desagradables para los usuarios, desplegando una versión paralela del producto e invitando a los usuarios a probarlo a su gusto.

    Pedirle a la gente que cambie de versión reduce el factor sorpresa. Las personas que elijan explícitamente usar la nueva versión estarán preparadas para pequeñas inconsistencias.

    Verifica outcomes con usuarios reales

    Es absolutamente esencial que pruebes tus ideas con usuarios finales reales. Podría decirse que es la parte más importante de tu trabajo.

    Escribe historias de usuario para que puedan verificarse los outcomes después de la entrega. Y, de hecho, ve y verifica con usuarios reales que los resultados planificados se han materializado.

    Tira las historias después de que se entreguen

    Muchos equipos se quedan apegados a la idea de usar las historias hechas como documentación, esto es insostenible incluso para productos no demasiado complejos.

    Las especificaciones y pruebas organizadas por áreas funcionales describen el comportamiento actual sin la necesidad de que alguien comprenda toda la historia.


    Obviamente estas anotaciones no pretenden servir como sustitución al libro, pero tal vez resulte de inspiración para investigar más en profundidad alguna de estas ideas.

    » Leer más, comentarios, etc...

    Blog Bitix

    La controversia sobre el sistema de inicio systemd adoptado en GNU/Linux

    mayo 17, 2020 10:00

    systemd ya tiene una década de desarrollo, ha sido adoptado como sistema de inicio en las distribuciones GNU/Linux más importantes como Debian, Ubuntu, Fedora o Arch Linux y derivadas. Durante este tiempo ha recibido múltiples críticas en varios aspectos. Sigue evolucionando cambiando aspectos importantes de cómo han sido siempre las distribuciones, uno de los próximos es posible que sea systemd-homed con la intención de hacer portables y autocontenidos las carpetas de inicio de los usuarios.

    systemd

    Linux

    El sistema de inicio y control de procesos systemd creado por Lennart Poettering ha reemplazado en la mayoría de distribuciones GNU/Linux al más antiguo sistema SysVinit. A systemd se le ha criticado en varios aspectos incluso algunas personas llegando a crear forks de distribuciones con el principio de no usar systemd.

    Poettering escribió un artículo respondiendo a 30 de los mayores mitos sobre systemd. También hay opiniones contrarias a systemd, otra persona respondía con 13 de las mayores falacias acerca de systemd. Aún con las críticas en su recepción que se ha hecho a systemd es admirable la fuerza de voluntad y determinación de Lennart Poettering que un día se propuso hacer un sistema de inicio que sea usado en toda la base de distribuciones de Linux, es capaz de sobreponerse a no hacerlo y un día proporciona ese sistema de inicio que funciona siendo adoptado en la mayoría de distribuciones Linux.

    Componentes de systemd Jerarquía unificada del kernel Linux, cgroups y systemd

    Componentes de systemd y jerarquía unificada del kernel Linux, cgroups y systemd

    En un comentario de reddit el mantenedor de los script init para Arch Linux compartía varios puntos en los que systemd es mucho mejor que los scripts init. Hay que tener en cuenta que los sistemas modernos actuales son más complejos, dinámicos y asíncronos que lo eran en la época que se creó SysVinit. No es posible determinar cuando una pieza de hardware estará disponible por ejemplo con los medios extraíbles. Durante bastante tiempo, esto ha sido resuelto lanzando eventos y esperando a udev. Esto toma mucho tiempo sin haber garantía que todo el hardware está disponible. Hacer esto con scripts de shell puede ser muy complejo, lento y propenso a errores. Hay que reintentar todo tipos de operaciones en un bucle hasta tener éxito. La solución es un sistema que realice acciones basado en eventos, esta es una de las más importantes características de systemd.

    Estos eran varios de los problemas de los scripts init:

    • Los scripts init son estúpidos. En su primera fase son una serie de pasos estáticos que son ejecutados en cada inicio sin casi posibilidad de ajustar el comportamiento. En su segunda fase los demonios son iniciados en orden lo que significa que cada script init es llamado en serie uno detrás de otro.
    • Las complejas tareas en los scripts shell requieren lanzar muchos programas externos de ayuda. Esto hace las cosas lentas. systemd trata la mayoría de estas tareas en código C o mediante las librerías correctas. No llama a muchos programas externos para realizar sus tareas.
    • El proceso de inicio completo está serializado lo que también lo hace muy lento. systemd puede paralelizarlo y lo hace bastante bien.
    • No hay indicación de cuándo un cierto demonio ha sido iniciado. Cada script init tiene que implementar algún tipo de manejo de archivos PID o similar. La mayoría de los scripts init no lo hacían. systemd tiene una solución totalmente confiable basada en los cgroups de Linux.
    • Eran posibles condiciones de carrera entre demonios iniciados con reglas udev, activación de dbus y configuraciones manuales. Puede ocurrir que un demonio sea iniciado múltiples veces incluso simultáneamente, lo que ocasiona resultados inesperados (esto era un problema real con bluez). systemd proporciona una única instancia donde todos los demonios son manejados. Ahora ni udev ni dbus inician demonios, ahora le dicen a systemd que necesitan un demonio específico y systemd se preocupa de ello.
    • Falta de configurabilidad. Era imposible cambiar el comportamiento de los scripts init de una forma que sobreviviese a las actualizaciones del sistema. systemd proporciona buenos mecanismos con redefiniciones específicas para la máquina, elementos que estén presentes y ocultamiento.
    • Mantenimiento costoso. Adicionalmente a los problemas de diseño mencionados los scripts init también tenían un largo número de errores. Corregir esos errores era siempre complicado y tomaba tiempo que no siempre se disponían mantenedores. Delegar esta tarea a una comunidad más grande, en este caso a la comunidad de systemd, ha hecho las cosas mucho más fáciles para los mantenedores.

    Aunque algunos de estos problemas pueden ser resueltos con algo de trabajo y algunos han sido resueltos por otros sistemas de inicio basados en SysV no hay ningún sistema que haya resuelto todos estos problemas y lo haya hecho de una manera confiable como lo ha hecho systemd. Lo que la mayoría de las críticas consideran bloat el mantenedor de Arch los considera una complejidad necesaria para resolver problemas complejos de una manera genérica. Se puede decir lo que se quiera de Poettering pero él se ha dado cuenta de cuáles eran los problemas del sistema de inicio y ha proporcionado una solución que funciona.

    Las críticas que se le hace a systemd son:

    • Viola la filosofía de UNIX. La filosofía de UNIX es que cada programa debe tener un propósito específico, si es necesario crear varios programas uno por cada propósito. Se dice que systemd es un único binario que viola la filosofía UNIX, la realidad es que systemd se compone de múltiples binarios, pero están separados y modularizados. En su uso dependen unos de otros, no pueden usarse sin los otros y esta es la violación a la filosofía UNIX y por la que en este aspecto no se los considera modulares.
    • Está sobrecargado (bloated) y es monolítico. De nuevo systemd está compuesto de múltiples binarios no es un un único binario.
    • Tiene errores. Como todo software, en el caso de un sistema de inicio por su criticidad son más notables. Cualquier otro sistema de inicio no estaría exento de errores. Tiene manejo de errores que en vez de fallar y tumbar el sistema deja el sistema en un punto que al menos se puede reiniciar.
    • No es portable. systemd es específico de Linux ya que usa varias funcionalidades que no están presentes en otros sistemas, una de ellas cgroups para el manejo de procesos. Esto aísla al resto de plataformas como la familia de sistemas operativos BSD que no tiene esas funcionalidades pero al mismo tiempo esas plataformas son libres de usar el sistema de incio que deseen.

    Algunas distribuciones no usan systemd o es opcional, dos notables son Gentoo y Alpine Linux que usan OpenRC. Otras alternativas son runit, procd y supervisor. La distribución Devuan que surgió como propósito principal no usar systemd abandera las distribuciones que no lo usan. En la wiki de Gentoo hay una compraración entre los diferentes sitemas de inicio.

    De esa comparación destaco dos cosas de systemd:

    • Los archivos de configuración de los servicios son proporcionados preferiblemente por los desarrolladores de los servicios y no los mantenedores de cada distribución, liberando a los mantenedores de esas tareas y haciendo que las mejoras en un servicio no sea exclusivo de una distribución sino que todas se beneficien de él.
    • El formato de los archivos de configuración de los servicios es descriptivo, no codificado en un lenguaje de programación con un script bash.

    » Leer más, comentarios, etc...

    Blog Bitix

    Introducción y uso básico de la distribución GNU/Linux Fedora Silverblue

    mayo 15, 2020 04:45

    Fedora Silbervlue es una distribución innovadora es su forma de sistema base usando OSTree e instalar aplicaciones gráficas con Flatpak y de paquetes de linea de comandos con Toolbox. Todas estas tecnologías le permiten considerarse una distribución rolling-release tanto en el sistema como aplicaciones y paquetes. Estas tecnologias y forma de actualizar el sistema hace que sea mucho menos propenso a errores que los tradicionales en las distribuciones GNU/Linux de actualización de paquetes, ¿la siguiente generación de distribuciones serán como Fedora Silverblue?.

    Distribuciones GNU/Linux hay muchas con diferencias en algunos aspectos, el más cercano al usuario es entorno de escritorio entre los más populares GNOME, KDE, XFCE, MATE o Cinnamon entre otros pero hay otros aspectos relevantes entre ellos el gestor de paquetes que utiliza, el modelo de actualización, en que otra distribución está basada, cual es su periodo de publicación de nuevas versiones, su popularidad, tiempo de vida o si está respaldada por una empresa.

    A pesar de estas diferencias la mayoría de las distribuciones para los usuarios son parecidas en muchos aspectos. Tradicionalmente cada distribución tiene su repositorio de paquetes y su comando gestor de paquetes con el que es posible instalar y desinstalar paquetes. La mayoría usa systemd como sistema de inicio para controlar los procesos y servicios. El entorno de escritorio GNOME o KDE es el mismo en cada distribución si no se tiene en cuenta que las versiones puedan ser diferentes.

    Las distribuciones GNU/Linux han cambiado en algunos aspectos importantes como la sustitución del sistema de inicio de SysV a systemd o cambiado el servidor gráfico Xorg por Wayland y van a hacerlo más en el futuro con el nuevo sistema para crear VPNs con WireGuard o el sistema multimedia para sonido y vídeo PipeWire.

    En mi caso utilizo Arch Linux desde hace ya casi una década y estoy contento con ella. Los motivos que tengo para preferir esta distribución son su modelo de actualizaciones rolling-release en el que cada actualización todos los paquetes se actualizan a la última versión, su gestor de paquetes pacman que es muy rápido, los paquetes tiene pocas modificaciones realizadas por los mantenedores, es altamente personalizable y también no menos importante casi como todo lo anterior su documentación wiki con información muy útil para cualquier usuario de GNU/Linux.

    Arch Linux es una de las distribuciones más populares pero no proporciona ningún asistente automatizado de instalación sino que después de arrancar el medio de inicio hay que introducir los comandos manualmente uno a uno hasta completar la instalación. Esto la hace difícil para los usuarios recién llegados a GNU/Linux o para los usuarios que no desean invertir tiempo en conocer cómo instalarla. También incluso para los usuarios expertos es que ya conocen como instalarla pero que el hecho de hacerlo manualmente es un tiempo que a veces no se dispone además de repetitivo.

    Por esos motivos creé un script de instalación de Arch Linux completamente automatizado y desatendido con cierto grado de personalización en las opciones más comunes, es un simple script bash con todos los comandos que componen el proceso de instalación y que simplemente revisarlo sirve como documentación que además si se desea se puede ejecutar.

    Pero a pesar de todo Arch Linux se basa en los principios básicos de los que hasta hoy han estado basadas las distribuciones. Gestor de paquetes, repositorio de paquetes y actualizaciones frágiles. Es muy posible que las distribuciones cambien tal y como las hemos conocido hasta ahora, ya se está produciendo cambios con Flatpak como sistema de instalar aplicaciones independientemente de la distribución y mantenidos por los propios desarrolladores del software y no los mantenedores de la distribución.

    Una distribución que se basa en principios diferentes que pueden ser el futuro próximo es Fedora Silverblue.

    Fedora Silverblue

    Fedora Silverblue

    La distribución Fedora Silverblue

    Una de las mayores fuentes de problemas de las distribuciones y de los sistemas operativos son las actualizaciones que por los cambios que introducen con nuevas versiones del software en ocasiones hace que algunas partes dejen de funcionar. Son solucionables desactualizando un paquete o en los casos más graves hace que el sistema ni siquiera se inicie correctamente llegando incluso a tener que reinstalar el sistema o peor aún provocando pédida de datos.

    Otro problema es que algunas distribuciones tienen como principio la estabilidad del software y dado que las nuevas versiones de los programas son una fuente de inestabilidades optan por únicamente proporciona actualizaciones para errores de seguridad. Esto proporciona una mayor estabilidad pero hace que los programas no estén actualizados a las últimas versiones con lo que no se benefician de mejoras en nuevas características, mejoras de soporte de hardware, de rendimiento o incluso de seguridad.

    Fedora Silverblue adopta varios principios para solucionar esos problemas. Uno es utilizar una base inmutable igual para todos los sistemas en los que se instala de modo que no haya diferencias ni errores por variaciones en el software del sistema. Es posible volver a una versión anterior en caso de algún error de modo que el sistema nunca quede completamente roto. Las aplicaciones de usuario y paquetes se instalan independientemente del sistema base inmutable lo que hace que no afecten a la estabilidad del sistema.

    Fedora Silverblue Fedora Silverblue Fedora Silverblue

    Fedora Silverblue

    Las tecnologías que permiten adoptar esos principios a Fedora Silverblue son OSTree para el sistema base inmutable, Flatpak para las aplicaciones de usuario gráficas y Toolbox para instalar software de línea de comandos en contenedores.

    OSTree es un proyecto que combina un modelo parecido a git para establecer y descargar árboles de sistemas de archivos de arranque, junto con una capa para disponerlos y gestionarlos con la configuración de arranque. OSTree es usado por rmp-ostree, un sistema híbrido de paquete e imágenes que usa Silverblue. Replica de forma atómica un sistema operativo base que permite al usuario añadir capas de paquetes RPM tradicionales encima del sistema base si se necesita.

    Instalación

    La instalación se realiza con un asistente gráfico después de haber descargado el medio de instalación y haberlo grabado en una memoria USB para inciar el sistema con él. Es necesario poco más que seleccionar la distribución del teclado, el particionado y la clave del superusuario root para realizar la instalación.

    Instalación de Fedora Silverblue Instalación de Fedora Silverblue Instalación de Fedora Silverblue

    Instalación de Fedora Silverblue Instalación de Fedora Silverblue Instalación de Fedora Silverblue

    Instalación de Fedora Silverblue

    Instalación de Fedora Silverblue

    Primer inicio

    Al iniciar el sistema por primera vez un nuevo asistente permite crear la cuenta de usuario compuesto de nombre y contraseña con la que iniciar sesión en el sistema.

    Primer inicio Primer inicio Primer inicio

    Primer inicio Primer inicio Primer inicio

    Primer inicio

    Primer inicio

    Al usar GNOME como entorno de escritorio no se diferencia a cualquier otro sistema con GNOME. La mayor diferencia está en que las aplicaciones preinstaladas son muy pocas, reduciéndose a las básicas como el navegador Firefox, la terminal, el explorador de archivos y editor de texto. Este permite al usuario tener instaladas únicamente las aplicaciones que desee o no tener que desinstalar las aplicaciones que no desea. Las aplicaciones que se deseen se deben instalar con Flatpak.

    Administración del sistema, actualización

    El software que compone el sistema base se puede actualizar, los siguientes comandos permiten conocer cuales son las actualizaciones disponibles. Las actualizaciones están integradas con el programa Software de GNOME que muestra notificaciones cuando hay alguna actualización disponible.

    Pero también puede realizarse desde la línea de comandos. El siguiente comando realiza la operación de actualización.

    1
    
    $ rpm-ostree upgrade
    rpm-ostree-upgrade.sh

    Para simplemente comprobar que actualizaciones hay disponibles sin instalarlas.

    1
    2
    
    $ rmp-ostree status
    $ rpm-ostree upgrade --check
    rpm-ostree-status.sh

    Para actualizar entre versiones mayores, de la 32 a posteriores, de Foedora Silverblue se utilizan los siguientes comandos en los que cambiará el número de la versión.

    1
    2
    3
    
    $ ostree remote list
    $ sudo ostree remote gpg-import fedora -k /etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-32-primary
    $ rpm-ostree rebase fedora:fedora/32/x86_64/silverblue
    rpm-ostree-rebase.sh

    La mayoría del software de usuario se instala con Flatpak y Toolbox siendo la forma recomendada de hacerlo. Sin embargo, algunos programas ha de instalarse modificando la instalación de Silverblue utilizando package layering, como un intérprete de comandos distinto a bash. La mayoría de paquetes RMP es posible instalarlos, aún así este método debe usarse en casos excepcionales que no sea posible hacerlo con Flatpak o Toolbox ya que podría comprometer la estabilidad del sistema.

    1
    
    $ rpm-ostree install <package name>
    rpm-ostree-install.sh

    Actualización Actualización Actualización

    Actualización

    En caso de que una actualización produzca algún error se puede volver a la versión anterior con el siguiente comando.

    1
    
    $ rpm-ostree rollback
    rpm-ostree-rollback.sh

    Instalación de programas gráficos

    Las aplicaciones de usuario gráficas se instalan con Flatpak y en el caso de GNOME con la aplicación Software. A medida que pasa el tiempo hay más programas disponibles en esta forma de distribuir software y muchos de los programas más comunes está disponibles como la colección ofimática LibreOffice, el reproductor multimedia VLC, el editor de texto avanzado Visual Studio Code o el entorno de desarrollo integrado IntelliJ.

    Con la aplicación de software es posible encontrar todo este software, instalarlo y desinstalarlo con un clic en un botón. Lo único necesario es añadir el repositorio Flathub como fuente de programas.

    Repositorio Flathub Repositorio Flathub

    Repositorio Flathub

    Instalación de software Instalación de software Instalación de software

    Instalación de software

    Instalación de software

    Dos programas instalados como paquetes Flatpak.

    Intellij IDEA Visual Studio Code

    Programas instalados como Flatpak

    También es posible instalar el paquetes Flatpak desde la linea de comandos.

    Uso de Toolbox, programas de línea de comandos en contenedores

    El resto de paquetes de línea de comandos se pueden instalar en contenedores con Toolbox basados en podman que es una alternativa compatible de Docker. Lo especial de estos contenedores es que tiene acceso a la carpeta personal o directorio home del usuario de modo que pueden crear archivos o modificar los existentes en esta ubicación.

    Destro de estos componentes se instalan los paquetes con el gestor de paquetes dnf.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    $ toolbox run dnf search openjdk
    Last metadata expiration check: 5 days, 18:10:47 ago on Sun May 10 00:09:19 2020.
    ======================= Name & Summary Matched: openjdk ========================
    java-11-openjdk-demo.x86_64 : OpenJDK Demos 11
    java-1.8.0-openjdk-demo.x86_64 : OpenJDK Demos 8
    java-latest-openjdk-demo.x86_64 : OpenJDK Demos 14
    java-11-openjdk-jmods.x86_64 : JMods for OpenJDK 11
    java-11-openjdk-src.x86_64 : OpenJDK Source Bundle 11
    java-11-openjdk.x86_64 : OpenJDK Runtime Environment 11
    java-11-openjdk.i686 : OpenJDK Runtime Environment 11
    java-latest-openjdk-jmods.x86_64 : JMods for OpenJDK 14
    java-1.8.0-openjdk-src.x86_64 : OpenJDK Source Bundle 8
    java-latest-openjdk-src.x86_64 : OpenJDK Source Bundle 14
    ....
    
    $ sudo dnf install java-11-openjdk.x86_64
    $ java -version
    toolbox-package-install.sh

    Instalación de programas de línea de comandos Instalación de programas de línea de comandos Instalación de programas de línea de comandos

    Instalación de programas de línea de comandos

    Para no modificar el sistema base de Silverblue otra forma de instalar Java es con la utilidad sdkman que además permite cambiar entre versiones fácilmente y tener acceso a diferentes implementaciones del JDK, entre otras utilidades instalables con esta herramienta.

    1
    2
    
    $ sdk list java
    $ sdk install java 11.0.7-open
    sdk-usage.sh

    Conclusión

    Si tuviese que probar o usar otra distribución diferente Arch Linux probablemente la que elegiría sería Fedora Silverblue por los principios innovadores en las que está basada que proporcionan varias mejoras en puntos importantes sobre las distribuciones como las hemos conocido tradicionalmente. Igualmente permite tener el software actualizado, es también rolling-release y mejora la fiabilidad de las actualizaciones.

    En estos vídeos se proporciona una introducción sobre esta distribución que quizá marque el camino de aquí en adelante para otras.

    » Leer más, comentarios, etc...

    Picando Código

    Paquete para Emacs: helm-treemacs-icons

    mayo 13, 2020 07:15

    Hace un tiempo me encontré con este paquete para Emacs que me resultó sumamente útil. Integra helm, el sistema que uso para cambiar de buffers o archivos, con treemacs (y treemacs-icons), el sistema que muestra íconos en el árbol de archivos de un proyecto. De esta forma, podemos ver los íconos de los archivos cuando estamos usando helm para cambiar de buffer. Esto hace mucho más práctico el cambio de buffers y la navegación de archivos. Al tener un ícono que representa el tipo de archivo, y el cerebro va aprendiendo a identificar los íconos, me viene resultando todavía más rápido encontrar por tipos de archivo (generalmente mantengo sesiones de Emacs por muchos días y a veces hasta cientos de buffers y archivos abiertos). Sumado a que helm permite ir filtrando por texto, es una forma súper práctica de cambiar de uno a otro.

    Lo pueden encontrar en GitHub: yyoncho/helm-treemacs-icons.

    helm-treemacs-icons

    Por ahora, para usar el paquete hay que instalarlo manualmente. Pero el README promete instalación con Melpa próximamente. Las instrucciones son: asegurarse que tengamos helm y treemacs instalados, clonar el proyecto y agregarlo al path de Emacs. En mi caso estoy usando Spacemacs, y lo agregué así:

    (load-file "~/.emacs.d/private/helm-treemacs-icons/helm-treemacs-icons.el")
    (require 'helm-treemacs-icons)
    (helm-treemacs-icons-enable)
    

    Creo que este mismo código debería funcionar en cualquier configuración de Emacs, siempre y cuando definamos bien el path de dónde cargar el archivo helm-treemacs-icons.el.

    Le pregunté al desarrollador cómo agregarlo como capa a Spacemacs, y me respondió que tiene pensado agregarlo a la capa treemacs, algo que me parece una buena idea. Así que posiblemente lo veamos como parte de treemacs en algún momento.

    Hubo un Pull Request inicial a melpa para agregar este paquete. Pero el autor lo cerró comentando que renombraría el paquete a helm-icons y lo integratía con all-the-icons. Pero esto fue antes de las últimas actualizaciones. Mientras tanto, funciona bastante bien y resulta súper práctico, así que seguiré atento a su desarrollo.

    » Leer más, comentarios, etc...

    Variable not found

    Cómo mostrar el número de usuarios conectados a una aplicación Blazor Server, en tiempo real

    mayo 12, 2020 02:09

    BlazorEn Blazor Server, cada usuario que esté utilizando la aplicación establece una conexión SignalR para enviar al servidor sus interacciones con la página, así como para retornar de vuelta al usuario las modificaciones del DOM realizadas desde el servidor. Esta conexión, junto con la información del estado del cliente almacenada en el servidor, es lo que en Blazor Server se denomina un "circuito".

    Y ya que el servidor es consciente en todo momento de los usuarios conectados, en teoría debería ser posible contarlos para, por ejemplo, mostrarlos en pantalla como en la siguiente captura:

    Aplicación Blazor mostrando el número de usuarios conectados

    En este post vamos a ver cómo conseguir precisamente eso: incluir en las páginas del proyecto un contador de usuarios conectados que irá actualizándose en tiempo real, para, por el camino, profundizar un poco en el ciclo de vida de los circuitos de Blazor Server.

    1. El servicio contador de usuarios

    Antes de profundizar en las particularidades de Blazor Server, vamos a implementar un servicio bastante sencillito, que nos ayudará a mantener en memoria el contador de usuarios conectados y nos ofrecerá un par de métodos para gestionar los cambios.

    El código, como se puede comprobar, habla por sí mismo:
    public class UserCountService
    {
    public event EventHandler<int> OnChanged;
    private int _counter = 0;
    public int CurrentUsersCount => _counter;

    public void Increment()
    {
    Interlocked.Increment(ref _counter);
    OnChanged?.Invoke(this, _counter);
    }

    public void Decrement()
    {
    Interlocked.Decrement(ref _counter);
    OnChanged?.Invoke(this, _counter);
    }
    }
    No hay nada más allá de lo normal, aunque quizás lo que más os llame la atención la definición del evento OnChanged que utilizamos para notificar a los consumidores de este servicio de que se han producido cambios en el número de usuarios conectados. Más adelante veremos por qué es necesario esto.

    Este servicio deberíamos registrarlo en el método ConfigureServices() como Singleton, pues sólo necesitaremos una instancia compartida durante todo el tiempo de vida de la aplicación:
    public void ConfigureServices(IServiceCollection services)
    {
    services.AddSingleton<UserCountService>()
    ... // More service registrations here
    }

    2. ¿Cómo nos enteramos cuando un usuario conecta o desconecta?

    Blazor permite introducir lógica personalizada en el ciclo de vida de los circuitos, gracias a la abstracción CircuitHandler. Esta clase abstracta define cuatro métodos que permiten tomar el control en distintos momentos:
    • OnCircuitOpenedAsync(): invocado cuando nuevo circuito es creado
    • OnConnectionUpAsync(): cuando la conexión ha sido establecida o restablecida
    • OnConnectionDownAsync(): la conexión con un cliente se cerró
    • OnCircuitClosedAsync(): notifica que el circuito ha sido eliminado
    En nuestro caso, simplemente heredaremos de CircuitHandler e implementaremos OnCircuitOpenedAsync() y OnCircuitClosedAsync() para incrementar y decrementar, respectivamente, el contador de usuarios conectados, utilizando para ello el servicio que hemos creado algo más arriba:
    public class UserCountCircuitHandler: CircuitHandler
    {
    private readonly UserCountService _userCountService;

    public UserCountCircuitHandler(UserCountService userCountService)
    {
    _userCountService = userCountService;
    }

    public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
    {
    _userCountService.Increment();
    return base.OnCircuitOpenedAsync(circuit, cancellationToken);
    }

    public override Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
    {
    _userCountService.Decrement();
    return base.OnCircuitClosedAsync(circuit, cancellationToken);
    }
    }
    Para hacer que Blazor Server conozca la existencia de nuestra clase UserCountCircuitHandler es neceario registrarla en el inyector de dependencias asociada a la clase abstracta CircuitHandler, de nuevo como Singleton porque sólo necesitaremos una instancia:
    services.AddSingleton<CircuitHandler, UserCountCircuitHandler>();
    Durante los cambios de estado del circuito, Blazor Server invocará los métodos correspondientes en todas las clases registradas como CircuitHandler.

    Con lo que hemos desarrollado hasta este momento, nuestra aplicación Blazor Server ya irá registrando las aperturas y cierres de circuitos, manteniendo el contador de usuarios sincronizado. Veamos ahora cómo mostrar esta información y actualizarla en tiempo real sobre la página.

    3. ¿Cómo mostramos los usuarios conectados?

    Sin duda, lo más sencillo es crear un componente Blazor reutilizable que muestre en todo momento del valor de la propiedad CurrentUsersCount del servicio UserCountService, algo como lo siguiente:
    @* File: UserCount.razor *@
    @inject UserCountService UserCountService

    <span @attributes="Attributes">
    @UserCountService.CurrentUsersCount
    </span>

    @code {

    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> Attributes { get; set; }
    }
    Observad que estamos utilizando CaptureUnmatchedValues en el parámetro Attributes, con objeto de expandir estos atributos en la etiqueta <span>. Puedes leer más sobre ello en el post "Capturar todos los parámetros enviados a un componente Blazor".
    De esta forma, podríamos utilizarlo desde cualquier componente de la siguiente forma:
    <UserCount style="font-weight: bold" />
    Sin probamos el código anterior, veremos que funcionará bien... al menos la primera vez. Si abrimos varias ventanas, podremos comprobar que este valor no se va modificando en tiempo real conforme se vayan abriendo nuevos circuitos.

    Pero si lo pensamos, tiene su lógica: UserCountService.CurrentUsersCount es modificado de forma asíncrona por otros hilos cuando otros circuitos cambian su estado, y la interfaz de usuario no es notificada para que actualice el valor. Por tanto, el contador que veremos siempre en pantalla será el valor inicial del contador en el momento de cargar el componente.

    4. Actualizar los usuarios conectados en tiempo real

    ¿Y cómo podemos notificar a la interfaz de usuario que el valor ha cambiado? Pues aquí es donde entra en juego el evento OnChanged que recordaréis que habíamos declarado en UserCountService y que lanzábamos cada vez que el contador era modificado:
    public class UserCountService
    {
    public event EventHandler<int> OnChanged;
    ...
    public void Increment()
    {
    ...
    OnChanged?.Invoke(this, _counter);
    }

    public void Decrement()
    {
    ...
    OnChanged?.Invoke(this, _counter);
    }
    }
    Para ser notificado de los cambios, el componente UserCount.razor debe suscribirse a este evento, y utilizarlo para refrescar la interfaz. El siguiente código muestra el código ya completo, y lo comentamos justo abajo:
    @* File: UserCount.razor *@
    @implements IDisposable
    @inject UserCountService UserCountService

    <span @attributes="Attributes">
    @UserCountService.CurrentCount
    </span>

    @code {

    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> Attributes { get; set; }

    protected override void OnInitialized()
    {
    UserCountService.OnChanged += UpdateCounter;
    base.OnInitialized();
    }

    private async void UpdateCounter(object sender, int counter)
    {
    await InvokeAsync(StateHasChanged);
    }

    public void Dispose()
    {
    UserCountService.OnChanged -= UpdateCounter;
    }
    }
    Lo primero a tener en cuenta, es que en OnInitialized() nos suscribimos al evento, pero debemos desuscribirnos cuando éste sea eliminado para evitar fugas de memoria. Para ello, hacemos que implemente IDisposable usando la directiva @implements, y utilizamos Dispose() para eliminar la suscripción.

    Por otra parte, observaréis que hemos implementado en UpdateCounter() el handler del evento lanzado cuando se producen cambios. En su interior, vemos que mediante InvokeAsync() aseguramos que el contexto de sincronización de Blazor, y en él ejecutamos StateHasChanged() para notificar al componente que su estado cambió, por lo que debe refrescar la interfaz.

    ¡Y eso es todo! Una vez unidas las piezas ya tendremos nuestro flamante componente <UserCount> capaz de mostrar en tiempo real el número de usuarios conectados a nuestra aplicación Blazor Server.

    Blazor counter en acción

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Variable not found

    Enlaces interesantes 403

    mayo 11, 2020 06:37

    Enlaces interesantesCuando una petición retorna un código de estado HTTP 403 (Forbidden), quiere decir que ésta fue realizada correctamente, pero el cliente no está autorizado para acceder al recurso o servicio que se intenta utilizar. Quizás porque la petición no incluyó las credenciales correctas, o tal vez porque eran insuficientes para acceder a él, pero la petición no debe ser repetida porque el resultado será el mismo.

    A veces se confunde con el HTTP 401, pero son muy diferentes:
    • HTTP 401 (Unauthorized) indica, a pesar de su nombre, un problema de autenticación, y debe evaluarse antes de decidir si el usuario tiene acceso o no al recurso concreto.
    • HTTP 403 (Forbidden) indica un problema de autorización, y es más específico que el anterior porque indica que el servidor sabe quién es el cliente y conoce el recurso al que intenta acceder, pero decide que no está autorizado a hacerlo.
    Y dicho esto, vamos a por nuestra ración de enlaces que, como es habitual, espero que os resulten interesantes :)

    Por si te lo perdiste...

    • Hace unos días me entrevistaron en la Resistencia Tecnológica, el divertido programa de Crossdvlup guiado por Alberto Díaz (@adiazcan), David Vidal (@D_Vid_45) y Sergio Hernández (@shmancebo). Algo más de hora y media charlando sobre la vida, el blog, ASP.NET, Blazor y algunas otras cosillas, que podéis ver en Youtube.
      Aparte, os recomiendo suscribiros al canal y echar un vistazo a los programas anteriores, porque podréis encontrar temas muy interesantes y personajes ilustres :)

    .NET Core / .NET

    ASP.NET Core / ASP.NET

    Azure / Cloud

    Conceptos / Patrones / Buenas prácticas

    Data

    Machine learning / IA / Bots

    Web / HTML / CSS / Javascript

    Visual Studio / Complementos / Herramientas

    Xamarin

    Otros

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Picando Código

    Plataforma de videoconferencia y colaboración íntegramente en euskera y basada en software libre

    mayo 08, 2020 02:07

    La empresa vasca Irontec ofrece una plataforma de videoconferencia y colaboración gratuita íntegramente en euskera. Está basada en Jitsi una plataforma software libre para mensajería instantánea, videoconferencia y llamadas. Una alternativa a Zoom, Hangouts y demás plataformas privativas: https://meet.irontec.com/

    Del comunicado de prensa:

    Desde mediados de marzo, y en el contexto actual de crisis sanitaria provocada por la Covid-19, ​Irontec​ ha acompañado a muchos de sus clientes a la hora de incorporar soluciones tecnológicas que hagan posible que la comunicación y la actividad siga fluyendo. Ahora, con el objetivo de aportar nuevos recursos a la sociedad, la empresa bilbaina ha comenzado a ofrecer un servicio gratuito de colaboración y videoconferencia localizado al euskera y basado en tecnologías abiertas.

    Irontec Meet: plataforma de videoconferencia en euskera

    Con el fin de seguir contribuyendo dentro de sus posibilidades, la compañía ha creado “Irontec Meet“, una plataforma de videoconferencia y colaboración sobre la tecnología open source de Jitsi​, que ha sido montada en su propia infraestructura cloud. Además, teniendo en cuenta la realidad de nuestro entorno, la ofrece íntegramente en euskera y de forma gratuíta en estos momentos difíciles.

    Desde ahora y quien lo desee, ya sean empresas, administraciones, colegios, universidades, grupos de amigos, familias etc., puede hacer uso de “Irontec Meet”, una solución que permite la realización videollamadas en HD con tiempo ilimitado y desde cualquier dispositivo. La localización al euskera ha sido llevada a cabo de forma colaborativa y solidaria por parte del equipo de Irontec y están preparando los ficheros necesarios para aportar al proyecto Jitsi estas traducciones en los próximos días.

    “Irontec Meet” se integra en la iniciativa Open Solidarity

    Durante la crisis sanitaria actual, son muchas las empresas que, de forma altruista, están poniendo a disposición de la sociedad diferentes ​recursos y herramientas tecnológicas​. Con el lanzamiento de “Irontec Meet”, la empresa se suma a ​Open Solidarity​, una iniciativa colectiva y abierta de ​cooperación digital ​lanzada desde OVH Cloud, cuyo objetivo es ofrecer soluciones tecnológicas solidarias y gratuitas​ basadas en sus infraestructuras mientras dure la crisis sanitaria.

    Jitsi meet: la solución open source ilimitada, abierta y accesible

    Jitsi Meet es una plataforma abierta y gratuita, desarrollada y liberada inicialmente por la empresa Atlassian y comprada por la empresa 8×8 en 2018, con el compromiso de mantenerla abierta y continuar con su desarrollo y mejora continua.

    En la actualidad, Jitsi ofrece desde su web acceso libre al uso de la plataforma, convirtiéndose así en una alternativa real a plataformas como Zoom, Teams, Hangouts, Webbex y demás sistemas de multivideoconferencia, ya que ofrece múltiples ventajas respecto a todas ellas:

    • 100% open source.
    • Usuarios y salas ilimitados.
    • Encriptación por defecto.
    • Audio HD.
    • Usable sin cuenta.
    • Funcionalidades avanzadas: chat, compartir escritorio y pantalla o streaming de sesiones.
    • Invitaciones a través de URLs sencillas personalizables.

    Jitsi meet, permite tener un sistema de colaboración y videoconferencias personalizado, seguro y privado, al poder ser desplegado en una infraestructura dedicada, como es el caso de “Irontec Meet”.

    » Leer más, comentarios, etc...

    Variable not found

    14 años buscando una variable

    mayo 06, 2020 10:54

    Fuegos artificialesHace pocos días celebraba con vosotros el décimo aniversario de la serie enlaces interesantes y su entrada número 400, y hoy vengo con otra buena noticia: ¡Variable not found cumple 14 años!

    Durante todo este tiempo, el blog ha formado parte de mi vida, y quiero creer que de algunas de las vuestras, aunque sea a pequeña escala. Esos minutillos que echáis los lunes para hacer vuestra propia selección de mis enlaces interesantes, cuando llegáis a él buscando cualquier asunto en Google, las veces que comentáis los contenidos, o cuando contactáis conmigo gracias a esta pequeña ventana en la red son la gasolina que hace que este motor que siga funcionando como el primer día.

    Y es que después de 1.200 posts, más de mil comentarios y superando holgadamente los cuatro millones de páginas servidas, creo que aún nos queda combustible para rato. Sigo disfrutando de plasmar por aquí lo que voy aprendiendo, y de poder aportar un pequeño granito de arena a la comunidad ayudando a alguien de vez en cuando :)

    Tradicionalmente aprovecho el cumpleaños del blog para echar un vistazo atrás y analizar cómo ha ido la cosa, así que allá vamos...

    El blog: cómo fue el año pasado

    Durante el año han pasado por aquí unas 113.500 personas, de los cuales son nuevos visitantes cerca del 85%. Superamos por poco las 200.000 páginas vistas, igual que el año pasado, y el tiempo de estancia sigue rondando los 4 minutos.

    Si hiciéramos una lectura "empresarial" del número de visitantes y seguidores del blog comparadas con el año anterior (un 3% de incremento), podríamos decir que nuestras ventas están estancadas. Pero como no es el caso, simplemente opino que estamos en un momento de calma dulce ;)

    No sé si será una visión optimista, pero creo que el nivel de visitas es razonable para ser un blog tan de nicho. El incremento porcentual de visitas que se ha producido respecto al año pasado es muy pequeño, casi insignificante, pero no podría esperar otra cosa dado que no dedico tiempo a promocionar el blog, ni publico más artículos de lo que suelo hacer, ni le he introducido cambios que pudieran afectar al SEO (por ignorancia, más que otra cosa)... de hecho, alguien me comentó una vez que quizás el hecho de publicar tantos enlaces podría afectar negativamente al posicionamiento, pero, bah, ¿qué sería esto sin nuestros enlaces?

    En cuanto a redes sociales, podríamos decir que sigue la misma tónica. En Twitter alcanzamos los 2.565 seguidores entre @jmaguilar y @variablnotfound, mientras que la página de Facebook ha superado recientemente los 1.000 amigos. En ambos casos creo que son números muy buenos teniendo en cuenta el poco tiempo que dedico a estas redes.

    Los visitantes de Variable not found siguen siendo principalmente hombres (79,6%), aunque las mujeres continúan avanzando posiciones, con un incremento ligeramente inferior al 1% respecto al año anterior. Lentamente, pero parece que algo está cambiando.

    También la juventud, desarrolladores entre 25 y 34 años, sigue constituyendo el mayor grupo de visitantes. Como cada año, se puede observar también un incremento de los lectores de mayor edad, señal de que todos vamos cumpliendo años y de que las canas no están reñidas con el desarrollo ;)

    El 80% utilizáis Chrome, el 10% preferís Firefox, y Edge sube posiciones hasta el 3%, seguido de cerca por Internet Explorer (2,5%), Opera (2,29%), Safari (1,18%) y otros, desde un ordenador en el 99,3% de las ocasiones.

    Google siendo la principal vía de acceso a los contenidos, alcanzando el 88,1% de las visitas; el 8,8% acude al blog de forma directa, y el 1,4% llegan a través de enlaces. El resto, casi insignificante, procede de las redes sociales, feeds y otras fuentes.
    Distribución geográfica de visitantes
    La distribución geográfica es muy parecida a la de otros años. La mayoría de los lectores proceden de México, superando el 25% de los visitantes totales. España ronda el 18%, seguida por los amigos de Colombia (10,64%), Perú (8,51%), Argentina (7,4%), Chile (5,84%) y varios orígenes más que no suben del 4% (Ecuador, Estados Unidos, Costa Rica, República Dominicana, Venezuela, Bolivia...)

    En resumen, Variable not found sigue siendo aquél pequeño rincón en Internet, humilde y sin pretensiones, que comenzó su andadura hace catorce años con la clara determinación de continuar ayudando a quien se acerque a él.

    Sigo sintiéndome afortunado de poder hacerlo y de las oportunidades, tanto personales como profesionales, que esto me ha brindado. Por ejemplo, hace poco tuve el placer de conocer en persona a alguien (¡hola, Sergio!) que me comentaba lo mucho que le gustaba el blog y que lo seguía desde el principio de los tiempos. ¿Hay algo más gratificante que esto?

    Una vez más, muchas gracias a todos por cada minuto que dedicáis a leer los contenidos, compartirlos o comentarlos, porque sois los que le dáis sentido a esto. Espero que sigáis por aquí al menos un año más, acompañándome a buscar la variable :)

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Fixed Buffer

    Cómo ejecutar pruebas de código dentro de contendores Docker

    mayo 05, 2020 08:00

    Tiempo de lectura: 9 minutos
    Imagen ornamental con el logo de Docker para la entrada Cómo ejecutar pruebas de código dentro de contendores Docker

    Una de las cosas que me encantan de mi trabajo es que me toca salir muy frecuentemente de mi zona de confort para solucionar las necesidades de los clientes. Este mes me ha tocado un caso del que he encontrado muy poca documentación y he tenido eso me ha llevado a varias charlas casi filosóficas con varios compañeros (y no, no ha sido sobre leer y escribir contadores de rendimiento o código superoptimizado).

    Por el título de la entrada ya imaginarás sobre que han tratado esas charlas… cómo y dónde hacer las pruebas de código cuando trabajamos con contenedores Docker. Si todo el trabajo que limitase a mi equipo local no habría ningún problema al respecto y cualquier modo sería suficiente, el problema viene cuando entra en juego la integración continua.

    Disclaimer: En esta entrada se da por supuesto que se conoce un mínimo de Docker y de pruebas de código en .Net. La idea es exponer el problema que me encontré y la solución que le di. Entrar en gran detalle requeriría de varias entradas para plantear los diferentes conceptos que se tocan.

    Planteemos el problema

    Por un lado, todo proyecto que desarrollemos debería ir acompañado de sus pruebas de código para asegurar la calidad, hasta aquí todo bien. Por otro lado, Docker nos facilita la vida al poder tener todo dentro de nuestro contenedor y no necesitar en nuestra máquina nada más que el propio Docker pero… ¿Cómo podemos juntar estas dos cosas?

    Es un escenario muy habitual en proyectos donde existe una integración continua el hecho de generar un informe de cobertura, utilizar un analizador estático como SonarCloud, o simplemente recoger elementos intermedios del proceso.

    Si estamos trabajando sin Docker, esto es muy sencillo ya que tenemos acceso total sobre todos esos elementos generados como pueden ser los reportes, aquí no hay fallo.

    Por el contrario, si estamos trabajando con Docker y no necesitamos recolectar ninguno de estos elementos, el proceso también es sencillo, basta con añadir un stage en el Dockerfile que ejecute las pruebas de código, de modo que no se genere nuestra imagen Docker si estas no pasan.

    Algo así podría bastar:

    FROM mcr.microsoft.com/dotnet/core/runtime:3.1-buster-slim AS base
    WORKDIR /app
    
    FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS restore
    WORKDIR /source
    COPY ["src/Servicio/Servicio.csproj", "src/Servicio/"]
    COPY ["test/UnitTests/UnitTests.csproj", "test/UnitTests/"]
    COPY ["*.sln", "."]
    RUN dotnet restore 
    
    FROM restore AS build
    COPY . .
    RUN dotnet build  -c Release --no-restore
    
    FROM build AS test
    VOLUME ["/source"]
    RUN dotnet test -c Release --no-build --logger trx
    
    FROM build AS publish
    RUN dotnet publish "src/Servicio/Servicio.csproj" -c Release --no-build -o /app/publish 
    
    FROM base AS final
    WORKDIR /app
    COPY --from=publish /app/publish .
    ENTRYPOINT ["dotnet", "Servicio.dll"]
    

    En el proceso de creación de esta imagen Docker, se ejecutarán las pruebas de código en una etapa y no nos tenemos que preocupar de nada más.

    El problema aquí es que, utilizando una imagen como esta, no podemos recoger ningún elemento propio del proceso de testing, por lo tanto, no podemos generar ningún tipo de cobertura.

    En una de esas muchas conversaciones sobre el tema, una de las soluciones que me aportaba un compañero era la de cambiar el punto de vista y en vez de intentar hacer todo en Docker, hacerlo fuera. Esta solución básicamente consiste en que me olvide de que estoy trabajando con Docker para el proceso de compilación y pruebas de código, y que solamente después de que todo haya terminado bien, genere la imagen copiando directamente los propios binarios que ya he utilizado antes.

    Esta aproximación, aunque simplifica mucho el proceso, pierde la potencia inherente a Docker, lo que corre en mi máquina correrá igual en cualquier sitio. Personalmente pienso que, si estamos utilizando Docker, todo el proceso debería ir dentro de este. De este modo, cualquier persona en cualquier máquina, puede replicar lo mismo que he hecho yo sin necesidad de tener nada más aparte de Docker (y el código fuente).

    Ojo, esto es una opinión personal. Cada persona puede tener una opinión diferente y ser válida. El hecho de hacerlo todo utilizando Docker hace que, aunque el proceso sea muy fácilmente replicable, requiera de más trabajo y de una estructura y código extra que hay que mantener.

    Cómo ejecutar pruebas de código dentro de Docker y recuperar los reportes

    Llegados a este punto y asumiendo que lo vamos a hacer las pruebas de código utilizando Docker, ¿qué opciones tenemos?

    • Todo en el mismo Dockerfile
    • Un Dockerfile exclusivo para las pruebas

    Como sabemos, en un Dockerfile la imagen final se construye a partir del último FROM, por tanto, podemos pensar en crear un fichero Dockerfile para las pruebas de código parecido a este:

    FROM mcr.microsoft.com/dotnet/core/runtime:3.1-buster-slim AS base
    WORKDIR /app
    
    FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS restore
    WORKDIR /source
    COPY ["src/Servicio/Servicio.csproj", "src/Servicio/"]
    COPY ["test/UnitTests/UnitTests.csproj", "test/UnitTests/"]
    COPY ["*.sln", "."]
    RUN dotnet restore 
    
    FROM restore AS build
    COPY . .
    RUN dotnet build  -c Release --no-restore
    
    FROM build AS test
    VOLUME ["/source"]
    ENTRYPOINT dotnet test -c Release --no-build --logger trx
    
    

    Con esto ya estaría solucionado el problema porque vamos a tener una imagen sobre la que lanzar las pruebas. Basta con hacer un binding mount para asociar una ruta del contenedor con una del equipo local, y ya tendríamos los reportes. El problema de esta aproximación es que tenermos dos ficheros Dockerfile que mantener en vez de uno, por tanto, es muy probable que con el paso del tiempo cambien entre ellos. Es por eso que esta opción la descartamos directamente.

    En cambio, ¿cómo podemos tener un único fichero Dockerfile y generar dos imágenes distintas? Podemos utilizar para eso el modificador --target indicándole la etapa de generación que queremos. Con eso vamos a poder generar 2 imágenes distintas.

    La ventaja de esta aproximación es que solamente vamos a necesitar mantener un fichero, y vamos a aprovechar el hecho de que Docker cachea las capas de un Dockerfile que no han cambiado.

    Esto lo podemos comprobar muy fácilmente ejecutando desde la carpeta que contiene el fichero .sln el comando

    docker build -f .\src\Servicio\Dockerfile .
    

    y después creamos la imagen Docker para pasar las pruebas de código con el comando

    docker build --target test -f .\src\Servicio\Dockerfile .
    

    podemos comprobar que todo el proceso lo ha recuperado desde la cache:

    Sending build context to Docker daemon  23.04kB
    Step 1/14 : FROM mcr.microsoft.com/dotnet/core/runtime:3.1-buster-slim AS base
     ---> e6780479db63
    Step 2/14 : WORKDIR /app
     ---> Using cache
     ---> aa2fa7df8294
    Step 3/14 : FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS restore
     ---> 4aa6a74611ff
    Step 4/14 : WORKDIR /source
     ---> Using cache
     ---> c941096ae2df
    Step 5/14 : COPY ["src/Servicio/Servicio.csproj", "src/Servicio/"]
     ---> Using cache
     ---> e9e1202510a2
    Step 6/14 : COPY ["test/UnitTests/UnitTests.csproj", "test/UnitTests/"]
     ---> Using cache
     ---> 70d24d6b3f35
    Step 7/14 : COPY ["*.sln", "."]
     ---> Using cache
     ---> 59599862587b
    Step 8/14 : RUN dotnet restore
     ---> Using cache
     ---> 50a0d635033f
    Step 9/14 : FROM restore AS build
     ---> 50a0d635033f
    Step 10/14 : COPY . .
     ---> Using cache
     ---> ae9959c7cd9d
    Step 11/14 : RUN dotnet build  -c Release --no-restore
     ---> Using cache
     ---> 423be5fb0df8
    Step 12/14 : FROM build AS test
     ---> 423be5fb0df8
    Step 13/14 : VOLUME ["/source"]
     ---> Using cache
     ---> bb09913d383e
    Step 14/14 : RUN dotnet test -c Release --no-build --logger trx
     ---> Using cache
     ---> 32e49850ef89
    Successfully built 32e49850ef89
    

    Esta aproximación tiene un problema, y es que cuando hagamos

    docker run
    

    sobrescribiendo el ENTRYPOINT (ya que de hecho no tiene ninguno), vamos a volver a ejecutar las pruebas. Es decir, vamos a ejecutar las pruebas durante la generación de la imagen y después durante la generación de la cobertura.

    Ejecutando solo una vez las pruebas de código dentro de Docker

    En muchos casos, el hecho de que las pruebas se ejecuten dos veces no será un problema ya que en caso de pruebas unitarias donde se tarda unos segundos, la diferencia no es notable. Pero… ¿qué pasaría si tenemos unas pruebas de integración y funcionales que si tardan tiempo? Pues simplemente que estaríamos duplicando el tiempo de ejecución, ya que por un lado ejecutamos las pruebas al generar la imagen Docker y otra vez al generar los reportes.

    Podemos solucionar esto cambiando ligeramente el Dockerfile y cambiando la etapa de test por esta otra:

    FROM build AS test
    VOLUME ["/source"]
    ENTRYPOINT ["/bin/sh", "-c", "dotnet test -c Release --no-build"]
    

    Con esto estamos sacando de la generación de la imagen la ejecución de las pruebas, pero hemos dejado preparado el proceso ejecutarlas en la nueva imagen.

    ¡OJO! Literalmente hemos sacado del proceso de generación de la imagen Docker la ejecución de las pruebas. Si optamos por esta aproximación es requisito indispensable generar la imagen de pruebas y ejecutarla como parte de la integración continua para que, en caso de dar un error, se produzca el fallo y no sigamos adelante.

    Con este pequeño cambio, basta con ejecutar el contenedor con el volumen y especificar en la salida de las pruebas para tener acceso a los resultados:

    docker run --rm --entrypoint dotnet -v ruta_interna:/test-results imagen  test -c Release --no-build /p:CollectCoverage=true /p:CoverletOutputFormat=\"opencover,cobertura\" /p:CoverletOutput=/test-results/
    

    Dependiendo de que terminal utilices (cmd, powershell, bash,…) puede que necesites indicar el parámetro de escape para que reconozca las comillas.

    Con estos pasos hemos conseguido ejecutar todo el proceso de compilación y ejecución de pruebas de código dentro de Docker, y además hemos conseguido recolectar todos los resultados de las pruebas para poder generar reportes.

    Limitaciones de los reportes de cobertura con Coverlet

    Si ya hemos ejecutado las pruebas de código dentro de Docker y hemos recolectado los reportes, ¿estamos listos para generar un informe de cobertura?

    La verdad es que por desgracia no… si intentásemos utilizar ese reporte de cobertura para generar un informe vamos a obtener un error avisando de que no se puede cargar el código fuente. Este error viene de que coverlet ha utilizado las rutas internas del contendor como podemos comprobar en el informe:

    <?xml version="1.0" encoding="utf-8"?>
    <coverage ...>
      <sources>
        <source>/source/src/Servicio/</source>
      </sources>
      <packages>
        <package name="Servicio" ...>
          <classes>
            <class name="Servicio.Program" filename="Program.cs" ...>
    

    Ahora mismo hay una incidencia abierta planteando si es útil que coverlet soporte el indicar las rutas y que no las detecte pero de momento, lo único que nos queda es reemplazar las rutas como buenamente podamos. Por ejemplo, con un script de powershell que reemplace las rutas internas del contenedor por las externas de la máquina.

    Si tienes dudas sobre como poder hacerlo, te dejo un enlace a un repositorio donde utilizando powershell se reemplazan los valores para poder continuar adelante en un pipeline de Azure DevOps.

    Una vez reemplazados los valores, ya podemos seguir adelante con el proceso normal y los siguientes pasos no sabrán si hemos ejecutado las pruebas de código dentro o fuera de un contenedor Docker.

    Utilizando docker-compose

    Si bien es cierto que esto es muy útil, cuando tenemos varios proyectos de pruebas la cosa se puede complicar si vamos imagen a imagen ejecutando los test… Además, puede que necesitemos alguna dependencia externa como una base de datos o una cache distribuida…

    Gracias a docker-compose vamos a poder atajar de un plumazo ambas situaciones, basta con que creemos un fichero docker-compose.yml para las pruebas. En este fichero podemos declarar todas las dependencias que necesitemos como contenedores adicionales y además, ejecutar cada contenedor utilizando una etapa concreta de un Dockerfile.

    version: '3.4'
    
    services:
      unit-test:
        build:
          context: .
          dockerfile: src/Servicio/Dockerfile
          target: unittest
        entrypoint:
          - dotnet
          - test
          - --logger
          - trx;LogFileName=/test-results/unit-test-results.trx
          - --configuration
          - Release
          - --no-build
          - /p:CollectCoverage=true
          - /p:CoverletOutputFormat="opencover,cobertura"
          - /p:CoverletOutput=/test-results/
        volumes:
        - ./test-results/unit-test-results:/test-results
    
      integration-test:
        build:
          context: .
          dockerfile: src/Servicio/Dockerfile
          target: integrationtest
        entrypoint:
          - dotnet
          - test
          - --logger
          - trx;LogFileName=/test-results/integration-test-results.trx
          - --configuration
          - Release
          - --no-build
          - /p:CollectCoverage=true
          - /p:CoverletOutputFormat="opencover,cobertura"
          - /p:CoverletOutput=/test-results/
        volumes:
        - ./test-results/integration-test-results:/test-results
    

    La idea detrás de este planteamiento es que podemos generar la imagen partiendo del Dockerfile de manera normal, pero a la vez ese mismo fichero contiene todo lo necesario para ejecutar las pruebas en las diferentes etapas.

    Conclusión

    Docker es un mundo en si mismo y en esta entrada se han dado muchas cosas por sabidas para no alargarnos indefinidamente. Aun así, ya se puede comprobar la potencia y la versatilidad que ofrece incluso durante las pruebas de código.

    A la pregunta de si hacer las pruebas de código dentro o fuera de Docker, personalmente pienso que, si utilizamos Docker en el proyecto, deberíamos intentar utilizarlo para todo lo posible. Con esto vamos a conseguir reducir las dependencias para el resto del equipo. Esto es solo una opinión personal y ni siquiera compartida por todos los miembros de mi propio equipo así que tómala con cautela y reflexión.

    Y tú, ¿ qué opinas al respecto? ¿conocías esta posibilidad de trabajar para hacer pruebas de código dentro de un contenedor Docker? Por otro lado, si te has quedado con ganas de conocer en detalle todo el proceso, deja un comentario y si hay interés preparo una serie de entradas entrando más a fondo en estos temas.

    Para que puedas probar en primera persona, he dejado el código subido a Github para que puedas descargarlo y jugar con él.

    **La entrada Cómo ejecutar pruebas de código dentro de contendores Docker se publicó primero en Fixed Buffer.**

    » Leer más, comentarios, etc...

    Coding Potions

    Angular avanzado - ¿Qué necesitas aprender?

    mayo 01, 2020 12:00

    Lleva tus bases al siguiente nivel

    Si has llegado hasta este punto significa que ya tienes una base básica de Angular y quieres aprender conceptos más avanzados para convertirte en un auténtico experto de este framework.

    En este artículo veremos una serie de puntos que pueden ser interesantes para seguir aprendiendo este framework.

    Curso de Angular avanzado

    Pero antes de entrar en detalle he de contaros algo muy interesante.

    Te recomiendo que le eches un vistazo a este curso avanzado de Angular. Aparte de cubrir lo que vamos a ver en este artículo, aprenderás otros conceptos como inyección avanzada de dependencias, RxJS, directivas avanzadas y mucho más.

    Además, tengo una buena noticia que darte:

    ¡He conseguido un descuento exclusivo para ti de 100 €!

    Para conseguir este descuento solo tienes que rellenar el formulario o llamar por teléfono diciendo que vas de mi parte con el siguiente código: CODINGPOINT100.

    Te dejo aquí abajo el link al curso:

    Curso avanzado de Angular

    Ahora sí, ya podemos empezar con el post de angular avanzado.

    NGRX

    De todos los apartados que vamos a ver en este artículo este es el más esencial,todo el mundo debería saber NGRX ya que es fundamental en aplicaciones webs grandes y complejas.

    NGRX es la librería que aplica el patrón Redux muy de moda en este tipo de frameworks para el desarrollo web. Se trata de una librería que sirve para centralizar el estado de una aplicación web.

    Esto del estado lo tienes que ver como una forma de tener en un solo sitio una serie de datos e información que van a usar los componentes. Piensa en una web muy grande hecha con Angular. Si necesitas los mismos datos en distintos sitios de la web lo más común es usar servicios, pero, al final todo llega a ser demasiado lioso y además pierdes el control de los datos.

    Con NGRX tienes el estado de la aplicación en un solo lugar llamado store. El store es inmutable, eso quiere decir que los estados son solo de lectura y cada vez que quieras modificar uno tienes que devolver un objeto nuevo. Además para cambiar este estado hay que usar funciones puras llamadas acciones.

    • 🏪 Store: Contiene el estado global de la aplicación. Inmutable
    • 🔨 Reducers: Devuelven un nuevo estado accediendo al store. Funciones puras.
    • 👷 Actions: Hacen uso de los reducers. Normalmente tienen un tipo y un payload.
    • 🔎 Selectors: Se usan desde los componentes para seleccionar una parte del estado.

    Imagina que tienes en tu web un sistema de login de usuarios. Puedes usar el store para guardar dentro el usuario que está logueado en el sistema. Lo bueno de esto es que usando los selectores dentro del componente puedes tener el usuario logueado en los componentes que necesites.

    Además, si desde un componente utilizas un action para crear un nuevo estado cambiando el usuario logueado, desde los otros componentes lo tendrás actualizado sin tener que hacer nada.

    Si te fijas en el gráfico de arriba, también aparecen los “effects”. Los effects se encargan de controlar los “side effects” de fuentes externas, es decir, normalmente se usan en peticiones HTTP y en sockets. Estos effects pueden ser síncronos o asíncronos.

    Schematics

    Esta es una característica de Angular CLI. Se trata de una herramienta para generar código con una estructura. Es una especie de scaffolding de código pero más complejo porque también permite editar código existente e incluir lógica.

    Lo bueno de los schematics es que puedes crear los tuyos propios e incluso compartirlos en forma de librería para que otros los puedan usar.

    Imagina que quieres añadir un sistema de login a tu aplicación web. Una solución es crearlo tu mismo y modificar tu proyecto manualmente.

    Otra forma maś cómoda es un usar un shematic que añada un sistema de login. Al instalarse leerá tu proyecto y hará los ajustes necesarios (creando archivos y modificando los existentes) de tal forma que no tengas que cambiar tú manualmente el código.

    Como hemos dicho, lo mejor de los schematics es que puedes crear los tuyos propios. Esto permite que puedas crear tu propio conjunto de ficheros y utilidades instalables en cualquiera de tus proyectos. Por ejemplo, yo tengo creado para mis poryectos un schematic para poder generar en cualquier proyecto un archivo que me recoja los errores de forma global en todas las peticiones a las APIS para llevar al usuario a la página de error, y todo eso siendo instalable con un solo comando.

    PWA

    ¿Alguna vez al entrar en una web desde el móvil te ha salido un cartel diciendo que puedes instalar esa web? Eso es porque es una web PWA.

    Esto de las PWA es un concepto relativamente nuevo, pero cada vez lo están adoptando más webs. Para ciertos casos es muy recomendable que la web sea PWA y además implementarlo no es demasiado complejo.

    Que una web sea PWA realmente lo que significa es que la web puede ser utilizada de forma offline, por eso interesa especialmente en aplicaciones web. Para conseguir esto se hace uso de los service workers que lo que hacen es “cachear” la web para que se pueda abrir sin conexión.

    Al estar toda la web cacheada los tiempos de carga son casi nulos, esto permite que pueda ser instalada como una aplicación más del sistema. Cada vez más sistemas operativos y navegadores dan esta opción.

    Otra ventaja muy interesante de las aplicaciones PWA es que facilitan el uso de las notificaciones push. Las notificaciones push se trata de una forma de generar una notificación en el dispositivo del usuario de forma nativa. Lo bueno de las notificaciones push es que se pueden enviar en tiempo real desde el servidor aunque la aplicación web no se esté ejecutando.

    Internacionalización con i18n

    Internacionalización consiste en crear una web multilenguaje. Como en todos los frameworks, en Angular también existen sistemas para montar esta lógica sin que lo tengas que hacer todo a mano.

    Lo que se suele hacer es tener un archivo JSON por cada lenguaje (es.json, en.json, de.json, it.json…) con cada uno de las cadenas de texto en cada uno de los lenguajes.

    Lo que consigues con i18n es acceder a estas cadenas de texto dependiendo del lenguaje configurado en la aplicación web.

    Esto es lo básico pero se pueden hacer más cosas como gestionar las cadenas de texto en singular/plural de tal forma que no tengas que tener la misma dos veces. También puedes insertar variables dentro de la cadena pera que no tengas que crear varias para una frase e incluso puedes tener textos por defecto por si no se encuentra la cadena dentro de los locales.

    Testing

    Si tienes un proyecto grande es esencial tener una buena suite de tests para poder probar la aplicación sin tener que hacerlo manualmente.

    Los tests permiten tener la seguridad de que el código va a funcionar y asi evitamos posibles bugs a futuros. Los tests se suelen combinar con la integración continua para que siempre se hagan pruebas antes de realizar despliegues de la web.

    Hay varios tipos de tests:

    • Tests Unitarios: Consiste en probar unidades pequeñas. Lo suyo es testear componentes o pequeñas partes de los componentes de forma aislada. Para estos tests se suele usar Jasmine.

    • Tests de Integración: Consiste en probar el conjunto de la aplicación asegurando la correcta comunicación entre los distintos elementos de la aplicación. Por ejemplo, en Angular observando cómo se comunican los servicios con la API y con los componentes. Lo más usado en Angular es Karma, que añade a los tests el motor de renderización de los nevegadores.

    • Tests End to End (E2E): Consiste en probar toda la aplicación simulando la acción de un usuario, es decir, por ejemplo para desarrollo web, mediante herramientas automáticas, abrimos el navegador y navegamos y usamos la página como lo haría un usuario normal. En esta parte lo más usado es Protractor.

    Decoradores

    Si has usado Angular, aunque sea de forma básica, has tenido que usar decoradores, lo que pasa es que no te has dado cuenta. Cuando declaras un servicio inyectable con @Service o creas un componente con @Component realmente lo que estás haciendo es usar decoradores.

    Al principio no le veía el sentido a usar estos decoradores, no les encontraba el uso, pero con el paso del tiempo, me dí cuenta de que crear decoradores tiene mucho potencial y no solo me refiero a controladores para usar en Angular.

    Si, has leído bien, puedes crear tus propios controladores. Creando tus propios decoradores vas a agilizar mucho el desarrollo de aplicaciones de Angular porque los vas a definir una sola vez y los vas a poder reutilizar en muchos sitios.

    Los decoradores no son más que funciones que se ejecutan cuando Angular llama al decorador. La función puede recibir como parámetro la clase que se ha decorado (si es un decorador de clase), el atributo o parámetro o el método si es un decorador de método.

    Lo bueno es que además se pueden crear decoradores de varios tipos: de clase, de métodos y de propiedades por lo que se adaptan bien a cualquier proyecto.

    Animaciones

    animación css ejemplo

    Las animaciones son cada vez más importantes. Además de añadir dinamismo a las webs, hacen que las transicciones entre páginas o estados no sean tan abruptas y sea más suaves.

    Aunque puedes crear tus propias animaciones con CSS, o incluso tus propios componentes con Animaciones, Angular tiene una forma nativa de crear animaciones. Además de poder crearlas, ofrece un montón de formas de poder controlarlas y gestionarlas.

    Por ejemplo, puedes crear múltiples animaciones sobre una lista de tal forma que cada animación se vaya escalonando una detrás de la otra. También puedes controlar la finalización de la animación para poder ejecutar cosas a continuación.

    En definitiva, en la mayoría de casos no vas a echar en falta un sistema de animaciones como el de greenshock.

    Conclusiones

    Espero que te haya gustado esta lista y sobre todo que te haya servido para orientarte o darte ideas sobre qué seguir aprendiendo de este fantástico framework.

    Esta lista no sigue ningún orden en particular y es mi visión personal, lo que yo opino.

    Por último, te recomiendo, si te interesa, que aprendas cómo está hecho Angular por debajo (mirando su código fuente en github). Aunque creas que no te va a servir, sabrás mejor por qué y cómo pasan las cosas y puede que te sirva para no cometer ciertos errores.

    » Leer más, comentarios, etc...

    Meta-Info

    ¿Que es?

    Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

    rss subscripción

    Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
    planetacodigo

    planetacodigo

    Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

    Idea: Juanjo Navarro

    Diseño: Albin