Quantcast
Channel: Planeta Código
Viewing all 2711 articles
Browse latest View live

Picando Código: Gleam – Nuevo lenguaje de programación funcional estáticamente tipado en BEAM

$
0
0

El pasado lunes 15 de abril se publicó la primera versión de Gleam (0.1): Gleam es un lenguaje de programación funcional estáticamente tipado diseñado para escribir sistemas concurrentes mantenibles y escalables. Compila a Erlang y tiene interoperabilidad directa con otros lenguajes de BEAM (la máquina virtual de Erlang) como Erlang, Elixir y LFE.

Obviamente es un lenguaje bastante nuevo, así que no está listo para usar en producción. Está interesante y ya se puede probar, programar alguna cosa divertida y aprender a usarlo. El código fuente está disponible en GitHub bajo licencia Apache 2.0, y chat del proyecto se encuentra en IRC en #gleam-lang de Freenode. Aplaudo el uso de IRC para proyectos de Software Libre en vez de Slack 👏
¡El compilador está escrito en Rust! Y si bien no conozco Rust, parece que saca algo de inspiración de ese lenguaje en la sintaxis y le veo también algunas cosas de Elixir.

Principios de Gleam

Ser seguro

Un sistema de tipados expresivo inspirado por la familia de lenguajes ML nos ayuda a encontrar y prevenir bugs en tiempo de compilación, mucho antes que el código llegue a los usuarios.

Para los problemas que no pueden ser resueltos con el sistema de tipos (como que le caiga un rayo al servidor) el runtime Erlang/OTP provee mecanismos bien testeados para manejar fallas.

Ser amigable

Perseguir bugs puede ser estresante así que la realimentación del compilador debe ayudar y ser lo más clara posible. Queremos pasar más tiempo trabajando en nuestra aplicación y menos tiempo buscando errores tipográficos o descifrando mensajes de error crípticos.

Como comunidad queremos ser amigables también. Personas de todos los orígenes, géneros y niveles de experiencia son bienvenidas y deben recibir respeto por igual.

Ser performante

El runtime Erlang/OTP es conocido por su velocidad y habilidad para escalar, permitiendo que organizaciones como WhatsApp y Ericsson manejen cantidades masivas de tráfico de manera segura con baja latencia. Gleam debería aprovechar por completo el runtime y ser tan rápido como otros lenguajes de la BEAM como Erlang y Elixir.

Ser un buen ciudadano

Gleam facilita el uso de código escrito en otros lenguajes BEAM como Erlang, Elixir y LFE, así que hay un ecosistema rico de herramientas y librerías para que los usuarios de Gleam aprovechen.

A cambio los usuarios de lenguajes BEAM deberían poder aprovechar Gleam, ya sea usando librerías escritas en Gleam de manera transparente, o agregando módulos Gleam a sus proyectos existentes con mínimo trabajo.

Instalación

Para instalarlo, debemos seguir esta guía, Necesitamos tener Rust, Erlang y rebar3 instalados de antemano, clonar el código con git, y compilar el compilador. El soporte para editores de texto todavía está un poco en pañales, pero ya hay modos para Emacs, Vim y Visual Studio Code.

Empezando a programar

Al ser un lenguaje tan nuevo, todavía no es un proceso tan directo crear un proyecto. De todas formas sólo hay que seguir estos pasos. El compilador puede trabajar con proyectos generados con rebar3, una herramienta de builds de Erlang.

Los tipos básicos de Gleam son String, Bool (True || && False), Int (enteros) y Float (decimales). Los valores pueden nombrarse con let para ser reusados después, pero los valores contenidos son inmutables:

let x = 1
let y = x
let x = 2

x // => 2
y // => 1

Tiene Tuplas, colecciones ordenadas de tamaño fijo con elementos que pueden ser de distintos tipos:

{"Texto", 2, True}

Y acá me empiezo a acordar de Elixir, podemos extraer los valores de la tupla así:

> valores = {"Texto", 2, True}
{"Texto", 2, True}
> {a, b, c} = valores
{"Texto", 2, True}
> a
"Texto"> b
2
)> c
True

Otra estructura de datos son las Listas: Colecciones ordenadas de elementos del mismo tipo:

[7, 8, 9]
// Y para agregar nuevos valores, como en Elixir, se usa:
> ["N" | ["a", "n", "d", "o"]] 
["N", "a", "n", "d", "o"]

Por último están los Mapas, colecciones de nombre y valores que pueden ser de cualquier tipo:

{
  nombre = "Nicanor",
  edad = 20,
}

Y se puede acceder al valor con la sintaxis `mapa.nombre_del_campo`:

let persona = { nombre = "Nicanor", edad = 20 }
let nombre = persona.nombre

nombre // => "Nicanor"

Para actualizar o agregar valores a un mapa se usa la sintaxis: { mapa | nombre_del_campo = valor }:

let persona1 = { nombre = "Marcela", edad = 22 }
let persona2 = { persona1 | edad = 23, idioma: "español" }

persona1 // { nombre = "Marcela", edad = 22 }
persona2 // { nombre = "Marcela", edad = 23, idioma: "español" }

Los tipos del mapa dependen de los nombres y tipos de los campos. El compilador lleva un registro de los campos y valores de cada mapa y presenta un error en tiempo de compilación en caso de querer usar un campo que no exista o tenga el tipo incorrecto.

Las funciones con nombre se declaran con pub fn:

pub fn sumar(x, y) {
  x + y
}

pub fn multiplicar(x, y) {
  x * y
}

Y como son valores de primera clase se pueden asignar a variables, pasar a funciones y todo lo demás que se puede hacer con cualquier otro tipo de datos:

pub fn dos_veces(f, x) {
  f(f(x))
} // Wooooo!

También tiene funciones anónimas con una sintaxis similar:

pub fn ejecutar() {
  let sumar = fn(x, y) { x + y }
  sumar(1, 2)
}

No puedo evitar las comparaciones con Elixir, pero es a lo que me hace acuerdo… Hay una sintaxis corta para crear funciones anónimas que toman un argumento y lo pasan a otra función. Y esto se usa generalmente con pipes para crear una serie de transformaciones de datos:

pub fn sumar(x, y) {
  x + y
}

pub fn ejecutar() {
  // esto equivale a sumar(sumar(sumar(1,2), 4), 6)
  1
  |> sumar(_, 2)
  |> sumar(_, 4)
  |> sumar(_, 6)
}

Vistas las funciones otra cosa interesante es pasarles mapas como parámetros. El lenguaje apunta a ser muy permisivo con esto, y muestra un ejemplo de cómo funciona:

fn numero_siguiente(mapa) {
   mapa.numero + 1
}

El tipo de la función es fn({ a | numero = Int}) -> Int. La a en este caso puede ser “cualquier otro campo”, así que la función se puede llamar con cualquier mapa siempre y cuando tenga el campo numero con un valor del tipo Int.

let articulo = { nombre: "tenedor", numero: 17 }
let nintendo = { nombre: "gameboy", numero: 4 }
let fernando = { nombre: "Fernando", edad: 33 }

numero_siguiente(articulo) // => 18
numero_siguiente(nintendo) // => 5
numero_siguiente(fernando) // => Compile time error! No numero field

La expresión case se usa como estructura de control por medio de la técnica “pattern matching” (ya escribí algo sobre Pattern Matching en el blog antes). Cómo se usa:

case numero {
| 0 -> "Cero"
| 1 -> "Uno"
| 2 -> "Dos"
| n -> "Otro número" // machea todo lo que no sea lo anterior
}

Como alternativa al if else de otros lenguajes, hace pattern matching con los valores Bool:

case alguna_condicion {
| True -> "Es verdadera"
| False -> "Es falsa"
}

La expresión case retorna un valor por lo que podemos asignarle el resultado a una variable. El pattern matching me resulta una cosa mágica y feliz, pero debe ser porque no estoy acostumbrado a usarlo.

El lenguaje también cuenta con Enums, y el tipo Bool está definido como uno:

enum Bool =
  | True
  | False

Una variante que muestra como ejemplo para extraer valores es:

enum User =
  | LoggedIn(String)
  | Guest

let diego = LoggedIn("Diego")
let leticia = LoggedIn("Leticia")
let visitor = Guest

Los enums también pueden ser “patternmacheados” para asignarle nombres a distintas variantes y podemos usarlos en un let.
🤯

Por último, al ser un lenguaje de la BEAM, podemos usar funciones de otros lenguajes directamente ¡Santa interoperabilidad Batman! Al ser lenguajes distintos, el compilador no puede determinar el tipo de las funciones, así que es la responsabilidad del programador en esos casos de hacer las cosas bien o veremos hermosas explosiones en nuestra aplicación. Pero llamar funciones de otros lenguajes es bastante sencillo:

// Llamando a la función uniform del módulo rand de Erlang:
pub external fn random_float() -> Float = "rand""uniform"

// Llamando a IO.inspect de Elixir:
pub external fn inspect(a) -> a = "Elixir.IO""inspect"

Como si fuera poco, también podemos importar tipos externos.

Conclusión

Si bien está en una etapa muy temprana de desarrollo, Gleam me resultó un lenguaje súper interesante que iré siguiendo con atención. Al ser tan nuevo, todavía hay mucho por implementar, así que resulta un buen aprendizaje y bastante interesante ir viendo cómo se desarrollan nuevas funcionalidades.

Elixir, Crystal y ahora Gleam son los lenguajes que me interesaría usar más aparte de Ruby, y en algún momento me voy a poner a leer también sobre Rust.

Si quieren aprender más de Gleam, pueden visitar su sitio web.


Blog Bitix: Estrategias de despliegue para microservicios con Nomad

$
0
0
Nomad
Consul
HashiCorp

El ciclo de vida de una aplicación no consiste solo en desarrollarla, incluye también su puesta en producción o despliegue en un entorno de pruebas, pero también una vez la aplicación está desplegada en algún momento será necesario actualizarla con una nueva versión.

Las aplicaciones monolíticas tienen otros problemas pero en el aspecto de despliegue es sencillo ya que solo hay una aplicación, basta con desplegar la nueva versión. En una aplicación con arquitectura de microservicios es un reto mayor debido a que hay múltiples aplicaciones.

En cualquiera de ellas puede darse el caso de que para ganar en escalabilidad o para aumentar la disponibilidad o tolerancia a fallos es posible que haya varias instancias, las cuales han de ser actualizadas con el requisito si es necesario de que el servicio no deje de prestar su servicio, es decir que el despliegue no suponga una caída del servicio.

Hay varias estrategias para desplegar una nueva versión de una aplicación:

  • Rolling update: actualizar todas las instancias de forma progresiva. Una vez se termina de actualizar una se espera un tiempo y se actualiza la siguiente hasta que todas estén actualizadas.
  • Blue/Green: manteniendo en funcionamiento las instancias con la versión antigua se crea el mismo número de instancias con la nueva versión y se redirige tráfico hacia ellas. Una vez se ha comprobado que la nueva versión funciona correctamente se promociona la nueva versión y se eliminan las instancias de con la versión antigua. Esta estrategia permite volver a la versión anterior rápidamente si se detecta algún problema.
  • Canary: se siguen manteniendo las instancias con la versión antigua, a diferencia de la estrategia blue/green se crea un número menor de instancias con la versión nueva que el número de instancias con la versión antigua. Una vez comprobado que la nueva versión es correcta se promociona la nueva versión y se actualizan todas las instancias restantes mediante rolling update a la nueva versión. También permite volver a la versión antigua si se detecta algún problema.

Docker Swarm permite la estrategia de despliegue rolling update sin embargo las estrategias blue/green y canary son interesante para tratar de que un error en una versión nueva no afecte al funcionamiento de la aplicación y obligue hacer un rollback que posiblemente tarde más tiempo durante el cual el servicio funcionará con el defecto descubierto. Nomad permite despliegues con las estrategias blue/gree y canary.

Para actualizar un servicio en Nomad basta con modificar la definición del job y enviarlo a Nomad, y este se encarga de orquestar la actualización en las instancias según la estrategia de despliegue configurada. En este caso se actualiza la versión de nginx de la versión nginx:stable-alpine a nginx:alpine usando una estrategia rolling update para las cinco instancias del servicio.

La estrategia de despliegue en Nomad se define en la sección de configuración update. El parámetro min_healthy_time es el tiempo que se espera cuando se hace un rolling update para considerar una instancia como sana y continuar la actualización con la siguiente, max_parallel indica el número de instancias que se migran al mismo tiempo. El parámetro canary indica el número de instancias que se crean en las estrategias blue/green y canary, en la primera el número de instancias coincidirá con el parámetro canary que indica el número de instancias de un servicio. Nomad con los parámetros health_check, min_healthy_time, healthy_deadline, progress_deadline, stagger y auto_revert se puede poner unos límites para considerar válido un despliegue y en caso de no serlo realizar un rollback de forma autmática.

 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
job "nginx" {
datacenters = ["localhost"]
type = "service"
update {
stagger = "30s"
max_parallel = 1
}
group "services" {
count = 5
task "nginx" {
driver = "docker"
config {
image = "nginx:stable-alpine"
port_map {
http = 80
}
}
resources {
memory = 1024 # MB
network {
port "http" {}
}
}
}
}
}
1
2
$ nomad job run nginx.nomad

En el caso de los despliegues blue/green y canary una vez comprobado que la versión de los nuevos servicios funcionan correctamente se promocionan y actualizan el resto de instancias en el caso de canary o se detienen las instancias antiguas en el caso de blue/green.

1
2
$ nomad job promote nginx

Desde la línea de comandos se puede observar el estado del servicio y el proceso de actualización, el primero es el estado previo a realizar el despliegue, el segundo durante el proceso de actualización con rolling update y el tercero una vez finalizado el proceso de despliegue y marcado como exitoso en el que todas las instancias han pasado de la versión 0 a la 1.

 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
61
62
63
64
$ nomad job status nginx
ID= nginx
Name= nginx
Submit Date=2019-04-18T10:50:11+02:00
Type= service
Priority=50Datacenters= localhost
Status= running
Periodic=falseParameterized=false
Summary
Task Group Queued Starting Running Failed Complete Lost
services 005000
Latest Deployment
ID= 67d4497a
Status= successful
Description= Deployment completed successfully
Deployed
Task Group Desired Placed Healthy Unhealthy Progress Deadline
services 55502019-04-18T11:00:22+02:00
Allocations
ID Node ID Task Group Version Desired Status Created Modified
0bee3ae1 1806498b services 0 run running 25s ago 14s ago
295f72ec 1806498b services 0 run running 25s ago 14s ago
6b2dcff1 1806498b services 0 run running 25s ago 14s ago
94a1a79b 1806498b services 0 run running 25s ago 14s ago
f163316f 1806498b services 0 run running 25s ago 14s ago
$ nomad alloc status 0bee3ae1
ID= 0bee3ae1
Eval ID= 2c63f7a1
Name= nginx.services[0]
Node ID= 1806498b
Job ID= nginx
Job Version=0
Client Status= running
Client Description= Tasks are running
Desired Status= run
Desired Description=<none>
Created= 39s ago
Modified= 28s ago
Deployment ID= 67d4497a
Deployment Health= healthy
Task "nginx" is "running"
Task Resources
CPU Memory Disk Addresses
0/100 MHz 1004 KiB/1.0 GiB 300 MiB http: 127.0.0.1:27902
Task Events:
Started At=2019-04-18T08:50:12Z
Finished At= N/A
Total Restarts=0
Last Restart= N/A
Recent Events:
Time Type Description
2019-04-18T10:50:12+02:00 Started Task started by client
2019-04-18T10:50:11+02:00 Task Setup Building Task Directory
2019-04-18T10:50:11+02:00 Received Task received by client
 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
$ nomad job status nginx
ID= nginx
Name= nginx
Submit Date=2019-04-18T10:55:59+02:00
Type= service
Priority=50Datacenters= localhost
Status= running
Periodic=falseParameterized=false
Summary
Task Group Queued Starting Running Failed Complete Lost
services 005010
Latest Deployment
ID= 8b96049d
Status= running
Description= Deployment is running
Deployed
Task Group Desired Placed Healthy Unhealthy Progress Deadline
services 51002019-04-18T11:05:59+02:00
Allocations
ID Node ID Task Group Version Desired Status Created Modified
d9e3a0c9 1806498b services 1 run running 4s ago 4s ago
0bee3ae1 1806498b services 0 stop complete 5m53s ago 4s ago
295f72ec 1806498b services 0 run running 5m53s ago 5m42s ago
6b2dcff1 1806498b services 0 run running 5m53s ago 5m42s ago
94a1a79b 1806498b services 0 run running 5m53s ago 5m42s ago
f163316f 1806498b services 0 run running 5m53s ago 5m42s ago
 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
$ nomad job status nginx
ID= nginx
Name= nginx
Submit Date=2019-04-18T10:55:59+02:00
Type= service
Priority=50Datacenters= localhost
Status= running
Periodic=falseParameterized=false
Summary
Task Group Queued Starting Running Failed Complete Lost
services 005050
Latest Deployment
ID= 8b96049d
Status= successful
Description= Deployment completed successfully
Deployed
Task Group Desired Placed Healthy Unhealthy Progress Deadline
services 55502019-04-18T11:06:59+02:00
Allocations
ID Node ID Task Group Version Desired Status Created Modified
a6a1423a 1806498b services 1 run running 33s ago 22s ago
c529ec57 1806498b services 1 run running 45s ago 34s ago
1cda0083 1806498b services 1 run running 57s ago 46s ago
aff0eaf9 1806498b services 1 run running 1m9s ago 58s ago
d9e3a0c9 1806498b services 1 run running 1m21s ago 1m11s ago
6b2dcff1 1806498b services 0 stop complete 7m10s ago 1m9s ago
94a1a79b 1806498b services 0 stop complete 7m10s ago 44s ago
0bee3ae1 1806498b services 0 stop complete 7m10s ago 1m21s ago
295f72ec 1806498b services 0 stop complete 7m10s ago 32s ago
f163316f 1806498b services 0 stop complete 7m10s ago 57s ago

El proceso de despliegue también se puede monitorizar desde la interfaz web que ofrece Nomad.

Proceso de despliegue rolling update en Nomad

En este ejemplo los servicios están en contenedores docker, también se observa que la versión de los contenedores en ejecución pasan de la versión stable-alpine a alpine.

1
2
3
4
5
6
7
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8d09e91f7306 nginx:stable-alpine "nginx -g 'daemon of…"53 seconds ago Up 52 seconds 127.0.0.1:20093->80/tcp, 127.0.0.1:20093->80/udp nginx-e974a9c1-0d6b-4d4b-59e7-c86570260509
fa53aa43eccc nginx:stable-alpine "nginx -g 'daemon of…" About a minute ago Up About a minute 127.0.0.1:31503->80/tcp, 127.0.0.1:31503->80/udp nginx-3014056a-a8a5-e63c-c6e6-e9c2e9cafde1
12f482b6fe67 nginx:stable-alpine "nginx -g 'daemon of…" About a minute ago Up About a minute 127.0.0.1:22122->80/tcp, 127.0.0.1:22122->80/udp nginx-a55d7647-a48e-eace-c40f-b2a9abd0ad17
3d09a4329279 nginx:stable-alpine "nginx -g 'daemon of…" About a minute ago Up About a minute 127.0.0.1:24899->80/tcp, 127.0.0.1:24899->80/udp nginx-d682548a-d3d3-bddb-2cd8-cfe489a492b3
24e70fe9443a nginx:stable-alpine "nginx -g 'daemon of…" About a minute ago Up About a minute 127.0.0.1:20772->80/tcp, 127.0.0.1:20772->80/udp nginx-99dab397-69fd-97b0-6507-39195931031c
1
2
3
4
5
6
7
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0495fd1b6261 nginx:alpine "nginx -g 'daemon of…"6 minutes ago Up 6 minutes 127.0.0.1:27703->80/tcp, 127.0.0.1:27703->80/udp nginx-a6a1423a-896d-5716-34bc-533630f45c93
ecb361f99b57 nginx:alpine "nginx -g 'daemon of…"6 minutes ago Up 6 minutes 127.0.0.1:23859->80/tcp, 127.0.0.1:23859->80/udp nginx-c529ec57-ff92-73ae-6e68-46a2d5c55bed
82429bac4974 nginx:alpine "nginx -g 'daemon of…"6 minutes ago Up 6 minutes 127.0.0.1:27878->80/tcp, 127.0.0.1:27878->80/udp nginx-1cda0083-9fa0-6e6d-5276-ab0bd1de8762
69cbf7fd37d8 nginx:alpine "nginx -g 'daemon of…"7 minutes ago Up 7 minutes 127.0.0.1:24634->80/tcp, 127.0.0.1:24634->80/udp nginx-aff0eaf9-a099-42fd-58ea-4603f96ccd3f
4935502d58ce nginx:alpine "nginx -g 'daemon of…"7 minutes ago Up 7 minutes 127.0.0.1:22657->80/tcp, 127.0.0.1:22657->80/udp nginx-d9e3a0c9-82bd-8b19-14a7-0a663f539c00

Nomad y Consul se inician con los siguientes comandos en modo desarrollo comentados en el artículo Introducción a Nomad para gestionar aplicaciones y microservicios.

1
2
$ consul agent -dev -datacenter localhost
$ sudo nomad agent -dev -dc localhost

Koalite: Unpopular opinions: software development edition

$
0
0

A lo largo del tiempo he ido escribiendo bastantes posts en los que intentaba analizar desde un punto de vista (más o menos) racional varios aspectos relacionados con el desarrollo de software. En este post aprovecharé para repasar algunos de ellos. En realidad, nada es ni tan blanco ni tan negro y en los posts enlazados podréis ver un poco mejor la gama de grises.

Quizá no sean exactamente opiniones impopulares. Más bien podríamos decir que son opiniones controvertidas sobre temas que pueden ser examinados con cierto detalle y en los que podemos (y debemos) replantearnos nuestro punto de vista periódicamente.

Veamos algunas.

Utilizar métodos estáticos está bien

En los diseños orientados a objetos se tiende a demonizar su uso, pero los métodos estáticos no son más que funciones, y como tales son muy útiles. Bien utilizados ofrecen alternativas interesantes a la hora de diseñar y pueden simplificar muchos escenarios de testing (en contra de lo que se suele pensar).

Utilizar tipos primitivos ayuda a escribir código más mantenible

Evitar modelar todo con strings o decimals es una cosa, pero introducir tipos específicos para cada concepto que te encuentras provoca que tengas que reimplementar muchas operaciones básicas que están disponibles para los tipos estándar.

Es preferible evitar utilizar clases

Si tu lenguaje lo permite, es mejor intentar diseñar utilizando estructuras más simples (valores, funciones o módulos) antes que empezar a pensar en clases. Los objetos acoplan estado y operaciones, pero además tienen un ciclo de vida que hay considerar. Si no vas a necesitar varias instancias de una clase, probablemente no necesites una clase.

No pasa nada porque (algunas) clases sean grandes

Partir la funcionalidad de un sistema en una miríada de pequeñas clases muy focalizadas y (supuestamente) reutilizables está muy bien, pero puede hacer más difícil entender como funciona todo que tener un código más líneal (y procedural) empaquetado en menos clases. Además, si necesitas garantizar que se mantienen ciertos invariantes en partes críticas de una aplicación, a veces sólo te quedan las clases como mecanismo para encapsular información y evitar usos indebidos de la misma.

En muchos casos utilizar TDD no tiene sentido

Pese a que suene a anatema en determinados ámbitos, hay muchas situaciones en las que utilizar TDD no ayuda a mejorar el diseño ni el proceso de desarrollo. Ni siquiera tener tests automatizados debería ser algo a perseguir sin pararse a pensar antes qué pretendes obtener con los tests.

La inyección de dependencias no debe ser la opción por defecto

Diseñar un sistema para permitir realizar inyección de dependencias en todos los puntos posibles añade una complejidad a la hora de utilizarlo que, sencillamente, no compensa si al final vas a tener una única implementación de cada dependencia. Refactoriza el código para usar inyección de dependencias cuando lo necesites, pero no lo hagas de forma especulativa.

Los ORMs son muy útiles (y no por evitarte escribir SQL)

Los ORMs tienen muchos detractores e implican varios sacrificios (curva de aprendizaje, abstracciones incompletas, …). Pese a todo ofrecen muchas ventajas a la hora de modelar, especialmente gracias a cosas como el Identity Map, la persistencia por alcance o el polimorfismo, que van mucho más allá de evitarte escribir SQL (algo de lo que nunca te puedes pretender aislar por completo).

Los repositorios son importantes (aunque utilices un ORM)

Cuando se utiliza un ORM existe cierta tendencia a despreciar el uso de repositorios por considerarlos una parte “ya cubierta” por el propio ORM. Utilizar repositorios es más que encapsular llamadas a una capa de persitencia. Utilizar repositorios permite establecer un lenguaje común sobre el tipo de operaciones que es pueden realizar sobre cada entidad y ayudar así a proteger los invariantes de tu dominio.

Dejar ficheros en una carpeta es un mecanismo de integración válido

Es indudable que se trata de un sistema prehistórico y con muchos inconvenientes frente a utilizar alternativas más modernas como APIs Web o colas de mensajes. Sin embargo, la facilidad para implementarlo, usarlo y comprobar los datos que se mueven cuando hay errores son ventajas que no hay despreciar.

Hay que comentar el código

El código limpio de los auténticos artesanos ágiles es completamente autoexplicativo y con un nivel de abstracción tal que permite leerlo como si fuese una novela. Sí, vale, me lo creo. Pero lo que no puede leerse es la mente de quien lo programó para saber por qué tomó unas deciciones y no otras. Puedes llevar esa documentación fuera del código (mensajes de commits, pull requests, etc.), pero documentar el porqué de las cosas junto al propio código hace que sea más sencillo consultarlo.

Asumir deuda técnica es algo perfectamente razonable

“Como clean-coder quiero que mi código sea limpio y mantenible para que así…” Todo eso está muy bien, pero nunca hay que olvidar que el código hasta que no está en producción no sirve para nada. Saber gestionar la deuda técnica, ser consciente de cuándo hay que asumirla y de cuándo hay que empezar a pagarla es clave para el desarrollo de software.

Subir las dependencias al repositorio de código es la mejor forma de crear compilaciones reproducibles

Incluir dentro del repositorio de código fuente las dependencias de terceros está considerado una mala práctica en muchos entornos, pero te permite independizarte por completo de servicios de terceros durante el proceso de compilación y garantizar que las compilaciones son realmente reproducibles.

Menos despliegue continuo y más despliegue de calidad

Queda muy bien presumir de que puedes hacer 400 despliegues en producción diarios y que tu tiempo para resolver una incidencia es menor de 4 minutos desde que la implementas hasta que llega a los usuarios, pero como usuario preferiría no haberme encontrado con la incidencia directamente. Hace falta dedicar más tiempo a diseñar y probar antes de empezar a utilizar a tus usuarios como testers gratuitos.

Conclusión

En el fondo, no hay tantas cosas en el desarrollo de software que sean realmente tan opinables.

No es cuestión de “yo tengo derecho a opinar como quiera y toda opinión es respetable”. Es cuestión de analizar los pros y los contras de todo sin empezar a dar por sentado cosas sólo porque estén consideras buenas prácticas o las llevemos haciendo toda la vida.

Estos análisis no pueden realizarse de forma completamente abstracta y generalista y, ahí sí, entra en juego el contexto (personal, tecnológico, de equipo, de negocio, …) en el que nos encontramos a la hora de tomar decisiones.

No hay posts relacionados.

Variable not found: Cómo probar C#8 desde Visual Studio 2019 o CLI

$
0
0
Visual Studio 2019Como sabréis, C# 8 está aún en preview, pero hace ya tiempo que estamos recibiendo información sobre las novedades que traerá la nueva versión de nuestro querido lenguaje: rangos, tipos referencia anulables, mejoras en patrones, más usos de using... pinta divertido, sin duda :D

En el futuro iremos comentando las características más interesantes, pero, de momento, este post vamos a dedicarlo exclusivamente a ver cómo podemos comenzar a probar C# 8 desde nuestro flamante Visual Studio 2019 u otros entornos, como VS Code o incluso la CLI.
Si aún no habéis tenido el ratillo para instalar la última versión de Visual Studio, ya estáis tardando ;D
El único problema es que necesitamos compiladores que entiendan la sintaxis de C# 8, y de momento esto sólo es posible usando la preview de .NET Core 3. Pero vaya, nada que no podamos solucionar en un par de minutos; veamos cómo.

Paso 1: Instalamos la preview de .NET Core 3

Tan simple como ir a la página oficial de descargas, elegir el paquete apropiado e instalarlo en nuestro equipo.

Al finalizar, una ejecución en la línea de comandos de 'dotnet --info' filtrando por el texto "preview" debería mostrar algo como lo siguiente:
C:\>dotnet --info | find "preview"
Version: 3.0.100-preview3-010431
Base Path: C:\dotnet\sdk\3.0.100-preview3-010431\
Version: 3.0.0-preview3-27503-5
3.0.100-preview3-010431 [C:\dotnet\sdk]
Microsoft.AspNetCore.App 3.0.0-preview3-19153-02 [C:\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.0.0-preview3-27503-5 [C:\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.0.0-preview3-27504-2 [C:\dotnet\shared\Microsoft.WindowsDesktop.App]

C:\>_
Si queremos probar C# 8 desde fuera de Visual Studio, no sería necesario realizar nada más. Por ejemplo, bastaría con crear un proyecto con dotnet new console e introducir un código como el siguiente, donde vemos que se utilizan rangos, una de las nuevas características del lenguaje:
static void Main(string[] args)
{
var arr = new[] { 1, 2, 3, 4, 5, 6 };
var ouch = arr[2..4];
Console.WriteLine(ouch[0] + ouch[1]); // 3 + 4 = 7
}
Ojo: esto significa que a partir del momento de su instalación, esta versión preliminar será vuestro SDK por defecto. Tenedlo en cuenta si vais a crear nuevos proyectos desde la línea de comandos.

Paso 2: Activamos el uso de previews de .NET Core en Visual Studio 2019

Por defecto, el entorno de desarrollo sólo utilizará las versiones finales de .NET Core que tengáis instaladas en vuestro equipo. Para usar una versión preliminar hay que indicárselo explícitamente accediendo a la opción Herramientas>Opciones del menú principal, buscando la configuración de .NET Core y marcando la opción "Usar versiones preliminares de .NET Core SDK":

Activar el uso de previews de .NET Core
En mi caso, este settings no fue aplicado hasta que reinicié Visual Studio. Por tanto, quizás no esté de más que lo hagáis antes de continuar, para asegurar que todo irá bien. Total, con VS2019 el arranque es más rápido que nunca ;)
A partir de este momento, Visual Studio podrá crear y manejar proyectos .NET Core 3. Para comprobarlo de forma sencilla, sólo tenemos que crear un proyecto de consola desde el entorno e introducir en él un código como el anterior para comprobar que compila y se ejecuta sin problema :)

También, a la hora de crear proyectos ASP.NET Core, aparecerá esta versión en el selector:

ASP.NET Core 3 en Visual Studio 2019

Ya en siguientes artículos iremos viendo las nuevas incorporaciones al lenguaje, que prometen traernos diversión a raudales :D

Publicado en: www.variablenotfound.com.

Fixed Buffer: “Terraformando” nuestra infraestructura desde Azure Pipelines

$
0
0
La imagen muestra los logos de terraform, azure pipelines y azure cloud

En la última entrada, hablamos sobre Terraform como herramienta para para gestionar nuestra infraestructura mediante código. También hemos hablado hace poco sobre el despliegue continuo desde Azure Pipeline con ARM. Como pudimos comprobar, trabajar con Terraform nos facilita la vida respecto a lo que es el trabajo con ARM directamente. Hoy vamos a unir las dos cosas utilizando Terraform desde Azure Pipelines.

En su momento comentábamos que una de las grandes ventajas de Terraform es que mantiene el estado de los recursos. Esto es algo muy útil y que con trabajo local no supone ningún problema, pero eso se nos queda corto si trabajamos desde el agente de Azure Pipelines. Esto es porque cuando el agente termine va a borrar los recursos, y esto incluye el fichero de estado del despliegue. Para solucionar este “inconveniente” y persistir el estado entre despliegues, vamos a utilizar un Azure Storage donde guardar el fichero.

Una vez aclarado esto, ¡vamos a crear una WebApp en Linux y conectarla a una base de datos Sql Server!

Codificando la infraestructura

Para poder generar la infraestructura y conectarla entre si sin revisión manual, vamos a utilizar la potencia que nos ofrece Terraform para relacionar recursos y así añadir datos como la cadena de conexión. En GitHub está el proyecto completo, dividido en secciones para facilitar su mantenimiento, y utilizando la posibilidad de modular partes para crear la Web App y el Sql Server.

Utilizando estas ventajas, vamos a generar la cadena de conexión de la base de datos utilizando los datos de salida y se lo vamos a pasar a la configuración de la webapp. Para eso, vamos a crear una salida en el módulo de sql server donde vamos a generar la cadena de conexión:

output "conection_string_value" {
  description = "Sql Server ConnectionString"
  value = "Server=${azurerm_sql_server.sqlServer.fully_qualified_domain_name};Initial Catalog=${azurerm_sql_database.sqlServerDb.name};User Id=${var.SQL_ADMIN_ID};Password=${var.SQL_PASSWORD};"
}

Con esto, vamos a poder utilizarla más adelante:

module "linuxwebapp" {
  //...
  conection_string_value = "${module.sqlserver.conection_string_value}"
}

El código de la infraestructura es fácil de seguir, pero aun así recomiendo echarle un ojo en profundidad para entender el concepto anterior.

Creando la integración continua

Para poder desplegar Terraform desde Azure Pipelines, el primer paso es crear el pipeline de integración. De esto hablamos hace algún tiempo en una entrada sobre CI, pero esta vez vamos a utilizar la interfaz gráfica en vez de yml. Como se pueden ver, los pasos son los mismos que cuando utilizábamos yml:

La imagen muestra el pipeline de integración

Aquí solo hay un pequeño cambio respecto a lo que vimos anteriormente, y es que tenemos que meter los ficheros de Terraform al artefacto para poder utilizarlos más adelante:

La imagen señala los campos que hay que rellenar en la tarea de copiar archivos

Para configurar esta nueva tarea, basta con decirle el nombre de la carpeta donde está el código Terraform que queremos desplegar, ponerle el filtro de selección (** para seleccionarlo todo), y por último crear una carpeta en el directorio del artefacto y pegar los ficheros Terraform en él.

El hecho de utilizar la interfaz gráfica para la integración es solo por cambiar, pero para el despliegue solo se puede utilizar la interfaz gráfica de momento.

Ejecutar Terraform desde Azure Pipelines

Una vez que tenemos la integración lista, vamos a crear una Release para que despliegue la infraestructura y el proyecto. Para ahorrarnos trabajo, vamos a utilizar el Task “Terraform Build & Release Tasks“, así que vamos a instalarlo en el pipeline:

La imagen muestra el botón para instalar las Task  de Terraform en Azure Pipelines

Una vez que lo tenemos instalado, vamos a crear el pipeline donde instalaremos Terraform, lo inicializaremos y aplicaremos para desplegar los recursos, y por último publicaremos la web:

La imagen muestra el pipeline de release

Al igual que hacíamos con ARM, lo ideal sería tener diferentes ranuras donde tengamos las diferentes etapas de dev, pre, pro, etc.

Además, vamos a necesitar diferentes variables, que vamos a registrar también en el pipeline, para eso, vamos a la pestaña “Variables”:

La imagen señala la pestaña "Variables"

Y vamos a registrar las variables que necesitamos para nuestro Terraform:

La imagen muestra las variables del pipeline

Por convención, el pipeline le pasa directamente las variables que coincidan y empiecen por TF_VAR a Terraform siempre que no sean secretos. Esto es algo a tener en cuenta para evitarnos un comando apply larguísimo donde le pasemos muchísimas variables.

Volviendo al pipeline, lo primero que tenemos que hacer es instalar Terraform, para eso, vamos a utilizar la Task “Terraform Installer” y le vamos a indicar la versión de Terraform que queremos utilizar:

La imagen muestra donde indicar la versión

El siguiente paso, es configurar una Task de tipo “Terraform CLI” para ejecutar el comando init:

La imagen muestra la interfaz de la Task Terraform CLI

Dentro de esta Task, vamos a seleccionar el comando “init”, y vamos a indicar la ruta donde está el código Terraform, por último, vamos a seleccionar el tipo de backend, que como dijimos al principio, será en Azure Storage, por lo tanto, seleccionamos “azurerm”. Esto nos permite ampliar la configuración pulsando sobre “AzureRM Backend Configuration”, y así indicarle los datos de configuración:

La imagen muestra la configuración del backend en el Task

Con esto listo, el último paso con Terraform es crear una tercera Task de tipo “Terraform CLI”. Esta vez vamos a elegir el tipo de comando “apply”, le vamos a indicar la ruta a donde están los ficheros de código Terrafom, y por último le vamos a indicar las opciones. Entre las opciones, vamos a indicarle “-auto-approve” para que no pida confirmación antes de desplegar los cambios, y le vamos a pasar todas las variables de tipo “secrets” mediante “-var VARIABLE=VARIABLE_PIPELINE” (recordemos que las demás variables se le pasan por convención):

Un ejemplo de las opciones utilizadas es:

-auto-approve -var AZURE_SUBSCRIPTION_ID=$(TF_VAR_AZURE_SUBSCRIPTION_ID) -var AZURE_CLIENT_ID=$(TF_VAR_AZURE_CLIENT_ID) -var AZURE_CLIENT_SECRET=$(TF_VAR_AZURE_CLIENT_SECRET) -var AZURE_TENANT_ID=$(TF_VAR_AZURE_TENANT_ID) -var SQL_PASSWORD=$(TF_VAR_SQL_PASSWORD)

Con esto, y si todo ha ido bien, ya vamos a conseguir desplegar los recursos en Azure. Para desplegar la Web, solo nos queda añadir un Task de tipo “Azure App Service Deploy” tal cual hicimos en el caso de ARM para desplegar la web.

Tras lanzar una release, dentro de nuestro portal en azure podremos encontrar algo como esto:

La imagen muestra los recursos de Azure deplegados mediante Terraform y Azure Pipelines

Para este ejemplo, hemos utilizado el template de ASP NET Core y le hemos añadido que ejecute las migraciones de la base de datos al iniciar, para que la web este lista para funcionar directamente.

Como siempre, he dejado el código fuente completo (tanto Terraform como la Web) en GitHub.

Conclusión

Como hemos podido comprobar, Terraform es una herramienta muy potente que nos facilita mucho la vida manejando infraestructura como código (IaC), además de permitirnos trabajar con múltiples proveedores. Podemos integrar perfectamente Terraform y Azure Pipelines, o cualquier otro servicio CI/CD utilizando los comandos directamente, por lo que es algo que vale la pena revisar y conocer.

**La entrada “Terraformando” nuestra infraestructura desde Azure Pipelines se publicó primero en Fixed Buffer.**

Variable not found: Crear proyectos usando versiones específicas del SDK de .NET Core

$
0
0
.NET CoreAl hilo del post de ayer, uno de los problemas que vamos a encontrar cuando instalemos versiones preliminares del SDK de .NET Core es que éstas se establecen como las versiones por defecto para el equipo.

Por ejemplo, si instalamos en nuestro equipo la preview del SDK 3.0, a partir de ese momento todos los comandos de la CLI se ejecutarán utilizando esta versión preliminar, como cuando creamos un nuevo proyecto usando dotnet new; en este caso, el proyecto se construirá usando la plantilla proporcionada por la versión más actual del framework (aunque sea preview), lo cual puede resultar molesto en algunas ocasiones.

En este post vamos a ver cómo el propio SDK dispone de mecanismos que nos permiten seleccionar una versión específica, para lo cual trataremos varios comandos útiles de la CLI.

¿Qué versión del SDK estamos utilizando actualmente?

Sencillo: basta con ejecutar el comando dotnet --version. En mi caso, como podéis observar, se trata de una preview de .NET Core 3:
C:\>dotnet --version
3.0.100-preview3-010431

C:\>_

¿Y qué versiones del SDK tenemos instaladas en el equipo?

Esta información podemos obtenerla ejecutando el comando dotnet --list-sdks, como se muestra seguidamente:
C:\>dotnet --list-sdks
1.1.11 [C:\Program Files\dotnet\sdk]
2.1.202 [C:\Program Files\dotnet\sdk]
2.1.503 [C:\Program Files\dotnet\sdk]
2.1.504 [C:\Program Files\dotnet\sdk]
2.1.505 [C:\Program Files\dotnet\sdk]
2.1.602 [C:\Program Files\dotnet\sdk]
2.2.102 [C:\Program Files\dotnet\sdk]
2.2.105 [C:\Program Files\dotnet\sdk]
2.2.202 [C:\Program Files\dotnet\sdk]
2.2.203 [C:\Program Files\dotnet\sdk]
3.0.100-preview3-010431 [C:\Program Files\dotnet\sdk]

C:\>_

¿Cómo creamos un proyecto usando una versión específica del SDK?

Imaginemos que por alguna razón queremos crear un proyecto de consola para .NET Core 1. No es posible hacerlo de forma directa pues, por defecto, los nuevos proyectos serán creados usando la versión más reciente del SDK (en mi caso la preview de .NET Core 3).

Pues bien, lo primero que debemos hacer es crear un archivo llamado global.json en la carpeta raíz del proyecto. Este archivo, cuyo contenido veremos a continuación, permite indicar la versión del SDK que será utilizada en todas las carpetas que se encuentre por debajo en la estructura de directorios.

Podemos crear un global.json a mano (eso sólo un archivo de texto), o bien usando el comando dotnet new global.json especificando la versión del SDK que queremos utilizar que, obviamente, debe ser una de las que tenemos instaladas en el equipo:
C:\test>dotnet new global.json --sdk-version 1.1.11
The template "global.json file" was created successfully.

C:\test>type global.json
{
"sdk": {
"version": "1.1.11"
}
}

C:\test>_
Ahora ya podemos crear el proyecto usando el conocido comando dotnet new, que usará la versión del SDK indicada en global.json:
C:\test>dotnet new console
The template "Console Application" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on C:\test\test.csproj...
Restoring packages for C:\test\test.csproj...
Generating MSBuild file C:\test\obj\test.csproj.nuget.g.props.
Generating MSBuild file C:\test\obj\test.csproj.nuget.g.targets.
Restore completed in 712,13 ms for C:\test\test.csproj.

Restore succeeded.

C:\test>_
Para comprobar que el proyecto ha sido creado correctamente, podemos consultar el archivo .csproj, donde veremos las referencias a las versiones correctas del framework, lo que indica que la plantilla utilizada para crearlo ha sido la apropiada:
C:\test>type test.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
</Project>

C:\test>_

Publicado en: www.variablenotfound.com.

Blog Bitix: Servicios con persistencia en el orquestador de microservicos Nomad

$
0
0
Nomad
HashiCorp

Algunos servicios no necesitan almacenar ningún estado porque no lo necesitan o porque el estado se mantiene en otro servicio. Que un servicio no necesite mantener estado es bueno porque de esta manera el servicio se puede escalar al número de instancias adecuadas para prestar el servicio, también porque si falla una instancia la petición puede ser reenviada a otra instancia, una instancia que falla puede ser reemplazada sin problema en otro host. Sin embargo, hay otro tipo de instancias que si almacenan estado como las bases de datos ya sea PostgreSQL, MySQL, Redis, MongoDB, otra o simplemente archivos en el sistema de archivos.

Las instancias que tienen estado no son tan fáciles de reemplazar dado que los datos son necesarios para su funcionamiento, una instancia de un servicio con estado como no puede iniciarse en otro nodo libremente solo se puede iniciar en el nodo que contenga los datos. Eso o cuando el servicio se inicia en otro nodo los datos son trasladados o por algún mechanismo transparente a los servicios están disponibles en el nuevo nodo.

En Docker Swarm ciertos drivers de volúmenes pueden proporcionar volúmenes accesibles desde cualquier host del cluster pero por defecto Swarm no lo ofrece. En Kubernetes los volúmenes pueden ser dispositivos de almacenamiento provenientes de EBS de modo que si un pod es movido a otro host basta con que el pod sea conectado de nuevo al EBS anterior y los datos están accesibles en el nuevo nodo.

Nomad no proporciona soporte para que el almacenamiento persistente sea migrado a un nuevo nodo de Nomad si el servicio cambiado de ubicación. Para solventar esta limitación en el caso de los servicios con estado estos pueden ser tratados en cierta forma como animales de compañía o pets haciendo que siempre se ubiquen en el mismo nodo, una vez tiene siempre la misma ubicación basta con proporcionar el almacenamiento en el host ya sea en su sistema de archivos o para externalizarlo montando un almacenamiento EBS.

Para conseguir que un job de Nomad se ubique siempre en un mismo nodo hay que usar restricciones o costraints en la especificación del job. Las restricciones son las reglas que utiliza Nomad para elegir como candidatos los posibles nodos en los que ubicar el job, task group o task. Se pueden utilizar varios operadores entre los que está el de igualdad utilizado en el ejemplo. Una de las variables utilizables es el identificativo del nodo de Nomad, con él es posible conseguir que el job se ubique siempre en el mismo nodo. Los identificativos de los nodos son asignados por Nomad cuando se unen al cluster.

Con los siguientes comandos se inspecciona los nodos que forman parte del cluster de Nomad, entre sus datos está el identificativo de cada nodo formado por una cadena de 36 caracteres. En el modo verboso se emite el identificativo completo y una lista de propiedades del nodo entre los que están detalles de Consul, la CPU, driver que soporta, kernel, sistema operativo, … En documentación sobre interpolación de variables hay una lista de variables disponibles.

 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
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
$ nomad node status
ID DC Name Class Drain Eligibility Status
44511e01 localhost archlinux <none> false eligible ready
$ nomad node status 44511e01
ID= 44511e01
Name= archlinux
Class=<none>
DC= localhost
Drain=falseEligibility= eligible
Status= ready
Uptime= 1h40m31s
Driver Status= docker,exec,java,qemu,raw_exec
Node Events
Time Subsystem Message
2019-04-26T17:57:41+02:00 Cluster Node registered
Allocated Resources
CPU Memory Disk
0/30400 MHz 0 B/31 GiB 0 B/16 GiB
Allocation Resource Utilization
CPU Memory
0/30400 MHz 0 B/31 GiB
Host Resource Utilization
CPU Memory Disk
3288/30400 MHz 5.2 GiB/31 GiB (tmpfs)
Allocations
No allocations placed
$ nomad node status -verbose 44511e01
ID= 44511e01-34b8-c1e6-7fe5-60be0ff35d0e
Name= archlinux
Class=<none>
DC= localhost
Drain=falseEligibility= eligible
Status= ready
Uptime= 1h44m39s
Drivers
Driver Detected Healthy Message Time
docker truetrue Healthy 2019-04-26T17:57:41+02:00
exectruetrue Healthy 2019-04-26T17:57:41+02:00
java truetrue Healthy 2019-04-26T17:57:41+02:00
qemu truetrue Healthy 2019-04-26T17:57:41+02:00
raw_exec truetrue Healthy 2019-04-26T17:57:41+02:00
rkt falsefalse Failed to execute rkt version: exec: "rkt": executable file not found in $PATH2019-04-26T17:57:41+02:00
Node Events
Time Subsystem Message Details
2019-04-26T17:57:41+02:00 Cluster Node registered <none>
Allocated Resources
CPU Memory Disk
100/30400 MHz 1.0 GiB/31 GiB 300 MiB/16 GiB
Allocation Resource Utilization
CPU Memory
19/30400 MHz 38 MiB/31 GiB
Host Resource Utilization
CPU Memory Disk
4564/30400 MHz 5.5 GiB/31 GiB (tmpfs)
Allocations
ID Eval ID Node ID Task Group Version Desired Status Created Modified
cd45371d-501a-1373-dfde-bb16c4ff20d3 ab9f5675-b5cb-9e5f-8e20-cb308dbfba32 44511e01-34b8-c1e6-7fe5-60be0ff35d0e services 3 run running 2019-04-26T18:14:28+02:00 2019-04-26T18:16:32+02:00
Attributes
consul.datacenter = localhost
consul.revision = ea5210a30
consul.server =true
consul.version =1.4.4
cpu.arch = amd64
cpu.frequency =3800
cpu.modelname = Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
cpu.numcores =8
cpu.totalcompute =30400
driver.docker =1
driver.docker.bridge_ip =172.17.0.1
driver.docker.os_type = linux
driver.docker.runtimes = runc
driver.docker.version =18.09.4-ce
driver.docker.volumes.enabled =true
driver.exec =1
driver.java =1
driver.java.runtime = OpenJDK Runtime Environment (build 1.8.0_212-b01)
driver.java.version =1.8.0_212
driver.java.vm = OpenJDK 64-Bit Server VM (build 25.212-b01, mixed mode)
driver.qemu =1
driver.qemu.version =3.1.0
driver.raw_exec =1
kernel.name = linux
kernel.version =5.0.7-arch1-1-ARCH
memory.totalbytes =33592107008
nomad.advertise.address =127.0.0.1:4646
nomad.revision = 18dd59056ee1d7b2df51256fe900a98460d3d6b9
nomad.version =0.9.0
os.name = arch
os.signals = SIGQUIT,SIGTTOU,SIGFPE,SIGTRAP,SIGTSTP,SIGINT,SIGURG,SIGTTIN,SIGUSR1,SIGIO,SIGIOT,SIGKILL,SIGSTOP,SIGCONT,SIGILL,SIGPROF,SIGSEGV,SIGSYS,SIGTERM,SIGXFSZ,SIGHUP,SIGWINCH,SIGABRT,SIGBUS,SIGCHLD,SIGPIPE,SIGUSR2,SIGXCPU,SIGALRM
unique.cgroup.mountpoint = /sys/fs/cgroup
unique.consul.name = archlinux
unique.hostname = archlinux
unique.network.ip-address =127.0.0.1
unique.storage.bytesfree =16795955200
unique.storage.bytestotal =16796053504
unique.storage.volume = tmpfs
Meta

En este caso solo hay un nodo registrado en Nomad, la siguiente definición de job en el fragmento constraint hace que Nomad lo ubique siempre en él.

 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
job "mongodb" {
datacenters = ["localhost"]
type = "service"
constraint {
attribute = "${node.unique.id}"
value = "44511e01-34b8-c1e6-7fe5-60be0ff35d0e"
}
group "services" {
count = 1
task "mongodb" {
driver = "docker"
config {
image = "mongo:latest"
port_map {
port = 27017
}
volumes = [
"/home/picodotdev/Software/nomad/mongodb:/data/db/"
]
}
resources {
memory = 1024 # MB
network {
port "port" {}
}
}
}
}
}

Como el job se ubica en el mismo nodo siempre montando un directorio del nodo como un volumen de datos en el job y contenedor de Docker, los datos se persisten en el sistema de archivos y transcienden al tiempo de vida del job, se puede iniciar el job, insertar datos en la base de datos en este caso de MongoDB, eliminar el job, volverlo a iniciar y los mismos datos están presentes en MongoDB.

1
2
$ nomad job run mongodb.nomad
 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
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b0d3f42c92fc mongo:latest "docker-entrypoint.s…"3 minutes ago Up 3 minutes 127.0.0.1:20180->27017/tcp, 127.0.0.1:20180->27017/udp mongodb-ea10d440-1176-3bfb-5301-7ccd17af0281
$ docker exec -it b0d3f42c92fc bash
root@b0d3f42c92fc:/# mongo
MongoDB shell version v4.0.9
connecting to: mongodb://127.0.0.1:27017/?gssapiServiceName=mongodb
Implicit session: session {"id" : UUID("ba120679-b965-49d0-a774-dff39d6b630a")}
MongoDB server version: 4.0.9
Server has startup warnings:
2019-04-26T16:47:49.308+0000 I STORAGE [initandlisten]2019-04-26T16:47:49.308+0000 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2019-04-26T16:47:49.308+0000 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem
2019-04-26T16:47:50.133+0000 I CONTROL [initandlisten]2019-04-26T16:47:50.133+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database.
2019-04-26T16:47:50.133+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted.
2019-04-26T16:47:50.133+0000 I CONTROL [initandlisten]
---
Enable MongoDB's free cloud-based monitoring service, which will then receive and display
metrics about your deployment (disk utilization, CPU, operation statistics, etc).
The monitoring data will be available on a MongoDB website with a unique URL accessible to you
and anyone you share the URL with. MongoDB may use this information to make product
improvements and to suggest MongoDB products and deployment options to you.
To enable free monitoring, run the following command: db.enableFreeMonitoring()
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
> db.articles.insert({title: "Introducción a la base de datos NoSQL MongoDB", author: "picodotdev", date: new Date(2017,05,18,12,30), tags: ['mongodb', 'database', 'NoSQL'], comments: [{user: "jones", message: "MongoDB is great!"}, {user: "lina", message: "MongoDB is great!"}]})
WriteResult({ "nInserted" : 1 })
> db.articles.insert({title: "Introducción a la base de datos relacional PostgreSQL", author: "picodotdev", date: new Date(2017,05,17,12,00), likes: 100, tags: ['postgresql', 'database', 'SQL'], comments: [{user: "katy", message: "PostgreSQL rocks!"}, {user: "smith", message: "SQL language is powerful!"}]})
WriteResult({"nInserted" : 1})> db.articles.find(){"_id" : ObjectId("5cc335873b17081f2ca1d4d5"), "title" : "Introducción a la base de datos NoSQL MongoDB", "author" : "picodotdev", "date" : ISODate("2017-06-18T12:30:00Z"), "tags" : ["mongodb", "database", "NoSQL"], "comments" : [{"user" : "jones", "message" : "MongoDB is great!"}, {"user" : "lina", "message" : "MongoDB is great!"}]}{"_id" : ObjectId("5cc335993b17081f2ca1d4d6"), "title" : "Introducción a la base de datos relacional PostgreSQL", "author" : "picodotdev", "date" : ISODate("2017-06-17T12:00:00Z"), "likes" : 100, "tags" : ["postgresql", "database", "SQL"], "comments" : [{"user" : "katy", "message" : "PostgreSQL rocks!"}, {"user" : "smith", "message" : "SQL language is powerful!"}]}> db.articles.count()2> exit
bye
root@b0d3f42c92fc:/# exitexit
$ nomad job stop --purge mongodb==> Monitoring evaluation "f10589c6"
Evaluation triggered by job "mongodb"
Evaluation status changed: "pending" -> "complete"==> Evaluation "f10589c6" finished with status "complete"
1
2
$ nomad job stop --prune mongodb

Para iniciar Consul y Nomad hay que utilizar los siguientes comandos y para el ejecutar job es requisito haber instalado Docker dado que en este ejemplo lo utiliza.

 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
$ consul agent -dev -datacenter localhost==> Starting Consul agent...
==> Consul agent running!
Version: 'v1.4.4'
Node ID: '34294bf0-5802-0d94-4acd-cf8c9d090205'
Node name: 'archlinux'
Datacenter: 'localhost'(Segment: '<all>')
Server: true(Bootstrap: false)
Client Addr: [127.0.0.1](HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600)
Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false
$ sudo nomad agent -dev -dc localhost
[sudo] password for picodotdev:
==> No configuration files loaded==> Starting Nomad agent...
==> Nomad agent configuration:
Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
Bind Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
Client: true
Log Level: DEBUG
Region: global (DC: localhost)
Server: true
Version: 0.9.0

Las restricciones se han de cumplir para elegir un nodo, por otro lado está también la afinidad. La afinidad es una preferencia utilizada por Nomad al seleccionar los nodos que tratará de cumplir si hay algún nodo disponible con las propiedades de afinidad deseadas pero si no hay un nodo disponible se elige algún otro.

Variable not found: Enlaces interesantes 359

$
0
0
Enlaces interesantesAhí va una edición bastante cargadita, con enlaces recopilados durante las dos últimas semanas. Como siempre, espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Otros

Publicado en: www.variablenotfound.com.

Israel Perales: Terminé el curso de agilidad y lean de Javier Garzás

$
0
0
Terminé el curso de agilidad y lean de Javier Garzás

El nombre completo del curso se llama Agilidad y Lean. Gestionando los proyectos y negocios del siglo. XXI (7ª. edición) , este curso esta alojado en la plataforma Miriada X, en la cual ya había hecho anteriormente un curso de aplicaciones web para Firefox OS.

No se si la persona que lo creo tenga en cuenta que México es el país que mas horas trabaja de la OCDE o que sólo el 1.7% de las personas gana más de 20 mil pesos al mes(unos 875 Euros).

Lo anterior va por que a lo largo del curso se estudia los principios del manifiesto ágil, uno de ellos es Individuos e Interacciones sobre procesos y herramientas y vemos temas como el people ware, no trabajar mas de 40 horas por semana, niko niko para 'medir' la felicidad de los equipos, Kudo box para incentivar la motivación, auto organización, equipos pequeños, transferencia del conocimiento por medio de la programación en pareja y lo que le sigue a la programación en pareja llamado Mob Programming.

Se comienza con una animación sin sonido de un equipo de 3-4 personas que están por utilizar SCRUM para desarrollar un proyecto.

En la primera lección se hace referencia que debemos estar conscientes de que hacer software no es lo mismo que construir casas o carros, por que nuestro trabajo la mayoría de las veces es intangible, lleva un proceso de construcción intelectual, es una industria relativamente joven, el software es difícil de predecir, la agilidad no es para todos los proyectos, aunque debería estar presente en mas de los que esta hoy y se debe evitar tener un rambo(persona que es la única que puede modificar ese Stored Procedure de 20k de lineas) en el equipo.

En las lecciones 3( El “Product Owner” y las historias de usuario
), 4(SCRUM) y 5(La planificación ágil) se tratan temas muy ligados a SCRUM y a lo que se le podría llamar agilidad "clásica" , te enseña lo que es, las practicas y técnicas para estimar(Planning poker), organizar (Board, historias de usuario, juntas) y como medir el rendimiento de un proyecto(Burn down y Burn up), velocidad, así como algunos consejos y comparaciones con el ciclo de vida en cascada(historias de usuario vs casos de uso).

Después en las ultimas lecciones 6(Lean y Kanban) y 7(Deuda Técnica y Testing Ágil), hablan de cosas que vi en la facultad como Lean y sabia que de alguna forma se podía aplicar al desarrollo de software, pero nunca lo concretaron en ninguna clase, era mas para manufactura de piezas o productos de acero, cosas que realmente no me importan.

Entonces saltan nombres en el curso como Toyota y Deming hablando sobre el éxito de la calidad, los principios de la calidad y como se aplica al proceso de creación del software como un producto, eliminar el desperdicio, re aprendizaje, etc.

Aparece un concepto nuevo para mi llamado Kanban, que es la técnica principal de Lean, la traducción de Google es letrero, en el curso se maneja como tarjeta visual y trata de tener la visibilidad del trabajo, limitarlo y controlar el flujo del mismo.

Para la visibilidad del trabajo se habla de los elementos/ítems que tenemos como tareas, ya sea una historia de usuario, un issue, requisito, funcionalidad nueva, etc. Debemos definir les los estados en los que pueden estar y que significado tiene cada uno, ya que muchas veces lo que una persona del equipo entiende por terminado no es lo que otros piensan.

Por ejemplo yo puedo decir que ya esta reparado un error y hasta lo tengo en la rama de desarrollo, pero quizá si mi jefe me pregunta, el esperaría que ese reparado es por que ya esta en producción, con esto tratamos de evitar los malos entendidos.

También vuelve la importancia de los tableros en físico en el lugar de trabajo para que sea visible para todo el equipo y que tu tablero de SCRUM se vuelve casi lo mismo que un tablero de Kanban, la diferencia es que Kanban no limita por tiempo, cosa que SCRUM si, con los sprints.

En la limitación del trabajo trata el concepto WIP(Work In Progress, presente en Gitlab ), en el que no debemos de tener mas de "n" cosas sin terminar al mismo tiempo, se debe definir cuantas tareas pueden estar abiertas en cada uno de los estatus y respetarlo, para centrar el equipo en cerrar tareas, por eso de muchas tareas abiertas y ninguna terminada.

La última lección es más técnica y me da entender que no importa que metodología uses, ya sea tradicional o ágil, la excelencia tecnología es lo más importante.

Esto es así por qué si no controlas y mides la complejidad del código en tus proyectos, estos terminan arrastrándose como un zombi, en el que día a día se le van agregando pedazos y pedazos de parches y lo que antes te tomaba un día hacerlo, ahora te toma 2 semanas, ¿Les ha pasado?.

Aquí a los hacks, work arounds, métodos llenos de complejidad se les pone nombre y apellido, uno es el concepto de deuda técnica y el otro es el de la complejidad ciclomatica de los métodos o funciones.

La deuda técnica son nuestros pequeños pecados, que vamos dejando en el código para poder salir a producción, seguir operando, cosas que técnicamente están mal pero que por el contexto son necesarias.

Se explica similar a un crédito bancario donde adquieres deuda para poder ganar un beneficio a corto plazo, pero en algún momento tendrás que pagar sus intereses.

La complejidad ciclomatica en palabras simples es cuántos caminos diferentes pueden existir en tu código, estos caminos se crean por sentencias condicionantes o bucles.

Explicado lo anterior ¿por qué es tan importante mantener una complejidad ciclomatica baja?, La respuesta es que código simple es código sencillo de mantener y fácil de probar, debido a que también se aborda el tema de TDD(Test drive development), el cual evoluciona a BDD(Behavior Drive development).

El desarrollo dirigido por pruebas es una práctica que consiste en desarrollar las pruebas primero y después la implementación, seguido de un nuevo concepto llamado refactorización del código, en el que se pule el código, estás pruebas deben estar automatizadas y deben ejecutarse por lo menos diariamente al construir el ejecutable, aquí entra otro concepto técnico llamado integración continua.

La integración continua es una práctica de la cultura DevOps en la que se debe integrar el trabajo de todos los desarrolladores diariamente.

Veo que es un curso muy completo y vale mucho la pena si estas envuelto en esto del software, aunque no seas desarrollador, muchas técnicas e ideas de este curso te vendrán a la mente en el día a día de tu trabajo en los problemas de organización, normalmente los proyectos de software fracasan por mala organización, ademas se ve muy padre el certificado en Linkedin.

Fuentes:

Israel Perales: Mi experiencia con ICEWM

$
0
0
Mi experiencia con ICEWM

Actualmente me encuentro trabajando bajo mucha presión y por esta razón necesito que los recursos de mi PC estén 100% enfocados en el desarrollo.

Tener arriba los servicios de Docker, IntelliJ y Firefox hacen que GNOME Shell se sienta pesado, cuando instale Open SUSE Tumbleweed me dí cuenta que tenia otros escritorios además de GNOME con Wayland y GNOME con Xorg, uno de ellos es ICEWM.

ICEWM es un entorno de escritorio a la Windows, muy ligero y me permite trabajar con fluidez, tiene los 4 escritorios acomodados de forma horizontal, un botón de inicio, una barra de tareas no personalizable y un monitor de recursos sencillo aun lado del reloj, lo único que necesito.

Este minimalismo, como siempre llega a ser un problema, ya que ahora faltan opciones que en GNOME son fundamentales, como lo es la selección de redes WIFI o cableadas, para la cual uno debe correr el siguiente comando por terminal.

nm-applet &

Listo aparecerá un icono para seleccionar la red que necesites.

Después puede que se cambie el teclado y por mas que busques no encontraras nada en el menú para poder cambiar la distribución del teclado.

Para cambiar la distribución a español, debes ejecutar el siguiente comando.

setxkbmap -option grp:switch,grp:alt_shift_toggle,grp_led:­scroll es

Listo teclado en Español.

Si queremos cuidar nuestra vista con el filtro de luz azul corremos el comando :

redshift -O 3200k

Existen algunas otras cosas molestas de este muy ligero entorno de escritorio, como que al presionar ctrl + alt + t en lugar de abrir Gnome Terminal , termina abriendo xterm, no se puede personalizar el toolbar de inicio y pequeños detalles que al inicio te hacen extrañar GNOME, pero que conforme estas trabajando, ya ni te importan, el rendimiento lo compensa.

Fuentes

Blog Bitix: Autenticación mediante clave pública y privada con SSH

$
0
0
GNU
Linux

Una de las formas más comunes de autenticar a un usuario, comprobar que un usuario es quien dice ser, es mediante un usuario y contraseña. Contraseña que (en principio) solo conoce el usuario por lo que si este proporciona la correcta se determina que es quien dice ser. Sin embargo, las contraseñas son débiles si no incluyen letras en mayúsculas y minúsculas, números y símbolos pero también difíciles de recordar agravado porque en cada servicio se debería usar una diferente para impedir que ante el descubrimiento de una no sea posible acceder a todos los otros servicios donde se usase la misma.

Para generar contraseñas fuertes se puede usar Strong Password Generator y para almacenarlas el programa KeePassXC.

Con OpenSSH también se puede iniciar sesión de línea de comandos en otro sistema proporcionando un usuario y una contraseña que se solicitará. O mejor aún con una clave pública y privada sin necesidad de introducir una contraseña.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ ssh ubuntu@192.168.33.10
Welcome to Ubuntu 18.04.2 LTS (GNU/Linux 4.15.0-48-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Fri May 318:04:48 UTC 2019
System load: 0.0 Processes: 102
Usage of /: 10.0% of 9.63GB Users logged in: 1
Memory usage: 6% IP address for enp0s3: 10.0.2.15
Swap usage: 0% IP address for enp0s8: 192.168.33.10
0 packages can be updated.
0 updates are security updates.
Last login: Fri May 318:04:00 2019 from 192.168.33.1
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root"for details.
ubuntu@ubuntu-bionic:~$

Usar SSH es más seguro y más cómodo que usar contraseñas para realizar la autenticación, es más seguro ya que una clave pública y privada son más largas y es más cómodo ya que no se solicita contraseña constantemente que de otra manera al cabo de un tiempo se convierte en un paso tedioso y molesto. Lo primero que hay que hacer es generar una clave SSH pública y privada propia. Una clave de 2048 bits ya se considera segura pero como cuesta lo mismo generar una de más bits se puede generar una de 8192 bits para más seguridad.

1
2
$ openssl genrsa -out ubuntu 8192
$ ssh-keygen -y -f ubuntu > ubuntu.pub

En el servidor SSH se deben modificar algunas propiedades de configuración del archivo de configuración /etc/ssh/sshd_config para permitir la autenticación con usuario y contraseña para poder copiar la clave pública y una vez copiada la clave para mayor seguridad no permitir la autenticación mediante usuario y contraseña.

1
PasswordAuthentication yes

Una vez generado el par de claves hay que copiar la clave pública al servidor donde se desee iniciar sesión. Manualmente concatenando la clave pública al archivo _.ssh/authorizedkeys del directorio home del usuario con el que se quiere iniciar sesión o también se puede copiar la clave pública usando el comando ssh-copy-id. Para revocar el acceso mediante esa clave basta con eliminar su linea del archivo de claves autorizadas.

1
$ ssh-copy-id -i ~/.ssh/ubuntu ubuntu@192.168.33.10
1
$ sudo passwd ubuntu

Si se poseen varios pares de claves públicas y privadas se puede especificar que clave privada usar para cada máquina a la que se desee conectar en el archivo ~/.ssh/config.

1
2
3
4
Host192.168.33.10HostName192.168.33.10UserubuntuIdentityFile~/.ssh/ubuntu

La clave privada debe tener permisos restringidos sino se muestra una advertencia e impide el inicio de sesión.

1
$chmod600ubuntu

Para probar la autenticación con SSH se puede utilizar una máquina virtual de VirtualBox creada con Vagrant. En el caso de utilizar el usuario ubuntu hay que asignarle una clave con el comando passwd para ejecutar comando ssh-copy-id ya que se solicita su contraseña en este paso, una vez realizado se puede desactivar la autenticación mediante usuario y contraseña cambiando el valor de la configuración PasswordAuthentication a no.

 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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# -*- mode: ruby -*-
# vi: set ft=ruby :
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.
# Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search.
config.vm.box = "ubuntu/bionic64"
config.vm.provider :virtualbox do |vb|
vb.name = "Ubuntu 18.04 (Vagrant)"
end
# Disable automatic box update checking. If you disable this, then
# boxes will only be checked for updates when the user runs
# `vagrant box outdated`. This is not recommended.
# config.vm.box_check_update = false
# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
# NOTE: This will enable public access to the opened port
# config.vm.network "forwarded_port", guest: 80, host: 8080
# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine and only allow access
# via 127.0.0.1 to disable public access
# config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
# Create a private network, which allows host-only access to the machine
# using a specific IP.
config.vm.network "private_network", ip: "192.168.33.10"
# Create a public network, which generally matched to bridged network.
# Bridged networks make the machine appear as another physical device on
# your network.
# config.vm.network "public_network"
# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
# config.vm.synced_folder "../data", "/vagrant_data"
# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
#
config.vm.provider "virtualbox" do |vb|
# Display the VirtualBox GUI when booting the machine
#vb.gui = true
# Customize the amount of memory on the VM:
vb.memory = "2048"
end
# View the documentation for the provider you are using for more
# information on available options.
# Enable provisioning with a shell script. Additional provisioners such as
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
# documentation for more information about their specific syntax and use.
# config.vm.provision "shell", inline: <<-SHELL
# apt-get update
# apt-get install -y apache2
# SHELL
end
1
$ vagrant up

Blog Bitix: De un móvil Samsung Galaxy S3 a un Samsung Galaxy S6

$
0
0
Samsung
Android

El móvil es posiblemente el primer contacto de mucha gente nacida sobre los años 1970 en una época que su juventud la pasó sin la tecnología actual como internet, los ordenadores personales y los citados móviles que también pueden ser donde realizan la mayor parte de su actividad tecnológica. Los móviles primero llegaron como sustitutos de los teléfonos fijos para enviar y recibir llamadas incorporando la funcionalidad de mensajes de texto y de forma muy limitada conexión servicios por red no diría internet.

Sobre la década de los años 2000 se desarrollaron los primeros teléfonos inteligentes o smartphones con mayores capacidades, teclados físicos y aún pantallas pequeñas, también adquieren otras funciones como la incorporación de cámaras fotográficas o conectividad con WiFi y conexión a internet través de redes móviles. La siguiente tendencia ha sido incorporar pantallas táctiles cada vez más grandes, mejores cámaras fotográficas y otros sensores como GPS para ubicación, NFC para pagos por proximidad o de huellas. En realidad se han convertido en un dispositivo muy versátil con una capacidad de cómputo y memoria significativa que rivaliza con algunos portátiles, tanto que incluso para muchas personas hace innecesario tener un ordenador personal portátil o de escritorio y para otras personas puede haberlo incluso sustituido.

En mi caso el móvil me sigue pareciendo un dispositivo incómodo para trabajar sobre todo para escribir texto y sigo utilizando en mucha mayor medida un ordenador de escritorio. Aunque hay algunos intentos como el Samsung Galaxy DeX u otro aún no se ha masificado el baticinio que hacía en La siguiente disrupción tecnológica, la convergencia en el 2016 como elemento que puede convertir el móvil en un ordenador personal. Mis necesidades no son tales como para que el móvil pueda sustituir el ordenador personal pero ya he pasado por varios móviles y hoy ha llegado el momento de buscar un susituto al Samsung Galaxy S3 que me ha servido perfectamente y con el que he estado muy contento más aún después de instalarle LineageOS 14.1 para tener Android 7.1 (Nougat) desde su original 4.0 (Ice Cream Sandwich) actualizado a 4.3 (Jelly Bean) y 4.4 (KitKat).

He tenido varios móviles empezando por un Motorola C350 y pasando por Nokia 6630, HTC Wildfire CDMA, Motorola Defy, el citado Samsung Galaxy S3 lanzado en el 2012 hasta que he vuelto a cambiar. Realmente el único móvil que he comprado fue el primero el resto los he ido heredando de los descartes de otras personas y el Samsung Galaxy S3 es un móvil con el que he hecho todo lo que he necesitado. Principalmente usar GMail, Twitter, mensajería con WhatsApp, ocasionalmente Firefox para el móvil y algún juego como Clash Royale. En buenas condiciones de luz realizando un enfoque adecuado con sus 8 megapixeles de cámara principal es capaz de tomar buenas fotografías y sacar buenos vídeos.

Móviles

Pero el Galaxy S3 también ya me estaba dando algunos síntomas de agotamiento, en modo espera me aguanta un día pero con un uso algo más intensivo la batería baja rápidamente y requiere alguna carga más de una carga al día. Algunas aplicaciones como GMail y Twitter se nota que el procesador Exynos 4412 quad-core que tiene cumple pero no va sobrado para la época actual. En total el S3 habrá tenido 7 años de vida, menos lo que no sería mucho si no lo hubiese dado un segundo uso y un tercero con LineageOS.

Como opciones candidatas he estado barajando un Samsung Galaxy A50, Samsung Galaxy A70, Xiaomi Mi 9 y Xiaomi Mi 9SE. Cualquiera de ellos unos pedazo móviles en el rango de los 300-450€ que no tienen mucho que envidiar a otros de gama alta pero que cuestan el doble o más.

Sin embargo, en vez de comprar uno de estos anteriores nuevos como en ocasiones anteriores he tenido la posibilidad de heredar otro móvil, en este caso un Samsung Galaxy S6 lanzado en el 2015 que para haber pasado 4 años a día de hoy y por las especificaciones que tiene ha resistido el paso del tiempo bastante bien creo para los móviles del 2019 de gama media-baja. Y es que es un móvil de gama alta de su época al igual que lo fue el S3 en su momento. En primer contacto con él se nota una apreciable fluidez al abrir aplicaciones comparado con el Samsung Galaxy S3.

  • Actualizable a Android 7.0 (Nougat).
  • Pantalla 5.1”, Super AMOLED, 1440 x 2560 pixels, 577 ppi.
  • Procesador Exynos 7420 Octa (14 nm), CPU Octa-core (4x2.1 GHz Cortex-A57 & 4x1.5 GHz Cortex-A53), GPU Mali-T760MP8.
  • Memoria 3 GiB RAM, 32 GiB interna, sin microSD.
  • Cámara principal 16 MP, f/1.9, cámara delantera 5 MP, f/1.9.
  • Comunicaciones Wi-Fi 802.11 a/b/g/n/ac, Bluetooth 4.1, GPS, NFC, infrarrojos.
  • Sensores huellas en el botón de inicio, acelerómetro, giroscopio, proximidad, brújula, barómetro, frecuencia cardiaca.
  • Batería 2550mAh no extraíble, carga rápida de 15W, carga inalámbrica.
Nuevo móvil, Samsung Galaxy S6

Tiene marcos grandes, no tiene un sensor de huellas integrado en la pantalla, sin triple cámara con gran angular, ni zoom óptico y efecto desenfoque como están incorporando los móviles nuevos como los anteriores y echo de menos una tarjeta microSD para ampliarle la capacidad de almacenamiento sobre todo para añadirle mi colección de música. Pero suficiente para mi.

La tecnología avanza muy rápido cada año hay un nuevo modelo de móvil que supera al anterior, pero suelen ser mejoras graduales que hasta pasado varios años más un móvil como el Samsung Galaxy S6 después de 4 años sigue siendo un gran móvil al que le daré una segunda vida. Quizá dentro de unos años herede un Samsung Galaxy S8 o S9 y así estoy tirando millas sin necesidad real de tener que comprar un móvil. Mejor que reciclar es reutilizar.

Variable not found: Enlaces interesantes 360

$
0
0
Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Otros

Publicado en Variable not found.

Variable not found: Backing fields en Entity Framework Core

$
0
0
Entity Framework CorePara los desarrolladores que hemos utilizado Entity Framework 6.x y anteriores durante años, lo normal es que al dar el salto a Entity Framework Core continuemos utilizando más o menos las mismas cosas que ya conocíamos, y muchas veces no nos detenemos a ver qué novedades interesantes acompañan al nuevo marco de trabajo.

Ya llevamos varios posts dedicados a comentar algunas características novedosas o que cambian bastante respecto a las versiones anteriores (como la evaluación en cliente o las shadow properties, y vamos a continuar en esta línea presentando ahora otra interesante novedad: el soporte para campos de respaldo o backing fields.

¿Backing fields? ¿No me suena eso de algo?

Pues claro, el concepto de backing fields no es nuevo, ni propio de Entity Framework Core: lo usamos en .NET desde el principio de los tiempos. Como se define en la guía oficial de programación con C#:
Un campo de respaldo es un campo privado que almacena los datos expuestos por una propiedad pública.
Vaya, básicamente es lo que utilizábamos a diario antes de que se introdujeran en el lenguaje las propiedades automáticas:
public class Friend
{
private string _name; // <-- backing field

public string Name
{
get { return _name; }
set { _name = value; }
}
}

Vale, ¿y por qué necesitamos backing fields en Entity Framework?

Si habéis trabajado ya con Entity Framework "clásico", probablemente que alguna vez habréis necesitado proteger las entidades de accesos no controlados a algunas de sus propiedades. Aunque algunos de esos escenarios eran salvables jugando con la visibilidad de las propiedades, hay otros donde esto no era técnicamente posible, suponiendo un peligro desde el punto de vista de la encapsulación de datos, el mantenimiento de invariantes o la consistencia de la información almacenada.

Por ejemplo, en la siguiente entidad tenemos una propiedad llamada LastUpdate para mantener la fecha en que los datos de la persona son actualizados. Para asegurar la consistencia, en el setter de Name actualizamos la fecha de actualización:
public class Friend
{
private string _fullName;

public string Name
{
get => _fullName;
set
{
_fullName = value;
LastUpdate = DateTime.Now;
}
}

public int Id { get; set; }
public DateTime LastUpdate { get; private set; }
}

De esta forma, cuando desde código asignemos la propiedad Name, se actualizará su fecha de modificación. Además, para evitar que se modifique desde fuera, hacemos el setter privado: aparentemente, todo correcto.

Sin embargo, este código tiene un problema: cada vez que una entidad Friend sea materializada desde la base de datos se establecerá la propiedad Name, y esto provocará que a su vez se actualice el contenido de LastUpdate con la fecha actual. Por tanto, estaremos machacándola continuamente y perderemos el valor original de la propiedad en cuanto salvemos cambios.

Otro caso frecuente lo podemos encontrar en el siguiente ejemplo. Echad un vistazo, ¿qué impide añadir una línea a la factura directamente, obviando todos los controles y lógica que el método AddLine() pudiera contener?
public class Invoice
{
...
public HashSet<InvoiceLine> Lines { get; private set; }
public void AddLine(InvoiceLine line)
{
if (!IsEditable())
{
throw new InvalidOperationException("You can only add lines to editable invoices");
}
if (line.Product.Stock <= 0)
{
throw new InvalidOperationException("You can't add products out of stock");
}
Amount += (line.Quantity * line.UnitPrice);
Lines.Add(line);
}
}
En este escenario, el ideal sería que la propiedad Lines fuera de algún tipo que no permitiera alterar el contenido de la colección, como un IEnumerable<InvoiceLine>.

Pues aquí es donde entran en escena los backing fields, cuya idea de es permitir que el framework sea capaz de leer y escribir directamente en campos privados de las entidades, sin que tengan que existir propiedades públicas o setters que abran la puerta a accesos arbitrarios a sus datos.

Entity Framework detectará los campos privados de las entidades, bien mediante convenciones o bien porque así lo hayamos indicado expresamente en la configuración, y los usará para almacenar los datos directamente durante el proceso de materialización.

Uso de backing fields

La configuración de este tipo de campos la debemos hacer desde el método OnModelCreating() del contexto de datos, donde asociaremos los backing fields con las propiedades de la entidad, como en el siguiente ejemplo:
// En el contexto de datos, indicamos el backing field para "Name":
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Friend>()
.Property(f => f.Name)
.HasField("_fullName");
...
}

// En la entidad:
public class Friend
{
private string _fullName;
public int Id { get; set; }
public string Name
{
get => _fullName;
set
{
_fullName = value;
LastUpdate = DateTime.Now;
}
}
public DateTime LastUpdate { get; private set; }
}
Hecho esto, cuando EF vaya a materializar un objeto Friend desde el almacén, escribirá directamente sobre el campo privado_fullName en lugar de hacerlo sobre la propiedad Name, por lo que el valor de LastUpdated no será actualizado, aglo que si ocurrirá cuando establezcamos manualmente el nombre.

¿Y cómo podemos aprovechar esta característica también para proteger las colecciones, como en el escenario que describíamos algo más arriba? Pues en este caso, lo que nos interesa es que el campo privado sea de tipo HashSet o similar y permita todas las operaciones de adición y eliminación de elementos, pero que hacia fuera nuestra entidad exponga un IEnumerable de sólo lectura, como en el siguiente código:
public class Invoice
{
private HashSet<InvoiceLine> _invoiceLines;

public IEnumerable<InvoiceLine> Lines => _invoiceLines;
public void AddLine(InvoiceLine line)
{
// Hacer algo y añadir la línea a _lines
}
...
}
En esta ocasión, al tratarse de una propiedad de navegación, la forma de configurarlo será ligeramente diferente:
// En el contexto de datos:
protected override void OnModelCreating(ModelBuilder builder)
{
var navigation = builder.Entity<Invoice>().Metadata.FindNavigation(nameof(Invoice.Lines));
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
navigation.SetField("_invoiceLines");
...
}

Convenciones de nombrado de backing fields

Por defecto, Entity Framework Core detectará backing fields de forma automática cuando en la entidad exista un campo privado cuyo nombre coincida con el de una propiedad incluida en el modelo según una convención de nombres predeterminada. Por ejemplo, a una propiedad llamada CustomerEmail le asociará automáticamente, sin necesidad de indicarlo expresamente en el OnModelCreating(), un backing field llamado (en orden de prioridad):
  • _customerEmail
  • _CustomerEmail
  • m_customerEmail
  • m_CustomerEmail
Y esto es todo, ¡espero que os sea útil!

Publicado en Variable not found.

Fixed Buffer: Cómo crear un servicio Net Core multiplataforma

$
0
0
Imagen para el post de crear servicios para .Net Core

Después de varias semanas hablando sobre las maravillas de Terraform o de Integración y Despliegue continuos (CI/CD), hoy vengo a hablaros de un problema con el que me he encontrado con un proyecto que tengo entre manos.

Por necesidades del proyecto, quiero hacer un servicio .Net Core, y poder instalarlo en cualquier plataforma. Esto no es ningún problema en Linux por ejemplo, pero para poder correr un servicio en Windows hay que cumplir con ciertos requisitos. Para .Net Framework existen múltiples herramientas que nos lo permiten, como puede ser un proyecto de instalador de servicio o utilizar Topselph, pero para .Net Core ya es otra cosa…

Para que un servicio se pueda instalar y funcione en Windows, tiene que heredar de «ServiceBase«, lo que difiere un poco de cómo funciona en otras plataformas, y hace que crear un servicio multiplataforma no sea algo directo. Esto se puede solucionar fácilmente con un poco de código.

Creando nuestro servicio Net Core

Para empezar, vamos a crear una aplicación de consola .Net Core:

La imagen muestra la creación de un proyecto de consola net core

Para poder hospedar un servicio cualquiera, vamos a necesitar añadir el paquete NuGet «Microsoft.Extensions.Hosting«, y además, para poder correrlo cuando funcione en Windows, vamos a necesitar también el paquete NuGet «System.ServiceProcess.ServiceController«. Una vez que los hemos añadido, vamos a crear nuestro IHost:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace PostServicioNetCore
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var host = new HostBuilder()
                 .ConfigureHostConfiguration(configHost =>
                 {
                     //Configuración del host
                 })
                 .ConfigureAppConfiguration((hostContext, configApp) =>
                 {
                     //Configuración de la aplicacion

                 })
                .ConfigureServices((hostContext, services) =>
                {
                    //Configuración de los servicios
                    services.AddHostedService<LifetimeHostedService>();
                });
            if (!Debugger.IsAttached) //En caso de no haber debugger, lanzamos como servicio
            {
                await host.RunServiceAsync();
            }
            else
            {
                await host.RunConsoleAsync();
            }
        }
    }
}

Al igual que haríamos en un proyecto ASP NET Core en el Startup.cs y en Program.cs, podemos configurar las diferentes partes de nuestro servicio Net Core mediante los métodos:

  • «ConfigureHostConfiguration» (en Program.cs)
  • «ConfigureAppConfiguration» («Configure» en Startup.cs)
  • «ConfigureServices» (en Startup.cs)

En este caso, estamos levantando un IHost genérico al cual le vamos a inyectar la dependencia del servicio/servicios que queremos ejecutar registrándolos mediante «AddHostedService».

Para poder probar, el servicio que hemos registrado es este (tiene que implementar IHostedService):

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace PostServicioNetCore
{
    public class LifetimeHostedService : IHostedService, IDisposable
    {
        ILogger<LifetimeHostedService> logger;
        private Timer _timer;
        private string _path;
        public LifetimeHostedService(ILogger<LifetimeHostedService> logger, IHostingEnvironment hostingEnvironment)
        {
            this.logger = logger;
            _path = $"{hostingEnvironment.ContentRootPath}PostServicioCore.txt";
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            try
            {
                WriteToFile("Inicia el servicio");
                _timer = new Timer((e) => WriteTimeToFile(), null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
            }
            catch (Exception ex)
            {
                logger.LogError($"{ex.Message},{ex.StackTrace}");
            }
            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            _timer?.Change(Timeout.Infinite, 0);
            WriteToFile("Finaliza el servicio");
            return Task.CompletedTask;
        }

        private void WriteTimeToFile()
        {
            WriteToFile(DateTime.Now.ToString());
        }

        private void WriteToFile(string message)
        {
            if (!File.Exists(_path))
            {
                using (var sw = File.CreateText(_path))
                {
                    sw.WriteLine(message);
                }
            }
            else
            {
                using (var sw = File.AppendText(_path))
                {
                    sw.WriteLine(message);
                }
            }
        }

        public void Dispose()
        {
            _timer?.Dispose();
        }
    }
}

En él, simplemente se añaden mensajes a un fichero que se encuentra junto al binario para indicar cuando empieza, cuando finaliza y cada minuto mientras esté activo, pero aquí sería donde vamos a poner que es lo que hace nuestro servicio (consultar una cola de mensajes, lecturas a una Db, etc).

Hasta aquí, este es el mismo concepto que utilizamos en ASP NET Core para crear servicios que corran en segundo plano en una web. Creamos el servicio heredando de «IHostedService» y lo registramos desde «ConfigureServices». El problema como decía al principio, es que los servicios en Windows no funcionan así, tienen que heredar de «ServiceBase».

Preparando nuestro servicio para Windows

En primer lugar, vamos a necesitar crear nuestra clase heredada:

using Microsoft.Extensions.Hosting;
using System;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;

namespace PostServicioNetCore
{
    public class ServiceBaseLifetime : ServiceBase, IHostLifetime
    {
        private readonly TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();

        public ServiceBaseLifetime(IApplicationLifetime applicationLifetime)
        {
            ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
        }

        private IApplicationLifetime ApplicationLifetime { get; }

        public Task WaitForStartAsync(CancellationToken cancellationToken)
        {
            cancellationToken.Register(() => _delayStart.TrySetCanceled());
            ApplicationLifetime.ApplicationStopping.Register(Stop);

            new Thread(Run).Start(); //Ejecutamos la tarea en un hilo para bloquear y prevenir que IHost.StartAsync termine.
            return _delayStart.Task;
        }

        private void Run()
        {
            try
            {
                Run(this); // Bloqueamos la ejecución hasta que el servicio termine.
                _delayStart.TrySetException(new InvalidOperationException("Stopped without starting"));
            }
            catch (Exception ex)
            {
                _delayStart.TrySetException(ex);
            }
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            Stop();
            return Task.CompletedTask;
        }

        // Se llama desde base.Run() cuando el servicio esta listo para funcionar.
        protected override void OnStart(string[] args)
        {
            _delayStart.TrySetResult(null);
            base.OnStart(args);
        }

        protected override void OnStop()
        {
            ApplicationLifetime.StopApplication();
            base.OnStop();
        }
    }
}

Sin ánimo de entrar muy en profundidad, ya que esta clase es una copia del ejemplo que ofrece Microsoft navegando entre los repositorios en GitHub, en ella básicamente se implementan los métodos propios de un servicio Windows y de la interfaz «IHostLifetime«. De este modo, la aplicación puede correr como un servicio en Windows y ser usada desde «IHost».

Esta clase tiene que estar en el contenedor de inyección de dependencias siempre que el servicio corra sobre Windows, así que vamos a añadir unos métodos de extensión que nos facilite incluirla si es necesario:

using System.Runtime.InteropServices;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;

namespace PostServicioNetCore
{
    public static class ServiceBaseLifetimeHostExtensions
    {
        public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder)
        {
            return hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton<IHostLifetime, ServiceBaseLifetime>());
        }

        public static Task RunServiceAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
        {
            //Si es Windows, añadimos el ServiceBaseLifetime a la inyeccion de dependencias
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                return hostBuilder.UseServiceBaseLifetime().Build().RunAsync(cancellationToken);
            }
            else //Sino, ejecutamos normalmente
            {
                return hostBuilder.Build().RunAsync(cancellationToken);
            }
        }
    }
}

Gracias a la condición que pusimos al arrancar el IHost para detectar si estábamos en sin el debugger, y con la condición que añadimos dentro de la extensión RunAsServiceAsync, vamos a poder ejecutar nuestro programa como un servicio para Windows, para Linux o como un programa de consola (que es ideal para depurarlo).

Por último, si queremos instalar el servicio en Windows, primero vamos a publicarlo de manera que generemos un .exe indicándole al publicar que queremos que el «Tiempo de ejecución de destino» sea una versión de Windows»

La imagen muestra como indicarle el destino de la publicación

Una vez que hemos publicado nuestro .exe, vamos a tirar de «Service Controller Commands» para instalarlo. Desde la terminal:

sc create "Servicio Core" binpath= "C:\Users\jorge\source\repos\PostServicioNetCore\PostServicioNetCore\bin\Release\netcoreapp2.2\publish\PostServicioNetCore.exe"

Por el contrario, si queremos que el servicio sea para Linux, basta con que no generemos un .exe al publicar y lo registremos normalmente (como veremos en la próxima entrada aprovechando este código).

Con esto, ya tenemos nuestro servicio Net Core multiplataforma listo para ser instalado y ejecutado tanto en Windows como en Linux, y al que solo tenemos que ir añadiéndole la funcionalidad que queramos mediante nuevas implementaciones de «IHostedService».

Como siempre, dejo el código fuente en GitHub para que puedas descargártelo y probar.

**La entrada Cómo crear un servicio Net Core multiplataforma se publicó primero en Fixed Buffer.**


Variable not found: Triscaidecafílicos del mundo, regocijaos: ¡13 años de Variable Not Found!

$
0
0
La triscaidecafobia es el miedo irracional al número trece, muy asociado a supersticiones y creencias arraigadas en distintas épocas y culturas, como la egipcia, vikinga, el propio cristianismo, o incluso las órdenes templarias. Por contraposición, la triscaidecafilia sería justamente lo contrario: el gusto excesivo por todo lo relacionado con dicho número.

Y hasta aquí la píldora cultural del día, ahora vamos a lo importante :D

Hoy Variable not found cumple trece años, una auténtica barbaridad. Por supuesto, mucho más de lo que habría podido esperar en aquél remoto 2006, cuando gritaba al maquinista de este tren llamado blogosfera (suena viejuno, ¿eh?) para que me dejara subir a él. Quizás en ese momento, desde el andén, no habría apostado demasiado por un viaje de más allá del par de años.

Y aquí seguimos: más de 1.100 entradas, algo más de 1.000 comentarios y, según Blogger, acercándonos a los cuatro millones de páginas servidas. Pero lo mejor, que aún seguimos con muchas, muchas ganas de seguir aportando granitos de arena a la comunidad, compartiendo lo que vamos aprendiendo cada día.

La salud del blog: cómo fue el año pasado

Pues como suele decirse, no nos podemos quejar, aunque con nuestros achaques ;D

Ya el año anterior noté un paulatino descenso del número de visitas, cosa que atribuí a penalizaciones por la ausencia de HTTPS en el blog, y durante gran parte del pasado año hemos seguido en la misma tónica. Afortunadamente, parece que desde primeros de 2019 han debido apretar algún tornillo en Google y la cosa lleva meses empezado a retomar cifras de antes de ese tropezón.

En cualquier caso, dado que soy un absoluto ignorante en temas de SEO, posicionamiento y similares, tampoco es algo que me preocupe más de la cuenta. Obviamente me alegra saber que el tiempo que dedico a esto es de utilidad para alguien, y cuantos más mejor, pero no es algo que me obsesione.

A pesar de lo dicho anteriormente, creo que aún seguimos teniendo cifras bastante buenas, para tratarse de un blog tan especializado. Desde mayo de 2018 han pasado por aquí más de 110.000 personas, visitando un total de 200.000 páginas durante un promedio de cerca de 4 minutos. La página de Facebook supera los 900 seguidores, y a través de Twitter nos siguen cerca de 2.500 amigos (sumando @jmaguilar y @variablnotfound). Muchas gracias a todos, porque sois los que le dais sentido a esta historia :)

DeveloperGoogle, que todo lo sabe, dice que la mayoría de vosotros sois hombres (80%). Curiosamente, esta cifra ha ido disminuyendo durante los últimos años en favor del sexo femenino (por ejemplo, en 2015 este dato ascendía al 90%), lo cual quiero creer que indica que la monopolización masculina en nuestro sector tiende a desaparecer, o al menos a reducirse, la larga.

La mayoría de vosotros os encontráis en un rango de edad entre 25 y 34 años, subiendo ligeramente las visitas de desarrolladores algo mayores. En cualquier caso, el 90% de las visitas tenéis menos de 45 años. Juventud, divino tesoro ;)

Como no podía ser de otra forma, arrasáis los lectores que Google clasifica como tecnófilos, profesionales del desarrollo de software, amantes del cine y videojuegos. Cerca del 80% utilizáis Chrome, seguido de lejos por un 11% que prefiere Firefox, y ya con menos del 5% encontramos Internet Explorer (¿¡todavía anda eso por ahí!?), Edge, Opera, Safari y otros.

Por goleada, la principal vía de acceso al blog es a través de búsquedas, con un 88% (obviamente, casi todo procedente de Google), un 11% entradas directas, y el resto repartidas entre redes sociales y enlaces entrantes.

Distribución por paísesRespecto a la ubicación geográfica, los amigos de México continúan su ascenso, llegando a establecerse en algo más del 25% del total de visitantes. España queda relegado al segundo puesto, con el 17%, y le siguen Colombia (10.24%), Perú (8.35%), Argentina (6.87%) y varios países más con una diferencia entre ellos del 1% (Chile, Estados Unidos, Ecuador, Costa Rica, y los hermanos de Venezuela).

Como siempre digo, y se pone claramente de manifiesto viendo los números, este espacio sigue siendo un blog humilde y con pocas pretensiones, pero eso sí,con un ambicioso objetivo: que aprendamos juntos, y pasarlo lo mejor posible durante el transcurso.

Una vez más, muchas gracias a todos por cada visita, comentario, sugerencia o contacto, porque sin vosotros esto no tendría sentido alguno.

Así que, si os parece, nos seguimos viendo por aquí un año más, buscando la variable :)

Publicado en: www.variablenotfound.com.

Coding Potions: Tutorial SASS. Todo sobre el preprocesador CSS

$
0
0
Introducción ¿Alguna vez has sentido que escribes demasiado código CSS? Pues estás de suerte, porque con sass te vas a ahorrar mucho código. Sass lo que hace es compilar lo que escribes a código css que pueda entender el navegador. Es cierto que cada vez están implementando nuevas funcionalidades a css, pero antiguamente no tenía tantas y LESS y posteriormente SASS vinieron para resolver este problema. Incluso en los tiempos que corren SASS sigue siendo una buena ida para usar junto a cualquier framework.

Blog Bitix: Crear de forma sencilla y rápida máquinas virtuales de VirtualBox con Vagrant

$
0
0
Vagrant
HashiCorp

VirtualBox es una de las herramientas que permiten virtualizar un sistema operativo completo y sus aplicaciones dentro de otra máquina. Como es un sistema operativo completo requiere que el sistema que la alberga tenga RAM suficiente para sí mismo y RAM suficiente para el sistema virtualizado, se puede configurar la cantidad de RAM y almacenamiento persistente de la máquina virtual. En el proceso de virtualización se pierde algo de rendimiento por la sobrecarga que añade virtualizar un sistema operativo completo, los procesadores modernos ofrecen soporte para que el rendimiento sea lo mayor posible pero no es igual a ejecutar el sistema de forma nativa en el sistema, sobre todo en el aspecto de interfaces gráficas y aceleración 2D y 3D. La virtualización es una buena forma de probar una distribución GNU/Linux para evaluarla o ejecutar Windows en un Linux. Hay otras herramientas de virtualización como QEMU y KVM pero la virtud de VirtualBox es que es muy sencilla y está disponible para Windows, GNU/Linux y macOS.

VirtualBox

Para tener una máquina virtual el proceso se puede hacer desde el principio desde el medio de instalación ofrecido siguiendo los de su instalador. Pero para hacer alguna prueba de desarrollo y si se necesitan virtualizar varias máquinas el proceso manual es incómodo además de repetitivo. Vagrant es una de las herramientas ofrecidas por HashiCorp que permite automatizar la creación y aprovisionamiento de máquinas virtuales en VirtualBox mediante la especificación de un archivo de configuración. Permite replicar entornos y crear un cluster de máquinas que resulta muy útil al desarrollar o probar cierto software.

En este ejemplo se configura una máquina virtual usando como sistema operativo base Ubuntu 18.04, y se aprovisiona configurando ella Docker. El aprovisionamiento se realiza mediante una serie de comandos y archivos que se añaden del host al sistema virtualizado tal como se hace en un sistema Ubuntu desde su estado de instalación inicial. Entre las opciones de configuración permitidas están el nombre de la máquina virtual, su sistema operativo, la cantidad de memoria que se le asigna, propiedades de red, asignar direcciones IP estáticas, …

 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
#-*-mode:ruby-*-#vi:setft=ruby:Vagrant.configure("2")do|config|config.vm.define"ubuntu-docker"do|instance|instance.vm.box="ubuntu/bionic64"instance.vm.provider:virtualboxdo|vb|vb.name="Ubuntu Docker (Vagrant)"endinstance.vm.network"private_network",ip:"192.168.33.10"instance.vm.provider"virtualbox"do|vb|vb.memory="1024"instance.vm.provision"file",source:"docker-compose.yml",destination:"/home/vagrant/docker-compose.yml"instance.vm.provision"shell",inline:$docker_role_scriptendend$docker_role_script=<<-SCRIPTecho"Updating..."sudoapt-getupdatesudoapt-getupgradeecho"Installing docker..."sudoapt-getinstall-yapt-transport-httpsca-certificatescurlgnupg-agentsoftware-properties-commoncurl-fsSLhttps://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudoapt-keyfingerprint0EBFCD88sudoadd-apt-repository"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"sudoapt-getupdatesudoapt-getinstall-ydocker-cedocker-ce-clicontainerd.iosudousermod-aGdockervagrantsudocurl-L"https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)"-o/usr/local/bin/docker-composesudochmod+x/usr/local/bin/docker-composeecho"Starting Docker service..."sudosystemctldaemon-reloadsudosystemctlenabledocker.servicesudosystemctlstartdocker.serviceSCRIPTend

Se puede crear un archivo inicia con comentarios para empezar a configurar la máquina virtual.

1
2
$ vagrant init ubuntu/bionic64

Definido el archivo de configuración para Vagrant se inician las máquina virtual con un comando. Y se detienen con otro. Si hay necesidad en el mismo archivo se pueden definir varias máquinas virtuales.

1
2
$ vagrant up
$ vagrant halt
VirtualBox Vagrant VM

Una vez iniciada la máquina virtual Vagrant configura SSH para tener acceso a su terminal, hay que especificar el nombre de la máquina virtual.

1
2
$ vagrant ssh ubuntu-docker
Vagrant SSH

La máquina en el ejemplo ha sido aprovisionada con Docker mediante un script con los comandos para instalarlo y un archivo de Docker Compose con un servicio del servidor web nginx. Desde la terminal de la máquina virtual se inicia el servicio con Docker que queda accesible tanto desde la pripia máquina virtual como desde el host indicando la dirección IP que se le ha asignado.

docker-compose up y curl desde la MV y desde el host

Vagrant tiene un repositorio de imágenes entre las que elegir para el sistema, están las más populares como Ubuntu, Fedora, Debian y CentOS. Es un repositorio en donde los usuarios pueden subir sus propias imágenes aunque por defecto es mejor usar las oficiales de cada sistema.

Posee varias páginas de documentación bastante completas donde conocer los todos los detalles de uso de Vagrant.

Israel Perales: Sonarqube

$
0
0
Sonarqube

Después de escuchar mucho sobre la agilidad y medir la calidad del código en muchos blogs, comencé a preguntar a amigos como revisaban sus proyectos.

La mayoría no lo hacían por que en sus empresas tienen un departamento de 'QA', a lo que yo les recomendé usar FindBugs , un plugin sencillo para Eclipse que conocí allí por el 2014 y que me salvo de muchas.

Al instalarlo y correrlo por primera vez me di cuenta de la falta que me hacia, ¿solo se media la calidad por los bugs?, ¿no había otra cosa que medir? , cuanto mas ví el termino deuda técnica en los blogs, sentí que algo estaba mal en mi código.

Creo que fue en este post donde encontré algo llamado Sonar.

Su nombre completo es Sonarqube, es un Software Open Source que te ayuda a medir la calidad del código, donde combina FindBugs con otros proyectos para medir prácticamente TODO y te lo da en una amigable interfaz web a modo de reportes.

¿Como se instala?

Al ser una aplicación cliente/servidor se necesita una base de datos SQL Server / Oracle / PostgreSQL y debemos blablabla ...
Puff! las instalaciones son tediosas, puedes irte a la antigua instalando base de datos desde 0 , pero en estos años, ¿quien no tiene Docker instalado en su maquina ?.

Instala Docker y docker-compose

Una vez hecho esto, debemos crear un directorio llamado Sonarqube y el archivo docker-compose.yml

version: "2"

services:

  sonarqube:
    image: sonarqube:latest
    ports:
      - "9000:9000"
    networks:
      - sonarnett
    environment:
      - SONARQUBE_JDBC_URL=jdbc:postgresql://db:5432/sonar
    volumes:
      - sonarqube_conf:/opt/sonarqube/conf
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_bundled-plugins:/opt/sonarqube/lib/bundled-plugins

  db:
    image: postgres:9.6
    mem_limit: 256m
    networks:
      - sonarnett
    environment:
      - POSTGRES_USER=sonar
      - POSTGRES_PASSWORD=sonar
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

networks:
  sonarnett:
    driver: bridge
  default:
    driver: bridge

volumes:
  sonarqube_conf:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_bundled-plugins:
  postgresql:
  postgresql_data:

Así se ve la salida de tree:

perales@linux-h4ne:~/Escritorio> tree sonarqube/
sonarqube/
└── docker-compose.yml

0 directories, 1 file
perales@linux-h4ne:~/Escritorio> 

Y ejecutamos docker-compose up

Veremos algo así:

Creating network "sonarqube_sonarnett" with driver "bridge"
Creating volume "sonarqube_sonarqube_conf" with default driver
Creating volume "sonarqube_sonarqube_data" with default driver
Creating volume "sonarqube_sonarqube_extensions" with default driver
Creating volume "sonarqube_sonarqube_bundled-plugins" with default driver
Creating volume "sonarqube_postgresql" with default driver
Creating volume "sonarqube_postgresql_data" with default driver
Pulling sonarqube (sonarqube:latest)...
latest: Pulling from library/sonarqube
e79bb959ec00: Pull complete
d4b7902036fe: Pull complete
1b2a72d4e030: Pull complete
d54db43011fd: Pull complete
1a97c78dad71: Pull complete
6dcb79eeeda4: Pull complete
bd56246cf4fd: Pull complete
88cea60f56c5: Pull complete
7ed4501359c8: Pull complete
4bbf191118e2: Pull complete
11032ed7b930: Pull complete
c59744850b29: Pull complete
Digest: sha256:7e3086215d86430bb1bde555d755605e351f59ed7fa721aaff0d7478b665086a
Status: Downloaded newer image for sonarqube:latest
Pulling db (postgres:9.6)...
9.6: Pulling from library/postgres
27833a3ba0a5: Already exists
ed00742830a6: Already exists
dc611c2aceba: Already exists
a61becab5279: Already exists
8dcff41e7aea: Already exists
820bf1bbf0d7: Already exists
050804429905: Already exists
782c81275334: Already exists
89de6f9e489b: Pull complete
6fd7948190c1: Pull complete
2b248dfe0f9f: Pull complete
2bb82f5734a9: Pull complete
6165fd6c366d: Pull complete
b91f3b4e0a77: Pull complete
Digest: sha256:ee723e25ac72edaede1c7209c33f295b56875bdee62fa08f913117c2fbca9287
Status: Downloaded newer image for postgres:9.6
Creating sonarqube_db_1        ... done
Creating sonarqube_sonarqube_1 ... done
Attaching to sonarqube_db_1, sonarqube_sonarqube_1

Al terminar el log de la consola entramos a la liga http://localhost:9000 y podremos ver la interfaz web.

Para loguearnos usamos admin, admin.

Sonarqube

Sonarqube

Sonarqube

¿Como se usa?

Sonar esta vació, ¿como lo usamos?, la dinámica a seguir es la siguiente:

  • Tenemos un código fuente.

  • Agregamos un archivo de configuración de sonar(sonar-project.properties).

  • Generamos un token de autentificación y lo agregamos a la configuración.

  • Ejecutamos el analizador de código de nuestra preferencia.

Analicemos un proyecto de ejemplo:

Clonamos el repo

git clone https://github.com/ripper2hl/fuego.git

Agregamos el archivo sonar-project.properties

cd fuego/

touch sonar-project.properties

Aquí la documentación de como configurar lo

https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner

Así debería de quedarnos:

sonar.projectName=fuego
sonar.projectKey=com.perales:fuego
sonar.host.url=http://localhost:9000
sonar.sources=src/
sonar.sourceEncoding=UTF-8

Para generar el token debemos ir a My Account que se encuentra en la parte superior derecha y después a la pestaña de Security, esta es la liga http://localhost:9000/account/security/ .

El token se agrega al archivo de propiedades como sonar.login

sonar.projectName=fuego
sonar.projectKey=com.perales:fuego
sonar.host.url=http://localhost:9000
sonar.sources=src/
sonar.sourceEncoding=UTF-8
sonar.login=1d5a5466cec2a33ead727a2082454e25b9cdef49

Ahora debemos ejecutar un Sonar Scanner , de los cuales existen diferentes tipos, para java existe uno que se combina con maven , pero nosotros usaremos un binario de propósito general, el cual funciona para cualquier lenguaje de programación.

En esta liga puedes descargar los binarios, GNU/Linux, Mac OS o Windows

https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner

En GNU/Linux creamos un enlace simbólico para poder ejecutarlo desde terminal

ln -s ~/Descargas/sonar-scanner-3.3.0.1492-linux/bin/sonar-scanner ~/.local/bin/sonar-scanner

Si ejecutamos sonar-scanner --version nos debe mostrar esto:

sonar-scanner --version
INFO: Scanner configuration file: /home/perales/Descargas/sonar-scanner-3.3.0.1492-linux/conf/sonar-scanner.properties
INFO: Project root configuration file: NONE
INFO: SonarQube Scanner 3.3.0.1492
INFO: Java 1.8.0_121 Oracle Corporation (64-bit)
INFO: Linux 5.0.11-1-default amd64

Para analizar nuestro proyecto , solamente ejecutamos sonar-scanner en la raíz del proyecto y la terminal mostrara lo siguiente:

INFO: Scanner configuration file: /home/perales/Descargas/sonar-scanner-3.3.0.1492-linux/conf/sonar-scanner.properties
INFO: Project root configuration file: /home/perales/Documentos/git/fuego/sonar-project.properties
INFO: SonarQube Scanner 3.3.0.1492
INFO: Java 1.8.0_121 Oracle Corporation (64-bit)
INFO: Linux 5.0.11-1-default amd64
INFO: User cache: /home/perales/.sonar/cache
INFO: SonarQube server 7.7.0
INFO: Default locale: "es_MX", source code encoding: "UTF-8"
INFO: Load global settings
INFO: Load global settings (done) | time=275ms
INFO: Server id: 243B8A4D-AWqQ27WJuNHxbkg6tnXs
INFO: User cache: /home/perales/.sonar/cache
INFO: Load/download plugins
INFO: Load plugins index
INFO: Load plugins index (done) | time=125ms
INFO: Load/download plugins (done) | time=4077ms
INFO: Process project properties
INFO: Execute project builders
INFO: Execute project builders (done) | time=34ms
INFO: Project key: com.perales:fuego
INFO: Base dir: /home/perales/Documentos/git/fuego
INFO: Working dir: /home/perales/Documentos/git/fuego/.scannerwork
INFO: Load project settings for component key: 'com.perales:fuego'
INFO: Load project repositories
INFO: Load project repositories (done) | time=32ms
INFO: Load quality profiles
INFO: Load quality profiles (done) | time=305ms
INFO: Load active rules
INFO: Load active rules (done) | time=6580ms
INFO: Indexing files...
INFO: Project configuration:
INFO: 6 files indexed
INFO: 0 files ignored because of scm ignore settings
INFO: Quality profile for js: Sonar way
INFO: Quality profile for web: Sonar way
INFO: ------------- Run sensors on module fuego
INFO: Load metrics repository
INFO: Load metrics repository (done) | time=200ms
INFO: Sensor JaCoCo XML Report Importer [jacoco]
INFO: Sensor JaCoCo XML Report Importer [jacoco] (done) | time=11ms
INFO: Sensor SonarJS [javascript]
INFO: 3 source files to be analyzed
INFO: 3/3 source files have been analyzed
INFO: Sensor SonarJS [javascript] (done) | time=2646ms
INFO: Sensor ESLint-based SonarJS [javascript]
INFO: 3 source files to be analyzed
INFO: Sensor ESLint-based SonarJS [javascript] (done) | time=9792ms
INFO: Sensor JavaXmlSensor [java]
INFO: 3/3 source files have been analyzed
INFO: Sensor JavaXmlSensor [java] (done) | time=20ms
INFO: Sensor HTML [web]
INFO: Sensor HTML [web] (done) | time=395ms
INFO: ------------- Run sensors on project
INFO: Sensor Zero Coverage Sensor
INFO: Sensor Zero Coverage Sensor (done) | time=115ms
INFO: SCM provider for this project is: git
INFO: 4 files to be analyzed
INFO: 4/4 files analyzed
INFO: Calculating CPD for 4 files
INFO: CPD calculation finished
INFO: Analysis report generated in 451ms, dir size=91 KB
INFO: Analysis report compressed in 165ms, zip size=21 KB
INFO: Analysis report uploaded in 1700ms
INFO: ANALYSIS SUCCESSFUL, you can browse http://localhost:9000/dashboard?id=com.perales%3Afuego
INFO: Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
INFO: More about the report processing at http://localhost:9000/api/ce/task?id=AWqie1fuYKZYNF3HzZ-O
INFO: Analysis total time: 36.800 s
INFO: ------------------------------------------------------------------------
INFO: EXECUTION SUCCESS
INFO: ------------------------------------------------------------------------
INFO: Total time: 44.311s
INFO: Final Memory: 18M/291M
INFO: ------------------------------------------------------------------------

Si vamos a la interfaz web, podremos ver los reportes generados.

Sonarqube

Sonarqube

Sonarqube

Sonarqube

Fuentes:

Blog Bitix: Rediseño de Blog Bitix con contenido centrado, más grande horizontalmente y publicidad lateral sticky

$
0
0

He cambiado ligeramente el diseño del blog con la intención de mejorar la experiencia de usuario haciendo que el contenido quede centrado en la pantalla en vez de estar desplazado un poco a la izquierda por un panel lateral. Pero más espacio horizontal para el contenido podría afectar negativamente al rendmiento de los banners de publicidad laterales si tenía que quitarlos con lo que he tenido que buscar una solución para ambos requerimientos. También he experimentado con la disposición de la publicidad, incluido un billboard y publicidad lateral sticky para que permanezca más tiempo visible y quizá mejorar su rendmiento.

Hugo

Hacía tiempo que como mejora para el diseño del blog quería que el contenido de los artículos estuviese centrado en la pantalla y tuviese más espacio horizontal, ambas cosas porque considero que mejoraría un poco la experiencia de lectura de los usuarios. Un panel a la derecha que ocupaba cierto espacio hacía que el contenido del artículo no estuviese centrado y quedase escorado un poco hacia la izquierda, además en ciertos artículos donde incluyo código por el espacio ocupaban requería de una barra de desplazamiento horizontal para ver la parte derecha de los listados.

Este deseo me obligaba a quitar el panel lateral donde tenía un banner de publicidad fijo estilo large-skycraper de 300px de ancho por 600px de alto junto a otro debajo large-rectangle de 336px por 280px, una imagen con un enlace de Yo apoyo al software libre, tu también con los artículos de donaciones que he hecho y un enlace al Archivo y hemeroteca. Pero perder esos banners de publicidad para dejar más espacio al contenido posiblemente afectaría al rendimiento de los anuncios.

Querer que el contenido estuviese centrado y ocupase más espacio horizontal y querer publicidad lateral competían entre sí así que tenía que buscar una solución que cumpliese ambos.

Diseño de Blog Bitix nates de hacer cambios

Hacer que el contenido estuviese centrado y ocupase más espacio horizontal obligaba poner la publicidad lateral más a la derecha, fuera del espacio del contenido central. Como mejora para esa publicidad lateral también quería que permaneciese visible aún haciendo desplazamiento vertical, ya que antes la publicidad siempre permanecía en la misma posición al inicio del artículo y se dejaba de ver en la parte inferior del artículo posiblemente quedando desaprovechada alguna oportunidad. Para hacer que la publicidad se desplace verticalmente he usado el posicionamiento sticky que ya soportan los navegadores. Con los primeros cambios el diseño queda como deseaba.

Primer diseño de Blog Bitix después de hacer algunos cambios

Sin embargo, hacer que el contenido ocupe todo el espacio horizontal de lo que tenía anteriormente y poner la publicidad más a la derecha requiere que los usuarios tengan una pantalla con suficientemente resolución para que quepa todo. Yo tengo una pantalla de resolución 2560x1440 y lo veía todo bien pero Google Analytics me indicaba que un porcentaje importante del los usuarios, un 32%, tienen una resolución habitual en los portátiles de 1366x768 píxeles. Con esa resolución los 1140 píxeles requeridos para el contenido más los 300 del banner horizontal en la parte derecha no entraba. También agravado si para algunas páginas deseaba en la parte lateral izquierda otro panel sticky de publicidad.

Mantener la publicidad lateral sticky hace que no entre un skycraper y un large-rectangle verticalmente en una resolución de 768px de alto de modo que he dejado solo un espacio para publicidad. Como idea para el futuro quizá haga que al llegar a cierto desplazamiento vertical se cambier el espacio de publicidad por otro.

Resolución de pantalla de los usuarios

Ocupando 1140 príxeles el contenido poco espacio queda en los laterales en una resolución de 1366 de ancho. La solución que he aplicado para poder poner publicidad en ambos laterales es reducir un poco el ancho para el contenido central, a 1080px, y que la publicidad lateral se adapte al espacio que queda, no entran banners de 300px en los laterales pero si dos skycraper de 120px de ancho al menos, con la que en buena medida la experiencia de usuario se mantiene igual que en resoluciones mayores.

La página es ahora un poco menos ancha que antes 1080px frente a 1140px pero el contenido ocupa más espacio horizontal, 1080px frente a unos 840px. Haciendo que la publicidad lateral se adapte al espacio restante según la resolución que quede me ha permitido cumplir los dos objetivos de hacer que el contenido quede centrado y con más espacio horizontal sin perder la publicidad lateral y haciendo que esta permanezca visible en la pantalla aún con desplazamiento vertical y en ambos laterales en algunas páginas.

En resoluciones menores de 1366 he optado por quitar completamente la publicidad lateral ya que aún aplicando un diseño adaptativo en cualquier caso no aparecería en los laterales porque físicamente no hay espacio suficiente.

Así queda en las resoluciones habituales de 2560 píxeles, 1920, 1600, 1440, 1366 y 1200.

Diseño después de los cambios a diferentes resoluciones (2560, 1920, 1600, 1440, 1366 y 1200)

Padría hacer algún experimento o test a/b de que diseño resulta mejor si el anterior o el contenido más ancho y medirlo por el porcentaje de rebote, tiempo medio de permanencia en la página o retorno de usuarios pero dudo que esas métricas fuesen suficientemente buenas como para determinar que un diseño sea mejor que otro, es dedicar un tiempo y esfuerzo a medir algo que considero claramente es mejor. En lo que si podría hacer algún experimento es medir que disposición de anuncios, en ubicación y tamaño da mejor resultado pero prefiero dedicar el tiempo a escribir artículos, no creo que los cambios que he hecho en los anuncios impacten muy negativamente o quiza se compense con la posición sticky de los banners laterales.

También he introducirdo un bannerbillboard en la cabecera de la página a ver que tal resultado da, el leaderboard del inicio de los artículos tenía buen rendimiento aún siendo bastante pequeño, lo he mantenido en los artículos que incluyo un resumen. En los que no tienen un resumen lo he sustituido por el billboard ya que mantener ambos me resutlaba un exceso de publicidad. Dentro de unos meses compararé con los anteriores o del año pasado si los cambios que he hecho dan buen resultado.

Viewing all 2711 articles
Browse latest View live