Weblogs Código

info.xailer.com

Animaciones en Xailer 7

octubre 27, 2020 07:24

Las animaciones son clases que permiten establecer distintos tipos de animaciones en cualquier control visual. Su funcionamiento se basa en utilizar Futuros para de esta forma no provocar la congelación de la aplicación durante todo el tiempo que dure la animación. Para más información acerca de los futuros, consulte el artículo donde se trata.

Demo de animaciones en el canal de Xailer en YouTube

La clase base de todas las animaciones es TAnimation la cual ofrece toda funcionalidad necesaria para realizar cualquier tipo de animación. Existen dos clases que heredan de TAnimation que están especializadas en realizar la animación de una forma muy sencilla:

TAniNumProperty: Esta clase permite establecer la animación en base a una de las propiedades del control.

En este ejemplo puede entender perfectamente su comportamiento. El objeto TAniNumProperty controla la propiedad ‘nWidth‘ del control ‘oEdit1‘ y va modificar dicha propiedad desde su valor actual (lStartFromCurrent a verdadero) hasta un valor de 300 (nStopValue) y lo hará en un periodo de 1 segundo (nDuration). Todo el proceso comenzará en cuanto la propiedad lEnabled se encuentre a verdadero o ejecute su método Start(). Es así de fácil establecer una animación en cualquier control.

TAniControlSize: Esta clase permite establecer la animación en base a las dimensiones del control. Es decir, de su tamaño marcado por sus propiedades: nLeft, nTop, nWidth y nHeight. Su uso se restringe básicamente a modificar el tamaño de cualquier control desde una posición y tamaño inicial a una posición y tamaño final.

En este ejemplo puede entender perfectamente su comportamiento. El objeto TAniControlSize controla las dimensiones del control ‘oMemo1‘ y va modificar dichas dimensiones desde su valor actual (lStartFromCurrent a verdadero) hasta un valor de {96,48,300,200} (aStopValues) y lo hará en un periodo de 1 segundo (nDuration). Todo el proceso comenzará en cuanto la propiedad lEnabled se encuentre a verdadero o ejecute su método Start().

Ambas clases tienen además una serie de propiedades muy interesantes, que son:

  • nInterpolation: Esta propiedad permite establecer el tipo de animación. La forma más básica sería una interpolación lineal (aiLINEAR) que es la única existente en Xailer Personal y Xailer Professional. Xailer Enterprise dispone de muchos más tipos: rebote, explosión, elástica y circular.
  • nDelay: Permite establecer una demora en milisegundos en el inicio de la animación. Esta propiedad es muy útil cuando se pone la propiedad lEnabled a verdadero en el propio IDE y por lo tanto es necesario que se cargue y muestre el formulario antes de que se procese la animación.
  • lLoop: Si esta propiedad está a verdadero la animación comenzará de nuevo automáticamente cuando termine. Y por lo tanto la animación seguirá hasta que lEnabled pase a ser falso.
  • lReverse: Si esta propiedad está a verdadero se intercambian los valores de ‘Start‘ y ‘Stop
  • lAutoReverse: Esta propiedad permite que se haga una reversión automática cuando termine un ciclo, de tal forma que en el siguiente ciclo se convierte el efecto contrario. Esta propiedad conjuntada con la propiedad lLoop a verdadero provoca el clásico efecto de zoom in- zoom out.
  • nSyncsInSec: Esta propiedad permite establecer el número de sincronizaciones que se harán del control por segundo. Cuanto mayor sea este número, en principio, más suave y fluido se verá el movimiento, pero no es así. Dependerá de la velocidad de su PC y el trabajo que tenga que hacer la animación, ya que puede ser que arrastre a otros controles que también estén visibles.

Espero que estas animaciones sean de su agrado. Estarán disponibles en Xailer 7.

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

Variable not found

Detectar la prerenderización en Blazor

octubre 27, 2020 07:05

Blazor

Hace unos días, un alumno de mi curso de Blazor en CampusMVP me preguntaba sobre la posibilidad de detectar cuándo un componente se estaba procesando en "modo prerenderización", con el fin de ejecutar cierta lógica únicamente en ese caso.

Me habría gustado responderle algo como "usa la propiedad IsPrerendering del componente y ya lo tienes". Pero no, las cosas no son tan sencillas; Blazor no proporciona ninguna propiedad del estilo, y no existen fórmulas directas para conseguirlas, más allá de un par de hacks bastante poco elegantes:

  • Intentar acceder al contexto HTTP, sólo disponible cuando estamos ejecutando código en el interior de una petición (es decir, cuando el componente se está prerenderizando).
  • Ejecutar una función Javascript mediante interop, y capturar la excepción que se produce al hacerlo mientras se está prerenderizando el resultado, pues durante este proceso no es posible utilizar los mecanismos de interoperación.

En este post vamos a ver una alternativa más apropiada que las anteriores, sacando partido al inyector de dependencias y al hecho de que la prerenderización se ejecuta en el propio proceso de la petición que carga la página inicialmente. Y como veréis, la idea es muy sencilla.

¡Manos a la obra!

En primer lugar, vamos a crear un servicio para almacenar el modo de renderización actual. Basta con algo tan simple como lo siguiente:

public class RenderStatus
{
public bool IsPrerendering { get; set; }
}

A continuación, registramos el servicio como scoped en el inyector de dependencias de ASP.NET Core, en Startup.cs, de forma que las instancias de RenderStatus sean distintas para cada petición entrante:

public void ConfigureServices(IServiceCollection services)
{
... // Otros servicios
services.AddScoped<RenderStatus>();
}

Fijaos que a partir de ese momento, ya podríamos inyectar la dependencia RenderStatus en cualquier componente, aunque su propiedad IsPrerendering de momento siempre valdrá false, el valor por defecto.

Dado que nos interesa que IsPrerendering sea cierto durante la prerenderización, lo más sencillo es acudir a la página contenedora de la aplicación Pages/_Host.cshtml y establecerlo manualmente justo en el momento en que el componente Blazor va ser prerenderizado:

@page "/"
@inject RenderStatus RenderStatus
...
<body>
@{
RenderStatus.IsPrerendering = true;
}
<component type="typeof(App)" render-mode="ServerPrerendered" />
@{
RenderStatus.IsPrerendering = false;
}
...

¡Eso es todo! A partir de este momento ya podríamos inyectar el servicio RenderStatus en cualquier componente Blazor y usar su propiedad IsPrerendering para saber si está siendo prerenderizado o no, y actuar en consecuencia.

Por ejemplo, el siguiente componente utiliza el servicio para evitar que durante la prerenderización se realice una tarea compleja que podría hacer que la carga de la primera página se retrasase:

@page "/test"
@inject RenderStatus RenderStatus

<h1>@message</h1>

@code {
string message = "Starting...";
protected override async Task OnInitializedAsync()
{
if (!RenderStatus.IsPrerendering)
{
message = "Calculating...";
await Task.Delay(2000); // Do something complex here...
message = "Done!";
}
}
}

¡Espero que os sea de ayuda!

Publicado en Variable not found.

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

Variable not found

Enlaces interesantes 418 (¡soy una tetera!)

octubre 26, 2020 07:05

Enlaces interesantes

Je, estaba deseando llegar a esta entrega de enlaces para poder contaros algo acerca del código de estado HTTP 418 😊

HTTP 418, cuya reason phrase es "I'm a teapot" (soy una tetera), es un código de estado definido por la IETF para dar soporte al HTCPCP (Hyper Text Coffe Pot Control Protocol), un protocolo especificado oficialmente en la RFC 2324 en el año 1998, con el objetivo de definir las normas de comunicación para posibilitar el control, la monitorización y el diagnóstico de cafeteras a través de Internet.

El protocolo, basado en HTTP, establece nuevos verbos (como BREW, para iniciar la elaboración de café), media types, encabezados personalizados e incluso un nuevo esquema de URIs llamado coffee URI scheme que puede utilizarse para identificar servidores y tipologías de café usando una sintaxis estándar.

Y también define nuevos códigos de retorno. Concretamente, el error 418 es usado por el servidor (el dispositivo, en este caso) para indicar al cliente que no puede procesar la petición al tratarse de una tetera y no una cafetera. Un error de cliente 4xx en toda regla ;)

Obviamente esto no era real, se trató de una trabajada broma del April's Fools Day (algo similar a nuestro Día de los Inocentes), pero que ha perdurado como una anécdota curiosa y simpática en el frío mundo de la definición de estándares.

Y ahora, van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

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

Blog Bitix

Entorno de desarrollo Java para editar, compilar y ejecutar programas

octubre 24, 2020 10:00

El entorno de desarrollo son la colección de herramientas necesarias para editar código fuente, compilarlo y ejecutar sus programas. El primer paso para aprender a programar en Java es disponer de un entorno de desarrollo con las herramientas mínimas necesarias para practicar y aplicar los conocimientos aprendidos de un curso de formación o utilizando algún libro sobre el lenguaje Java. El entorno mínimo necesario consta del JDK que incluye el compilador Java, otras herramientas que facilitan la programación son un editor avanzado o IDE con asistencia de código y una herramienta de construcción para automatizar tareas.

Java

Java es un lenguaje ya con 25 años de vida, desarrollado originalmente por Sun Microsystems y adquirida por Oracle con toda su propiedad intelectual incluyendo la plataforma y lenguaje Java. Es uno de los lenguajes de programación que ha mantenido durante muchos años la primera posición como el lenguaje más utilizado. Algunas de sus características que han contribuido a su éxito son independencia del entorno de ejecución y bytecode, recolección de basura, clases de la API y documentación Javadoc, legibilidad del código y compatibilidad hacia atrás además de ser un lenguaje fuertemente tipado y orientado a objetos.

El lenguaje de programación Java es un lenguaje compilado, eso tiene sus ventajas y algunos inconvenientes, la mayoría son ventajas. Uno de sus inconvenientes es que ejecutar un programa Java requiere el paso de compilación, a diferencia de los lenguajes dinámicos e interpretados que pueden ejecutarse desde el código fuente pero a riesgo de que en tiempo de ejecución se produzcan errores que en los lenguajes compilados se detectan en tiempo de compilación.

Durante todos estos años el lenguaje ha incorporado muchas novedades al lenguaje original desde las colecciones genéricas hasta más recientemente lambdas y modularidad entre otras muchas novedades más, en cada nueva vesión se siguen añadiendo nuevas características.

Este artículo contiene las herramientas básicas para disponer del entorno mínimo para editar, compilar y ejecutar programas escritos con el lenguaje de programación Java.

Contenido del artículo

Libros sobre Java para aprender

Una buena forma de aprender a programar en un lenguaje es utilizando un libro específico sobre el lenguaje, un libro que comience si es necesario por los fundamentos del lenguaje considerando que el lector no tiene conocimientos previos en el lenguaje ni en la programación. Los libros tienen un contenido bien estructurado y son más didácticos que artículos individuales.

De Java hay mucha documetación y libros con los que aprender a programar, desde básicos hasta más avanzados o temas específicos como la concurrencia. Tres de los mejores libros sobre Java son Thinking in Java, Effective Java y Java 8 in Action sin olvidar otros buenos libros para mejorar como programadores.

El programa Hola Mundo en Java

El primer programa que se suele escribir al empezar a aprender un lenguaje de programación, es el programa Hola Mundo que emite en la consola simplemente un mensaje. En Java es el siguiente.

1
2
3
4
5
6
7
8
package io.github.picodotdev.blogbitix.java.helloworld;

public class Main {

    public static void main(String[] args) {
        System.out.println("Hola mundo!");
    }
}
Main.java

Todo programa contiene un punto de entrada desde la que se inicia la ejecución, en Java es un método estático de nombre main que tiene como argumento un array de Strings con los argumentos con los que se ha invocado la ejecución. En Java el método main se declara dentro de una clase dado que Java es un lenguaje orientado a objetos.

El programa Hola Mundo aún con su sencillez permite escribir el mínimo programa en un lenguaje, ejecutarlo, ver su resultado pero sobre todo disponer de todas las herramientas en el entorno de desarrollo local para posteriormente escribir programas más complejos.

Las sentencias de control son los elementos básicos con los que se construyen los programas.

El JDK y la máquina virtual de Java

En Java la herramienta básica que se necesita para compilar programas y ejecutarlos es un JDK que incluye el compilador que transforma el código fuente en bytecode independiente de la arquitectura del procesador de la máquina ya sea x86, Arm, PowerPC o RISC-V y sistema operativo Windows, GNU/Linux, macOS o FreeBSD.

El programa compilador de Java es javac que informa de errores de compilación como referencias a variables, métodos o clases que no existen o de uso o asignación de tipos incorrectos entre otros y el intérprete del bytecode Java que transforma en tiempo de ejecución el bytecode en código máquina para su ejecución e incluye el recolector de basura que exime al programador de liberar explícitamente la memoria reservada.

El comando para compilar el programa Hola Mundo es el siguiente. En sus parámetros se indica el directorio donde se encuentra el código fuente y el directorio de salida para los archivos compilados a bytecode.

1
2
#!/usr/bin/env bash
javac -sourcepath src/main/java -d target/classes src/main/java/io/github/picodotdev/blogbitix/java/helloworld/Main.java
javac.sh

El compilador genera un archivo de extensión class por cada clase del programa como se muestra en el siguiente listado de archivos del directorio de salida del compilador.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ tree target/
target/
└── classes
    └── io
        └── github
            └── picodotdev
                └── blogbitix
                    └── java
                        └── helloworld
                            └── Main.class

7 directories, 1 file
tree.sh

Para ejecutar un programa Java se indica la ubicación de los archivos de bytecode, las librerías jar de clases compiladas adicionales si se utilizase alguna y la clase que contiene el método principal del programa, en Java el método estático main.

1
2
#!/usr/bin/env bash
java -classpath "target/classes" io.github.picodotdev.blogbitix.java.helloworld.Main
java.sh

El resultado de la ejecución del programa es un mensaje en la terminal.

1
2
Hola mundo!

System.out

La herramienta SDKMAN

A lo largo del tiempo Java ha publicado varias versiones del lenguaje y del JDK. Con el calendario de publicaciones aplicado desde la versión 9 se publica una nueva versión de Java cada seis meses y una versión de soporte a largo plazo cada tres años siendo la 11 la primera LTS en el 2018/09 y la 17 en el 2021/09.

Con este prolífico calendario de publicación de nuevas versiones es necesario una herramienta con la que administrar los JDK, tanto para instalar, actualizar versiones menores con parches de seguridad, desinstalar JDKs e incluso tener instaladas varias versiones del JDK al mismo tiempo.

La herramienta SDKMAN sirve para administrar los JDK, además de diferentes versiones hay diferentes distribuciones del JDK, todas parten del código fuente del proyecto OpenJDK y diferentes organizaciones proporcionan su distribución compilada y usable del JDK sin cambios o con algunos cambios adicionales. También sirve para instalar otras herramientas como una herramienta de construcción de proyectos como Gradle o los lenguajes Kotlin y Groovy.

SDKMAN

La herramienta SDKMAN se instala y usa con los siguientes comandos.

1
2
3
$ curl -s "https://get.sdkman.io" | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
$ sdk version
sdk-install.sh
 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
$ sdk

Usage: sdk <command> [candidate] [version]
      sdk offline <enable|disable>

  commands:
      install   or i    <candidate> [version] [local-path]
      uninstall or rm   <candidate> <version>
      list      or ls   [candidate]
      use       or u    <candidate> <version>
      default   or d    <candidate> [version]
      current   or c    [candidate]
      upgrade   or ug   [candidate]
      version   or v
      broadcast or b
      help      or h
      offline           [enable|disable]
      selfupdate        [force]
      update
      flush             <broadcast|archives|temp>

  candidate  :  the SDK to install: groovy, scala, grails, gradle, kotlin, etc.
                use list command for comprehensive list of candidates
                eg: $ sdk list
  version    :  where optional, defaults to latest stable if not provided
                eg: $ sdk install groovy
  local-path :  optional path to an existing local installation
                eg: $ sdk install groovy 2.4.13-local /opt/groovy-2.4.13
sdk-usage.sh
 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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
$ sdk list java
================================================================================
Available Java Versions
================================================================================
 Vendor        | Use | Version      | Dist    | Status     | Identifier
--------------------------------------------------------------------------------
 AdoptOpenJDK  |     | 14.0.1.j9    | adpt    |            | 14.0.1.j9-adpt      
               |     | 14.0.1.hs    | adpt    |            | 14.0.1.hs-adpt      
               |     | 13.0.2.j9    | adpt    |            | 13.0.2.j9-adpt      
               |     | 13.0.2.hs    | adpt    |            | 13.0.2.hs-adpt      
               |     | 12.0.2.j9    | adpt    |            | 12.0.2.j9-adpt      
               |     | 12.0.2.hs    | adpt    |            | 12.0.2.hs-adpt      
               |     | 11.0.7.j9    | adpt    |            | 11.0.7.j9-adpt      
               |     | 11.0.7.hs    | adpt    |            | 11.0.7.hs-adpt      
               |     | 8.0.252.j9   | adpt    |            | 8.0.252.j9-adpt     
               |     | 8.0.252.hs   | adpt    |            | 8.0.252.hs-adpt     
 Amazon        |     | 11.0.7       | amzn    |            | 11.0.7-amzn         
               |     | 8.0.252      | amzn    |            | 8.0.252-amzn        
...
 GraalVM       |     | 20.1.0.r11   | grl     |            | 20.1.0.r11-grl      
...
 Java.net      |     | 15.ea.26     | open    |            | 15.ea.26-open       
               |     | 14.0.1       | open    |            | 14.0.1-open         
               |     | 13.0.2       | open    |            | 13.0.2-open         
               |     | 12.0.2       | open    |            | 12.0.2-open         
               |     | 11.0.7       | open    |            | 11.0.7-open         
               |     | 10.0.2       | open    |            | 10.0.2-open         
               |     | 9.0.4        | open    |            | 9.0.4-open          
               |     | 8.0.252      | open    |            | 8.0.252-open        
...  
================================================================================
Use the Identifier for installation:

    $ sdk install java 11.0.3.hs-adpt
================================================================================

$ sdk install java 8.0.252-open
$ sdk install java 11.0.7-open
$ sdk install java 14.0.1-open
$ sdk default java 11.0.7-open

$ sdk use java 8.0.252-open
$ java -version
openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-b09)
OpenJDK 64-Bit Server VM (build 25.252-b09, mixed mode)

$ sdk use java 11.0.7-open
$ java -version
openjdk version "11.0.7" 2020-04-14
OpenJDK Runtime Environment 18.9 (build 11.0.7+10)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.7+10, mixed mode)

$ sdk use java 14.0.1-open
$ java -version
openjdk version "14.0.1" 2020-04-14
OpenJDK Runtime Environment (build 14.0.1+7)
OpenJDK 64-Bit Server VM (build 14.0.1+7, mixed mode, sharing)

$ sdk upgrade
sdk-commands.sh

La herramienta de construcción Gradle

En programas y proyectos grandes con muchas clases no se usa el compilador del JDK directamente y su comando para compilar las clases sino que se suele utilizar una herramienta de construcción, una de ellas es Maven que con un archivo de descripción del proyecto en formato XML y siguiendo varias convenciones compila el programa, otra herramienta es Gradle que a diferencia de Maven su archivo descriptor de proyecto es con el lenguaje Groovy o Kotlin menos verboso y propenso a errores que el XML.

Gradle

Además de para compilar un proyecto, una herramienta de construcción proporciona otras funcionalidades como ejecutar las pruebas unitarias o de integración, generar la documentación de las clases de la documentción incluida en el código fuente con la herramienta Javadoc, descargar las dependencias de librerías definidas por el proyecto, empaquetar las clases del proyecto en una librería jar y distribuir ese artefacto en los repositorios de librerías para otros proyectos.

SDKMAN también sirve para instalar una herramienta de construcción, Gradle se instala con el siguiente comando.

1
2
$ sdk install gradle

sdk-install-gradle.sh

El siguiente archivo de configuración de proyecto, con la estructura de directorios según las convenciones de Gradle permite compilar el código Java y ejecutar el programa a partir de la clase que contiene el método main.

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

group = 'io.github.picodotdev.blogbitix.java'
version = '1.0'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
}

application {
    mainClassName = 'io.github.picodotdev.blogbitix.java.helloworld.Main'
}

build.gradle

Estos son los comandos para construir el proyecto y ejecutar el método main del programa con Gradle.

1
2
$ ./gradlew build

gradle-build.sh
1
2
3
$ ./gradlew run
...
Hola mundo!
gradle-run.sh

Además, Gradle permite crear la estructura básica de directorios y archivos necesarios de forma automatizada con un comando para iniciar un proyecto de forma rápida y sin esfuerzo.

Ejecutar un programa Java desde el código fuente

Para simplificar el primer acercamiento al lenguaje Java desde la versión 11 de Java se ofrece la posibilidad de ejecutar un archivo de código fuente Java sin necesidad de compilar el código previamente de forma explícita. El propio comando java ofrece esta posibilidad. La limitación está en que el programa Java ha de estar contenido en un único archivo de código fuente aunque con la posiblidad de contener múltiples clases en el mismo archivo y no puede tener dependencias de librerías de terceros.

1
2
$ java src/main/java/io/github/picodotdev/blogbitix/java/helloworld/Main.java
Hola mundo!
java-run.sh

Otra de las facilidades proporcionada por Java es una consola interactiva REPL (read, eval, print, loop) similar a las existentes en lenguajes dinámicos e interpretados. La consola REPL consiste en un bucle para leer una entrada, evaluar, imprimir en la salida y repetir los pasos.

La consola REPL de Java se inicia con el comando jshell y sirve para hacer pequeñas pruebas de código e incluso probar el programa Hola Mundo sin necesidad de realizar los pasos de compilación y ejecución desde el código fuente ni crear un proyecto o archivos de código fuente.

1
2
3
4
5
6
7
8
$ jshell
|  Welcome to JShell -- Version 11.0.7
|  For an introduction type: /help intro

jshell> System.out.println("Hola mundo!");
Hola mundo!

jshell>
jshell.sh

Entorno integrado de desarrollo

Escribir código el código fuente de un programa es posible con cualquier editor de texto incluído el básico que incluyen los sistemas operativos de escritorio como Bloc de notas en Windows o Gedit del entorno de escritorio GNOME y GNU/Linux, también con editores basados en consola como Nano o Vim.

Sin embargo, en la plataforma Java la mayor parte de los programadores utilizamos un editor especializado para editar código fuente con un entorno integrado de desarrollo o IDE. En la plataforma Java los IDE más populares son IntelliJ IDEA que tiene una licencia comercial pero una versión para la comunidad gratuita muy completa, eclipse y NetBeans tienen una licencia de software libre y son gratuitos aún así por sus características y buen funcionamiento IntelliJ se ha convertido en una de los preferidos de los programadores Java.

IntelliJ IDEA

IntelliJ IDEA

Son muchos los beneficios de usar un IDE con un lenguaje compilado y fuertemente tipado como Java. Los IDE aprovechan estas características para proporcionar errores precisos y descriptivos de compilación según se escriben las líneas de código, asistencia de código en métodos disponibles de una clase que aún siendo Java un lenguaje verboso permite escribir código pulsando pocas teclas, refactors de código que permiten cambiar el código existente de forma automatizada evitando mucho trabajo manual que aumenta dramáticamente la productividad, integran una terminal para ejecutar comandos sin necesidad de salir del IDE e integración con las herramientas de construcción como Gradle y de pruebas unitarias automatizadas entre otras muchas funcionalidades muy útiles.

Distribuciones GNU/Linux

Uno de los beneficios del software libre y del código abierto es que normalmente es gratuito, no tienen costosas licencias comerciales para usar el software lo que permite usarlo sin grandes barreras ya sea con fines educativos o comerciales.

GNU/Linux siempre ha sido un sistema operativo con un excelente soporte y herramientas destinadas a los desarrolladores con cantidad de lenguajes de programación disponibles, compiladores, editores y una excelente línea de comandos con multitud de utilidades para automatizar tareas repetitivas. A día de hoy hay muchas distribuciones de GNU/Linux que son tan fáciles de instalar y usar como Windows o macOS ni tienen nada que envidiar a estos sitemas operativos comerciales con licencias privativas. En los últimos años cualquier hardware conocido funciona bien sin necesidad de instalar controladores adicionales y el soporte para juegos y controladores gráficos poseen un rendimiento similar a Windows.

Dado que muchos de los servidores funcionan con GNU/Linux su conocimiento es muy útil en la vida laboral y demandado por las empresas. Usarlo es la mejor forma de aprender y adquirir conocimientos.

El primer paso para usar GNU/Linux es elegir una distribución GNU/Linux según nuestras preferencias, una de las más populares y recomendadas para usuarios que dan el salto desde Windows es Ubuntu. El siguiente paso es descargar e instalar Ubuntu paso a paso desde cero con un instalador guiado con varios pasos en los que hay que hacer poco más que seleccionar el idioma, la disposición del teclado y nombre de usuario, en menos de una hora se instala Ubuntu. Uno de los motivos por el que muchos usuarios siguen usando Windows son los juegos, con Wine para ejecutar programas de Windows en GNU/Linux y la plataforma de juegos Steam la mayoría de los juegos desarrollados para Windows funcionan en GNU/Linux.

Ubuntu Fedora Arch Linux

GNU Linux

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.

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

Blog Bitix

Cómo deduplicar eventos de dominio

octubre 23, 2020 02:00

Las aplicaciones distribuidas utilizan la comunicación de mensajes para notificar de la ocurrencia de ciertos eventos en el sistema que los interesados reciben. En el envío y recepción de mensajes pueden ocurrir dos situaciones que hay que manejar, una es garantizar que cada mensaje se envíe al menos una vez para lo que se emplea el patrón outbox pattern y la segunda es no procesar un evento recibido por duplicado para lo que se emplea deduplicación de mensajes.

Java

El patrón outbox pattern garantiza que los eventos de dominio se envíen al menos una vez, pero que se envíe una vez no impide que sean enviados varias veces. La deduplicación de eventos permite evitar procesar el mismo evento varias veces si se envía repetido. Una forma de conseguir de duplicación de eventos es asignando a cada evento un identificativo único y que la parte receptora de los eventos compruebe si el evento recibido ha sido ya procesado. La parte receptora para determinar si un evento ha sido ya procesado guarda los identificativos de los ya procesados.

Los servicios de una aplicación pueden utilizar comunicación con mensajes mediante RabbitMQ. La parte receptora lee los mensajes y los procesa en la misma transacción que el resto de operaciones guardando en la base de datos el identificativo del evento procesado, de modo que si es recibido varias veces la parte receptora lo deduplica. Si la transacción falla el evento no se marca como recibido y el sistema de mensajería lo mantiene para enviarlo de nuevo hasta que se procese correctamente, si la transacción se completa pero el mensaje del evento no se notifica como procesado correctamente en el sistema de mensajería el sistema de mensajería lo enviará de nuevo pero la parte receptora lo deduplica. Si la parte receptora completa la transacción y notifica como procesado el mensaje en el sistema de mensajería el sistema de mensajería ya no lo enviará de nuevo.

Con el paso del tiempo y dependiendo del volumen de eventos procesados el número de eventos marcados como procesados en la base de datos si es tan grande como para suponer un problema de rendimiento para saber si un evento ya ha sido procesado se puede eliminar de forma periódica aquellos que ya se estimen que ya no van a volver a llegar pasado un tiempo, puede ser tan simple como eliminar todos los eventos ya procesados de hace más de un mes o la fecha más adecuada que se estime.

Ejemplo de implementación de deduplicación de eventos de dominio

Para asignar un identificativo a cada mensaje se puede utilizar un identificador único universal, en Java estos se generan con la clase UUID. El mensaje además de los datos que incluya incluye este identificativo del mensaje.

 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
package io.github.picodotdev.blogbitix.eventbus.domain.shared.eventbus;

...

public class Event {

    private EventId id;
    private LocalDateTime date;
    private Map<String, Object> data;

    public Event() {
        this(Collections.emptyMap());
    }

    public Event(Map<String, Object> data) {
        this.id = new EventId(UUID.randomUUID());
        this.date = LocalDateTime.now(ZoneId.of("UTC"));
        this.data = data;
    }

    public EventId getId() {
        return id;
    }

    public LocalDateTime getDate() {
        return date;
    }

    public Map<String, Object> getData() {
        return data;
    }
}
Event.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package io.github.picodotdev.blogbitix.eventbus.domain.order;

...

public class OrderCreated extends Event {

    private OrderId orderId;

    public OrderCreated(OrderId orderId) {
        this.orderId = orderId;
    }

    public OrderId getOrderId() {
        return orderId;
    }
}
OrderCreated.java

En el ejemplo de los artículos relacionados eventos de dominio en agregados y bus de comandos y consultas cuando se realiza una orden de compra se emite un mensaje para notificar a otros servicios, en el ejemplo el evento de orden de compra creada se utiliza para actualizar el inventario de productos.

Si en el contexto del inventario se recibe por duplicado un mensaje de orden creada resulta en que el inventario de los productos se resta en cada recepción de evento provocando un error en la información de inventario. Para evitarlo hay que implementar deduplicación de mensajes.

Para deduplicar los mensajes creo un repositorio que almacena los eventos ya procesados correctamente y permite comprobar si un mensaje recibido ya se ha procesado, esta implementación almacena los mensajes en memoria pero a partir de la interfaz cualquier otra implementación sería posible como una base de datos relacional que persista los eventos en la misma transacción que los cambios que provoca el mensaje.

1
2
3
4
5
6
7
8
9
package io.github.picodotdev.blogbitix.eventbus.domain.shared.repository;

...

public interface EventRepository {

    void add(Event event);
    boolean exists(Event event);
}
EventRepository.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package io.github.picodotdev.blogbitix.eventbus.infrastructure;

...

@Component
public class MemoryEventRepository implements EventRepository {

    private Map<EventId, Event> store;

    public MemoryEventRepository() {
        store = new HashMap<>();
    }

    @Override
    public void add(Event event) {
        store.put(event.getId(), event);
    }

    @Override
    public boolean exists(Event event) {
        return store.containsKey(event.getId());
    }
}
MemoryEventRepository.java

El servicio de aplicación que recibe los mensajes de orden creada obtiene el identificativo del mensaje recibido, comprueba si ya se ha procesado, si ya se ha procesado no se realiza ninguna acción, si no se ha procesado con anterioridad se procesa y se guarda el identificativo del mensaje para no procesarlo de nuevo.

 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
package io.github.picodotdev.blogbitix.eventbus.application.inventory;

...

@Component
public class OrderCreatedCommandHandler implements CommandHandler<OrderCreatedCommand> {

    private ProductRepository productRepository;
    private OrderRepository orderRepository;
    private EventRepository eventRepository;
    private EventBus eventBus;

    public OrderCreatedCommandHandler(ProductRepository productRepository, OrderRepository orderRepository, EventRepository eventRepository, EventBus eventBus) {
        this.productRepository = productRepository;
        this.orderRepository = orderRepository;
        this.eventRepository = eventRepository;
        this.eventBus = eventBus;
    }

    @Override
    public void handle(OrderCreatedCommand command) {
        OrderCreated event = command.getEvent();

        if (eventRepository.exists(event)) {
            System.out.printf("Duplicated event %s%n", event.getId().getValue());
            return;
        }
        
        ...

        eventRepository.add(event);
    }
}
OrderCreatedCommandHandler.java

Implementada la deduplicación para simular en el ejemplo en envío por duplicado el mensaje en el bus de eventos de dominio se realiza la operación de envío de eventos dos veces de modo que cada mensaje se envía por duplicado. En la salida del programa con la deduplicación implementada se observa que el contexto de inventario deduplica el segundo mensaje y emite una traza en la consola.

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

...

@Component("SpringEventBus")
@Primary
public class SpringEventBus implements EventBus {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void publish(Event event) {
        System.out.printf("%s %s %s%n", event.getClass().getName(), event.getId().getValue(), event.getDate().format(DateTimeFormatter.ISO_DATE_TIME));
        applicationEventPublisher.publishEvent(event);
        System.out.printf("%s %s %s%n", event.getClass().getName(), event.getId().getValue(), event.getDate().format(DateTimeFormatter.ISO_DATE_TIME));
        applicationEventPublisher.publishEvent(event);
    }
}
SpringEventBus.java

Sin deduplicación de mensajes si un evento se recibe por duplicado se procesa dos veces, en este caso el inventario se reduce dos veces.

1
2
3
4
5
Stock: 5
io.github.picodotdev.blogbitix.eventbus.domain.order.OrderCreated 382e81de-445f-45a9-ba77-4c5275f661d9 2020-10-23T13:54:41.667705
io.github.picodotdev.blogbitix.eventbus.domain.order.OrderCreated 382e81de-445f-45a9-ba77-4c5275f661d9 2020-10-23T13:54:41.667705
OrderId: io.github.picodotdev.blogbitix.eventbus.domain.order.OrderId@fde4110d, Items: 1
Stock: 1
System.out-1

Implementando deduplicación de mensajes los mensajes duplicados se detectan y se ignoran, el inventario solo se reduce una vez.

1
2
3
4
5
6
Stock: 5
io.github.picodotdev.blogbitix.eventbus.domain.order.OrderCreated 1bda82d5-d70b-4f48-ad19-b342350fa6c9 2020-10-23T14:04:51.732244
io.github.picodotdev.blogbitix.eventbus.domain.order.OrderCreated 1bda82d5-d70b-4f48-ad19-b342350fa6c9 2020-10-23T14:04:51.732244
Duplicated event 1bda82d5-d70b-4f48-ad19-b342350fa6c9
OrderId: io.github.picodotdev.blogbitix.eventbus.domain.order.OrderId@b8757a45, Items: 1
Stock: 3
System.out-2

De Domain Driven Design hay varios libros, el libro de referencia sobre la teoría de DDD son Domain-Driven Design: Tackling Complexity in the Heart of Software, Domain-Driven Design Distilled, Patterns, Principles, and Practices of Domain-Driven Design otros más prácticos son Implementing Domain-Driven Design y Domain-Driven Design in PHP: A Highly Practical Guide.

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

info.xailer.com

Exprimiendo el FOR EACH de Harbour

octubre 21, 2020 10:03

La cláusula FOR EACH que Harbour incorporó al lenguaje CA-Clipper fue una gran mejora, pero hay mucha gente que sólo lo utiliza de su forma más básica:

FOR EACH value IN aValues
  ? value
NEXT

Sin embargo ofrece multitud de posibilidades. Vamos a enumerar unas cuantas:

  • Iterar sobre más de una variable:
    FOR EACH a, b, c IN aVal, cVal, hVal
      ? a, b, c
    NEXT
  • Establecer orden descendente:
    FOR EACH a IN aVal DESCEND
      ? a
    NEXT
  • Asignar caracteres de una cadena de forma directa: (atención a la @)
    s := "abcdefghijk"
    FOR EACH c IN @s
      IF c $ "aei"
        c := Upper( c )
      ENDIF
    NEXT
    ? s // AbcdEfghIjk
  • Control por POO sobre las variables de iterador:
    • :__enumIndex() retorna el actual ordinal en la iteración. El equivalente a nFor
    • :__enumIsFirst() retorna si es el primer elemento en la iteración
    • :__enumIsLast() retorna si es el último elemento en la iteración
    • :__enumBase() retorna la variable base que está siendo procesada. Útil cuando se procesan más de una variable.
    • :__enumValue() retorna el valor de la variable que está siendo procesada. Útil cuando se procesan más de una variable.
      FOR EACH a IN aVal
        ? a:__enumIndex()
      NEX

Harbour incluso permite modificar como se debe de recorrer la iteración utilizando los métodos :__enumStart(), :__enumSkip() y :__EnumStop(). Tenéis un ejemplo de uso en el fichero \harbour\tests\foreach2.prg.

Espero que os haya sido de utilidad.

Un saludo

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

Fixed Buffer

Creando módulos especializados para Azure IoT Edge

octubre 20, 2020 08:00

Tiempo de lectura: 12 minutos
Imagen ornamental para la entrada "Creando módulos especializados para Azure IoT Edge"

Después de unas semanas muy liado con unas complicaciones personales (solo he tenido tiempo de publicar la review de NDepend), ya estamos de vuelta y además con una entrada que toca muchos palos. Vamos a hablar de Azure, vamos a hablar de C# y vamos a hablar de Docker.

Por el título de la entrada, no es ningún secreto el tema del que vamos a hablar de Azure IoT Edge y sus módulos. La idea de escribir esto es porque no hace mucho escribí en el blog de Plain Concepts una entrada sobre cómo desplegar cargas de trabajo ‘on-prem’ utilizando IoT Hub.

Aunque lo que se plantea en la entrada es totalmente cierto y el runtime de IoT Edge permite correr imágenes Docker sin más, la verdad es que nos permite ir un paso más allá. IoT Edge nos va a permitir crear módulos especializados con los que vamos a poder utilizar las funcionalidades propias de IoT Hub y todo ello empaquetado en una imagen Docker. Con esto vamos a conseguir que nuestro IoT Edge deje de ser un ‘nodo’ de Docker al que podemos mandar cosas y se convierta en un verdadero punto de computación de borde.

¿Por qué debería usar módulos de IoT Hub en lugar de imágenes Docker normales?

Es cierto que con una imagen Docker las cosas soy mucho más simples comunes a la hora de desarrollar. Tienes un fichero Dockerfile, expones unos puertos, creas tus APIs, y si necesitas comunicarte con IoT Hub para enviar mensajes, puedes utilizar directamente el cliente de IoT Hub para el lenguaje con el que estés trabajando. ¿Por qué debería entonces complicarme la vida?

Pues, aunque los módulos implican añadir ciertos cambios a la hora de programar (más bien de desplegar), no dejan de ser una aplicación de consola sin más, al que se le ha dotado de funcionalidad para interactuar con el runtime sobre el que está corriendo. Esto se traduce en 2 grandes ventajas:

  • Delegamos en el runtime el enrutado de los mensajes entre los diferentes módulos.
  • Tenemos a nuestra disposición toda la potencia de IoT Hub en cada módulo (envío de métricas/mensajes, dispositivo gemelo, mensajes desde la nube al dispositivo, etc…), utilizando la conexión del runtime de IoT Edge.

¿Las ventajas son realmente ventajas?

Vale, he planteado las 2 cosas que a mi parecer son las principales mejoras, ¿pero esto realmente son mejoras? Analicemos la primera de ellas, el enrutado de mensajes.

El planteamiento de IoT Edge es tener pequeños programas que vayan añadiendo procesado sobre los datos de entrada. Imaginemos un caso donde tenemos una cámara capturando imágenes de una autopista, las imágenes que se obtienen se procesan y por último se etiquetan. Es cierto que esto se podría hacer de manera monolítica todo en el mismo proceso, pero eso dificulta el reutilizar código. Si separamos la adquisición en una imagen, el procesado en otra y el etiquetado en otra, vamos a poder cambiar una de las piezas por otra y reutilizar al máximo.

Por poner un caso, podríamos tener otro escenario en el que solo queramos capturar imágenes y mandarlas a un almacenamiento, el hecho de que la adquisición este modularizada, nos va a permitir reaprovechar ese módulo y añadir solo la parte nueva de persistencia.

Hecha esta aclaración sobre la modularización y reutilización, podríamos extraer varios módulos del conjunto de los dos escenarios:

  • Adquisición de imágenes.
  • Procesado de las imágenes.
  • Etiquetado de las imágenes.
  • Persistencia de las imágenes.

Y su representación sería algo así:

La imagen muestra un diagrama de bloques en que que hay 2 zonas. La primera zona dice "escenario 1" y tienes 3 bloques unidos por flechas. Desde bloque Captura sale una flecha hacia Procesado, y desde ahí sale una flecha hacia Etiquetado. La segunda zona es "escenario 2" y tiene una flecha que sale desde el bloque Captura y llega a Persistencia

En una situación multi contenedor corriendo sobre Docker, simplemente podríamos decirle a un contenedor, por ejemplo, Captura, que una vez que tenga una imagen la mande al siguiente contenedor siguiendo un modelo push (es el propio contenedor el que se encarga de empujar los datos al siguiente). Incluso podríamos cambiar el planteamiento y utilizar un modelo pull donde cada paso obtenga los datos del paso previo.

Esto tiene problemas a la hora de reutilizar los módulos, ya que cada módulo necesita tener consciencia o desde donde obtiene los datos o a donde los tiene que enviar. Tenemos acoplamiento entre los módulos. Si por ejemplo quisiéramos añadir al escenario 1 la persistencia entre la captura y el procesado (o en cualquier otra posición), tendríamos que modificar el código del módulo para que se adapte al nuevo escenario, no nos vale solo con modificar las configuraciones.

En este punto, voy a aclarar que en programación casi todo es posible, e incluso esto se puede solucionar añadiendo código o creando nosotros mismos sistemas complejos que lo solucionen. En algunos casos puede que sea necesario, pero generalmente es tiempo y esfuerzo en balde.

Para evitar esto, podríamos añadir algún sistema de colas donde cada módulo publique los resultados a una cola y lea las entradas desde otra cola. De este modo cada módulo no necesita saber nada al respecto del anterior ni del siguiente, simplemente lee los datos desde una cola y los deja en otra cola. El problema de este planteamiento es que somos nosotros los que vamos a tener que mantener la infraestructura de colas… y aquí es donde coge importancia el delegar en el runtime el enrutado de mensajes.

IoT Edge nos proporciona de serie un sistema de enrutado que podemos utilizar desde los módulos especializados de IoT Edge. Nosotros simplemente vamos a definir una o más entradas para nuestro módulo de IoT Edge, y de igual manera vamos a escribir una serie de salidas del módulo. Después, desde la propia configuración de IoT Edge vamos a definir mediante lenguaje de consulta el coger las salidas un el módulo A y enviarlas a la cola de entrada del módulo B. Es importante aquí el punto de lenguaje de consulta, ya que vamos a poder aplicar condiciones para que la ruta se cumpla.

Esto desacopla totalmente los módulos entre si, ya que estamos usando un sistema de colas, pero en este caso es el propio runtime quien lo gestiona.

Por otro lado, comentaba que la segunda gran ventaja era que el propio módulo de IoT Edge era capaz de ser un dispositivo IoT en si mismo, aprovechando la conexión del runtime. Esto significa que por ejemplo que podemos utilizar el sistema de rutas para enviar métricas a IoT Hub, es decir, es posible especificarle a IoT Edge que coja la cola de salida del módulo A y que directamente la envíe como una métrica a IoT Hub.

Creando la solución IoT Edge

Como hablar siempre es muy fácil, vamos a crear un módulo IoT Edge desde 0, que enrute los mensajes generados desde el simulador de pruebas que ofrece Microsoft. Lo primero de todo es preparar el entorno, el resumen ejecutivo de los elementos que necesitamos es .Net Core y una extensión para Visual Studio o Visual Studio Code que nos de soporte para desarrollar módulos IoT Edge. Una vez instalada la extensión, seguiremos la documentación específica para configurar la extensión con nuestro IoT Hub.

En este caso por versatilidad, yo estoy utilizando Visual Studio Code, por lo que, una vez instalada y configurada la extensión, basta con escribir «Azure IoT Edge: New IoT Edge solution» en la paleta de comandos.

La imagen muestra la paleta de Visual Studio Code con el comando "Azure IoT Edge: New IoT Edge solution" escrito

Esto iniciará un pequeño asistente en el que nos va a pedir una ruta, un nombre para la solución, y nos va a ofrecer una plantilla de módulo IoT Edge en varios lenguajes diferentes. Una vez seleccionado el nombre del módulo, nos va a pedir el último paso que es decirle cual es el registro de Docker en el que se tiene que subir la imagen con el módulo. Este registro puede ser público o privado.

Una vez hecho esto, ya tenemos listo el andamiaje del proyecto, y ya podemos editar y generar el módulo, desplegarlo, depurarlo,… Este andamiaje está compuesto de varios ficheros que vamos a analizar.

La imagen muestra la plantilla de IoT Edge end Visual Studio Code

El fichero .env contendrá variables que se reemplazan durante el proceso de generación. Principalmente valores sobre el registro de Docker.

Dentro de la carpeta .vscode, se ha creado el código necesario para ejecutar en local el código (que no deja de ser una aplicación de consola), o para depurarlo dentro de un contenedor Docker.

La carpeta config va a contener los ficheros de configuración que generamos como artefacto de salida del proceso. Estos ficheros son muy útiles ya que son el json que podemos utilizar para desplegar sobre un dispositivo IoT Edge el conjunto de módulos que definamos. Esto es así porque realmente hemos creado una solución completa de IoT Edge que contiene módulos, algunos pueden ser de terceros y algunos nuestros (como es el caso).

En la carpeta modules es donde encontramos la magia. Aquí es donde vamos a encontrar los diferentes módulos que estemos creando. Ahora entraremos en detalle.

Por último, nos encontramos los ficheros deployment.template.json y deployment.debug.template.json. Estos ficheros son los que van a servir de entrada para que durante el proceso de generación del módulo se cree el artefacto de salida en la carpeta config. La principal diferencia entre ellos es que el que contiene debug nos va a permitir depurar el código del contenedor.

El módulo IoT Edge

Para acabar de revisar que tenemos, cuando entramos en la carpeta modules nos encontramos con un buen número de ficheros Dockerfile, un fichero module.json y un program.cs.

Si comprobamos que contiene el fichero program.cs, nos encontramos con que no es más que una aplicación de consola. Hay que reconocer que la plantilla no es todo lo óptimo que debería ser en cuanto a buenas prácticas, pero podríamos reducirlo a algo así:

using System;
using System.Runtime.Loader;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Client;
using Microsoft.Azure.Devices.Client.Transport.Mqtt;

namespace SampleModule
{
    class Program
    {
        static int counter;

        static async Task Main(string[] args)
        {
            await InitAsync();

            var cts = new CancellationTokenSource();
            AssemblyLoadContext.Default.Unloading += (ctx) => cts.Cancel();
            Console.CancelKeyPress += (sender, cpe) => cts.Cancel();
            await WhenCancelledAsync(cts.Token);
        }

        public static Task WhenCancelledAsync(CancellationToken cancellationToken)
        {
            var tcs = new TaskCompletionSource<bool>();
            cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
            return tcs.Task;
        }

        static async Task InitAsync()
        {
            MqttTransportSettings mqttSetting = new MqttTransportSettings(TransportType.Mqtt_Tcp_Only);
            ITransportSettings[] settings = { mqttSetting };

            ModuleClient ioTHubModuleClient = await ModuleClient.CreateFromEnvironmentAsync(settings);
            await ioTHubModuleClient.OpenAsync();

            await ioTHubModuleClient.SetInputMessageHandlerAsync("input1", PipeMessage, ioTHubModuleClient);
        }

        static async Task<MessageResponse> PipeMessage(Message message, object userContext)
        {
            int counterValue = Interlocked.Increment(ref counter);

            var moduleClient = userContext as ModuleClient;
            if (moduleClient == null)
            {
                throw new InvalidOperationException("UserContext doesn't contain " + "expected values");
            }           
            
            //Encolamos el mensaje como salida
            await moduleClient.SendEventAsync("output1", message);
            return MessageResponse.Completed;
        }
    }
}

Si analizamos el código (he cambiado ligeramente para usar await en vez de .Wait() respecto a la plantilla y borrado código que no aporta), podemos comprobar que tenemos un Main que inicializa el dispositivo llamando a InitAsync (Init en la plantilla original). Dentro de este método, se inicializa el módulo IoT Edge a través del tipo ModuleClient.

Sobre este módulo registramos un manejador para cada vez que llegue un mensaje por la cola «input1» con la línea:

await ioTHubModuleClient.SetInputMessageHandlerAsync("input1", PipeMessage, ioTHubModuleClient);

Añadir nuevas entradas es tan simple como registrar más manejadores para otras colas de entrada.

Para escribir los mensajes a una cola de salida, se utiliza la línea:

await moduleClient.SendEventAsync("output1", message);

En este caso, simplemente estamos utilizando el mensaje de la cola de entrada «input1» del módulo IoT Edge y lo estamos colocando en la cola de salida «output1».

Adicionalmente a lo que nos ofrece la plantilla, el módulo nos ofrece también varios métodos con los que vamos a poder ampliar la utilidad del módulo mediante configuración de dispositivo gemelo, recepción de mensajes CloudToDevice, o llamadas a métodos directos. Esto se consigue simplemente añadiendo a la inicialización las líneas:

await ioTHubModuleClient.SetDesiredPropertyUpdateCallbackAsync(OnDesiredPropertiesUpdateAsync, ioTHubModuleClient);
await ioTHubModuleClient.SetMessageHandlerAsync(HandleMessageAsync,ioTHubModuleClient);
await ioTHubModuleClient.SetMethodHandlerAsync("hello",HelloMethodHandlerAsync,ioTHubModuleClient);

A este código hay que añadirle los callback de los manejadores, para no alargar una entrada ya de por si larga, puedes consultar el código completo en el repositorio de Github.

Hay que tener en cuenta que tanto las colas de entrada y salida, como el resto de las funcionalidades como métodos directos o dispositivo gemelo están ligados al módulo y estarán disponibles en cualquier runtime de IoT Edge donde despleguemos el módulo y dependerá de si queremos usarlos o no.

Una vez visto el código, ¿por qué tantos Dockerfile?. La idea de que haya tantos es que estamos creando un módulo que queremos que se pueda desplegar en cualquier sitio que pueda correr IoT Edge, y esto son muchas arquitecturas de microprocesador diferente. Gracias a tener todos esos Dockerfile ya en la plantilla, nos abre la puerta a poder hacer que nuestro módulo de IoT Edge corra en Linux, Windows, en microprocesadores AMD, ARM,…

Vamos a poder configurar la arquitectura de destino simplemente ejecutando en la paleta de comandos «Azure IoT Edge: Set Default Target Platform for Edge Solution«. Esto nos va a dar varias opciones disponibles, que son las que están definidas en el fichero module.json, donde vamos a relacionar las arquitecturas con los Dockerfile concretos entre otras cosas como especificar la versión del módulo.

Actualmente, Docker soporta de forma experimental el crear manifiestos de imágenes para múltiples arquitecturas simultáneamente, siendo el runtime de Docker el que se descarga la imagen de su arquitectura especifica.

Una vez que hemos terminado de configurar y programar nuestro módulo, vamos a poder generar la imagen o imágenes y subirlas al repositorio haciendo click derecho sobre el json del módulo o de la solución y pulsando sobre «Build and Push IoT Edge Module Image» o «Build and Push IoT Edge Solution» respectivamente.

En caso de que hayamos elegido la segunda opción, además de generar las imágenes y subirlas, va a generar el fichero de deployment en la carpeta config de la solución.

Configurando las rutas y desplegando la solución

Ya casi estamos listos para desplegar la solución con nuestro módulo en un dispositivo IoT Edge. Tenemos el módulo listo, pero para poder probar el sistema de módulos de manera sencilla, vamos a necesitar desplegar varios módulos juntos.

Por suerte la plantilla de la solución de IoT que hemos usado, ya nos ha creado los ficheros deployment con un módulo extra de simulación, que va a sacar por su cola de salida «temperatureOutput«. En la sección de rutas (routes) del json de despliegue deployment.template.json vamos a definir los diferentes enrutados internos y externos de los mensajes como parte de $edgeHub. En este caso la plantilla ya nos ofrece 2 rutas que son desde la salida del simulador a la entrada del módulo, y desde la salida del módulo a $upstream. Es importante aclarar $upstream significa que el mensaje será enviado directamente a IoT Hub como una métrica del dispositivo:

"routes": {
  "SampleModuleToIoTHub": "FROM /messages/modules/SampleModule/outputs/* INTO $upstream",
  "sensorToSampleModule": "FROM /messages/modules/SimulatedTemperatureSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/SampleModule/inputs/input1\")"
}

Cada vez que actualicemos la plantilla de despliegue, será necesario generar de nuevo el manifiesto de despliegue para la arquitectura concreta. Para esto vale con hacer click derecho sobre el json de la plantilla y seleccionar «Generate IoT Edge Deployment Manifest«.

Por último, ya solo queda desplegar la solución. Para esto es suficiente con hacer click derecho sobre el json del manifiesto en la carpeta de config y seleccionar «Create Deployment for Single Device«. Con esto vamos a poder elegir el dispositivo sobre el que queremos desplegar y «et voilà«. Ya hemos desplegado nuestro propio módulo IoT Edge y hemos configurado sus mensajes.

Conclusión

Soy consciente de que se han quedado muchas cosas en el tintero, no he entrado a como depurar el código, usar el simulador, configuraciones complejas,… La idea detrás de todo esto era dar una visión de alto nivel sobre cómo es posible crear y desplegar módulos IoT Edge sin sufrir mucho por el camino.

Siempre que he usado IoT Edge he intentado usar directamente imágenes stand-alone porque pensaba que el sistema de módulos era complejo y que no valía la pena entrar en él, pero evidentemente me equivocaba.

Una cosa importante que no he dicho pero que también es importante, es que IoT Edge no tiene coste adicional, sobre el coste de mensajes que tengas en IoT Hub y además funciona con el tier gratuito, por lo que es posible utilizar IoT Edge gratis siempre que no mandemos más de 8 mil mensajes al día, momento en el que se cortará la comunicación con IoT Hub hasta el día siguiente.

De momento, te dejo el enlace a la documentación oficial de IoT Edge, donde vas a poder encontrar no solo como desarrollar módulos, sino como hacer ajustes finos en muchas partes del sistema.

¿Y tú qué opinas? ¿Conocías los módulos para IoT Edge? Déjame en los comentarios si quieres que haga otra entrada más en detalle sobre cómo desarrollar módulos, depurarlos y hacer configuraciones más complejas.

**La entrada Creando módulos especializados para Azure IoT Edge se publicó primero en Fixed Buffer.**

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

Variable not found

Cómo personalizar los mensajes de error de conexión en Blazor Server

octubre 20, 2020 06:40

Todos los usuarios que están utilizando una aplicación Blazor Server mantienen una conexión abierta con el servidor para enviar eventos y recibir las modificaciones de la interfaz de usuario. Y si habéis trabajado algo con Blazor, sabréis que cuando dicha conexión se corta aparece en el navegador un mensaje como el mostrado en la siguiente captura de pantalla:

Mensaje modal indicando que se está intentando reconectar con el servidor

Como se puede ver en la captura anterior, cuando se detecta la desconexión, se añade automáticamente a la página un <div> con el identificador components-reconnect-modal que bloquea la página (observad su posición fija a pantalla completa), mostrando al usuario un mensaje informándole de que se está intentando reconectar.

Si, transcurridos algo más de 30 segundos, la reconexión no ha sido posible, el contenido del <div> cambiará para informar al usuario de que la conexión no pudo ser restablecida, y ofreciendo un botón para reintentarlo y un enlace para recargar la página completa:

Mensaje indicando que la reconexión no ha podido ser restablecida

Podemos comprobar muy fácilmente estos comportamientos si lanzamos la aplicación sin depuración (Ctrl+F5) desde Visual Studio y detenemos IIS Express desde su icono en la barra de herramientas de Windows.

Como comportamiento por defecto la verdad es que no está nada mal, pues nos proporciona una solución out of the box que será suficiente la mayoría de los casos. Sin embargo, la estética es obviamente mejorable... ¿y si quisiéramos modificar visualmente estos mensajes o incluso su comportamiento? Pues es lo que veremos en este post 🙂

Creando nuestra propia UI

Cuando se produce un problema en la conexión, el cliente de Blazor Server (es decir, el código del archivo blazor.server.js que se introduce en _Host.cshtml) busca en la página un elemento con el identificador components-reconnect-modal. En caso de no existir, el sistema se comportará como hemos descrito anteriormente.

Pero si el elemento existe, Blazor irá aplicándole distintas clases CSS en función del estado de la conexión:

Clase CSSEstado
components-reconnect-showSe ha perdido la conexión y el cliente está intentando reconectar. Deberíamos mostrar el mensaje para notificar al usuario de que algo va mal
components-reconnect-hideLa conexión ha sido restablecida, por lo que podemos ocultar el mensaje de notificación.
components-reconnect-failedLa reconexión falló, probablemente debido a un problema de red, aunque aún podríamos reintentar la reconexión llamando a window.Blazor.reconnect().
components-reconnect-rejectedLa reconexión fue rechazada; es posible que se haya podido contactar con el servidor, pero éste rechazó la conexión porque el estado del usuario ya no está en memoria, por lo que la única forma de reconectar al usuario es recargando la página.

Puedes leer algo más sobre estos estados en la documentación oficial.

Jugando con estas clases, podemos crear fácilmente una interfaz de usuario más apropiada.

Por ejemplo, considerad el siguiente código de marcado que podríamos añadir a la página contenedora _Host.cshtml de nuestro proyecto Blazor Server:

<div id="components-reconnect-modal">
<div class="components-messages">
<div class="components-reconnecting">
<h2>⚠ Se perdió la conexión</h2>
<p>Estamos intentando reconectar. Por favor, espere unos segundos...</p>
<div class="spinner-border"></div>
</div>
<div class="components-failed">
<h2>❌ No ha sido posible reconectar</h2>
<p>
No es posible conectar con el servidor, probablemente debido
a un problema en la red.<br />
Puede probar <a href="javascript:location.href='';">recargando la página</a>
en unos minutos.
</p>
</div>
<div class="components-rejected">
<h2>❌ No ha sido posible reconectar</h2>
<p>
Se ha perdido la conexión con el servidor, probablemente debido a <br/>
algún error o a que haya sido reiniciado.<br/>
Debe <a href="javascript:location.href='';">recargar la página</a>
para conectar de nuevo.
</p>
</div>
</div>
</div>

Como podéis observar, simplemente hemos añadido el elemento components-reconnect-modal que espera Blazor, y en su interior hemos creado un bloque para cada uno de los estados posibles. La visibilidad de estos bloques podemos modificarla muy fácilmente usando únicamente CSS, como en el siguiente ejemplo:

#components-reconnect-modal {
display: none;
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.8);
}

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
display: flex;
}

#components-reconnect-modal .components-messages {
color: #f0f0f0;
margin: auto;
text-align: center;
}

#components-reconnect-modal .components-messages h2 {
color: white;
}

#components-reconnect-modal .components-messages a {
color: white;
text-decoration: none;
border-bottom: 1px dotted white;
}


#components-reconnect-modal .components-reconnecting,
#components-reconnect-modal .components-failed,
#components-reconnect-modal .components-rejected {
display: none;
}

#components-reconnect-modal.components-reconnect-show .components-reconnecting,
#components-reconnect-modal.components-reconnect-failed .components-failed,
#components-reconnect-modal.components-reconnect-rejected .components-rejected {
display: block;
}

Y el resultado luce tan espectacular como el que se puede ver en la siguiente animación:

Mensajes de reconexión y error con la nueva interfaz de usuario

Publicado en: www.variablenotfound.com.

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

Variable not found

Enlaces interesantes 417

octubre 19, 2020 06:39

Enlaces interesantesA veces las expectativas no se cumplen, y el mundo de las peticiones HTTP no iba a ser la excepción ;) El código de estado 417 (Expectation Failed) es retornado por un servidor precisamente cuando eso ocurre: no es capaz de cumplir las expectativas indicadas por el cliente mediante el encabezado expect de la petición.

Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes y, por supuesto, que vuestras expectativas queden satisfechas :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

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

Blog Bitix

1 década en la misma empresa, 20 años trabajando

octubre 17, 2020 09:00

Llevo casi 20 años de vida laboral, 10 de los cuales en la misma empresa, en ese periodo hay a quien le da tiempo a venir, marchar, volver a venir y volver a marchar. En estos años he pasado diría que por pocas empresas comparado con lo rápido que otras personas se cambian de trabajo, aún así han sido empresas bastante diferentes entre ellas. Los últimos 10 años se me han pasado mucho más rápido que los 10 anteriores. Si pasase 10 años más en la misma empresa muy posiblemente sería bueno pero con la situación pandémica actual y el desenlace de acontecimientos que crea incertidumbre es mucho decir.

Prácticas en empresa

Mi primera experiencia laboral ha sido durante los últimos años de carrera a través de unas prácticas en empresa. Las prácticas duraron unos 6 meses y aunque no mucho fueron remuneradas.

Fue mi primera experiencia laboral aunque realmente más allá de eso no me sirvieron para lo que deberían haber sido su propósito que es el hacer uso de los conocimientos adquiridos durante la carrera, adquirir más conocimientos y ver por qué ramas laborales optar una vez en el mercado laboral.

A pesar de todo y dado que era una empresa dedicada a la industria pesada un día pude ver una máquina de colada continua produciendo barras metalúrgicas literalmente como churros y el horno junto con la sala de control bunkerizada donde se funde la chatarra en la cubeta que luego pasa a la máquina de colada continua, ver esto estuvo muy bien.

Las consultoras

En los 10 primeros años de vida laboral he estado trabajando en dos consultoras principalmente con proyectos para la administración pública. Cada proyecto de apenas una duración de más de 9 meses o menos, entre las cosas buenas de estos proyectos es que algunos se empezaban desde cero lo que es una buena oportunidad para aplicar lo aprendido en anteriores proyectos, en los muy pocos incluso con cierta libertad de elegir frameworks y librerías.

Las administraciones públicas tienen sus propias normativas y sistemas para el desarrollo de aplicaciones hasta donde he conocido con importantes limitaciones en librerías que se pueden elegir y obligan a usar sus propios frameworks de desarrollo propietarios a la vez que antiguos, con poca documentación y los cuales fuera de esa administración no tienen ningún valor. Para mi que hasta ahora he orientado mi carrera profesional en el aspecto técnico esto era una importante insatisfacción y por eso aunque no fue algo muy buscado ya que no estaba buscando activamente cualquier oportunidad cuando ví una oferta de trabajo interesante decidí cambiar a una startup dedicada al comercio electrónico, lugar de los siguientes 10 años con los que titulo el artículo.

Con el paso del tiempo las cosas se ven algo distintas, en la primera consultora estuve algo menos de 3 años, con la vista de ahora no debería haber estado más de 3 trimestres. Por el salario casi de becario ya que en esos primeros años lo único que fue posible para mejorarlo fue un cambio de empresa, aún no teniendo experiencia en el primer año estamos hablando de alguien con titulación que ha tenido que pasar 5 años de su vida estudiando en una universidad y hacer una cantidad importante de exámenes de los que aún algunos días con somnolencia me desvelo con «pesadillas» sobre que al día siguiente tengo examen y aún no he estudiado nada. Y por la falta de adquirir conocimientos, ahora me doy cuenta de lo poco que sabía pero también de lo poco que tenían para enseñarme. Nada de lo que hice aquí me siento orgulloso salvo por ser un junior en la empresa y aún así en algunos proyectos realizar tareas de analista-programador que debería haberlo hecho alguien con más experiencia que justifique su salario.

Aún con sus defectos en la segunda consultora hice varios proyectos mas satisfecho con el resultado. Y por otro lado guardo buen recuerdo de prácticamente todas las personas con las que tuve relación y algunas con esto de twitter sigo sabiendo de ellas. En la parte buena de los proyectos están una migración de bases de datos Microsoft Access a una base de datos Oracle. La dificultad de esto estaba en que la migración tenía que ser incremental dividiendo el proceso en tres pasos uno por provincia, para evitar colisiones de identificativos con los datos de la migración y los que se creen usando la aplicación la solución fue reservar una rango de identificativos para los datos de la migración. La otra dificultad estaba en que las bases de datos Access tenían numerosas inconsistencias en los datos y falta de integridad referencial que había que resolver para tratar de migrar la mayor cantidad de datos posible. La parte del proyecto que me encargué fue básicamente un programa Java, casi un script para realizar la migración incremental, buscar inconsistencias, emitir un informe con ellas para resolver las que se pudieran y generar una serie de archivos con las sentencias SQL que ejecutados migraban los datos a las bases de datos Oracle. Alguna época desplazado en el cliente con largos viajes en coche de ida y vuelta diarios, de los de Vitoria recuerdo la canción Anybody Seen My Baby de los Rolling Stones que sonaba todas las mañanas en la radio.

A pesar de que prefiero Apache Tapestry como framework web basado en componentes, otros de los proyectos de lo que que guardo buen recuerdo era precisamente uno hecho en JSF, que si no recuerdo mal servía para evaluar el grado de cumplimiento de una organización en cuanto a seguridad según diferentes parámetros. Tenía su parte de introducción de datos y formularios mediante una aplicación web y también una parte importante eran los informes PDF generados con JasperReports de varias páginas que incluían algunas gráficas de araña generadas con JFreeChart, me permitió descubrir lo muy interesante que era esa herramienta de JasperRports que a día de hoy todavía no se si hay algo mejor en su propósito. En esa época no hacía teses unitarios pero tengo el recuerdo de que tanto el código como la aplicación web tenían un aspecto muy decente y cuidado.

La startup

El motivo del cambio de una consultora a startup en mi caso no fue el salario fue más el aspecto técnico de poder usar algo más moderno que Java 1.4, frameworks propietarios de administraciones públicas y el potencial de poder usar cualesquiera otras cosas más modernas que se necesitasen. Aún siendo Grails y Groovy junto con la base de datos MySQL no mis favoritos al menos era más moderno que lo que podía usar hasta ese momento. El segundo motivo era la experiencia de conocer otro tipo de empresa distinta de una consultora y diferente proyecto.

En esta startup entré casi desde sus inicios y en estos 10 años he visto todo su proceso de crecimiento, desde el inicio en un polígono en una oficina que tendría unos 30m2 en el que al principio con las personas que estábamos sobraba incluso espacio, las mesas del Ikea cojeaban y sillas que habían pasado por mejor vida hace tiempo. Hasta su venta a una multinacional.

A medida que se incorporan personas a una velocidad notable aumentando la plantilla iba faltando espacio. Las segundas oficinas tampoco eran ningún lujo pedían una reforma integral a gritos pero al menos eran más grandes y céntricas en la ciudad. Con el mayor número de personas el baño y a la hora de comer había que hacer cola a medida que también se llenaban con más personas, mesas y sillas, al final también se quedaron pequeñas. Todas las personas muy jóvenes, de entre 20 y 40 años bajos… también debería haber de 50 y 60.

Las terceras oficinas al menos el continente era algo mejor por su reforma reciente y aún más grandes con varias salas de reunión. Eso sí, las mesas seguían siendo de las baratas de 100x50cm de Ikea que no deberían tener en su nombre la categoría de oficina, las sillas seguían siendo del Ikea y la luz de las mesas encadenadas en varias regletas sin necesidad de grandes reformas pero que en ciertas circunstancias hace que se salte la luz. Con el paso de tiempo y continuando el ritmo de contrataciones el espacio ya no daba mucho para más personas y las salas de reunión no era fácil reservar una cuando se quería, solo cuando se podía en los huecos libres.

¿Una consultora es peor o mejor que una startup? Con esta experiencia no diría que las startups sean mejores o peores, son diferentes, cada uno tiene sus puntos buenos y malos. Cada persona valora y prioriza diferentes apartados de diferentes forma. Casi seguro que uno de los aspectos que estará en las primeras posiciones será el salario, al final es la principal razón de ser del trabajo, y los tipos de proyectos de las startups en principio a más largo plazo por lo que el interés por que estén bien hechos es mayor, por otro lado esto hace que el proyecto casi siempre sea una evolución de lo existente en vez de algo completamente nuevo como los proyectos de una consultora.

En cuanto al salario dependiendo de la startup puede ser mayor, cuando empecé no mucho mayor que el que tenía en la consultora. Eso sí en esta startup cada año siempre ha estado muy por encima del IPC con bonus anuales aparte.

En cuanto al aspecto técnico Grails, Groovy y MySQL eran más moderno que lo que trabajaba en la consultora pero con el paso del tiempo lo que en su momento es tecnología moderna con hype, con el paso del tiempo deja de serlo, pierde el interés o no se adapta a las nuevas circunstancias. Lo que al principio cuando se tiene una base de código pequeña de código puede ser adecuado para añadir features nuevas rápidamente y cuando se son pocas personas en el equipo no es lo más adecuado cuando el equipo está formado por unas cuantas decenas de personas para seguir aportando features y corrigiendo errores con garantía no ya de que funcionen sino de que no lleguen errores de compilación a producción aún teniendo con teses unitarios, hacer code reviews para cada cambio y un equipo de varias personas dedicadas a QA. Más aún cuando ya ninguna persona tiene conocimiento de todo e incluso en el área en el que se está se maneja la mayor parte código está escrito por otras personas que ya no están en la empresa o por ti mismo pero que han pasado varios meses o algún año desde su última modificación o que ya ni siquiera desde el punto de vista de negocio se entiende la lógica de negocio implementada sin una investigación. Verdaderos hacks si con ese término se le quiere dar un aspecto positivo para convertir lo que antes era una aplicación Grails en dos pero compartiendo ciertas clases. Quedar anclado en una versión 2.5 obsoleta del 2015 con posibles fallos de seguridad en una aplicación de comercio electrónico que maneja dinero, tarjetas bancarias y datos personales cuando ya están por la 4.x por falta de tiempo, por miedo a actualizar, no estar seguro de romper cosas, suponer un esfuerzo ingente para arreglar incompatibilidades en la nueva versión y para actualizar la aplicación a las nuevas formas de hacer las cosas soportadas.

En el aspecto negativo de las startups están en que si el negocio no crece a un ritmo importante anual del año anterior puede dejar de recibir financiación para quemar dinero para seguir creciendo y con ello el tiempo de vida de la empresa terminar rápido. Otros aspectos son que muchas startups tiene un plan de negocio incierto sin garantía de éxito, ni plan a muy largos plazos y en algunos casos un modelo de negocio cuestionable que se aprovechan sin muchos escrúpulos de la falta de regulación legal o del desconocimiento del usuario con esta disrupción de las tecnologías que cambian y posibilitan nuevos modelos.

Por suerte en la que he estado aunque nunca ha llegado ha ser rentable, su objetivo principal era crecer aún a costa de quemar dinero, pero ha ido creciendo con números importantes que al final la ha hecho apetecible para ser vendida a una multinacional.

La multinacional

Donde antes era prácticamente austeridad como lo de las mesas, sillas y oficinas a una multinacional que no escatima en gastos, «money is not problem». En cuanto se dan las circunstancias nuevas oficinas completamente remodeladas con mesas grandes y de oficina, sillas ergonómicas nuevas no de Ikea, espacio para muchas más personas y salas de reuniones, lo mejor que he visto con mucha diferencia hasta el momento.

Las condiciones salariales no eran ya malas respecto a lo que yo conocía de una consultora pero mejoran notablemente aún más no ya solo en salario con subidas de salario porcentuales de dos dígitos además de bonus anuales, appreciation bonus o retention bonus sino en otros beneficios como gastos para transporte, gastos para comida, aún siendo pequeña una aportación de la empresa a un plan de pensiones individuales, seguro de salud privado que aquí no es tan importante pero entiendo que para los estadounidenses es vital, plan de acciones con concesiones de acciones cada dos o tres meses como incentivo para permanecer en la empresa, café, leche, cacao, frutos secos y fruta en los descansos de las mañanas o para despues de comer. Quizá todo esto se estile en las tecnológicas estadounidenses pero en España es poco habitual. Todos estos beneficios a parte del salario, no se si en la más de la mitad de vida profesional que me queda tendré mejores condiciones. Las revisiones anuales en categoría y salario aún siendo normalmente buenas son individuales para cada persona según una evaluación que pretende ser objetiva y justa, según esa evaluación individual se crean diferencias que generan unas importantes malas sensaciones contraproducentes.

El inglés se convierte en un conocimiento que hace más fácil entender de primera mano la información que se quiere transmitir sobre la empresa en los diferentes all-hands y reuniones recurrentes.

Al principio es un éxito que una multinacional norteamericana de renombre mundial te adquiera, como un objetivo cumplido, luego con el paso del tiempo lo que se publica en Twitter queda para la posteridad. Sin cambiar realmente de empresa, aunque en la práctica lo sea, la experiencia dentro de una multinacional grande es muy diferente al de una startup. Mucha más gente en diferentes regiones y culturas, que no conoces salvo por que la ves en el slack o en correos electrónicos pero aún sabiendo su categoría profesional y departamento desconociendo qué misión tiene en la empresa, un sistema tecnológico mucho más grande con más partes legacy, con más dependencias, procesos ya establecidos difíciles de cambiar y cuando antes podías decidir o cambiar cualquier cosa ahora ya no la decides tú, donde antes eras el más importante en tu área ahora eres uno más si no es que eres menos porque conoces poco y se imponen jerarquías en la nueva estructura organizativa. Cambiar cualquier cosa que tenga gran impacto se convierte en muy difícil.

Las circunstancias diferentes que no encajan con lo que esperan algunas personas profesionalmente unido a la oportunidad hace que cuando hasta el momento las bajas eran la excepción empiecen a ser un goteo constante cuando no un torrente. Y con el paso del tiempo unas arrastran otras, el conozco a que es incentivado en las propias empresas con compensaciones por referidos, por mucho esfuerzo o dedicación algunas cosas dan igual si tienes un buen contacto. ¿Las condiciones económicas hacen que por sí solas sean suficientes para retener a las personas? No a largo plazo, hay muchas más cosas que se valoran y no todas las personas valoran las mismas cosas en el mismo grado. Puedes tener un grupo de personas dedicado a crear actividades que hagan la estancia en la empresa atractiva para mantener a personas o atraer nuevas pero a pesar de todo si en lo importante que es la actividad laboral deja que desear es cuestión de tiempo que las personas decidan buscar oportunidades en otros sitios.

Puede que incluso llegue otra multinacional interesada en comprar la sección del negocio que en la que estabas, por varios miles de millones de euros y cuando digo varios miles de millones no es por decir una cantidad grande sino que es de ese volumen la transacción, pero luego la fusión tener que esperar a la revisión de un regulador de competencia que impone condiciones en la adquisición alargando la transacción, para agravar la situación llegar una pandemia que afecta especialmente al sector. En esa estamos ahora.

El salario, la experiencia u oportunidades que ofrece una multinacional son aspectos positivos, en los aspectos negativos están en que llegada la necesidad puede darse el caso de un ERE si la evolución de la empresa no cumple los objetivos previstos, puedes verlo venir pero existe la posibilidad es que de un día para otro puedas estar en un proceso de despido colectivo. Mucha más rotación que en la startup, ya sea por bajas voluntarias o despidos en algunos casos fulminantes también en algunos mandos importantes, en la startup aunque pocos tampoco estuvo exenta de alguno.

Quizá no en las mismas condiciones pero en esta época oportunidades no faltan que permitan encontrar otro empleo en no mucho tiempo, también decir que hay mucho trabajo de baja calidad, por lo que siempre conviene tener cierta garantía económica más en el caso de tener deudas de cuantías como una hipoteca, responsabilidades familiares o gastos recurrentes importantes como un alquiler.

Conclusión, ¿y el futuro?

El trabajo técnico en la informática puede parecer que sea tratar con ordenadores y sistemas informáticos, la realidad es que la mayor parte es tratar con personas. Durante estos 20 años he coincidido con muchas personas, aunque las ha habido por fortuna las malas experiencias han sido la excepción de todos modos al menos en estos casos por mi parte han sido lo suficientes para mantener una relación profesional, también ha habido algunas personas admirables.

20 años laborales y 10 años en una misma empresa dan para mucho, cosas buenas, malas, algunas para olvidar, otras para esperar que no se repitan pero sobre todo para tener más claro lo que busco profesionalmente. No se que va a ser de los 30 restantes porque no se que va a ser ni del siguiente en el que además hay una pandemia mundial. Lo que sí sé es que si me voy de una empresa no voy a considerar los maravillosos compañeros que he tenido, el montón de cosas que he aprendido o lo a gusto que he estado entre las típicas cosas de tópicos que se comentan en los mensajes de despedida… si no porque lo que busco profesionalmente no lo tengo unido a que 10 años son muchos años, nada nuevo. Y ahora tengo más claro algunas cosas que me gustaría tener, lo difícil es encontrar al mismo tiempo juntas las más importantes.

20 años laborales y 10 años en una misma empresa, pero en momentos me veo reflejado en algunos aspectos este artículo How to waste your career, one comfortable year at a time, si no he cambiado durante este tiempo de empresa es porque, aparte del salario y condiciones laborales, el proyecto tiene el potencial de ofrecer muchas oportunidades incluído el aspecto de las tecnologías. Como dice el artículo muchos años en una misma empresa puede ser indicativo de conformismo, lo contrario uno, dos o tres años como mucho en cada trabajo puede ser falta de resiliencia y constancia cuando las cosas no van bien.

Si algo de lo que estoy contento de estos 10 años es a estar en lo bueno y en lo malo, no abandonar fácil aunque no se le dé valor en muchos casos. Algunos motivos encuentro para ello… por las condiciones económicas seguro que alguno de mis hermanos me dirían que sería estúpido si lo hiciese… Después del salario en mi caso de lo que más valoro es el aspecto técnico y lo que tengo la mayor parte del tiempo dista bastante de lo que deseo. Lo que no estoy contento es a no defender más mi opinión con argumentos por no confrontar o por no saber defendela, en algunos casos mi opinión o sesgos no será mejor pero en otros puede que sí y el tiempo también muestra de flaquezas de la opinión y sesgos de otros. También de no haber aportado todo lo que podría, aun así no he dejado de seguir aprendiendo cosas nuevas o profundizando según mis intereses, quizá algún día surja la oportunidad.

10 años en la misma empresa, no se ven tantos de estos por el sector de las tecnologías. ¿Cambiaría estos 10 últimos años?, no. ¿Me gustaría que hubiesen sido mejores?, sí. ¿El siguiente será mejor?, ya veremos.

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

Blog Bitix

Implementar un bus de comandos y consultas en Java

octubre 16, 2020 03:00

Un bus de comandos y consultas permite separar en una aplicación las operaciones de modificación y operaciones de obtención de datos. Esto permite si es requerido dos bases de datos diferentes utilizando CQRS, una base de datos para operaciones de modificación y una base de datos para operaciones de consulta. Aún teniendo solo una base de datos para ambas operaciones un bus de comandos y eventos permite independizar a la aplicación de las interfaces con las que se use ya sea REST, GraphQL, línea de comandos o mensajería como RabbitQM y crear manejadores de operaciones siguiendo los principios SOLID de diseño.

Java

Las aplicaciones entre sus tareas están la de realizar operaciones de modificación y operaciones de lectura de la base de datos. Un comando representa la solicitud de una operación de modificación, tiene la característica de que no devuelven datos pero modifican datos. Las consultas representan la solicitud de información de la base de datos, a diferencia de los comandos devuelven datos pero no realizan cambios en la base de datos de modo que son idempotentes y se pueden repetir cuantas veces se quiera.

A medida que una aplicación crece necesita nuevos comandos y consultas, estando en una o varias clases de servicio estas requieren modificarse al añadir nuevos comandos o consultas, al mismo tiempo las clases de servicio tendrán el conjunto completo de todas las dependencias que necesiten todas las operaciones cuando muchas de las operaciones solo necesitan un pequeño conjunto de dependencias. La organización del código con servicios suele originar clases con múltiples responsabilidades convirtiéndose en un potencial problema de mantenimiento.

Separar los comandos y consultas permite aplicar CQRS, en el que las operaciones de consulta se lanzan contra una base de datos especializada en consultas y los comandos se lanzan contra otra base de datos. Tener dos base de datos permite escalar a cada base de datos de forma independiente según sus necesidades pero añade una gran complejidad al sistema ya que la base de datos que recibe los comandos ha de ser replicada en la base de datos de consultas. Una forma de replicar los datos en las bases de datos es mediante eventos de dominio con un bus de eventos y consistencia eventual e implementando deduplicación de eventos de dominio.

Un bus de comandos y consultas es una infraestructura que permite añadir nuevos comandos y consultas aplicando dos de los principios SOLID. La S de responsabilidad única haciendo que cada comando y consulta tenga una única responsabilidad y la O de abierto a extensión y cerrado a modificación.

Contenido del artículo

Interfaces del bus de comandos y consultas

Un bus de comandos y un bus de eventos son simplemente la definición de esta interfaz que tiene un único método a implementar. La interfaz del bus de comandos no devuelve datos y la interfaz del bus de consultas si devuelve datos.

1
2
3
4
5
6
package io.github.picodotdev.blogbitix.eventbus.domain.shared.commandbus;

public interface CommandBus {

    void handle(Command command) throws Exception;
}
CommandBus.java
1
2
3
4
5
6
package io.github.picodotdev.blogbitix.eventbus.domain.shared.querybus;

public interface QueryBus {

    <T> T handle(Query<T> query) throws Exception;
}
QueryBus.java

Ambas interfaces reciben un argumento que contiene los datos necesarios para ejecutar el comando y consulta. Todos los comandos y argumentos heredan de estas clases. Estas clases hacen de objeto de transferencia de datos o DTO entre la capa de interfaz de la infraestructura y la capa de aplicación de dominio.

1
2
3
4
package io.github.picodotdev.blogbitix.eventbus.domain.shared.commandbus;

public class Command {
}
Command.java
1
2
3
4
package io.github.picodotdev.blogbitix.eventbus.domain.shared.querybus;

public class Query<T> {
}
Query.java

Los comandos y consultas permiten independizar a la aplicación de la interfaz que se use para acceder a la aplicación. La aplicación puede ser accedida a través de una interfaz REST, una interfaz GraphQL, con RabbitMQ, línea de comandos. Esta independencia de la interfaz con la que se accede a la aplicación permite soportar varias interfaces de acceso o cambiar a otra en el futuro sin requerir grandes cambios o ninguno en la capa de aplicación ni de dominio.

Implementación de bus de comandos y consultas

La implementación de la interfaz del bus de comandos y consultas reciben clases concretas Command y Query, para aplicar los principios SOLID se necesita un manejador por cada clase Command y Query admitido por los buses. Esta clase manejador es la que contiene la lógica de dominio para proporcionar la funcionalidad del comando y consulta, contiene las dependencias de los servicios de dominio o repositorios de las entidades y hace uso de los métodos de las dependencias que necesita.

1
2
3
4
5
6
package io.github.picodotdev.blogbitix.eventbus.domain.shared.commandbus;

public interface CommandHandler<T extends Command> {

    void handle(T command) throws Exception;
}
CommandHandler.java
1
2
3
4
5
6
package io.github.picodotdev.blogbitix.eventbus.domain.shared.querybus;

public interface QueryHandler<T,U extends Query<T>> {

    T handle(U query) throws Exception;
}
QueryHandler.java

Las clases de DTO para los comandos y consultas que contienen los datos y sirve para el envío de la solicitud de la operación al bus.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.github.picodotdev.blogbitix.eventbus.application.order;

...

public class CreateOrderCommand extends Command {

    private OrderId orderId;
    private List<Item> items;

    public CreateOrderCommand(OrderId orderId, List<Item> items) {
        this.orderId = orderId;
        this.items = items;
    }

    public OrderId getOrderId() {
        return orderId;
    }

    public List<Item> getItems() {
        return items;
    }
}
CreateOrderCommand.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package io.github.picodotdev.blogbitix.eventbus.application.order;

...

public class GetOrderQuery extends Query<Order> {

    private OrderId orderId;

    public GetOrderQuery(OrderId orderId) {
        this.orderId = orderId;
    }

    public OrderId getOrderId() {
        return orderId;
    }
}
GetOrderQuery.java

Las clases manejadores de consultas y comandos tienen la ventaja de seguir los principios SOLID, pero al mismo tiempo, si se puede considerar un inconveniente, es que en una aplicación grande el número de comandos y consultas es grande lo que requiere un gran número de manejadores, cada operación requiere dos clases, la del comando o consulta y la del manejador en vez de simplemente una llamada a un método con sus argumentos.

 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.eventbus.application.order;

...

@Component
public class CreateOrderCommandHandler implements CommandHandler<CreateOrderCommand> {

    private OrderService orderService;

    public CreateOrderCommandHandler(OrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public void handle(CreateOrderCommand command) throws Exception {
        OrderId orderId = command.getOrderId();
        List<Item> items = command.getItems();
        orderService.create(orderId, items);
    }
}
CreateOrderCommandHandler.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package io.github.picodotdev.blogbitix.eventbus.application.order;

...

@Component
public class GetOrderQueryHandler implements QueryHandler<Order,GetOrderQuery> {

    private OrderRepository orderRepository;

    public GetOrderQueryHandler(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @Override
    public Order handle(GetOrderQuery query) throws Exception {
        return orderRepository.findById(query.getOrderId());
    }
}
GetOrderQueryHandler.java

Con la interfaz del bus de comandos y consultas, las clases concretas de comandos y consultas y los manejadores de cada comando y consulta, el bus de comandos y consultas consiste en tener una relación entre clase concreta de comando o consulta y manejador de esa clase de comando o consulta.

Utilizando la inyección de dependencias de Spring se permite recibir en el constructor una lista de clases que heredan de una clase o implementan una interfaz, Spring busca estas clases que además están anotadas con la anotación @Component. El constructor guarda en un mapa la relación de manejadores con su clase que maneja, buscando por reflexión qué clase de comando o consulta maneja. El método que implementa la interfaz del bus simplemente busca en el mapa el manejador de la clase recibida y le delega su tratamiento.

 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
package io.github.picodotdev.blogbitix.eventbus.infrastructure;

...

@Component
@Primary
public class SpringCommandBus implements CommandBus {

    private Map<Class, CommandHandler> handlers;

    public SpringCommandBus(List<CommandHandler> commandHandlerImplementations) {
        this.handlers = new HashMap<>();
        commandHandlerImplementations.forEach(commandHandler -> {
            Class<?> commandClass = getCommandClass(commandHandler);
            handlers.put(commandClass, commandHandler);
        });
    }


    @Override
    public void handle(Command command) throws Exception {
        if (!handlers.containsKey(command.getClass())) {
            throw new Exception(String.format("No handler for %s", command.getClass().getName()));
        }
        handlers.get(command.getClass()).handle(command);
    }

    private Class<?> getCommandClass(CommandHandler handler) {
        Type commandInterface = ((ParameterizedType) handler.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
        return getClass(commandInterface.getTypeName());
    }

    private Class<?> getClass(String name) {
        try {
            return Class.forName(name);
        } catch (Exception e) {
            return null;
        }
    }
}
SpringCommandBus.java
 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
package io.github.picodotdev.blogbitix.eventbus.infrastructure;

...

@Component
@Primary
public class SpringQueryBus implements QueryBus {

    private Map<Class, QueryHandler> handlers;

    public SpringQueryBus(List<QueryHandler> queryHandlerImplementations) {
        this.handlers = new HashMap<>();
        queryHandlerImplementations.forEach(queryHandler -> {
            Class queryClass = getQueryClass(queryHandler);
            handlers.put(queryClass, queryHandler);
        });
    }

    @Override
    public <T> T handle(Query<T> query) throws Exception {
        if (!handlers.containsKey(query.getClass())) {
            throw new Exception(String.format("No handler for %s", query.getClass().getName()));
        }
        return (T) handlers.get(query.getClass()).handle(query);
    }

    private Class<?> getQueryClass(QueryHandler handler) {
        Type commandInterface = ((ParameterizedType) handler.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[1];
        return getClass(commandInterface.getTypeName());
    }

    private Class<?> getClass(String name) {
        try {
            return Class.forName(name);
        } catch (Exception e) {
            return null;
        }
    }
}
SpringQueryBus.java

El siguiente código envía un comando y una consulta al bus de consultas y eventos. El comando crea una orden de compra y el segundo obtiene la orden de compra creada.

 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
package io.github.picodotdev.blogbitix.eventbus;

...

@SpringBootApplication
public class Main implements CommandLineRunner {

    @Autowired
    private QueryBus queryBus;

    @Autowired
    private CommandBus commandBus;

    @Autowired
    private ProductRepository productRepository;

    @Autowired
    private OrderRepository orderRepository;

    @Override
    public void run(String... args) throws Exception {
        Product product = productRepository.findAll().stream().findFirst().orElse(null);
        System.out.println("Stock: " + product.getStock());

        OrderId orderId = orderRepository.generateId();
        commandBus.handle(new CreateOrderCommand(orderId, List.of(new Item(product.getId(), product.getPrice(), 2, new BigDecimal("0.21")))));

        Order order = queryBus.handle(new GetOrderQuery(orderId));
        System.out.printf("OrderId: %s, Items: %s%n", orderId, order.getItems().size());

        System.out.println("Stock: " + product.getStock());
    }

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

En la salida del programa se observa como se procesa el comando de creación de la orden, la creación de la orden provoca el lanzamiento de un evento de dominio OrderCreated, el manejador de este evento de dominio en el dominio de inventario realiza la actualización del stock de los productos de la orden, en caso de no haber suficiente stock se emite un evento de dominio OrderOversold, el manejador de evento de dominio OrderOversoldCommandHandler podría marcar la orden como sobrevendida o realizar algún proceso con ella. Este lanzamiento de eventos de dominio muestra como funciona la consistencia eventual con el inventario de los productos.

1
2
3
4
Stock: 5
io.github.picodotdev.blogbitix.eventbus.domain.order.OrderCreated ceea5523-158e-4eb1-96d1-9aef60107ce2 2020-10-16T15:09:29.164497
OrderId: io.github.picodotdev.blogbitix.eventbus.domain.order.OrderId@63686af2, Items: 1
Stock: 3
System.out

Estas son las clases que manejan los eventos de dominio que son de interés para el bounded context de inventario.

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

...

@Component
public class InventorySpringEventBusListener {

    private CommandBus commandBus;

    private InventorySpringEventBusListener(CommandBus commandBus) {
        this.commandBus = commandBus;
    }

    @EventListener
    public void onOrderCreated(OrderCreated orderCreated) throws Exception {
        OrderCreatedCommand command = new OrderCreatedCommand(orderCreated);
        commandBus.handle(command);
    }
}
InventorySpringEventBusListener.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package io.github.picodotdev.blogbitix.eventbus.application.inventory;

...

public class OrderCreatedCommand extends Command {

    private OrderCreated event;

    public OrderCreatedCommand(OrderCreated event) {
        this.event = event;
    }

    public OrderCreated getEvent() {
        return event;
    }
}
OrderCreatedCommand.java
 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
package io.github.picodotdev.blogbitix.eventbus.application.inventory;

...

@Component
public class OrderCreatedCommandHandler implements CommandHandler<OrderCreatedCommand> {

    private ProductRepository productRepository;
    private OrderRepository orderRepository;
    private EventBus eventBus;

    public OrderCreatedCommandHandler(ProductRepository productRepository, OrderRepository orderRepository, EventBus eventBus) {
        this.productRepository = productRepository;
        this.orderRepository = orderRepository;
        this.eventBus = eventBus;
    }

    @Override
    public void handle(OrderCreatedCommand command) {
        OrderCreated event = command.getEvent();
        OrderId orderId = event.getOrderId();
        Order order = orderRepository.findById(orderId);

        List<ProductId> oversoldProductIds = order.getItems().stream().filter(it -> {
            Product product = productRepository.findById(it.getProductId());
            return !product.hasStock(it.getQuantity());
        }).map(Item::getProductId).collect(Collectors.toList());

        order.getItems().forEach(it -> {
            Product product = productRepository.findById(it.getProductId());
            product.subtractStock(it.getQuantity());
            eventBus.publish(product);
        });

        if (!oversoldProductIds.isEmpty()) {
            eventBus.publish(new OrderOversold(orderId, oversoldProductIds));
        }
    }
}
OrderCreatedCommandHandler.java

De Domain Driven Design hay varios libros, el libro de referencia sobre la teoría de DDD son Domain-Driven Design: Tackling Complexity in the Heart of Software, Domain-Driven Design Distilled, otros más prácticos son Implementing Domain-Driven Design y Domain-Driven Design in PHP: A Highly Practical Guide.

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

Picando Código

Sega celebra 60 años con una promoción especial en Steam y 60 días de contenido

octubre 15, 2020 11:00

Go Sega
2020 marca el aniversario N° 60 de SEGA, una de las compañías de videojuegos más importantes de la historia. Sega West lo celebra con 60 días de contenidos. La semana de festejos empezó ayer miércoles 14 de octubre en Steam. En la página de la promoción de Steam podemos encontrar descuentos en varios títulos. Particularmente interesante también varios minijuegos nuevos con inspiración retro, gratuitos, creados por los estudios SEGA específicamente para el 60° aniversario:

  • Armor of Heroes – Relic Entertainment™ – Un título multijugador de tanques para hasta 4 jugadores en combate versus y cooperativo offline. Disponible desde el 15 al 19 de Octubre.
  • Endless Zone – Amplitude Studios™ – El universo Endless y la Fantasy Zone™chocan en este shoot ’em up de scroll lateral. Disponible desde el 16 al 19 de Octubre.
  • Streets of Kamurocho – SEGA® – Kiryu y Majima de la premiada serie Yakuza™de SEGA pelean en las calles de Kamurocho en un formato nuevo pero familiar. Disponible desde el 17 al 19 de Octubre.
  • Golden Axed – SEGA® – Una versión sin finalizar y nunca antes vista de un proyecto cancelado llamado Golden Axe™: Reborn. Disponible desde el 18 al 19 de Octubre. Sí, solamente un día.

Golden Axed me resulta bastante interesante. El original fue uno de los juegos en los que invertí bastantes fichas en la época de los arcades, estaba genial. Ya podemos ver imágenes y video del gameplay de Golden Axed y agregarlo a nuestro wishlist en Steam:

Al comienzo de la década, SEGA Studios Australia estaba trabajando en una serie de reboots de propiedad intelectual clásica de SEGA, conocidos colectivamente como la serie SEGA Reborn. Esto incluia reboots 2.5D de Golden Axe, Altered Beast y Streets of Rage, así como una versión runner infinita de Shinobi, todo incluido en un mundo hub universal. Lamentablemente SEGA Studio Australia cerró sus puertas en 2013, y con eso el proyecto SEGA reborn se perdió en los anales de la historia de los videojuegos. Con la ocasión del 60° aniversario de SEGA, como regalo especial y para decir “¡Gracias!” a nuestros fans, SEGA está publicando un prototipo funcional de Golden Axe Reborn, un nivel único creado como prueba de concepto. Contiene representaciones de gore extremas, decapitaciones, corte de enemigos en dos y mucha, mucha sangre.

Con suerte de repente vemos una nueva versión de Golden Axe en breve si a esto le va bien.

Para canjear tus juegos gratis, todo lo que necesitarás hacer es dirigirte a la página del producto en Steam indicada arriba y hacer click en “Jugar Juego”. Esto agrega al título a tu biblioteca, y será tuyo para siempre. Actúa rápido, ya que una vez que estén retirados de Steam, será para siempre. Además, los usuarios de Steam que vinculen sus cuentas a su dirección de e-mail en sega60th.com recibirán un clásico de SEGA Saturn, NiGHTS™ into Dreams, gratuitamente durante los 60 días de celebración. Visiten el sitio de Sega 60 para ver más contenidos, historia y descargas de arte para nuestra biblioteca de juegos SEGA.

Los nuevos minijuegos serán lanzados a las 10am (Hora Estándar del Pacífico) en el día especificado y serán removidos a las 10am (Hora Estándar del Pacífico) en el día especificado. El período de 60 días es desde el 14 de Octubre al Domingo 13 de Diciembre.

Desde ayer las redes sociales y el sitio web del 60º Aniversario están iluminadas con 60 días de contenido de SEGA. Se están realizando let’s plays, competiciones regulares, entrevistas a incondicionales de SEGA de todos los rincones del negocio, y ofreciendo contenido de video y editorial cubriendo diferentes partes de la vasta historia de SEGA. Podemos mantenernos al tanto en las cuentas de SEGA en Twitter, Facebook, Instagram y Youtube para ver actualizaciones diarias de contenido.

YouTube Video

The post Sega celebra 60 años con una promoción especial en Steam y 60 días de contenido first appeared on Picando Código.

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

Variable not found

Enlaces interesantes 416

octubre 13, 2020 06:05

Enlaces interesantes

Hoy le toca el turno a un código de estado HTTP que no es nada habitual: HTTP 416 (Range Not Satisfiable). Este resultado se obtiene cuando la solicitud de un rango, especificado en el encabezado Range, indica un valor que no puede ser satisfecho por el servidor, quizás porque son inválidos o porque superan el tamaño del recurso.

Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

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

info.xailer.com

Futuros y/o promesas en Xailer 7

octubre 12, 2020 04:38

Sin duda, los futuros o promesas son la funcionalidad más importante que incorporamos en el futuro Xailer 7 e incluso al propio lenguaje de programación Harbour. Los futuros o promesas son ampliamente conocidos por programadores de otros lenguajes más modernos y para aquellos que aún no los conozcáis, creo que la mejor introducción para ellos es decir que son la simplificación completa de la programación multi-hilo.

Según la Wikipedia, un valor futuro (también llamado un futuro o una promesa) es «Un reemplazo para un resultado que todavía no está disponible, generalmente debido a que su cómputo todavía no ha terminado, o su transferencia por la red no se ha completado.» Puede parecer una definición un poco críptica, pero indica claramente su funcionalidad aunque no hace mención a cómo funcionan ni en que se basan. La funcionalidad de los futuros o promesas está totalmente ligada a la programación multi-hilo. La programación multi-hilo puede llegar a ser tremendamente compleja debido a la facilidad existente de cometer errores de programación que colapsen nuestras aplicaciones o las hagan actuar de forma inesperada.

Un futuro o promesa está estrechamente ligado con el concepto de asíncrono. Es decir, un código que sabemos cuándo lo ejecutamos, pero que no sabemos cuando retornará con un resultado. Pensemos un una función estándar; ésta siempre devuelve un resultado cuando se termina de ejecutar el código que tiene en su interior. Si dicha función tiene que acceder a Internet o realizar un trabajo pesado, nuestra aplicación se quedará completamente paralizada hasta que dicho código termine. Para evitar este problema, en clásica programación multi-hilo crearíamos un proceso asíncrono a través de un hilo:

TThread():Run( bCode )

Donde bCode, es por ejemplo, un bloque de código que se ejecutará cuando se inicie el hilo. Independientemente del tiempo de proceso que necesite dicho código, el retorno desde la ejecución de esa línea será inmediato. Por lo tanto, será responsabilidad suya el crear mecanismos para saber cuando ese código ha terminado y si lo ha hecho correctamente o no. El asunto se complica cuando desde dicho código que se ejecuta en un segundo hilo queremos acceder a zonas de memoria que no han sido creadas en ese mismo hilo o incluso se pretende acceder a la pantalla en el más amplio sentido. La primera regla de oro a tener en cuenta es que desde un hilo secundario nunca se debe acceder a la pantalla y esto es así para cualquier entorno de desarrollo. No es una limitación de Xailer, Harbour o Windows. Aunque le parezca que funciona, olvídese, es un auténtico espejismo, su aplicación terminará con un GPF esporádico más pronto que tarde. La segunda regla de oro es evitar acceder a áreas de memoria creadas por el hilo principal u otros hilos y si se hace, establecer los mecanismos necesarios para que todos los hilos involucrados puedan acceder a esas mismas zonas de memoria de forma ordenada y sin bloquearse. En definitiva, demasiadas complicaciones que impiden que la programación multi-hilo sea ampliamente utilizada.

Los futuros o promesas nos simplifican tremendamente estos inconvenientes ya que tienen mecanismos para solucionar esos dos problemas, que son:

  • Control de cuando el proceso en un segundo hilo ha terminado, bien con éxito o con error a través un simple evento que se dispara cuando esto se produce.
  • Posibilidad de encadenar procesos asíncronos que se ejecutan en cascada.
  • Posibilidad de ejecutar código en el hilo principal desde el propio futuro, lo que permite, por ejemplo, operar con los controles que se ven en pantalla.

Xailer 7 incorpora varias formas de implementar los futuros:

  • A través de una cláusula ASYNC en la definición de función o método
  • Vía estricta programación orientada a objetos

A través de cláusula ASYNC:

Sólo tiene que añadir la cláusula ASYNC en la definición de método o función. Algo así:

METHOD Btn1Click( oSender ) CLASS TForm1 ASYNC

El código que se ejecutará en el futuro debe de ir incluido en comandos AWAIT:

AWAIT INLINE {||
FOR nFor := 1 TO 100
Sleep(10)
RETURN "Exit from first task"

NEXT
}

Puede haber más de una sentencia AWAIT en una misma función o método. Cuando termine la primera sentencia AWAIT de forma asíncrona, se procederá con la ejecución del código del siguiente AWAIT. Esto le permite encadenar procesos asíncronos de una forma tremendamente sencilla.

FUNCTION MyTest(...) ASYNC
AWAIT INLINE {||....}
AWAIT INLINE {||....}
AWAIT INLINE {||....}

Observe que es posible que cada sentencia AWAIT devuelva un valor y además también es posible saber si el AWAIT terminado ha sido con éxito o no. La variable privada de nombre LastAwait recoge la tarea (TFutureTask) que se acaba de terminar de procesar. Y por lo tanto desde la siguiente cláusula AWAIT puede comprobar si la tarea terminó con éxito y el valor retornado:

LastAwait:nState (uncompleted, completed with value, completed with error)
LastAwait:ReturnValue

AWAIT admite la definición del código como un bloque de código extendido utilizando la expresión AWAIT INLINE. Pero también puede llamar directamente a una función con la siguiente sintaxis:

AWAIT FUNCTION MiFuncion( … )

Para controlar como y cuando ha terminado el proceso asíncrono puede crear un AWAIT INLINE adicional sólo para controlarlo o simplemente utilizar el evento TFuture:OnComplete. Si es un poco observador se habrá dado cuenta de que aparentemente no existe ninguna variable que haga referencia a ese objeto TFuture que se menciona. Xailer crea esa variable por usted con ámbito local cuando utiliza la cláusula ASYNC con el nombre ThisFuture. De hecho Xailer crea un objeto TFuture de forma automática en cada función o método con la cláusula ASYNC y lo asigna a dicha variable. Por lo tanto, controlar el fin del proceso asíncrono sería tan sencillo como hacer algo así:

ThisFuture:OnComplete := {|| Msginfo( LastAwait:ReturnValue ) }

Ya hemos visto como crear y ejecutar procesos asíncronos, que incluso se ejecuten con varias tareas en cascada, pero aún no hemos visto nada de como ejecutar código en el hilo principal desde la tarea asíncrona, como por ejemplo cualquier operación de pantalla, que ya hemos indicado que no se pueden hacer desde tareas asíncronas. Para ello Xailer ofrece otro comando de uso muy sencillo y de parecida sintaxis al comando AWAIT, que es el comando SYNCHRO. Este comando se debe de utilizar únicamente desde bloques AWAIT INLINE o desde funciones AWAIT FUNCTION. Por ejemplo:

AWAIT INLINE {||
FOR nFor := 1 TO 100
Sleep(10)

SYNCHRO INLINE {|| ::oProgressBar:nValue := nFor }
RETURN "Exit from first task"

NEXT
}

Observe como actualizamos una barra de progreso desde la propia tarea asíncrona. Más fácil imposible. Al igual que el comando AWAIT que puede utilizar una función con AWAIT FUNCTION, el comando SYNCHRO también admite la sintaxis SYNCHRO FUNCTION en la cual puedo desarrollar toda la complejidad que pudiera tener el código.

A través de programación orientada a objetos:

Este es el método recomendado si tiene experiencia con Xailer o la programación orientada a objetos que ofrece Harbour. Observará que básicamente es lo mismo que hemos explicado hasta ahora, pero sin tener que usar un sólo comando. El primer paso es crear un objeto de la clase TFuture (operación que realiza internamente la cláusula ASYNC):

LOCAL oFuture AS CLASS TFuture
oFuture := TFuture():New()

A continuación hay que añadir las tareas para dicho futuro. Y lo haremos con el método AddThreadTask( bCode ) que recibe como parámetro un bloque de código a ejecutar en dicha tarea:

LOCAL oTask AS CLASS TFutureTask
LOCAL bWork

bWork := { ||
FOR nFor := 1 TO 100
Sleep(30)
NEXT
RETURN "Exit from first task"
}

oTask := oFuture:AddThreadTask( bWork )

Para poder ejecutar código en el hilo principal desde la tarea asíncrona, utilizaremos el método RunSynchroTask( bCode ) y lógicamente las llamadas a este método deben de realizarse desde la propia tarea:

LOCAL oFuture AS CLASS TFuture
LOCAL oTask AS CLASS TFutureTask
LOCAL bWork

bWork := { ||
FOR nFor := 1 TO 100
Sleep(30)

oFuture:RunSynchroTask( {||::oProgressBar:nValue := nFor } )
NEXT
RETURN "Exit from first task"
}

oTask := oFuture:AddThreadTask( bWork )

Por último, para controlar el resultado final del proceso:

oFuture:OnComplete := {|| .... }

Una puntualización importante a realizar es el hecho de que los procesos asíncronos se disparan inmediatamente cuando se crea la tarea. Es decir, AWAIT INLINE y su equivalente en POO TFuture:AddThreadTask( bCode ) no esperan. Ellos son los responsables de lanzar el hilo de ejecución secundario. Si existen varios AWAIT INLINE, cuando se ejecutan, se añaden a la lista de tareas a procesar por el Futuro. Si no hay ninguna tarea pendiente, el primer AWAIT INLINE se procesará de inmediato. Cuando éste termine, se ejecutará el siguiente.

Todo se puede complicar un poquito más, pero no mucho 😉 Es probable que en una tarea se produzca un error de ejecución. ¿Es posible controlarlo? ¡Lo es! y de una forma muy sencilla. El objeto ThisFuture tiene un evento de nombre OnError que recibe como parámetros el objeto TError y la TFutureTask. Si desde dicho evento retornamos un valor lógico verdadero, la siguiente tarea del futuro se ejecutará como si nada hubiese pasado, en caso contrario, se producirá un error de ejecución.

Cuando se publique Xailer 7 le recomendamos que eche un vistazo a las clases TFuture y TFutureTask. Observará que tienen muy pocas propiedades y métodos y que su uso es tremendamente sencillo. Espero que así se lo parezca. Por último comentar, que los futuros estarán disponible en todas las versiones de Xailer, incluso la personal.

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

Blog Bitix

Análisis y guía completa del juego Horizon Zero Dawn

octubre 12, 2020 10:00

Horizon Zero Dawn un juego de mundo abierto cuenta una historia de acción ambientada en un mundo donde existen unas máquinas metálicas con rasgos de animales, pero un mundo habitado por tribus con una tecnología de arcos y fechas que siguen una religión de la diosa Madre y que repudian y temen el mundo de los antiguos. Aloy quiere conocer porque es una paria en la tribu y en su búsqueda de respuestas a sus preguntas descubre muchas otras cosas del mundo en el que vive.

Si algo hay que destacar de la generación de consolas de PlayStation 4 es que tiene un catálogo de juegos amplio y de calidad con buenas críticas. Uno de los títulos más destacados es Horizon Zero Dawn de la desarrolladora Guerrilla Games lanzado a principios del 2017 y con una expansión Frozen Wilds a finales de ese año, a mediados del 2020 ha sido publicado para PC. La segunda parte Horizon Forbidden West será un título intergeneracional estando disponible tanto para PS4 como para PS5. El juego tiene un precio de 20 € con la expansión incluida y en alguna oferta puede estar a 13 € en la tienda de PlayStation en su versión digital, ofrece entre 80 y 130 horas de juego completando todas las misiones secundarias y recados.

Horizon Zero Dawn es un juego de mundo abierto de acción ambientado donde prima la historia que cuenta, en un mundo aparentemente primitivo pero en el que extrañamente existen máquinas que asemejan a animales. La protagonista es una chica, Aloy, en la que nos encarnamos. El juego comienza siendo Aloy un bebé cuidado por un tutor, Rost, y que hace de mentor durante su niñez enseñando a Aloy a sobrevivir recolectando hierbas medicinales, recursos y cazar a las máquinas haciendo emboscadas con sigilo entre las hierbas sin ser vista o atrayéndolas con un silbido o piedras para tenderles un trampa.

Aunque no es un juego RPG tiene algunos de sus componentes sin ser muy extensos como experiencia con el que subir de nivel, un árbol de habilidades, algunos pequeños rompecabezas, nuevas armas y armaduras a las que les pueden aplicar mejoras o ampliar la capacidad de carga o recolección de recursos. Sin afectar a la historia principal en algunos diálogos con personajes del juego se puede elegir entre varias respuestas que varían un poco a su vez la respuesta de los personajes. Tiene una parte de recolección de recursos de varios tipos plantas, animales y recursos de las máquinas de sus componentes extraídos y una vez abatidas con la que elaborar flechas. Las esquirlas y recursos de las máquinas son la moneda del juego con la que comprar pociones, armas y armaduras en los diferentes comerciantes repartidos por el mapa.

Con un sistema de meteorología cambiante que hace que de repente empiece a llover de forma tormentosa, una ventisca de nieve o una tormenta de arena que reducen la visibilidad, también refleja el paso del tiempo con transiciones entre el día y la noche. En algunos aspectos The Legend of Zelda: Breath of the Wild de Nintendo y Horizon Zero Dawn son similares.

Tiene un cuaderno con el catálogo de máquinas examinadas con el foco, este permite ver sus puntos débiles, a qué daño elemental entre el fuego, electricidad, congelación o corrupción son vulnerables o las fortalecen, sus componentes y qué recursos proporcionan. El mapa permite ver los puntos de interés donde están ubicadas cada rebaño de tipo de máquinas, misiones o algunos coleccionables.

La variedad de máquinas con las hay que emplear armas o estrategias diferentes, variedad de armas, coleccionables, tipos de misiones y progresión de habilidades y recolección junto con una historia principal complementada con historias secundarias que permiten conocer a más personajes del mundo hace que el juego no se haga repetitivo. La calidad gráfica del juego incluso permite pararse en algún momento a ver el paisaje desde lo alto de una montaña a un valle o de una noche con aurora boreal.

Es uno de los juegos que tenía muchas ganas desde que compré la consola PlayStation 4 por su temática y tipo de juego. Uno de los mejores juegos de la PlayStation 4 por su historia, calidad gráfica, sonido, doblado al español correctamente tanto en subtítulos como diálogos. He tardado varios meses en completar la historia principal porque cuando he jugado lo he querido hacer tranquilamente disfrutando de cada momento del juego sin tener prisa por terminarlo, aún así algunos coleccionables que me faltan por conseguir por ser difíciles de encontrar.

Como los juegos de la PlayStation 5 son retrocompatibles con la PS4, se puede jugar incluso con mejoras de estabilidad de tasas de fotogramas por segundo y tiempos de carga más reducidos. En la PS4 hay tiempos de carga pero están dentro de la normalidad de la consola PS4 al tener un disco duro en vez de SSD como en la PS5.

Al final del juego te queda la sensación de todas las aventuras, toda la historia en la que has sido partícipe, las decisiones del personaje, los otros personajes. Al escuchar la banda sonora recuerdas todos esos momentos y sensaciones. Un juego obligatoria en la colección del catálogo de la PS4.

Pantalla inicial y menú

Pantalla inicial y menú
Anticipación del juego

Este artículo contiene información de estrategias para completar más fácilmente el juego, parte de la diversión de un juego es descubrir y superar los retos que se plantean por uno mismo. Sin embargo, algunos juegos son difíciles sin una pequeña ayuda que obliga a tener que dedicarles mucho más tiempo o a recomenzarlos.

En algunos juegos el argumento es una de las partes más importantes. El texto del artículo no contiene información acerca del argumento del juego, de la mitad o del final, ni hace ningún spoiler por lo que lo puedes leer sin riesgo de conocer alguna parte del argumento de forma anticipada. Sin embargo, algunos enlaces del artículo a otras páginas y vídeos sí pueden contener información del argumento de modo que recomiendo consultar solo las partes del juego una vez superadas.

Contenido del artículo

Inicio de la historia y argumento

El juego empieza con Aloy de niña y Rost como tutor y mentor en una tribu pero que están apartados ya que son unos parias, la tribu está regida una religión que adora a la diosa Madre y es temerosa del mundo de los antiguos. Enseguida Aloy se hace preguntas de porque es una paria, el juego se desarrolla con la intención de buscar esas respuestas, a medida que avanza el juego se desarrolla una historia con nuevas preguntas a las que buscar respuestas con las que se comprende que es lo que ha pasado en el mundo, cuál es el origen de las máquinas y que les está afectando para cada que cada vez sean más agresivas o de donde surgen nuevos tipos de máquinas aún más peligrosas.

Juego Juego Juego

Juego Juego Juego

Juego Juego Juego

Juego Juego Juego

Juego

Juego

Misiones

Las misiones son variadas, a medida que se avanza en el juego las de de la historia principal se intercalan con las misiones secundarias, algunos recados y conseguir los coleccionables. La mayoría de misiones secundarias y recados surgen a solicitud de diversos personajes a los que Aloy siempre está dispuesta a ayudar. En todas estas misiones se va descubriendo y explorando el mapa.

En algunas zonas del mapa se sitúan puntos clave como calderos que son instalaciones abandonadas y medio derruidas del mundo de los antiguos. En las zonas corrompidas hay rebaños de máquinas corrompidas que hay que matar para liberar la zona. Algunos grupos de bandidos han tomado como asentamiento algunos pueblos y capturado algunos rehenes o esclavos a algunos miembros de las tribus, hay que matar a los bandidos ya sea a base de sigilo y cuando se es descubierto con la lucha. En los terrenos de caza la logia de cazadores pone a prueba las habilidades de los cazadores que han de completar algunas misiones en el menor tiempo posible, según el tiempo empleado para completar la misión se obtiene una recompensa, dependiendo del tipo de misión hay que emplear una estrategia y armas diferentes.

Misiones Misiones

Misiones

Coleccionables

Al mismo tiempo que se completan las misiones y se va explorando el mapa en él hay repartidos numerosos y diferentes coleccionables, ya sean datos que permiten conocer más de la historia u objetos. Algunos coleccionables se van encontrando según se completan las misiones si se explora con un poco de exhaustividad el terreno o instalaciones, otros coleccionables hay que viajar a ese punto para conseguirlo.

La ubicación de algunos coleccionables se marcan en el mapa del mundo al comprar un mapa de sus ubicaciones. Otros coleccionables no aparecen en el mapa, para conseguirlos hay que ser observador o pasar relativamente cerca de su ubicación, muchos de ellos están un poco escondidos y requiere ver algún vídeo para conocer su ubicación si se quieren conseguir todos.

Los coleccionables que aparecen en el mapa son las vasijas antiguas, las flores de metal que han empezado a aparecer recientemente, observatorios que permiten ver cómo era esa zona en el mundo antiguo y las figuras banuk de la tribu de los banuk. Los datos de las misiones suelen están relacionados con las misiones de la historia principal, al realizar una si se explora con un poco de exhaustividad estando atento es fácil encontrarlas todas ya que no suelen estar muy escondidos.

Finalmente, hay unos coleccionables especiales que son unos grilletes, otro es un muñeco de juguete y el otro un collar, se encuentra con facilidad al completar las misiones.

Coleccionables observatorios Colecionables vasijas Coleccionables flores de metal

Coleccionables figuras banuk Coleccionables Frozen Wilds

Coleccionables

Máquinas

A lo largo del juego aparecen numerosos tipos de máquinas con diferentes comportamientos, algunas pequeñas con pocos puntos de vida, más grandes y fuertes, lentas, otras rápidas y otras voladoras. Todas las máquinas si no se actúa con sigilo se ponen en modo agresivo y atacan.

Las más pequeñas se pueden matar con sigilo atrayéndolas con un silbido o una piedra y un golpe crítico con la lanza, las grandes requieren aprovechar sus debilidades para matarlas con mayor facilidad. Una cosa que me ha gustado es que a algunas máquinas no se pueden matar empleando la fuerza bruta lanzándose de frente a por ellas, requiere emplear la estrategia y el arma adecuada.

Otra cosas buena es que no solo hay una táctica posible sino que está a disposición del jugador el crear una que la considere suficientemente efectiva, si a una máquina cuesta matarla mucho o nos hace gran cantidad de daño es que no estamos usando la estrategia adecuada. Empleando la estrategia y armas adecuadas todas las máquinas se pueden matar con facilidad, rapidez y gastando menos recursos. La táctica para la mayoría de ellas es apuntando a sus componentes y extrayéndolos con una flecha férrea con alto poder de extracción, quitar sus componentes les impide usar algunas de sus habilidades ya sea para descubrirnos aún ocultos en la hierba alta por los chatarreros, los acechadores que se camuflan con el terreno o usan armas de ataque a distancia como cañones que luego se pueen recoger como armas pesadas.

Para las máquinas mas grandes una estrategia es atarlas con el lanzacuerdas que durante un tiempo permite evitar sus ataques mientras se aprovecha para hacerle daño apuntando a algún componente. Para grupos de máquinas en los que es difícil atacar a una máquina sin que otras nos ataquen una estrategia es corromper a alguna lo que hará que se ataquen entre ellas, una vez que solo quede una o estén debilitadas es fácil matarlas con el último disparo.

Algunas máquinas son vulnerables al fuego como los rapaces que incendiados caen al suelo y el fuego les hace daño u otras se sobrecalientan como los portadores de muerte o corruptores que les inutiliza temporalmente o les hace más vulnerables. Con electricidad se pueden aturdir temporalmente y congelándolas el daño que reciben por impacto de una flecha en los componentes es considerablemente superior. Colocando algunas trampas antes del inicio de los combates permite añadir cierta seguridad y hacer daño adicional en caso de que les dé tiempo a atacar.

Máquinas Máquinas Máquinas

Máquinas

Habilidades

A medida que se matan máquinas se consigue experiencia que permite subir de nivel, cada nivel otorga un punto de habilidad, completando algunas misiones también se consiguen puntos de habilidad que emplear para conseguir alguna nueva habilidad del árbol de habilidades. Completar las misiones principales, secundarias y con los enemigos abatidos permite conseguir los puntos de habilidad suficientes para adquirir la mayor parte del árbol de habilidades.

Al principio las más útiles con las de recolección y viajera que permiten extraer más recursos de las máquinas con carroñera, más recursos naturales con cosechadora, duplicar la capcidad de la bolsa de medicinas con herborista, creadora de munición para crear más munición con los mismos recursos o quitar modificadores de armas y atuendos con manitas.

Cuando empiezan los combates con máquinas más grandes las habilidades que mejoran el combate son más útiles como concentración para ralentizar el tiempo, disparo tripe para disparar tres flechas al mismo tiempo o ataque silencioso para hacer un ataque crítico que permite matar a máquinas pequeñas de un solo golpe.

Habilidades recolectora Habilidades viajera Habilidades valiente

Habilidades merodeadora

Habilidades

Hierbas medicinales, recursos, pociones, armas y atuendos

Al igual que se pueden recoger recursos de las máquinas que se emplean para fabricar algunos tipos de armas y trampas, se pueden coger recursos de la naturaleza para fabricar medicinas para curar la vida al recibir daño en los combates, para fabricar algunos tipos de flechas y cazando animales conseguir algunas pieles y huesos con los que aumentar la capacidad de carga que reduce el número de veces en los que hay que ponerse a fabricar. Hay también cierta variedad de plantas como medicinales, madera, ígnea, eléctrica, … y animales como jabalí, zorro, rata, ganso, pavo o truchas. Algunas ubicaciones tienen más de estos animales donde cazar si se quieren conseguir sus recursos. Lo que no hace Aloy en ningún caso es acariciar a los animales como en otros juegos, Aloy simplemente los caza para conseguir sus recursos.

Las armas y atuendos tienen huecos para colocar modificadores con las que mejorar alguno de los tipos de daño elemental o en los atuendos para protegerse de ciertos tipos de daño. Según el tipo de daño elemental más habitual que se haga con cada tipo de arma conviene potenciar ese daño elemental, por ejemplo en el arco de caza potenciar el daño o la capacidad de extracción de la fecha férrea.

Conviene tener siempre la bolsa de medicinas llena antes de enfrentarse a un combate previsiblemente difícil, al viajar se suelen encontrar plantas medicinales que permite ir recogiéndolas para tener siempre casi llena la bolsa de medicinas y comprar pociones de vida parciales y completas al encontrarse con un mercader. Ampliar la capacidad de carga de medicinas también es útil, emplear las medicinas como curación permite ampliar la cantidad de vida teniendo menos riesgo de morir en los combates. Las pociones de vida parciales y completas también se pueden fabricar con recursos de grasa y carne de animales, las plantas medicinales no se pueden comprar solo recolectar de plantas, las pociones si se pueden comprar y es más fácil comprarlas a los mercaderes que conseguir los recursos de los animales. Avanzado el juego se tiene esquirlas y recursos de sobra para comprarlas.

Los paquetes de viaje rápido permiten viajar rápidamente a cualesquiera de las diferentes hogueras descubiertas sin tener que hacerlo a pie.

En una de las misiones se solicita conseguir unas células de energía para conseguir un atuendo avanzado, las células de energía se suelen encontrar según se realizan las misiones pero al igual que los coleccionables para alguna es necesario conocer su ubicación exacta con alguna guía de YouTube.

Pócimas Recursos Objetos especiales

Inventario

Crítica

El juego en su estilo no tiene prácticamente defectos. Simplemente algunos pequeños inconvenientes al jugar. Uno de ellos es que de todas las armas solo se puede tener equipadas cuatro lo que en alguna ocasión hace que haya que equiparse con el arma adecuada para enfrentarse a una determinada máquina. De forma similar con la limitación de botones del mando de las consolas en acceso rápido a las pociones solo permite poder activar las hierbas medicinales y una poción, en los combates usar los botones de la cruceta laterales es un tanto incómodo ya que requiere parase un pequeño tiempo a realizar la acción en el cual los enemigos nos pueden golpear.

Encontrar todos los datos de texto requiere utilizar alguna guía para conocer su ubicación ya que durante las misiones es difícil encontrarlos aún explorando detalladamente, algunos están en cierta medida ocultos y solo se encuentra si se buscan activamente.

El fallo más grave que me ha dado el juego es que en algunas pocas ocasiones al realizar un guardado manual se produce un error CE fatal que llega producir un apagado completo de la consola de forma repentina, en el siguiente reinicio de la consola se inicia un proceso de recuperación.

Tutoriales y guías

Cómo matar a cada tipo de máquina

En YouTube hay vídeos que explican cómo matar a cada máquina con una efectividad.

Dónde están y cómo conseguir todos los coleccionables

Los coleccionables de los que no aparece su ubicación en el mapa como los datos hace difícil conseguirlos todos. En algunos vídeos se muestran la ubicación de todos si se quieren conseguir.

Lo mismo si no hemos encontrado alguna célula de energía para conseguir el atuendo especial.

Enciclopedia del juego

En las siguientes wikis se ofrece información detallada del argumento, personajes, máquinas, armas y sus estadísticas.

Guía terrenos de caza

Si alguna prueba de habilidad de los terrenos de caza se nos resiste para conseguir todos los soles abrasadores es que quizá no estamos usando la estrategia adecuada, si no se nos ocurre una en el siguiente vídeo se muestran algunas posibilidades.

Gameplay

Este gameplay del inicio del juego muestra como es si jugabilidad y aspecto gŕafico.

Historia y argumento completo

Todas las elecciones

A lo largo del juego en los diálogos Aloy puede responder con varias posibilidades: con inteligencia, con compasión o con fortaleza según lo que consideremos más apropiado que en la mayoría únicamente varía como responden los personajes. Para ver todas las posibilidades de los diálogos también hay lista de vídeos con las que saber qué hubieran dicho eligiendo otra opción.

Película

Si se quiere repasar el hilo argumental principal del juego en algunos vídeos de YouTube están como si el juego se tratase de una película. Ya que es un juego largo con misiones secundarias intercaladas a veces hace perder el hilo argumental principal del juego.

Y la película de la expansión de Frozen Wilds.

Banda sonora original

Al igual que el apartado gráfico la banda sonora suelen completar un buen juego, con las que escuchar unos pocos segundos de la melodía permite identificarlos rápidamente y atraer los recuerdos del momento del juego. Algunas bandas sonoras tienen gran calidad que no tienen nada que envidiar a las de las producciones cinematográficas, como en el caso de este juego.

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

Coding Potions

Creando formularios en Vue con VeeValidate y Vuelidate

octubre 12, 2020 12:00

Introducción

A día de hoy en muchas webs es esencial el uso de formularios y eso conlleva la creación de validadores para los datos que introducen los usuarios.

Imagina que tienes un campo de texto en la web para que el usuario meta su nombre, y a la vez quieres que como mucho tenga 12 caracteres. Lo suyo sería validar que el texto que mete el usuario no pasa de esa longitud antes de mandarse al servidor, o que al menos directamente no deje al usuario escribir más si llega al límite de caracteres.

Ese ejemplo de validación no es muy complicado, pero si tienes un formulario más complejo, crear todo el sistema de validaciones puede ser complejo, especialmente si no usamos un framework como Vue.

En el artículo de hoy vamos a ver una forma de crear formularios simples con validaciones básicas de los datos. Si eso se te queda corto y necesitas cosas más complejas, también vamos a ver validaciones de los datos usando VeeValidate y VueValidate, dos de las librerías más populares para Vue a la hora de usar formularios.

v-model

Empecemos por lo básico, lo que todo el mundo debería conocer a estas alturas. Imaginemos que tenemos un formulario que por el momento solo tiene un input para escribir. Si queremos recoger el valor que escribe el usuario en tiempo real tenemos que hacer uso de la propiedad v-model.

<template>
  <div>
    <input type="text" v-model="result" placeholder="Escribe algo...">
    {{ text }}
  </div>
</template>

<script>
export default {
  data: () => ({
    result: ""
  ))
}
</script>

En este ejemplo de arriba lo que hago es usar el v-model con la variable que tengo declarada en el data, en este caso result pero la puedes llamar como quieras. Debajo del input pinto el resultado de esa variable. Si abres la página con este componente y empiezas a escribir, te darás cuenta de que al lado del input se escribe lo mismo que estás escribiendo tu en tiempo real.

Si ahora queremos crear el típico formulario de contacto, tan solo tenemos que crear unos cuantos input cada uno con v-model apuntando a una variable distinta y un botón de input del formulario con el evento de @submit para enviar el resultado o imprimirlo por pantalla. Algo así:

<template>
  <div class="contact">
    <h1 class="title">Contacto</h1>
    <form action class="form" @submit.prevent="contact">
      <label class="form-label" for="#name">Nombre</label>
      <input
        v-model="name"
        class="form-input"
        type="text"
        id="name"
        required
        placeholder="Nombre"
      >
      <label class="form-label" for="#email">Email</label>
      <input
        v-model="email"
        class="form-input"
        type="email"
        id="email"
        placeholder="Email"
      >
      <input class="form-submit" type="submit" value="Contactar">
    </form>
  </div>
</template>

<script>
export default {
  data: () => ({
    name: "",
    email: ""
  }),
  methods: {
    contact() {
      console.log(this.name);
      console.log(this.email);
    }
  }
};
</script>

Con el prevent del submit se evita que se haga la acción por defecto de los formularios que es la de recargar toda la página.

v-model a través de componentes

Una cosa que no mucha gente conoce de Vue es que puedes hacer v-model desde un componente a otro. Pongamos que creamos un componente en Vue y dentro del template colocamos unicamente un input.

Si no conocías esto que te voy a contar, en esta situación lo que muchos hacen es emitir un evento cada vez que el usuario escribe para poder tener su valor en el componente padre.

Lo malo de esa estrategia es que cada vez que quieres hacer esto tienes que crear un método en el padre para poder recibir el campo que viene en el evento.

Úna solución más elegante a esto consite en crear un v-model de forma global al componente. Realmente cuando usas un v-model lo que pasa internamente es que Vue va a cambiar eso por:

<input
  v-bind:value="something"
  v-on:input="something = $event.target.value">

Lo que nos lleva a que para hacer esto necesitas dos cosas en el componente hijo, el que tiene el input en este ejemplo:

  • Necesitas un prop llamado value que será el valor incicial que tiene el elemento
  • Necesitas que se emita un evento llamado input con el valor modificado para pasar al componente padre

El componente quedaría así:

// MyInput.vue
<template>
  <input 
    :value="value" 
    @input="$emit('input', $event.target.value)" />
</template>

<script>
export default {
  props: ['value']
};
</script>
// AnotherComponent.vue
<template>
  <div>
    <my-input v-model="name" />
     {{ name }}
  </div>
</template>

<script>
import MyInput from './MyInput.vue';

export default {
  components: { MyInput },
  data: () => ({
    name: ''
  })
};
</script>

Como ves arriba, haciando eso puedes crear un v-model para todo un componente creado por ti. Además, tienes la ventaja de que no necesitas poner @input ya que el valor actualizado automáticamente lo tienes dentro de la variable que pasas en el v-model.

Validación de los datos

Una parte fundamental de los formularios es poder validar los datos que introduce el usuario. Esto evita posibles problemas en el servidor y permite informar al usuario de un error en los datos antes de enviarlos.

Para validar los datos básicamente tienes dos formas: de forma manual y con librerías de terceros. Ninguna de estas estrategias es mejor en todas las situcaciones, tienes que leer y entender cómo funcionan para poder elegir la que mejor se adapte a tus necesidades.

De forma manual

Es la forma más compleja pero tiene la ventaja de que lo controlas todo y puedes complicarlo hasta donde quieras.

Ponte que quieres comprobar que la contraseña que ha metido el usuario tiene al menos un número. Lo más sencillo es crear una propiedad computada para que se ejecute cuando cambian las variables del data asociadas. Por ejemplo:

<template>
 <form>
   <label for="#password">Password</label>  
   <input type="password" id="password" v-model="editedPassword">
   <p class="error" v-if="editedPassword && !passwordHasNumbers">
     Error. La contraseña debe tener al menos un número
   </p>  
 </form>
</template>

<script>
export default {
  data: () => ({
    editedPassword: null
  }),
  computed: {
    passwordHasNumbers() {
      return /\d/.test(this.editedPassword);
    }
  }
}
</script>

Sería así sencillo de validar, pero, ¿qué pasa si queremos hacer más validaciones?

Que al final acabamos con componentes muy complejos, con mucho HTML, muchas computadas para cada una de las validaciones, etc.

Normalmente este sistema se suele usar para formularios que no requieran de muchas validaciones, al final tienes que mirar tus necesidades y analizar si te merece la pena usar este sistema.

Usando Veevalidate

https://logaretm.github.io/vee-validate/

Esta librería la he descubierto recientemente y la verdad es que me ha gustado mucho. Sirve para validar inputs y para ayudarte con la gestión de eventos y errores.

Lo primero, descargar la librería en el proyecto:

npm install vee-validate --save

Siguiente paso, registrar esta librería para poder usarla de forma global en el proyecto. Para ello añade en el main.js:

// src/main.js

import Vue from 'vue';
import VeeValidate from 'vee-validate';
import App from 'App.vue';

Vue.use(VeeValidate);

new Vue({
  el: '#app',
  render: h => h(App)
});

Empecemos con un ejemplo sencillito. Pongamos que queremos crear un formulario de registro con nombre, apellido, correo y contraseña.

En este ejemplo lo que vamos a validar es que al pulsar el botón ningún campo esté vacío y que ademas el email tenga formato de email. Además vamos a hacer que la contraseña tenga más de 6 caractéres.

Primero te voy a poner el código completo de este componente y ahora paso a explicarlo:

<template>
  <div class="jumbotron">
    <div class="container">
      <div class="row">
        <div class="col-sm-8 offset-sm-2">
          <div>
            <h2>Vue.js + VeeValidate - Form Validation</h2>
            <form @submit.prevent="handleSubmit">
              <div class="form-group">
                <label for="firstName">First Name</label>
                <input type="text" v-model="user.firstName" v-validate="'required'" id="firstName" name="firstName" class="form-control" :class="{ 'is-invalid': submitted && errors.has('firstName') }" />
                <div v-if="submitted && errors.has('firstName')" class="invalid-feedback">
                  {{ errors.first("firstName") }}
                </div>
              </div>
              <div class="form-group">
                <label for="lastName">Last Name</label>
                <input type="text" v-model="user.lastName" v-validate="'required'" id="lastName" name="lastName" class="form-control" :class="{ 'is-invalid': submitted && errors.has('lastName') }" />
                <div v-if="submitted && errors.has('lastName')" class="invalid-feedback">
                  {{ errors.first("lastName") }}
                </div>
              </div>
              <div class="form-group">
                <label for="email">Email</label>
                <input type="email" v-model="user.email" v-validate="'required|email'" id="email" name="email" class="form-control" :class="{ 'is-invalid': submitted && errors.has('email') }" />
                <div v-if="submitted && errors.has('email')" class="invalid-feedback">
                  {{ errors.first("email") }}
                </div>
              </div>
              <div class="form-group">
                <label for="password">Password</label>
                <input type="password" v-model="user.password" v-validate="{ required: true, min: 6 }" id="password" name="password" class="form-control" :class="{ 'is-invalid': submitted && errors.has('password') }" />
                <div v-if="submitted && errors.has('password')" class="invalid-feedback">
                  {{ errors.first("password") }}
                </div>
              </div>
              <div class="form-group">
                <button class="btn btn-primary">Register</button>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "app",
  data() {
    return {
      user: {
        firstName: "",
        lastName: "",
        email: "",
        password: ""
      },
      submitted: false
    };
  },
  methods: {
    handleSubmit(e) {
      this.submitted = true;
      this.$validator.validate().then(valid => {
        if (valid) {
          alert("SUCCESS!! :-)\n\n" + JSON.stringify(this.user));
        }
      });
    }
  }
};
</script>

Lo primero que tiene la vista es un formulario html <form>. Dentro del formulario se usa el sumbit.prevent para poder ejecutar un método del componente al pulsar el botón de registro.

Dentro de formulario están los labels y los input. Dentro de los input, hay una propiedad expecial llamada v-validate que sirve para poder meter validaciones para ese input. Por ejemplo el primer input, para escribir el nombre, tiene la validación de required para poder sacar el error si el usuario no escribe.

El input para escribir el nombre del usuario tiene dos validaciones separadas por |, el required y el validador del email. El input de la contraseña tiene dos validadores también, pero se pasan mediante un objeto para poder configurarlo, en este ejemplo para validar que como mínimo tiene que tener 6 caracteres.

Por último en la vista están los errores, por un lado se añade una clase a los inputs en caso de tener error y por otro debajo del input se muestra el mensaje de error. Para ello usa el objeto global de errores y se coge la clave de error que corresponda. Vee-validate coge para las claves de error el id que le pongas a cada input.

En la vista simplemente se mira al enviar el formulario que no haya ningún error y en caso de que no haya errrores se muestra un alert.

DEMO: https://codesandbox.io/s/nnqnonwn9p

Usando Vuelidate

https://vuelidate.js.org/

Vuelidate tiene un enfoque distinto a vee-validate aunque en esencia se pueden usar para lo mismo. Vee-validate se centra en validar inputs HTML mientras que Vuelidate sirve para validar cualquier valor. Es decir, vee-validate asume que vas a tener inputs asociados a valores mediante v-model, eso permite a vee-validate poder ofrecer más abstracciones, ayudas, estilos y accesibilidad a los inputs.

Vuelidate no tiene todas esas abstracciones pero tiene la ventaja de que también se puede usar para validar valores en el data o en las computadas de los componentes.

Para usar vue-validate lo primero es instalarlo:

npm install vuelidate --save

Luego en el archivo main.js tienes que añadir:

// src/main.js

import Vue from 'vue'
import Vuelidate from 'vuelidate'

Vue.use(Vuelidate)

Pongamos el mismo ejemplo que antes del formulario y ahora lo comentamos:

<template>
  <div class="jumbotron">
    <div class="container">
      <div class="row">
        <div class="col-sm-8 offset-sm-2">
          <div>
            <h2>Vue.js + Vuelidate - Form Validation</h2>
            <form @submit.prevent="handleSubmit">
              <div class="form-group">
                <label for="firstName">First Name</label>
                <input
                  type="text"
                  v-model="user.firstName"
                  id="firstName"
                  name="firstName"
                  class="form-control"
                  :class="{
                    'is-invalid': submitted && $v.user.firstName.$error
                  }"
                />
                <div
                  v-if="submitted && !$v.user.firstName.required"
                  class="invalid-feedback"
                >
                  First Name is required
                </div>
              </div>
              <div class="form-group">
                <label for="lastName">Last Name</label>
                <input
                  type="text"
                  v-model="user.lastName"
                  id="lastName"
                  name="lastName"
                  class="form-control"
                  :class="{
                    'is-invalid': submitted && $v.user.lastName.$error
                  }"
                />
                <div
                  v-if="submitted && !$v.user.lastName.required"
                  class="invalid-feedback"
                >
                  Last Name is required
                </div>
              </div>
              <div class="form-group">
                <label for="email">Email</label>
                <input
                  type="email"
                  v-model="user.email"
                  id="email"
                  name="email"
                  class="form-control"
                  :class="{ 'is-invalid': submitted && $v.user.email.$error }"
                />
                <div
                  v-if="submitted && $v.user.email.$error"
                  class="invalid-feedback"
                >
                  <span v-if="!$v.user.email.required">Email is required</span>
                  <span v-if="!$v.user.email.email">Email is invalid</span>
                </div>
              </div>
              <div class="form-group">
                <label for="password">Password</label>
                <input
                  type="password"
                  v-model="user.password"
                  id="password"
                  name="password"
                  class="form-control"
                  :class="{
                    'is-invalid': submitted && $v.user.password.$error
                  }"
                />
                <div
                  v-if="submitted && $v.user.password.$error"
                  class="invalid-feedback"
                >
                  <span v-if="!$v.user.password.required"
                    >Password is required</span
                  >
                  <span v-if="!$v.user.password.minLength"
                    >Password must be at least 6 characters</span
                  >
                </div>
              </div>
              <div class="form-group">
                <label for="confirmPassword">Confirm Password</label>
                <input
                  type="password"
                  v-model="user.confirmPassword"
                  id="confirmPassword"
                  name="confirmPassword"
                  class="form-control"
                  :class="{
                    'is-invalid': submitted && $v.user.confirmPassword.$error
                  }"
                />
                <div
                  v-if="submitted && $v.user.confirmPassword.$error"
                  class="invalid-feedback"
                >
                  <span v-if="!$v.user.confirmPassword.required"
                    >Confirm Password is required</span
                  >
                  <span v-else-if="!$v.user.confirmPassword.sameAsPassword"
                    >Passwords must match</span
                  >
                </div>
              </div>
              <div class="form-group">
                <button class="btn btn-primary">Register</button>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { required, email, minLength, sameAs } from "vuelidate/lib/validators";

export default {
  name: "app",
  data() {
    return {
      user: {
        firstName: "",
        lastName: "",
        email: "",
        password: "",
        confirmPassword: ""
      },
      submitted: false
    };
  },
  validations: {
    user: {
      firstName: { required },
      lastName: { required },
      email: { required, email },
      password: { required, minLength: minLength(6) },
      confirmPassword: { required, sameAsPassword: sameAs("password") }
    }
  },
  methods: {
    handleSubmit(e) {
      this.submitted = true;

      // stop here if form is invalid
      this.$v.$touch();
      if (this.$v.$invalid) {
        return;
      }

      alert("SUCCESS!! :-)\n\n" + JSON.stringify(this.user));
    }
  }
};
</script>

Lo primero que tienes que hacer es meter dentro de la lógica del componente un objeto llamado validations en el que tienes que escribir todas las propiedades que quieres validar.

Lo siguiente que tienes que saber es que Vuelidate expone un objeto llamado $v que contiene los errores de las validaciones. Además, este objeto contiene el objeto sobre el que tienes que hacer v-model en los inputs. Es decir tienes que hacer:

<input type="text" v-model="$v.user.firstName.$model" id="firstName" name="firstName" class="form-control" />

Otra forma de hacerlo, que es la que puedes ver en el ejemplo del formulario que estamos comentando, es crear un v-model con un objeto llamado igual que el que hay en las validaciones y con las mismas propiedades, es decir:

data() {
    return {
        user: {
            firstName: "",
            lastName: "",
            email: "",
            password: "",
            confirmPassword: ""
        },
        submitted: false
    };
},

Ahora los errores, como he dicho, dentro del objeto global $v tienes las validaciones. Por ejemplo para pintar el error del email:

<div v-if="submitted && $v.user.email.$error" class="invalid-feedback">
  <span v-if="!$v.user.email.required">Email is required</span>
  <span v-if="!$v.user.email.email">Email is invalid</span>
</div>

Además, tienes que poner el input con la clase error a mano también usando este mismo objeto.

Por último, dentro del botón de submit puedes comprobar si todos los campos son correctos mediante:

if (this.$v.$invalid) {
  return;
}

Como ves, esta librería es mucho más tediosa que vee-validate porque tienes que montar muchas cosas a mano.

DEMO: https://codesandbox.io/s/r0lkrn4wxo?file=/app/App.vue:4743-4828

Conclusiones

Otra forma de poder validar formularios, que no la he explicado para no hacer demasiado extenso este artículo, es la de instalar una librería completa de formularios. En lugar de tener que meter a mano los inputs, en este tipo de librerías tienes que crear un objeto de configuración con los campos que quieres en tu formulario y las validaciones que necesitas. Es otra alternativa también válida, aunque solo la recomiendo si tienes muchos formularios en la web.

Te dejo la librería de Vue-formulate por si quieres echar un ojo a este tipo de librerías:

https://vueformulate.com/

En general, yo recomiendo la librería de vee-validate siempre que sea posible ya que las abstracciones para los inputs permiten que el código sea menos complejo.

Si hay que validar otro tipo de datos aparte de inputs o hay que hacer cosas más complejas, si que puede interesar instalar la de Vuevalidate.

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

Blog Bitix

Sobre la generación de consolas PlayStation 5 y Xbox Series X/S antes de que se comercialice y la renovación del hardware de PC

octubre 10, 2020 10:30

En un mes se comenzará la comercialización de la siguiente generación de consolas tanto de la Xbox Series X/S de Microsoft que sustituye a la Xbox One y una semana más tarde la PlayStation 5 de Sony que sustituye a la PlayStation 4 poco antes de la época navideña. Ambas consolas prometen ofrecer una calidad de juegos mucho mayores que los que ya eran en la generación que sustituyen, empezando por un hardware mucho más actual que el que tuvieron sus predecesoras que cuando salieron casi salieron anticuadas con respecto al hardware de la época.

Aún ya habiendo noticias de la nueva genración decidí compra una PlayStation 4 por su amplio catálogo, porque prefiero el modelo slim que en las nueva generación no saldrá pasados unos años e igualmente en los primeros meses falta de juegos de la nueva generación.

Las consolas no son los únicos productos nuevos sino que en poco tiempo coincide con una importante renovación también su competencia del PC destinado a juegos y las plataformas de juego mediante streaming que están presentando las compañías más importantes del sector tecnológico de consumo que quizá dificulte la existencia de una siguiente generación de consolas a esta próxima.

Aún así a pocas semanas de sus lanzamientos quedan muchas dudas por resolver que no serán despejadas hasta pasado unos meses o algún año.

Contenido del artículo

El hardware de las consolas Xbox Series y PlayStation 5

Ambas consolas tienen un hardware similar, incorporan almacenamiento SSD y nuevamente el procesador y la gráfica está basado en un diseño personalizado por AMD para consolas de sus procesadores y gráficas para PC.

Las consolas de ambas marcas ofrecen una versión solo digital junto a otro modelo con lector de discos para los amantes de lo físico y coleccionistas, con todo la transición a lo digital parece claro. Si el juego en streaming no se impone en los siguientes años y hay una nueva Xbox y PlayStation es muy posible que ya sea solo digital.

Se lleva hablando de la nueva generación desde hace ya varios años, ya se conocen los detalles más importantes de ambos productos como el precio y su diseño pero aún quedan muchas dudas por resolver. De lo que no hay duda es que a lo largo de su generación va a haber juegos con una calidad gráfica muy importante. Los juegos de la Xbox One y PlayStation 4 ya tienen juegos reseñables, la nueva generación con una capacidad gráfica casi cinco veces superior va a posibilitar juegos espectaculares. Sin embargo, los gráficos no son lo único destacable ni quizá lo más destacable.

El almacenamiento SSD

Los SDD proporciona una importante mejora en la tasa de transferencia entre el almacenamiento y la memoria respecto al disco duro de las generaciones anteriores, si el disco duro tiene una tasa de transferencia de entre 50-100 MB/s el almacenamiento llega a los 5000 y 9000 MB/s con algoritmos de compresión lo que va a suponer una reducción importante en los tiempos de carga de los juegos, un aspecto muy criticado de la One y PS4. Al mismo tiempo, tener ese ancho de banda permite a los desarrolladores nuevas técnicas de programación aprovechando mejor el hardware al evitar precargar datos y mantenerlos en memoria cuando realmente no se usan, simplemente permanecen cargados por si se necesitan sin tener que recurrir al disco duro que tiene un ancho de banda insuficiente para cargar datos bajo demanda.

Procesador y gráfica, el SoC

El SoC tanto de la Xbox como de la PlayStation están basados igualmente en una CPU de 8 núcleos que la generación anterior pero basados en la arquitectura Zen 2 mucho más moderna y potente. Por otro lado la gráfica en el mismo chip está basada la arquitectura RNDA 2 con soporte para trazado de rayos e igual mucho más potente que la generación anterior, las gráficas para PC se presentan unos días antes de la comercialización de las consolas. Los SoC tienen diferencias en cuanto a frecuencias de trabajo y número de núcleos gráficos pero que ofrecen similar rendimiento de entre 10 y 12 TFOPS, en teoría capaces de ofrecer juegos en 4K con 60 FPS.

PlayStation 5

La PS5 esta generación ofrece dos modelos, uno solo digital sin disco y otra con disco pero que salvo por esto en el resto de características son iguales. El modelo solo digital con un precio de lanzamiento de 400 € y la que tiene disco con un precio de 500 €. La PS5 compite con la Series X.

El almacenamiento SSD soldado en la placa base parece que ofrecerá un mayor ancho de banda que en la Xbox con una capacidad de almacenamiento de 825 GB que disponibles para el usuario se quedará en unos 650 GB para el usuario, dispone de una ranura interna con la que ampliar el almacenamiento utilizando un SSD compatible en un formato similar a los disponibles a los de PC, para ampliar el almacenamiento es necesario desmontar la tapa de la consola pero se puede hacer sin perder la garantía. Un mando háptico que promete una nueva forma de interacción con el mando con los gatillos al ofrecer una resistencia variable según las circunstancias del juego, una diferencia respecto al mando de la Xbox. Está por ver si los desarrolladores de los juegos lo aprovechan igual que el panel táctil que se sigue manteniendo pero por ver su para algo más que como un botón más. El mando se actualiza con conectividad USB-C y sigue siendo con batería recargable de mayor duración.

La consola físicamente tiene un diseño bastante artístico que parece se sobrepone a lo funcional con un formato aplanado tradicional para su colocación tanto en horizontal como en vertical utilizando una peana en ambos casos. La presencia o no del lector de discos varía ligeramente el diseño de ambos modelos de consolas haciendo que la digital sea más simétrica. Ambas consolas tienen un tamaño sensiblemente más grande que la anterior generación, los primeros modelos de las generaciones suelen ser más grandes el los futuros modelos slim su tamaño se reducirá. La PS5 tiene unos conductos a través de los cuales aspirar el polvo aunque para llegar al disipador hay que desmontar la placa base. El ventilador y orificios de ventilación son más grandes que siempre es de agradecer para que la consola mantenga unas temperaturas de trabajo aceptables.

La PS5 con dos modelos en diferentes precios permite que el modelo digital salvo por el lector de discos sea más barata, compite en igualdad de características con la Series X de los dos modelos de Xbox aún así tiene un precio mayor justificado por ser más capaz que la Series S.

Se ha anunciado que los miembros de la suscripción Plus en el momento de lanzamiento tendrán acceso a Plus Collection formado por varios de los mejores juegos de la anterior generación, mostrando la retrocompatibilidad de la consola. También, Sony ha confirmado que todo el catálogo de la PS4 será compatible con la PS5 salvo unos 10 juegos entre los que no hay ninguno de los más conocidos, tanto para el formato físico de los juegos como digitales así como las partidas guardadas que se pueden trasferir a la nueva consola.

PlayStation 5 PlayStation 5 Digital PlayStation 5 Family

PlayStation 5

PlayStation

Especificaciones técnicas

Las características del hardware son las siguientes:

Especificaciones técnicas del hardware de PlayStation 5
PlayStation 5
CPU x86-64-AMD Ryzen Zen 8 Cores / 16 Threads at 3.5GHz (variable frequency)
GPU Ray Tracing Acceleration Up to 2.23 GHz (10.3 TFLOPS)
GPU Architecture AMD Radeon RDNA 2-based graphics engine
Memory/Interface 16GB GDDR6/256-bit
Memory Bandwidth 448GB/s
Internal Storage Custom 825GB SSD
IO Throughput 5.5GB/s (Raw), Typical 8-9GB/s (Compressed)
Expandable Storage NVMe SSD Slot
External Storage USB HDD Support
Optical Drive (optional)
Ultra HD Blu-ray (66G/100G) ~10xCAV
BD-ROM (25G/50G) ~8xCAV
BD-R/RE (25G/50G) ~8x CAV
DVD ~3.2xCLV
PS5 Game Disc Ultra HD Blu-ray, up to 100GB/disc
Audio "Tempest" 3D AudioTech
Video Out HDMI Out port
Support of 4K 120Hz TVs, VRR (specified by HDMI ver 2.1)
Dimensions
PS5 - 390mm x 104mm x 260mm (width x height x depth)
PS5 Digital Edition - 390mm x 92mm x 260mm (width x height x depth)
Weight
PS5 - 4.5kg
PS5 Digital Edition - 3.9kg
Power
PS5 - 350W
PS5 Digital Edition - 340W
Input / Output
USB Type-A port (Hi-Speed USB)
USB Type-A port (Super Speed USB 10Gbps) x2
USB Type-C port (Super Speed USB 10Gbps)
Networking
Ethernet (10BASE-T, 100BASE-TX, 1000BASE-T)
IEEE 802.11 a/b/g/n/ac/ax
Bluetooth 5.1

Xbox Series X/S

La Series X/S son dos modelos de nueva generación con diferentes características en cuanto almacenamiento, solo digital o con disco, diseño y potencia gráfica. También en precio son diferentes, la Series S se sitúa en el precio de los 300 € en el momento de lanzamiento y la Series X en los 500 €.

Las Series S ofrece un almacenamiento de 512 GB de los cuales parte quedan reservados para el sistema, la Series X por el contrario tiene un almacenamiento de 1 TB de los cuales también queda una parte reservados para el sistema, en ambos casos el almacenamiento también está soldado en la placa base la capacidad de almacenamiento puede se ampliada mediante unas tarjetas externas con un formato propietario con una ranura externa de fácil acceso. El mando salvo algún cambio estético no incorpora ninguna mejora y sigue siendo con pilas AA no con batería recargable. La Series S tiene un diseño más pequeño en blanco con la rejilla de ventilación en negro, no ofrece lector de discos siendo solo digital, una menor potencia estando destinada a jugar en resoluciones de 1080 o 1440 y un tamaño pequeño en formato rectangular algo aplanado.

La Series X ofrece más potencia gráfica, más almacenamiento, ofrece lector de discos y tiene un mayar tamaño en formato rectangular en negro. En ambas consolas prima la funcionalidad sobre el diseño aún siendo un diseño atractivo, por el diseño interno publicado con ventiladores y rejillas de ventilación grandes que garanticen una buena refrigeración con temperaturas contenidas y bajo nivel sonoro.

La estrategia de Microsoft con las Series X/S con dos modelos diferentes en potencia gráfica es precio es interesante por su diferencia de precio. La Series S ofrece una consola de nueva generación a un precio sensiblemente más económico.

Xbox Series X Xbox Series X Xbox Series X

Xbox Series X

Xbox Series S Xbox Series S

Xbox Series S

Xbox Series X Controller Xbox Series S Controller

Mandos de las consolas Xbox Series X/S

Xbox

Especificaciones técnicas

Las características del hardware son las siguientes:

Especificaciones técnicas del hardware de Xbox Series X
Xbox Series X
PROCESSOR
CPU 8X Cores @ 3.8 GHz (3.66 GHz w/SMT) Custom Zen 2 CPU
GPU 12 TFLOPS, 52 CUs @1.825 GHz Custom RDNA 2 GPU
SOC Die Size 360.45 mm Process. 7nm Enhanced
MEMORY & STORAGE
Memory 16GB GDDR6 w/320 bit-wide bus
Memory Bandwidth 10GB @ 560 GB/s, 6GB @ 336 GB/s.
Internal Storage 1TB Custom NVME SSD
I/O Throughput 2.4 GB/s (Raw), 4.8 GB/s (Compressed, with custom hardware decompression block)
Expandable Storage Support for 1TB Seagate Expansion Card for Xbox Series X|S matches internal storage exactly (sold separately). Support for USB 3.1 external HDD (sold separately).
VIDEO CAPABILITIES
Gaming Resolution True 4K
High Dynamic Range Up to 8K HDR
Optical Drive 4K UHD Blu-Ray
Performance Target Up to 120 FPS
HDMI Features Auto Low Latency Mode. HDMI Variable Refresh Rate. AMD FreeSync.
SOUND CAPABILITIES
Dolby Digital 5.1
DTS 5.1
Dolby TrueHD with Atmos
Up to 7.1 L-PCM
PORTS & CONNECTIVITY
HDMI 1x HDMI 2.1 port
USB 3x USB 3.1 Gen 1 ports
Wireless 802.11ac dual band
Ethernet 802.3 10/100/1000
Accessories radio Dedicated dual band Xbox Wireless radio.
DESIGN
Dimensions 15.1cm x 15.1cm x 30.1cm
Weight 9.8 lbs.
Especificaciones técnicas del hardware de Xbox Series S
Xbox Series S
PROCESSOR
CPU 8X Cores @ 3.6 GHz (3.4 GHz w/SMT) Custom Zen 2 CPU
GPU 4 TFLOPS, 20 CUs @1.565 GHz Custom RDNA 2 GPU
SOC Die Size. 197.05 mm2
MEMORY & STORAGE
Memory 10GB GDDR6 128 bit-wide bus
Memory Bandwidth 8GB @ 224 GB/s, 2GB @ 56 GB/s.
Internal Storage 512GB Custom NVME SSD
I/O Throughput 2.4 GB/s (Raw), 4.8 GB/s (Compressed, with custom hardware decompression block)
Expandable Storage Support for 1TB Seagate Expansion Card for Xbox Series X|S matches internal storage exactly (sold separately). Support for USB 3.1 external HDD (sold separately).
VIDEO CAPABILITIES
Gaming Resolution 1440p
Performance Target Up to 120 FPS
HDMI Features Auto Low Latency Mode. HDMI Variable Refresh Rate. AMD FreeSync.
SOUND CAPABILITIES
L-PCM, up to 7.1
Dolby Digital 5.1
DTS 5.1
Dolby TrueHD with Atmos
PORTS & CONNECTIVITY
HDMI 1x HDMI 2.1 port
USB 3x USB 3.1 Gen 1 ports
Wireless 802.11ac dual band
Ethernet 802.3 10/100/1000
Accessories radio Dedicated dual band Xbox Wireless radio
DESIGN
Dimensions 6.5cm x 15.1cm x 27.5cm
Weight 4.25 lbs.

Dudas no resueltas

Ya se conoce la información más importante de las consolas que son su precio, fecha de lanzamiento, especificaciones técnicas y aspecto externo e interno.

¿Que temperaturas alcanzarán? ¿Serán ruidosas? El calor generado y el nivel sonoro de funcionamiento es todavía una duda a despejar, el diseño de las Series X/S parece más eficiente para la disipación de calor, la PS5 también ofrece un disipador y ventilador grande pero los componentes parecen colocados habiendo dado prioridad al diseño estético de la consola, en cualquier caso la mitad del tamaño de la PS5 está ocupado por el ventilador y el disipador. En la PS5 el SoC queda en una esquina del disipador cuando en la Series X/S está en el medio.

¿El espacio de almacenamiento será suficiente para los juegos? La calidad gráfica mejorada significa que los juegos van a ocupar más espacio de almacenamiento. En la nueva generación el espacio de 512 GB, 825 GB y 1 TB de los cuales una parte está reservada para el sistema quizá no de para tener instalados al mismo tiempo muchos juegos y haya que adquirir algún elemento de almacenamiento externo, especialmente en la Series S.

Aún se han visto pocos juegos, lo que se ha visto en algunos casos sí muestran una calidad gráfica superior pero no tanto como la diferencia de potencia que tienen sobre la generación anterior de casi 5 veces más. Pasados uno o dos años desde el lanzamiento comenzarán a llegar los juegos desarrollados en exclusiva para la nueva generación y entonces se apreciará todo el potencial de las consolas.

¿Cómo será la retrocompatiblidad? Sony y Microsoft ya han aclarado que la PS5 será compatible con prácticamente todos los juegos de la generación anterior y la Xbox con los de las anteriores aunque mencionando que algunos juegos pueden estar no libres de defectos en la compatibilidad.

¿El mando háptico de la PS5 mejorará la experiencia de los juegos? El mando háptico de Sony promete nuevas formas de interacción pero puede ocurrirle lo mismo que al panel táctil, que la mayoría de los juegos no lo utilicen. Esto es una característica exclusiva de la PlayStation al igual que el panel táctil y los juegos que estén para ambas plataformas es posible que no lo aprovechen y solo lo sean en unos pocos exclusivos de PlayStation.

¿La Series S salvo la resolución tendrá la misma calidad gráfica? La Series S tiene una potencia 3 veces menor que la Series X o cualquiera de las PlayStation, está por ver si la única diferencia será la resolución o habrá diferencias en algunos detalles visuales más o menos importante como efectos, detalles o línea de horizonte. En el inicio de la generación con únicamente juegos de ĺa generación anterior la S rendirá muy bien y será un salto importante sobre la One pero está por ver si los juegos serán iguales en la X que en la S. También, es muy posible que Microsoft lance una Series X solo digital en el futuro para competir con la PlayStation solo digital.

Algunos juegos se juegan mejor con teclado y ratón, es el motivo por el que algunos jugadores prefieren el PC. En algunos juegos a los mandos les faltan botones y los desarrolladores tienen crear elementos para realizar acciones, poder jugar con teclado y ratón posiblemente sería algo mucho más interesante que algunas otras cosas (coo el desaprovechado panel táctil y los gatillos hápticos).

Las especificaciones técnicas no lo son todo, la diferencia la marcarán los juegos potentes que hagan vender consolas y millones de copias de esos juegos. Xbox en esta generación parece haber trabajado bien sus consolas y ha reforzafo a base de talonario sus desarrolladoras comprando Bethesda lo que le proporcionará futuros juegos de las conocidas franquicias The Elder Scrolls, DOOM, Fallout o Dishonored. Sin embargo, algo en lo que ha destacado Sony en sus consolas es en proporcionarles un catálogo amplio y de calidad durante toda la generación.

¿Comprar o esperar? ¿Si es comprar, cúal?

Aún quedan muchos e importantes detalles por revelar, realizar una prereserva antes de que el producto se lance sin esperar a las primeras opiniones de los usuarios es arriesgado ya que en los lanzamientos pasados unas semanas o meses se descubren los primeros problemas de diseño y experiencia en las consolas.

Aún con todas las presentaciones solo se han mostrado pequeños gameplays, no se han desvelado las interfaces de usuario. Por otro lado parece que en el lanzamiento no va ha haber ningún juego que aproveche la capacidad gráfica al completo de las consolas que sirva como ejemplo de la potencia gráfica que son capaces de desarrollar. Los primeros juegos exclusivos de la nueva generación tardarán unos meses y para que el catálogo sea amplio pasará algún año. Por otro lado, en los lanzamientos de los juegos estos van aser más caros, de unos 70 u 80 €, aunque pasado un tiempo se podrán adquirir con importantes descuentos en las habituales ofertas.

Aún habiendo estado recibiendo noticias a lo largo de los últimos dos años sobre la nueva generación de consolas no es mala idea esperar unos meses o el primer año hasta comprar la nueva consola. La generación de PS4 y Xbox One aún recibirán nuevos juegos intergeneracionales para ambas generaciones.

Es casi seguro que pasados unos años ambas marcas lancen modelos más pequeños slim o en el caso de Microsoft una Series X solo digital algo más barata y ya con algunos fallos de diseño de las consolas originales corregidos.

En cuanto a que consola comprar es una decisión que depende de cada uno. Si se es usuario de juegos digitales comprar la siguiente generación permite aprovechar el catálogo de la anterior con una mejor experiencia en tiempos de carga y fotogramas por segundo más estables. La Xbox Series S tiene un precio muy atractivo pero con importantes limitaciones. El siguiente año es casi seguro que reducirán algo su precio y se ofrezcan con algún juego incluido en el mismo paquete.

El hardware de los PCs

Pero las consolas no es el único hardware que va a ser renovado en las mismas fechas. Las gráficas Ampere de la serie 3000 de Nvidia con un mejor proceso litográfico que las Turing de la serie 2000 y otras mejoras implementadas proporcionan una mejora notable en el rendimiento con el que Nvidia confía mantenerse sin demasiada competencia en el segmento de la gama alta. Las gráficas Ampere presentan la segunda versión de sus núcleos dedicados a Ray Tracing que ofrecen mayor realismo gráfico en las luces y reflejos.

AMD en el mismo mes actualiza tanto los procesadores con una nueva generación basada en Zen 3 con mejoras importantes en rendimiento sobre la generación anterior que pone en dificultades a Intel también en el ámbito de loes juegos, desde los primeros Ryzen y con sus problemas con el nodo litográfico de 10 nanómetros Intel ha visto como AMD les ha superado tanto en rendimiento con unos precios incluso más económicos. Un poco más tarde AMD también presentará sus gráficas basadas en su arquitectura RNDA 2 con la que AMD pretende competir con Nvidia en la gama media-alta con una buena relación precio/rendimiento, esta arquitectura incorpora por primera vez en las gráficas de AMD la tecnología de Ray Tracing con lo que seguramente y gracias a que las consolas también incorporarán trazado de rayos serán más juegos los que lo implementen popularizando su uso.

Intel no se está quedando quieta aún habiendo pasado dificultades y cierto estancamiento que ha aprovechado AMD. Sus problemas con el nodo de 10 nanómetros parecen superados llegando los primeros productos con estos nodos, los 10nm de Intel son equivalentes a los nodos menores de 7nm y 8nm de TSMC y Sansung que utilizan AMD y Nvidia. También está trabajando en el apartado de gráficas con su gráficas Xe que son un salto importante de rendimiento tanto en gráficas integradas como la vuelta al mercado de gráficas discretas al menos para competir en la gama media y baja. La contratación de Raja Koduri que antes trabajó para AMD en sus gráficas ya es patente en la 11º generación de procesadores Ice Lake.

Otra noticia muy relevante es que Nvidia ha comprado Arm por la cantidad de 40K millones que si los reguladores de competencia aprueban proporciona a Nvidia una parte que le faltaba, procesadores. Procesadores basados en la arquitectura Arm que utilizan prácticamente todos los dispositivos móviles o que lleven batería por su bajo consumo. No solo eso sino que Arm ya tiene planes de diseños eliminando la restricción de consumo e igualarla a los de la arquitectura x86 de Intel y AMD con la intención de acceder a los ordenadores personales y centros de datos de alto rendimiento. Apple ya ha anunciado abandona los procesadores x86 en favor de Arm después de hace un tiempo de hacer lo mismo con los procesadores PowerPC en favor de x86 y aunque en ordenadores Apple no tiene una cuota importante si puede dar un impulso importante para que incitar a Microsoft a que no se quede atrás y también lance un Windows que soporte Arm, y entonces sí con la totalidad de la cuota de mercado suponer un potencial problema para x86 si el software de las aplicaciones funcionan correctamente y buen rendimiento.

Sobre qué es mejor para jugar, PC o consola, hay motivos para ambas plataformas, este es otro debate. Hay gente que pudiendo que no tiene ningún inconveniente en jugar tanto en consola como PC o incluso tener varias consolas de diferentes marcas de la misma generación. Lo que está claro es que en poco tiempo se van a renovar muchos de los elementos hardware dedicados a los juegos para disfrute de los jugadores.

El juego en la nube

Durante los próximos años las consolas seguirán siendo relevantes pero quizá no haya una siguiente generación si los servicios como de juego en streaming empiezan a tener un catálogo de juegos equivalente al de las consolas en títulos triple A. De aquí a unos 7 años de la siguiente generación las cosas no van a ser las mismas.

Los servicios en la nube tiene varias ventajas como no tener tiempos de descarga ni actualizaciones. No tener que comprar un hardware dedicado y poder jugar en varios dispositivos. Su desventaja es que son servicios de suscripción con una cuota mensual que no requiere un adelanto de compra de hardware pero que no son tampoco baratos.

Google ya tiene Stadia, Amazon ha presentado Luna, Nvidia tiene Geforce Now, Microsoft tiene xCloud y PlayStation tiene PlayStation Now.

Presentaciones

En los meses previos al lanzamiento tanto Sony como Microsoft han realizado varios eventos y vídeos en los que han ido proporcionando información tanto de las consolas como de juegos en desarrollo, gameplays e incluso el interior de las consolas. Todo esto para excitar a los usuarios para saciar la sed de información, mantener unas expectativas de compra altas e influenciar en la decisión de compra.

PlayStation 5

Presentación PS5.

El camino a PS5 con los detalles de las especificaciones.

Juegos de PS5.

Aspecto e interior de la PS5.

Xbox Series X/S

Presentación de Series X/S.

Juegos de Series X/S.

Aspecto e interior de Series X/S.

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

Variable not found

Valores y parámetros en cascada con Blazor

octubre 06, 2020 06:42

BlazorLos parámetros son el mecanismo básico de comunicación entre componentes, pues permiten que un componente padre o contenedor puede especificar, utilizando atributos, los valores que desea enviar de forma individualizada a los componentes utilizados en su interior. Un ejemplo lo vemos en el siguiente bloque de código, donde indicamos a un componente el número de veces que debe mostrar un contenido:
<NumberList Count="10" />
Desde el punto de vista del componente <NumberList>, la recepción de este parámetro es trivial, simplemente indicando el atributo [Parameter] en su propiedad pública Count:
<ul>
@for (int i = 0; i < Count; i++)
{
<li>@i</li>
}
</ul>
@code {
[Parameter]
public int Count { get; set; }
}
Estructura jerárquica de componentesEste mecanismo va bien si queremos pasar el parámetro a un componente específico mediante atributos, pero puede haber ocasiones en las que nos interesa pasar parámetros a todos los componentes que se encuentren por debajo en la estructura jerárquica sin tener que ir pasándolos de uno en uno. Esto puede ser especialmente útil si, como en el diagrama, existen varios niveles de componentes en los que deseamos tener acceso a la misma información.

¡Bienvenidos a los cascading values and parameters, o valores y parámetros en cascada!

Pasar valores a todos los componentes descendientes

Cuando un componente necesita pasar valores a todos los componentes que se encuentren por debajo de él en el árbol, debe utilizar la etiqueta <CascadingValue> como se muestra a continuación:
<CascadingValue Value="UserInfo">
<UserHeader>...</UserHeader>
<UserDetails>...</UserDetails>
</CascadingValue>

@code {
// UserInfo podríamos obtenerlo de algún sitio
private UserInformation UserInfo = new UserInformation() { Name="José M. Aguilar" }
}
Fijaos que en Value podemos introducir cualquier tipo de objeto .NET, incluso expresiones o referencias al componente actual como Value="this".
Lo que conseguimos con <CascadingValue> es añadir a la cascada un valor de tipo UserInformation que los descendientes podrán obtener directamente declarando un parámetro en cascada, como se puede ver a continuación:
@* File: UserDetails.razor *@
<h2>User details</h2>
@UserInfo.Name

@code {
[CascadingParameter]
public UserInformation UserInfo { get; set; }
}
El funcionamiento es idéntico a los parámetros tradicionales expresados como atributos al instanciar el componente, exceptuando que aquí usamos [CascadingParameter] para indicar que el parámetro debe ser poblado desde los valores introducidos en la cascada en niveles superiores de la jerarquía de componentes.

Los valores de los parámetros decorados con el atributo [CascadingParameter] se enlazarán durante la inicialización del componente a los valores en cascada del mismo tipo definidos en cualquier nivel anterior de la jerarquía de componentes. Así, en el ejemplo anterior, el parámetro UserInfo es enlazado correctamente porque existe un valor en la cascada de tipo UserInformation.

Obviamente esto supondría una limitación en caso de existir más de un valor del mismo tipo, como en el siguiente ejemplo, donde queremos propagar valores para el color de texto y de fondo a los componentes descendientes:
<CascadingValue Value="_backgroundColor">
<CascadingValue Value="_textColor">
<Title Text="This is the title"></Title>
...
</CascadingValue>
</CascadingValue>
@code {
string _backgroundColor = "blue";
string _textColor = "white";
}
En el bloque de código anterior, fijaos que estamos estableciendo dos valores en cascada de tipo string, pero los componentes descendientes que quieran utilizarlos no tendrán forma de distinguirlos al ser ambos del mismo tipo. La aplicación no fallará, pero los parámetros en cascada no serán poblados.

En estos casos debemos establecer un nombre para los valores en cascada, de forma que luego podamos referirnos a ellos de forma inequívoca:
<h1>Demo page</h1>
<CascadingValue Value="_backgroundColor" Name="BackgroundColor">
<CascadingValue Value="_textColor" Name="TextColor">
<Title Text="This is the title"></Title>
</CascadingValue>
</CascadingValue>

@code {
string _backgroundColor = "blue";
string _textColor = "white";
}
Y así, ya desde los componentes que los consumen podremos indicar a qué valores nos referimos en cada caso:
@* File: Title.razor *@
<h2 style="background-color: @BackgroundColor; color: @TextColor">@Text</h2>

@code {
[Parameter]
public string Text { get; set; }

[CascadingParameter(Name = "TextColor")]
public string TextColor { get; set; }
[CascadingParameter(Name = "BackgroundColor")]
public string BackgroundColor { get; set; }
}
Por último, vale la pena comentar que el uso de cascading parameters puede tener algo de repercusión en el rendimiento, sobre todo en estructuras muy complejas, pues cada cambio del valor introducido en la cascada obligará a recorrer el árbol de componentes para actualizar los valores. Por esta razón, establecer a cierto la propiedad IsFixed de <CascadingValue> es muy recomendable cuando estemos seguros de que el valor no cambiará, porque así informaremos al framework de que no es necesario que realice su seguimiento:

<CascadingValue Value="LoggedUser" IsFixed="true">
...
Espero que os resulte útil :)

Publicado en Variable not found.

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

Picando Código

Cómo empezar con Emacs: Distribuciones

octubre 05, 2020 11:45

Un hecho de empezar a usar Emacs es que la experiencia inicial para quienes lo prueban por primera vez puede ser un poco intimidante. Ha habido algo de discusión recientemente sobre si las opciones por defecto son suficentemente amigables o esperadas, y que Emacs debería incluir algunas configuraciones y paquetes por defecto que hagan una mejor experiencia. Si bien esta discusión sigue su camino, hay una alternativa: distribuciones.

Las distribuciones de Emacs se pueden comparar con las distribuciones GNU/Linux. Son conjuntos de scripts, paquetes y configuraciones que podemos usar en nuestro perfil para tener una experiencia de usuario predeterminada por dicha distribución. Esto es posible gracias a que Emacs es sumamente extensible, por lo que incluso usando estas distribuciones, siempre va a ser posible agregar nuestras preferencias personales.

Cuando Emacs inicia, intenta cargar un programa Lisp de un archivo de inicialización (o archivos init). Generalmente se usa ~/.emacs.d/init.el o el directorio ~/.emacs. En esta inicialización, podemos definir un montón de configuraciones visuales y de comportamiento, así como paquetes que queremos instalar, funciones que hayamos escrito en elisp, modos a habilitar/deshabilitar (cómo y cuándo), atajos de teclado y más. Estas distribuciones proveen archivos init que definen un comportamiento específico en Emacs.

Si tenemos más tiempo de aprender cuando comenzamos con Emacs, una buena práctica es empezar con un archivo vacío e ir agregando cosas hasta conseguir una configuración que nos gusta. Esto es un proceso sin fin, y una de las cosas que lo hacen genial (y a veces un vicio). Cuando necesitamos una funcionalidad específica, o vemos una característica de otro editor que nos gustaría poder usar en Emacs, generalmente encontramos un paquete o script para agregar a nuestra configuración personal. En mi caso empecé así, y mantuve una configuración por un buen tiempo: emacs-config.

En el blog mencioné también hace un tiempo Emacs Bootstrap, un sitio que te permite generar entornos de desarrollo en Emacs para distintos lenguajes de programación. Esto puede servir como base para iniciar un nuevo entorno de configuración personal.

Con el tiempo terminé usando una distribución, Spacemacs. Pero siempre ando observando lo que hacen otras distribuciones, y no descarto en algún momento volver a una configuración nueva de Emacs. Algo a aclarar es que ya sea usando Emacs desde cero o una distribución, vamos a tener que leer documentación, probar cosas, investigar, etc. Es una inversión a largo plazo que vale la pena. La extensibilidad y personalización que provee Emacs hace que terminemos convirtiéndolo en una herramienta de trabajo personalmente adaptada a nuestras necesidades. Y es un camino de ida, que no sé si termina… Pero genera mucha comodidad a la hora de trabajar con el editor de texto y un poco de satisfacción el tener tanto control sobre la herramienta.

Veamos algunas de las opciones de distribuciones para Emacs:

Spacemacs

Spacemacs

Spacemacs es la distribución de la que más puedo hablar porque es la que vengo usando desde hace unos años. Empecé por necesidad, en el trabajo hacíamos mucho pair programming entre usuarios de Emacs y Vim. Y uno de los puntos fuertes de Spacemacs es que une ambos mundos en un mismo editor. A través de evil-mode, implementa los atajos de teclado y otras características adicionales de Vim. Y se puede cambiar de un modo a otro (Vim/Emacs) con un sólo comando.

También organiza los atajos de teclado con prefijos mnemotécnicos y lo que llaman “leading key”, en modo vim la tecla de espacio inicia el modo “spacemacs” que nos provee un montón de funciones y un buffer navegable para descubrir distintos comportamientos (en modo Emacs creo que es configurable, en mi caso uso Alt + M). Por ejemplo las acciones relacionadas al Buffer se enlazan a la tecla b, Proyectos a la tecla p, y demás (las letras se corresponden con las palabras en inglés). Es muy fácil encontrar comandos gracias al autocompletado y búsqueda, y en general tiene un sistema consistente de hacer las cosas. Spacemacs cambia bastante el comportamiento de Emacs, aunque en el fondo sigue siendo estando ahí.

Para la instalación, Spacemacs clona el código del proyecto al directorio .emacs.d, y agrega un archivo .spacemacs donde podemos definir nuestras preferencias. Una recomendación si prueban Spacemacs, es usar la rama develop, y no la rama por defecto, ya que está mucho más al día con cosas nuevas, se actualiza más seguido y es bastante estable.

Trabajando con una configuración compartida, eventualmente creé una capa privada personal. En Spacemacs las capas son paquetes de código bajo un formato estándar que agregan funcionalidad. Por ejemplo las herramientas necesarias para cada lenguaje de programación están definidas en capas. Cuando terminé siendo el único que usaba la configuración “compartida”, me di cuenta que de repente era mejor mover mis configuraciones de la capa al archivo .spacemacs. Pero con la capa personal de última tengo todo separado de la misma manera que Spacemacs organiza sus capas, así que es relativamente fácil de mantener. Pueden ver mis archivos de configuración para Spacemacs en este repositorio.

Es una buena distribución de arranque. Incluye de todo, pero podemos ir ajustando la configuración personal a nuestras necesidades. Especialmente recomendada para personas que vienen de usar Vim y tienen ganas de probar Emacs.

Sitio oficial de Spacemacs

Doom Emacs

Doom Emacs

Doom Emacs surgió como archivo de configuración personal de un vimmer que se pasó a Emacs. Se denomina como un framework de configuración, otra forma de llamar una distribución, para usuarios que quieren menos framework en sus frameworks. Al igual que otras distribuciones, puede ser la fundación para un archivo de configuración personal o simplemente para empezar a aprender a usar Emacs.

Doom Emacs fue diseñado en base a algunos mantras:

Ser rápido, al iniciar y durante la ejecución, ponen prioridad en el rendimiento. Liviano, que haya la menor cantidad de cosas entre tú y Emacs. Opinionado pero no terco, incluye opciones por defecto razonables, pero podemos usar lo que queramos. Tu sistema, tus reglas, espera que el usuario sepa lo que quiere. Nix/Guix es una buena idea, el manejo de paquetes de Doom es declarativo y la configuración personal reproducible e incluye formas de retroceder versiones y actualizaciones.

La distribución está diseñada por un usuario veterano de Vim, por lo que avisan en la documentación que la experiencia general sin evil-mode no está tan pulida. De todas formas proveen una manera de deshabilitar evil-mode. Lo primero que hice al instalarlo fue buscar esta configuración entre los archivos de configuración y deshabilitarla, y después descubrí leyendo la documentación que había seguido los mismos pasos (excepto reasignar los atajos de teclado). Punto a favor para Doom Emacs que tenga una configuración fácil de modificar a pesar de no conocerla.

Doom Emacs también usa el directorio .emacs.d y agrega además .doom.d. Tenemos varios ejecutables para sincronizar la configuración, actualizar paquetes y más.

Probando Doom Emacs para este post, me quedé con ganas de aprender un poco más. Se ve súper lindo y provee una experiencia relativamente amigable para empezar. En su documentación responden a la comparación de Spacemacs con Doom Emacs, esto puede ayudar a decidir con cuál empezar.

Doom Emacs en GitHub

Prelude

Emacs Prelude

Prelude es una distribución orientada a mejorar la experiencia por defecto de Emacs. Fue desarrollada por Bozhidar Batsov, también desarrollador de Rubocop, Projectile, y otros tantos proyectos open source 🙌. Altera varias preferencias por defecto, agrega varios paquetes y librerías y provee como producto final una configuración fácil de usar para novatos y más poder para experimentados. La configuración por defecto se basa más en elegir qué queremos incluir, y no quitar cosas que no queremos usar, en comparación con otras distribuciones.

Su filosofía es: simple, fácil de entender y extender, estable, una fundación sobre la cual construir, en vez de un producto final orientado al usuario. Los paquetes elegidos son curados cuidadosamente y se caracterizan por ser bastante probados y estables. En la práctica esto implica que tiene menos cosas que Spacemas y Doom Emacs, haciéndolo una experiencia más cercana a Emacs desde cero.

La idea general es clonar el repositorio y seguir trabajando sobre él para usarlo como configuración personal. Prelude incluye un prelude-mode, modo menor de Emacs que arega algunas funcionalidades extra, y también agrega varios atajos de teclado específicos.

Uno de los paquetes importantes de la distribución es Projectile, que provee la funcionalidad de navegar y cambiar de proyectos. Otro paquete que usa es Helm, que personalmente también lo uso en Spacemacs, y recientemente dejó de ser mantenido 😱 (con suerte se continúe un fork en algún momento).

Prelude suena como algo más cercano para quienes quieran aprender o configurar desde cero pero arrancando desde algo un poquito más amigable que Emacs de cero.

Sitio de Emacs Prelude

Centaur Emacs

Centaur Emacs

Centaur Emacs es otra distribución que busca mejorar la experiencia por defecto, altera las preferencias, agrega paquetes y librerías y demás. Características: “out of the box”, una experiencia lista para empezar a usar, limpio y rápido, búsqueda con lógica difusa, mejor soporte para Org Mode y Markdown, soporte para varios lenguajes de programación, auto completado, chequeo de sintaxis y ortografía al vuelo, integración con proyectos y espacios de trabajo (workpsaces), integración pomodoro, soporte para Docker y mejor soporte para Chino.

Mis primeras impresiones fueron buenas. Además de la apariencia general que está bastante interesante, tiene muy buen autocompletado y se siente un poco más rápido que Spacemacs cuando navego archivos, no sé si tanto así cuando escribo código. Probablemente sea porque incluye lsp-mode y lo activa por defecto. Encontré que funciona (por lo menos en Ruby) sin tener que configurar nada. En general me dió la impresión de estar usando Emacs con un comportamiento más cercano a un IDE.

El defecto que le vi de primera mano fue que no encontré documentación detallada sobre cómo usarlo. De repente la idea es ir viendo los archivos de configuración y aprendiendo qué trae y cómo se usa. De todas formas es otra buena opción a tener en cuenta.

Centaur Emacs en GitHub

Conclusión

Hay varios “Starter kits” más, en este post sólo comenté los que conocía de antemano y aproveché la oportunidad para probarlos un poco. Pueden conocer más starter kits en la wiki de Emacs. Por ahora me voy a seguir quedando con Spacemacs pero me interesó de repente aprender Doom Emacs un poco más. Se nota un poco más liviano que Spacemacs, pero tendré que seguir probando a ver si vale la pena cambiar. Si fuera a empezar una configuración personal nueva, creo que empezaría con Prelude para no empezar desde cero.

Les invito a probarlos y formar su propia opinión. Lo bueno de esto es que se puede ir variando de distribución cambiando el nombre del directorio .emacs.d y respaldándolo. Así quedó mi home con todas estas pruebas:


$ ls -a | grep emacs
drwxr-xr-x 20 fernando fernando 4.0K .emacs.d
drwxr-xr-x 16 fernando fernando 4.0K .emacs.d.centaur
drwxr-xr-x 10 fernando fernando 4.0K .emacs.d.doom
drwxr-xr-x 17 fernando fernando 4.0K .emacs.d.prelude

Y el directorio .emacs.d actual es el de Spacemacs que tenía de antes. En otro post voy a comentar de una opción interesante para gestionar más de un perfil de Emacs. Si tienen interés en probar Emacs, espero que estas distribuciones les den un empujón más. Y por cualquier duda o ayuda, los comentarios están abiertos 🙂

The post Cómo empezar con Emacs: Distribuciones first appeared on Picando Código.

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

Fixed Buffer

Mejorando la calidad del código con NDepend (con sorteo de licencia!)

septiembre 29, 2020 08:00

Tiempo de lectura: 6 minutos
Imagen ornamental con el logo de NDpend

Hace ya unos meses, me escribió Patrick Smacchia, autor y lead del equipo de desarrollo de NDepend para ofrecerme probar su herramienta y hacer una review sobre ella.

Me ha costado sacar tiempo para trastear la herramienta (lo siento Patrick 🙁 ), pero finalmente lo he podido hacer y ahí vamos con ello.

Disclaimer: Nada de lo que digo aquí está condicionado, Patrick me ofreció una licencia para probar el software y hacer una review objetiva sobre lo que ofrece si me parecía interesante, pero en ningún caso ha revisado ni opinado sobre lo que aquí estoy escribiendo

¿Qué es NDepend?

NDepend es un analizador de código con el que vamos a poder generar informes interactivos con métricas sobre la calidad del código analizado. Cada métrica está documentada y es monitorizable.

Se trata por tanto de una herramienta, enfocada a desarrolladores y arquitectos software, que realiza análisis estático de proyectos .NET y .NET Core.

Además, puede utilizarse tanto en nuestro propio equipo durante el proceso de desarrollo (ejecutable standalone e integraciones con Visual Studio), como en varios sistemas de integración continua como pueden ser Azure DevOps, TFS, Teamcity, Jenkins y algunas más.

¿Para qué me puede servir NDepend?

Detectar errores y demás está muy bien, pero eso ya lo consigo con FxCop Analyzers, ¿no?. La verdad es que NDepend llega bastante más alla de los que puede ser análisis de código estático simplemente. Una de las ventajas que nos aporta es la detección automática de puntos donde algo raro está pasando (code smells), así como la detección de buenas prácticas (o ausencia de ellas). Obviamente, también tenemos métricas sobre cantidad de lineas, complejidad ciclomática, etc…

En resumen (de brocha gorda):

  • Detección de código sospechoso (code smells)
  • Detección de zonas donde el código es complejo (complejidad ciclomática)
  • Detección de referencias (que partes de nuestro código son las más utilizadas)
  • Detección de acoplamiento
  • Cálculo de deuda técnica (sí, aunque no lo reconozcas, no todo tu código es perfecto)
  • Tendencias y gráficos de evolución sobre la calidad del código
  • Estimación del tiempo invertido

Un caso práctico

Como siempre, hablar es fácil y por eso quiero poner un caso práctico del análisis de una aplicación. En mi caso concreto yo he probado NDepend con un cliente en el que tenemos nada más y nada menos que 36 proyectos dentro de la misma solución (pequeñito el sistema… xD). Como evidentemente no puedo dar datos sobre los clientes de mi trabajo, el caso práctico lo vamos a plantear sobre un proyecto público que a más de uno os puede resultar familiar como es Dapper, un micro-ORM desarrollado por la gente de Stack Exchange (casi nada…)

Lo primero de todo, es que yo he optado por utilizar la versión standalone directamente en lugar de la integración con Visual Studio, ya que en muchos proyectos utilizo Visual Studio Code. No obstante, en una prueba rápida con su integración en VS he podido comprobar que ofrece prácticamente lo mismo.

Para esto simplemente he tenido que ejecutar la aplicación que me he descargado sin necesidad de hacer ninguna instalación extra. Una vez dentro de Visual NDepend, basta con ir al menú de archivo y decirle que queremos analizar un proyecto o solución de VS.

Tras unos instantes analizando el proyecto (que dependerá del tamaño del proyecto que estemos analizando), podremos acceder a los diferentes paneles con la información.

Panel general (dashboard)

Este es el cuadro de mando general con las métricas del análisis, por ejemplo, en el proyecto de Dapper:

  • 13059 líneas de código
  • 129 archivos de código fuente
  • 11 ensamblados
  • 40 espacios de nombres
  • 6385 líneas de comentarios
  • Estimación del esfuerzo para realizar la aplicación: 345 días
  • Deuda técnica: 40 días
  • 5 días y 6 horas de esfuerzo para reducir la deuda técnica de C a B
  • 2 alertas de fallos primordiales a revisar
  • 8 alertas de reglas críticas no superadas
La imagen muestra el panel de Visual NDepend donde se leen los datos arriba descritos

Vista de métricas

Este es otro panel que aporta a mi modo de ver una información importante de manera muy visual. Gracias a sus tres selectores vamos a poder configurar el nivel de agrupación (cantidad de métodos, campos, espacios de nombres,…), dentro de la agrupación, vamos a poder seleccionar en base a que métrica queremos que tenga el tamaño, y por último vamos a poder seleccionar en base a que métrica queremos que tenga el color.

De este modo vamos a poder hacer una revisión visual muy rápida, por ejemplo, de la complejidad ciclomática de cada método, haciendo que se represente más grande cuantas más referencias tiene:

La imagen muestra un recuadro por cada método del proyecto en un color del verde al rojo en funcion de la complejidad ciclomática del método

Gráfico y matriz de dependencias

Otros de los paneles que ofrece y que me parecen extremadamente interesantes son los referentes a las dependencias dentro de la solución. Estos paneles de dependencias nos van a mostrar, valga la redundancia, las dependencias.

De manera gráfica:

La imagen muestra de manera visual las dependencias  entre partes del proyecto

Y para proyectos más grandes, en forma de matriz:

Conclusión

Aunque he tenido poco trato con NDepend en batallas del día a día, la verdad es que creo que es una herramienta con mucho potencial. La posibilidad de crear reglas de análisis propias le otorga un grado extra de flexibilidad que creo que puede conseguir que se adapte a más escenarios.

El soporte que ofrece para diferentes entornos de integración y despliegue continuo la verdad que me parece una maravilla también, ya que simplifica mucho el proceso de utilización de la herramienta.

Más allá del coste de licenciamiento, que puede ser o no una barrera para adoptar NDepend (como cualquier otro software), si que creo importante resaltar que es una herramienta muy focalizada en .Net.

También he de añadir que NDepend se integra con SonarQube y puede hacer su trabajo también junto a este si necesitas encajarlo con esta herramienta.

Si con todo esto que he comentado, crees que puede encajarte esta herramienta, te recomiendo echarle un ojo tanto a sus costes como a su versión de prueba.

¡Participa en el sorteo!

Como comentaba en el titulo, NDepend ha cedido muy amablemente una licencia de desarrollador, valorada en 399 Euros, para sortear entre los lectores de FixedBuffer.

Para participar, basta con escribir un comentario en este post (OJO, en la sección de correo electrónico hay que poner uno válido o no podré contactar), podéis aprovechar también para contarnos qué os parece NDepend o en qué pensáis que os podría ayudar, todo lo que os gusta FixedBuffer, o si hace frío o calor. La cosa es comentar algo para participar 😉

El sorteo se realizará el próximo domingo día 10 octubre de 2020 y consistirá en elegir al azar entre uno de los comentarios. Tras ello, me pondré en contacto con el autor para detallarle los pasos a dar para reclamar su licencia gratuita.

¡No dejéis de participar! ¡Suerte!

Edición 10-10-2020:
Felicidades Lorenzo, tu comentario ha sido el ganador!!!

La imagen muestra el resultado del sorteo donde se ve que Lorenzo es el ganador

**La entrada Mejorando la calidad del código con NDepend (con sorteo de licencia!) se publicó primero en Fixed Buffer.**

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

Navegapolis

Agilidad: perspectiva y principios del desarrollo ágil

septiembre 26, 2020 04:39

agilidad portada

He juntado en este libro los tres módulos que componen la asignatura que imparto en CESTE mostrando la agilidad para equipos y empresas desde la perspectiva del origen y debilidades del desarrollo basado en procesos y planificación.

Es una perspectiva que ayuda a comprender el valor de las personas comprometidas sobre la rigidez de la ingeniería de procesos; el valor de la capacidad para responder al cambio frente al cumplimiento de agendas planificadas, junto con las nuevas perspectivas que abre la agilidad al dirigir el foco de valor hacia el cliente, antes que hacia los propios resultados.

Aquí lo comparto para los os pueda resultar útil. 

 

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

Picando Código

ElasticON Observabilidad: Evento virtual en Español para América Latina

septiembre 22, 2020 03:50

ElasticON Latam Observability

El 12 de Noviembre a partir de las 9:00 CDT se va a realizar un evento virtual de ElasticON en vivo para América Latina:

ElasticON Observability: Obtén información de los expertos de Elastic en un evento gratuito diseñado para especialistas en observabilidad. Este evento está repleto de análisis técnicos a fondo e información de usuarios del mundo real que te ayudarán a unificar logs, métricas y rastreos de APM para una visibilidad integral de tu ecosistema.

Visión general – Llevar el MTTR hacia cero

Desde la ingesta simplificada hasta Machine Learning integrado, conoce cómo aprovechar el poder de Elastic Observability para sorprender y deleitar a tus clientes, tanto internos como externos. Saldrás con las mejores prácticas y detalles de roadmap que necesitas para aprovechar al máximo todos tus proyectos de Elastic Observability.

Roadmap y visión
Obtén el roadmap de Elastic Observability directamente de los creadores.

Historias de usuarios
Obtén información de un usuario del mundo real que proporciona valor comercial con Elastic Observability.

Análisis a fondo
Ve cómo optimizar el monitoreo de la infraestructura en cualquier entorno y proporciona observabilidad como servicio.

Avances en APM
Conoce cómo obtener una visión completa de tus aplicaciones puede ayudarte a detectar más rápido los cuellos de botella de rendimiento.

Más información, programa y registro en:
https://www.elastic.co/elasticon/observability/latin-america-es

The post ElasticON Observabilidad: Evento virtual en Español para América Latina first appeared on Picando Código.

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

Picando Código

Nuevos validadores de Cédula de Identidad Uruguaya: Prolog y Rust

septiembre 15, 2020 11:45

Se siguen agregando versiones en más lenguajes del validador de cédulas de identidad. Gracias a Bruno Cattáneo, tenemos una versión nueva en Prolog. Pueden ver el código fuente y ejemplos de cómo usarlo en el enlace.

Validador de Cédulas de Identidad Uruguaya

Inspirado por la versión de Bruno, finalmente me puse a escribir una versión que hacía tiempo quería escribir: Rust. El código para validar la cédula es lo suficientemente simple como para probar un lenguaje de programación nuevo. Hace tiempo que quería entrarle a Rust, así que fue una buena excusa. Me costó un poco, aprendí algo de Rust en el camino, pero tendría que leer mucho más para poder escribir código Rust como la gente. Logré que funcione y llegué a poder ejecutarlo con cargo run y un número de cédula como parámetro y agregué algunos tests unitarios. Pero hay partes que uso variables mutables que seguro se podrían reescribir y en general seguro hay muchas cosas a mejorar. Pero por lo menos logré escribir mi primer código Rust más allá del “Hola Mundo!”. Ya seguiré aprendiendo más Rust, por ejemplo cómo diseñar el código para que pueda ser usado como librería. El código fuente en GitHub.

Hace unos años creé mi primera gema en Ruby: Un validador de cédulas de identidad uruguaya. Poco más de un año después, escribí una versión en JavaScript. Desde entonces han surgido un montón de versiones más del validador de cédulas de identidad en distintos lenguajes y ya tenemos: Ruby, Python, JavaScript (plugin jQuery y versión en Node), PHP, Go, Crystal, Prolog y Rust.

 

The post Nuevos validadores de Cédula de Identidad Uruguaya: Prolog y Rust first appeared on Picando Código.

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

Fixed Buffer

¡¡FixedBuffer ha cumplido su segundo año!!

septiembre 11, 2020 08:00

Tiempo de lectura: 3 minutos
Imagen ornamental. Pastel de cumpleaños con una vela en forma de número 2

Fin del verano y vuelta a la rutina. Parece mentira que hoy hace 2 años empezase la andadura de este blog. Han sido dos años de descubrimiento personal y la verdad no pensaría que llegaría hasta aquí 🙂

Con esta entrada ya se convierte en tradición (2 de 2 veces) el día del aniversario echar la vista hacia atrás y hacer un resumen (breve, que no quiero aburrir) de los principales hitos del año anterior.

En primer lugar, el hecho más evidente, FixedBuffer sigue dando guerra y para celebrarlo, el blog ha tenido un lavado de cara para darle un aire más moderno y minimalista, pero sobre todo mejor optimizado para ordenador y móvil. El número de lectores desde móvil y tablet, aunque es mucho menor que el de ordenador, va creciendo poco a poco y todos os merecéis la mejor experiencia posible el tiempo que pasáis aquí.

Imagen animada de una persona haciendo un corazón con las manos

Estaré encantado de leer en los comentarios opiniones sobre el nuevo estilo 🙂

En segundo lugar, he sido renovado de nuevo como MVP en la categoría de ‘Tecnologías de desarrollo’ y ya llevo 2 anillos. Este año he recibido el premio en la mejor compañía como os contaba en su momento.

Tercero y no por eso menos importante, me he convertido en tutor de mi propio curso en CampusMVP: Testing de aplicaciones .NET y .NET Core con xUnit y Moq. Han sido muchos meses haciendo un gran esfuerzo para sacar el curso adelante, pero todo ha ido genial y la gente está muy contenta con él (cosa que me hace tremendamente feliz).

Por cierto, con motivo del aniversario la gente de CampusMVP hace un descuento del 10% utilizando el código FIXED2Y antes del 20 de septiembre de 2020. Si estabas pensando en hacer el curso, es la oportunidad perfecta.

Además, he podido coincidir con algunos de vosotros en las charlas que me han dejado dar por diferentes comunidades (esas cosas que hacíamos antes del COVID-19 como excusa para juntarnos y beber cerveza). Eso es algo que siempre gusta y es de agradecer porque echamos muy buenos ratos 🙂

Otra cosa que me gustaría decir es que por fin he puesto al día la sección de colaboraciones, donde si os habéis quedado con ganas de leer más cosillas de las que he escrito, podéis ir y consultar ya que hay cerca de 20 artículos más publicados en otros blogs.

Se avecina un año muy interesante por delante. De antemano os quiero dar las gracias a todos ya que, si no fuese por vosotros, FixedBuffer no estaría cumpliendo su segundo año online, y es que como he dicho ya muchas veces, un blog sin lectores no es un blog. Dicho esto, vamos a ver el top 5 de entradas del último año:

  1. Haciendo fácil el acceso a datos con Entity Framework Core (Parte 2)
  2. Worker Service: Cómo crear un servicio .Net Core 3 multiplataforma
  3. Generación de ficheros «Excel» (xlsx) con ClosedXML
  4. ClosedXML, una manera fácil de dar formato a nuestros .xlsx
  5. Haciendo fácil el acceso a datos con Entity Framework Core

Y seguido muy de cerca se ha quedado Escribiendo código de alto rendimiento en .Net Core. Una entrada muy especial por conseguir la friolera de 473 visitas en un solo día, un record nada despreciable para un pequeño blog como este 🙂

¡¡Muchas gracias a todos los seguidores de FixedBuffer, que sois los que hacéis que esto valga la pena!!

**La entrada ¡¡FixedBuffer ha cumplido su segundo año!! se publicó primero en Fixed Buffer.**

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

Picando Código

ElasticON Global 2020

septiembre 10, 2020 02:00

ElasticON

ElasticON es una conferencia global para usuarios de tecnologías Elastic. Son 3 días de sesiones, conversaciones de preguntas en vivo, oradores expertos de la industria y más. Este año la conferencia va a ser virtual, gratuita y abierta a todo público, y va a ser los días 13, 14 y 15 de octubre.

Ya sea que te estés arremangando para empeza a usar Elastic o escalando deployments existentes a nuevas alturas, hay algo para ti en ElasticON Global. Es la reunión de usuarios y expertos de Elastic y Elasticsearch, uniendo miembros de la comunidad de cada industria y región.

El público objetivo cubre desarrolladores, arquitectos, DevOps, SecOps, data analysts, expertos en búsqueda así como también managers y jefes. Ya está disponible la agenda para los 3 días y la lista de oradores. Hay charlas para todos los gustos y perfiles. Los temas incluyen seguridad, sector público, desarrollo open source (hay una charla sobre cómo empezar a contribuir a Elastic), observabilidad, monitoreo, Elastic Enterprise Search (esto es particularmente interesante), búsqueda (por supuesto) y más.

Voy a estar dando una charla junto a mis compañeros del equipo de Clientes donde vamos a comentar algunas de las cosas en las que hemos venido trabajando. Esto me ha tenido bastante nervioso recientemente, pero estoy en buena compañía. Veremos qué tal sale.

Como mencioné al principio, la conferencia es gratis y el registro está en este enlace. ¡Nos vemos ahí!

The post ElasticON Global 2020 first appeared on Picando Código.

» 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