Weblogs Código

Variable not found

AddMvc(), AddControllers(), AddControllersWithViews(), AddRazorPages()... ¿qué es todo eso?

marzo 31, 2020 06:05

ASP.NET Core Como sabemos, hasta ASP.NET Core 2.2, registrábamos los servicios de MVC y Razor Pages en el método ConfigureServices() de la clase Startup con una línea como la siguiente:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
...
}
Esto era todo lo que necesitábamos para poder utilizar cualquiera de estas tecnologías en nuestra aplicación. Sin embargo, a partir de ASP.NET Core 3.0, nuestro intellisense se vio inundado de opciones adicionales como AddControllers(), AddRazorPages() AddControllersWithViews().

¿Para qué sirven, y por qué ha sido necesario introducir estos nuevos extensores?

En busca de la modularidad

Desde sus comienzos, ASP.NET Core fue presentado como un framework modular, huyendo de monolitos como ASP.NET "clásico", en el que todas las funcionalidades estaban incrustadas en System.Web y no había forma de utilizarlas de forma separada.

Sin embargo, la forma de registrar los servicios que hemos visto arriba, llamando a services.AddMvc(), no parecía demasiado alineada con esa intención inicial. Esta llamada registraba todos los servicios relativos a la gestión de vistas MVC, así como a las páginas Razor. Aunque nuestra aplicación fuera únicamente en una API y no tuviese vistas, estábamos registrando bastantes servicios que no íbamos a necesitar.

Lo mismo ocurría si nuestra aplicación era MVC puro. Estábamos registrando servicios de Razor Pages que tampoco necesitaríamos. Y viceversa, en aplicaciones en cuya presentación usábamos únicamente páginas Razor estábamos introduciendo servicios propios de MVC.

Este aspecto es el que se solventa mediante la inclusión de esos nuevos extensores de IServiceCollection.
  • AddControllers() registra los servicios absolutamente necesarios para que funcionen los controladores, y nada relativo a vistas o páginas Razor. Esto incluye los servicios de autorización, soporte para formateadores, CORS, anotaciones de datos, el application model, mecanismos de selección de acciones, servicios de binding, etc.
    Este el método de registro ideal para backends que sólo actúan como API.
  • AddControllersWithViews() registra adicionalmente los servicios requeridos para poder ejecutar vistas MVC. Aparte de los servicios añadidos por AddControllers(), registra el soporte para vistas, helpers, view engines, Razor y algunos tag helpers básicos.
    Este es el extensor que debemos elegir si nuestra aplicación utiliza todas las piezas del MVC "tradicional".
  • AddRazorPages() introduce en el contenedor de dependencias los servicios necesarios para ejecutar Razor Pages, pero no vistas MVC. Curiosamente, también registra los servicios de controladores porque internamente Razor Pages está montado sobre MVC y se basa en algunas de sus funcionalidades.
    Es la opción ideal si vamos a programar las páginas de nuestro sitio web utilizando esta tecnología.
Estos métodos pueden combinarse de forma que sumemos posibilidades. Por ejemplo, el siguiente código registraría los servicios para una aplicación en la que utilizaremos vistas MVC y Razor Pages:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddRazorPages();
...
}
Por último, es importante saber que AddMvc() sigue funcionando como en versiones anteriores. De hecho, internamente llama consecutivamente a AddControllersWithViews() y a AddRazorPages(), por lo que sería equivalente al código anterior. Milagros de la retrocompatibilidad ;)

Publicado en Variable not found.

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

Poesía Binaria

Crear múltiples direcciones de correo desde el CLI de Plesk

marzo 30, 2020 08:35

Muchos usuarios disponen de un panel de control Plesk. Que, aunque no es libre, hoy vamos a hablar de él. Dicho panel de control, proporciona una forma sencilla de gestionar dominios, páginas web y servicios asociados, como el correo, desde su interfaz web. Lo que puede que no sea tan conocido es que dispone de una serie de herramientas en línea de comandos que nos pueden hacer la vida mucho más fácil.

Para utilizar estos consejos, es necesario tener acceso SSH a nuestro servidor.

Crear una dirección de e-mail

Para crear una dirección de correo con su buzón y todo, podemos utilizar la siguiente orden:

plesk bin mail -c email@dominio

Podemos crear una dirección de correo para cualquier dominio que tengamos administrado en nuestro servidor. Y también podemos especificar algunos parámetros en su creación como:
  • -mailbox true: Para activar el buzón de correo.
  • -passwd [password]: Para especificar la contraseña.
  • -mbox_quota 5G: Para especificar el espacio del buzón de correo (5GB en el ejemplo).
  • -description «Descripción»: Para especificar una descripción que veremos en el panel Plesk.
  • -manage-virusfilter true: Para activar el filtro antivirus.
  • -manage-spamfilter true: Para activar el filtro antispam.
  • -antivirus inout: Para configurar antivirus tanto de entrada como de salida.
  • -outgoing-messages-mbox-limit 100: Para configurar el número de mensajes que pueden enviarse cada hora.

Hay alguna opción más dependiendo de la versión de Plesk, como especificar grupos de correo, alias, etc. Podemos obtener el listado completo de opciones con el argumento –help. Por lo que finalmente podemos tener algo como esto:

plesk bin mail -c yo@midominio.com -mailbox true -passwd ‘D[8NoBj#3f’ -mbox_quota 5G -description ‘Mail principal’ -outgoing-messages-mbox-limit 150

Puede parecer algo complicado, aunque lo realmente bueno viene dentro de algunas líneas.

Modificar o Eliminar correos

Desde aquí también podemos modificar direcciones de correo, con las mismas opciones que antes, pero con el argumento -u. Por ejemplo, para cambiar una contraseña podemos:

plesk bin mail -u yo@midominio.com -passwd ‘clavesencilla’

Y si finalmente queremos eliminar el correo,

plesk bin mail -r yo@midominio.com

Crear múltiples direcciones de correo

Si queremos crear múltiples direcciones de correo, y esto es lo bueno, no tenemos que andar con la interfaz web, dirección a dirección. Basta con crear un bucle en Bash y llamar a la orden anterior múltiples veces.
Esto puede pasar cuando necesitemos migrar direcciones de correo de un servidor a otro, puede que unas pocas direcciones sean fáciles de crear en la interfaz web, pero cuando hablamos de decenas, o cientos, nos compensa más invertir un poco de tiempo en un pequeño script y ejecutarlo, y las tendremos todas generadas.
Podemos hacerlo de forma muy sencilla, desde la propia línea de comandos:

1
2
3
for direccion in info persona1 persona2 persona3; do
  plesk bin mail -c $direccion@midominio.com -mbox_quota 5G -passwd 'ClaveFacil'
done

Con esto crearemos los e-mails info@midominio.com , persona1@midominio.com , persona2@midominio.com y persona3@midominio.com con la misma cuota de buzón de correo y la misma contraseña.

Aunque si queremos, podemos ir un paso más allá, creando un fichero de texto (direcciones.list) donde guardamos todos los nombres de usuario, uno por línea, pueden ser cientos o miles (ya será la capacidad de nuestro servidor el límite).

Tras eso creamos el siguiente script:

1
2
3
4
5
6
7
8
#!/bin/bash
while IFS= read -r line
do
  password=$(pwgen -1 -y 12)
  email="$line@midominio.com"
  plesk bin mail -c "$email" -mbox_quota 5G -passwd "$password" -mailbox true -antivirus inout
  echo "$email:$password" >> passwords
done < direcciones.list

De esta manera podremos configurar un montón de direcciones de correo con las mismas características. Incluso de dominios diferentes si introducimos el dominio en el archivo direcciones.list y lo eliminamos de la variable $email. Las contraseñas, como vemos, las genero con la herramienta pwgen, aunque también podemos dejarlas fijas o utilizar cualquier otro método de generación de contraseñas.

Por otro lado, si ahora queremos modificar la cuota de espacio en disco de múltiples correos a la vez podremos:

1
2
3
4
5
6
#!/bin/bash
while IFS= read -r line
do
  email="$line@midominio.com"
  plesk bin mail -u "$email" -mbox_quota 2G
done < direcciones.list

Incluso utilizar el modificador -r para eliminar varias direcciones de correo a la vez.

Por cierto, es buena idea, una vez que tengamos los correos generados en el sistema, guardar en lugar seguro el fichero de contraseñas, al menos hasta que hayamos asignado las contraseñas a cada uno de los usuarios y en ese momento eliminarlo permanentemente.

Foto principal: unsplash-logoCarol Jeng

Binarideas logo

¿Necesitas un sysadmin?

Si te ha gustado el post y encuentras interesante lo que cuento en materia de sistemas. O si necesitas gestionar un servidor (o muchos), automatizar procesos o mejorar la calidad de los procesos actuales. No dudes en ponerte en contacto conmigo.

The post Crear múltiples direcciones de correo desde el CLI de Plesk appeared first on Poesía Binaria.

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

Variable not found

Enlaces interesantes 397

marzo 30, 2020 06:05

Enlaces interesantesPues otra semana de encierro, y otro buen número de enlaces recopilados que, como de costumbre, espero que os resulten interesantes.

¡Cuidaos mucho!

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

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

Blog Bitix

Uso de un paquete npm con Webpack creado con Storybook, React y TypeScript

marzo 29, 2020 01:00

TypeScript

html.svg

En el ejemplo Desarrollar componentes React con TypeScript y sistemas de diseño con Storybook mostraba cómo desarrollar componentes React con TypeScript y de forma aislada con Storybook junto con sus pruebas unitarias y visuales con Jest. El resultado de ese proyecto es un paquete npm a instalar y usar en otros proyectos como este.

Un paquete npm es un archivo comprimido que se instala como dependencia en un proyecto. En este ejemplo se usa el paquete directamente, utilizar un repositorio de paquetes facilita el uso y distribución de los paquetes a los proyectos que los usen y esta es la forma que se debe utilizar en un proyecto real.

Este comando crea la estructura inicial de archivos y carpetas de un proyecto JavaScript que use React y TypeScript. Sobre esta base he creado el archivo de configuración para Webpack, eliminado la dependencia de react-script y creada la tarea start en el archivo package.json.

1
$ npx create-react-app app --template typescript
create-react.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{
  "name": "app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.4.1",
    "@testing-library/user-event": "^7.2.1",
    "@types/jest": "^24.9.1",
    "@types/node": "^12.12.29",
    "@types/react": "^16.9.23",
    "@types/react-dom": "^16.9.5",
    "react": "^16.13.0",
    "react-dom": "^16.13.0",
    "typescript": "^3.7.5"
  },
  "devDependencies": {
    "css-loader": "^3.4.2",
    "less": "^3.11.1",
    "less-loader": "^5.0.0",
    "storybook": "file:storybook-1.0.0.tgz",
    "style-loader": "^1.1.3",
    "ts-loader": "^6.2.2",
    "webpack": "^4.42.1",
    "webpack-cli": "^3.3.11"
  },
  "scripts": {
    "start": "webpack-dev-server --mode development",
    "build": "webpack"    
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
package.json

El paquete npm del proyecto en el que se desarrolla el componente de ejemplo se instala con el siguiente comando.

1
$ npm install --save-dev storybook-1.0.0.tgz
npm-install-package.sh

Usando el gestor de módulos Webpack en un proyecto se puede hacer referencia a los componentes de los paquetes instalados y generar como resultado los archivos distribuibles que son los que realmente se enviarán al cliente. En Webpack se indica un punto de partida y todas las referencias necesarias a otros módulos se empaquetan. En este caso se hace referencia a un archivo JavaScript que hace tiene un uso del componente del paquete npm del componente ejemplo.

Lo primero es instalar el gestor de módulos Webpack y crear se archivo de configuración. En esta configuración se indica el directorio donde se generará el resultado del empaquetado de cada uno de los puntos de entrada y también la configuración para el servidor de desarrollo. En el servidor de desarrollo Webpack hace de servidor web que sirve los archivos procesados y un directorio público donde los html pueden hacer referencia a ellos como en el caso de index.html.

1
2
$ npm install --save-dev webpack webpack-cli
$ npm install --save-dev style-loader css-loader ts-loader less-loader
npm-install-webpack.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const path = require("path");
const webpack = require("webpack");

module.exports = {
  mode: 'development',
  entry: {
    index: './src/index.tsx'
  },
  output: {
    filename: '[name].js',
    path: __dirname + '/build/webpack/'
  },
  module: {
    rules: [
      { test: /\.(ts|tsx)$/, use: 'ts-loader' },
      { test: /\.(css|less)$/, use: [
        { loader: 'style-loader' },
        { loader: 'css-loader' },
        { loader: 'less-loader' }
      ]}
    ]
  },
  resolve: {
    extensions: ['.js', '.ts', '.tsx']
  },
  devServer: {
    contentBase: path.join(__dirname, "public"),
    port: 3000,
    publicPath: "/",
    hotOnly: true
  }
};
webpack.config.js

El archivo index.tsx sería un punto de entrada para Webpack, en el se importa el componente App y se incluye en la página.

1
2
3
4
5
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));
index.tsx
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import React from 'react';
import HelloWorld from 'storybook/components/HelloWorld';

import './App.css';

function App() {
  return (
    <div className="App">
      <HelloWorld />
    </div>
  );
}

export default App;
App.tsx

El archivo index.html permite probar la página con el componente incluído.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>React App</title>
    <script type="text/javascript" src="index.js" defer="defer"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
index.html

En el directorio indicado en la configuración de Webpack se generan los archivos de resultado. En el están los archivos .map para depurar en javascript y los .d.ts con definiciones de tipos de TypeScript para archivos JavaScript.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ tree build/
build/
├── typescript
│   ├── App.d.ts
│   ├── App.js
│   ├── App.js.map
│   ├── index.d.ts
│   ├── index.js
│   └── index.js.map
└── webpack
    └── index.js

2 directories, 7 files
tree-build.out

En la página de prueba que hace uso del JavaScript producido por Webpack se carga el componente del paquete npm desarrollado en otro proyecto haciendo uso de Storybook.

Componente desarrollado en Storybook en una página

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando npm install && npm run start.

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

Variable not found

¿Qué es Blazor, eso de lo que todo el mundo habla?

marzo 27, 2020 09:14

Blazor Recuerdo que la primera vez que, en una de mis visitas a Redmond, vi a Steve Sanderson haciendo una demo de Blazor "en vivo" me pareció casi magia: una aplicación web SPA cuya lógica de presentación y UI se implementaba exclusivamente en C#, eliminando por completo de la ecuación a Javascript. ¡Uau!

En aquellos tiempos era aún poco más que un prototipo y no se podía saber si aquella línea de investigación llevaría a alguna parte, pero era tan prometedor que, algún tiempo después, a primeros de 2018, Blazor pasó a ser un proyecto experimental del equipo ASP.NET de Microsoft.

A partir de ese momento se fueron despejando las incógnitas que quedaban en cuanto a la viabilidad real de convertir aquello en un producto y se continuaron implementando funcionalidades, hasta que, en abril de 2019, Daniel Roth anunció que daban por concluida la fase experimental y Blazor entraba oficialmente a formar parte del stack de tecnologías para la web.

A día de hoy, Blazor es un framework completo para el desarrollo de aplicaciones web SPA, como lo pueden ser Angular, React o Vue. Dispone de sistema de bindings, routing, componentes, ciclo de vida, validaciones, plantillas, gestión de errores, inyección de dependencias, etc. Todo lo que podemos necesitar para crear aplicaciones web profesionales de calidad.

La primera versión de Blazor se lanzó acompañando a .NET Core 3.0 en Septiembre de 2019, aunque sólo en uno de sus modelos de hospedado: el denominado server-side o Blazor Server.

Pero no era ese el único plan...

Los "sabores" de Blazor: client-side, server-side... y otros

Inicialmente, la idea de Blazor era conseguir ejecutar el código C# en el browser llevando a éste una versión deducida del runtime de .NET. Este enfoque suele conocerse como Blazor WebAssembly o client-side, pues se basa en llevar físicamente al browser la ejecución de la parte cliente de la aplicación web.

WebAssemblyLa clave del funcionamiento de Blazor client-side es un runtime de .NET que corre sobre WebAssembly (WASM), una especificación abierta que define un "lenguaje ensamblador" común a todas las máquinas virtuales presentes en los navegadores modernos (y pollyfillable para los anteriores). Sobre ese runtime, ya en el lado cliente, es donde corren nuestras aplicaciones Blazor cuando elegimos este modelo de hospedaje.

Así, la primera vez que un usuario accede a una aplicación Blazor WebAssembly, su navegador descarga tanto el runtime de .NET como los ensamblados propios de la aplicación (compilados desde C# en tiempo de desarrollo) y sus dependencias. Ya tras esta descarga inicial, que gracias al caché de los navegadores se realizaría sólo una vez, los ensamblados de la aplicación serían ejecutados por el runtime directamente desde el navegador.

Un detalle importante a tener en cuenta es que el hecho de que el código cliente de nuestras aplicaciones lo desarrollemos usando .NET no significa que estemos llevando al cliente todo el potencial de esta plataforma. Dado que este código se ejecuta en cliente, al igual que cuando programamos con Javascript, estamos limitados por el sandbox proporcionado por el navegador para la ejecución segura de aplicaciones. Es decir, no podremos acceder a dispositivos o recursos locales más allá de los permitidos por las APIs estándar de los navegadores.

Por otra parte, como la ejecución se realiza puramente en cliente, no sería estrictamente necesario disponer de un servidor ASP.NET, IIS ni nada parecido. Tanto el runtime como los ensamblados resultantes de la compilación podrían ser distribuidos en un CDN, y es lo único que se necesitaría para poner en marcha la aplicación (bueno, a no ser que ésta necesite de un backend, claro.)

¿Y qué ventajas tiene esto? Pues bueno, ya el simple hecho de sustituir Javascript por C# para implementar el lado cliente puede aportarnos ventajas interesantes. Aparte de que C# es un lenguaje muy superior, también será posible utilizar o reutilizar bibliotecas .NET existentes, compartir código entre cliente y servidor, o utilizar entornos tan potentes como Visual Studio durante la fase de desarrollo.

Pero como siempre ocurre, no es la panacea; también provocará que la carga inicial de la aplicación sea más lenta (aunque, como decíamos, la caché podrá ayudarnos con esto), o requerirá que los clientes sean compatibles con WASM (aunque esto ya no es un problema en la mayoría de los casos).
Y bueno, de alguna forma nos alejaremos del ecosistema Javascript, que ya sabemos que es enorme, aunque también es cierto que desde Blazor es posible interoperar con bibliotecas JS existentes.
En la actualidad, Blazor WebAssembly sólo está disponible en preview, y está previsto lanzar la primera versión oficial en mayo de 2020 (aunque ésta aún no será LTS).
Como comentaba algo más arriba, el enfoque client-side de Blazor fue el origen del proyecto y sigue siendo el objetivo principal del mismo. Sin embargo, durante la fase de desarrollo aparecieron otros escenarios de uso de Blazor que podrían resultar interesantes y entraron a formar parte del Roadmap del producto, modificando los planes iniciales:

Blazor Roadmap

En la ilustración anterior podemos observar que el Roadmap posicionó Blazor Server como el primer hito del conjunto de tecnologías Blazor. De hecho, es el único framework del que tenemos a día de hoy una versión estable, production ready y con soporte a largo plazo.

Blazor Server parte del mismo objetivo que Blazor WebAssembly respecto a utilizar C# para la implementación de la lógica de presentación, aunque el enfoque que sigue es bastante diferente. En este caso no se trata de llevar todo al cliente sino al contrario: hacer que todo se ejecute en el servidor, comunicándose con el browser mediante una conexión persistente de SignalR:

Blazor WebAssembly vs Blazor Server

Para conseguirlo, el servidor mantiene en memoria una representación del estado de la página, que es modificada por mensajes SignalR que son enviados cuando se producen cambios o interacciones en la interfaz de usuario.

Por ejemplo, si un usuario pulsa un botón en la página, el evento "click" no es procesado en cliente sino enviado a través de la conexión SignalR al servidor, que ejecutará el handler oportuno (escrito en C#). Los cambios realizados en el UI desde dicho handler son enviados de vuelta al cliente, que actualizará el DOM de forma automática.
Pues sí, en cierto modo es muy similar a lo que utilizábamos en Web Forms años atrás, pero eliminando la pesadez del postback, la carga de página completa y el mantenimiento del estado mediante el célebre viewstate. De hecho, Blazor Server es la tecnología propuesta por Microsoft para sustituir a Web Forms, al promover un tipo de desarrollo conceptualmente muy parecido.
Como siempre ocurre, la idea tiene sus ventajas e inconvenientes. Como parte positiva respecto al modelo client-side, en este caso la carga inicial sería más rápida porque no hay nada que descargar desde el cliente. También, dado que el código se ejecutaría en el servidor, podríamos utilizar todo tipo de bibliotecas y componentes para .NET, así como las herramientas habituales de desarrollo y depuración. Y para casos de necesidad, este enfoque conseguiría casi el soporte universal por parte de los browsers, pues no requiere ninguna característica especial en el lado cliente.

Pero ah, amigos, todo esto no es gratis. El hecho de que cada interacción o evento en el UI deba ser enviado y procesado en el servidor añade una latencia que no todas las aplicaciones podrán soportar sin destrozar su usabilidad. Además, las conexiones concurrentes y el mantenimiento del estado en memoria podrían limitar las opciones de escalabilidad de los sistemas.

La buena noticia es que el modelo de componentes, y por tanto la forma de desarrollar, es la misma. De esta forma, podríamos iniciar un proyecto utilizando Blazor Server y migrar a Blazor WebAssembly más adelante con relativamente poco esfuerzo. Bueno, o al menos en teoría ;)

Pero aparte de Blazor Server y Blazor WebAssemby, y como veíamos en el Roadmap algo más arriba, el resto de tecnologías Blazor van acercándose consecutivamente a la ejecución nativa de aplicaciones, tanto desde dispositivos móviles como de escritorio:
  • Blazor PWA, para la construcción de Progressive Web Applications (PWA), webs que proporcionan una experiencia más cercana a las aplicaciones nativas al ser capaces de trabajar offline, recibir notificaciones push, o ser instaladas en los equipos de los usuarios. Sin embargo, la ejecución se realizará siempre sobre el sandbox proporcionado por el browser, por lo que tendremos las mismas limitaciones que si utilizáramos JS.
     
  • Blazor Hybrid, aplicaciones que correrán sobre Electron y renderizarán su UI utilizando tecnologías web sobre WebViews o motores de renderizado de webs. Dado que serán aplicaciones .NET puras, en este caso sí se podrá acceder a todos los recursos del equipo donde se instalen.
     
  • Blazor Native, que permitirá en el futuro utilizar el mismo modelo de programación para crear aplicaciones nativas puras, capaces de renderizarse utilizando componentes nativos de la plataforma de ejecución, fuera del mundo web. En la práctica, quiere decir que podríamos utilizar esta tecnología en lugar de Xamarin o React Native para implementar nuestras aplicaciones nativas.
Aunque muy sujeto a cambios, pero está previsto lanzar previews de PWA e Hybrid a finales de 2020, acompañando a .NET 5. La última referencia que he encontrado de Blazor Native es que está en fase experimental y, por tanto, no existen aún previsiones de disponibilidad.

Solución usando Mobile Blazor BindingsAparte, hace apenas un par de días han anunciado otro giro de tuerca: Mobile Blazor Binding. Este proyecto, aún en fase experimental, permite utilizar el modelo de programación de Blazor y la sintaxis Razor para la construcción de aplicaciones nativas Android e iOS, en la práctica algo así como un híbrido entre Blazor y Xamarin. El objetivo es acercar al mundo de las apps nativas a esa gran masa de desarrolladores web acostumbrados a Razor, y la verdad es que tiene bastante buena pinta.

Esto promete, ¿eh? 😉

¿Cómo se desarrolla con Blazor?

Por si no os lo habéis preguntado, el nombre Blazor procede de la unión de "Browser" y "Razor" (la "L" no sé de dónde sale, supongo que dará más musicalidad al resultado :-P), así que ya podéis intuir por dónde van los tiros: Razor que se ejecuta en el navegador.

De hecho, los componentes y páginas Blazor se implementan utilizando sintaxis Razor, que muchos ya conocéis de otras películas como ASP.NET Core MVC o Razor Pages, aunque con algunos cambios. Por ejemplo, en Blazor las páginas o componentes se implementan en archivos .razor, a diferencia de los clásicos .cshtml de MVC o Razor Pages.

Pero como las cosas se suelen entender mejor con código, mejor que echéis primero un vistazo al siguiente archivo Sum.razor:
@page "/sum"

<h1>Calculator</h1>
<div>
<input type="number" @bind-value="@A" />
+
<input type="number" @bind-value="@B" />
<button @onclick="Calculate">Calculate</button>
</div>
@if (Result != null)
{
<div>
The sum of @A and @B is @Result
</div>
}

@code {
public int? A { get; set; }
public int? B { get; set; }
public int? Result { get; set; }

void Calculate()
{
Result = A + B;
}
}
Calculadora BlazorEchando un vistazo puede entenderse que se trata de una simple calculadora para realizar sumas que incluye un par de tags <input> para solicitar los sumandos y un botón para realizar el cálculo. Al pulsarlo, se muestra el resultado de la suma en un <div> que inicialmente no está visible.

Este bloque de código es suficiente para entender la magia de Blazor:
  • En el bloque @code, implementado por completo en C#, definimos todas las propiedades y métodos que necesitemos para implementar la lógica de nuestra página. En este caso, tenemos un par de propiedades para los sumandos, otra para el resultado, y un método que realiza el cálculo.
    @code {
    public int? A { get; set; }
    public int? B { get; set; }
    public int? Result { get; set; }

    void Calculate()
    {
    Result = A + B;
    }
    }
  • Utilizamos bindings sobre los controles HTML, muy al estilo de otros frameworks basados en MVVM, como Angular o React. En el ejemplo podemos ver que bindeamos el valor de los <input> a las propiedades definidas en el bloque @code, de la misma forma que establecemos el manejador del evento click al método Calculate().
    <input type="number" @bind-value="@A" />
    +
    <input type="number" @bind-value="@B" />
    <button @onclick="Calculate">Calculate</button>
  • Dado que usamos sintaxis Razor, es posible utilizar los habituales bloques de control de flujo como @if, @foreach, etc. En el caso anterior, usamos un condicional para añadir a la página el <div> con el resultado, pero sólo cuando hemos calculado la suma.
    @if (Result != null)
    {
    <div>
    The sum of @A and @B is @Result
    </div>
    }
Fijaos que hemos eliminado de un plumazo a Javascript, y la web funcionará sin cambiar de página, muy al estilo SPA:
  • Si hemos optado por utilizar el host Blazor Server, todos los eventos e interacciones sobre el UI (cambios de valores en controles bindeados, clicks, etc.) serán enviados al servidor a través de la conexión SignalR establecida automáticamente. El servidor ejecutará el código y enviará de vuelta al cliente, usando la misma conexión, las modificaciones a realizar en el DOM para actualizar la interfaz de usuario.

  • En cambio, si hemos optado por utilizar Blazor WebAssembly, la página será compilada y el ensamblado resultante será enviado al cliente, quien lo ejecutará sobre el runtime basado en WebAssembly. Por tanto, la ejecución de la página se realizará totalmente en cliente, como si la hubiéramos implementado totalmente Javascript, sin necesidad de interactuar con el servidor en ningún momento.
Y lo interesante es que en ambos casos nuestro código será el mismo. Es decir, la página Sum.razor podrá ser (en teoría) la misma para cualquiera de los modelos de ejecución de Blazor, por lo que podríamos crear nuestras aplicaciones e ir evolucionando posteriormente de un modelo a otro. Por ejemplo, a día de hoy sólo podríamos poner aplicaciones Blazor en producción usando Blazor Server, pero en unos meses podríamos pasar al modelo WebAssembly cuando esté disponible, realizando pocos cambios.

Pero no sólo eso, la idea también aplica para el resto de "sabores" de Blazor, como PWA, Hybrid o Native: lo único que cambiará es el "empaquetado" y, en algunos casos, el proceso interno de renderizado, pero nuestro código seguirá siendo principalmente el mismo... o al menos, estas son las intenciones iniciales; conforme avancen estos proyectos iremos viendo hasta qué punto es así.

En definitiva, Blazor es un framework del que seguro vamos a oír hablar bastante durante los próximos meses, y no sin razón. Aunque aún está empezando a rodar y quedan bastantes cosas por pulir, es conveniente no perderlo de vista porque realmente las expectativas están muy altas :)

Publicado en Variable not found.

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

Fixed Buffer

Cómo crear una base de datos en alta disponibilidad con MongoDB

marzo 24, 2020 09:00

Tiempo de lectura: 11 minutos
Imagen ornamental de MongoDb con replicaset para conseguir alta disponibilidad

Son tiempos convulsos en los que vivimos estos días y mantenernos ocupados es una buena manera de llevar la situación. Es por esto que hoy vamos a hablar sobre cómo crear una base de datos en alta disponibilidad utilizando MongoDb y su característica de Replica Set. Hace unas semanas hablamos sobre como crear aplicaciones de alto rendimiento, tanto en lo que se refiere a escribir código de alto rendimiento como a escribir un logger de alto rendimiento pero… por muy bien que funcione una sistema en cuanto a rendimiento, ¿qué podemos hacer si trabajamos con una base de datos y esta tiene un fallo? ¿De qué sirve el trabajo de optimizar si luego se cae la base de datos?

Pues sencillamente el sistema deja de funcionar, no hay más. Para poder solventar esas situaciones donde no se puede tolerar que el sistema falle se utilizan sistemas de alta disponibilidad que no es más que el hecho de haya redundancia de los elementos. Aunque es una explicación grosso modo, lo que se consigue al redundar los elementos es que si uno falla otro pueda seguir dando el servicio mientras se arregla la situación.

Esto es algo relativamente sencillo cuando tenemos una API a la que llamamos y contra la que trabajamos, si la API carece de estado basta con que llamemos a otra instancia y todo funciona bien. El problema se complica cuando es necesario que los datos que contiene se compartan entre las diferentes instancias del servicio. Una vez más, si ponemos el ejemplo de una API, se podría persistir el estado en una base de datos desde la que consuman los datos de estado todas las instancias, pero… ¿Dónde compartimos los datos si lo que queremos es redundar la base de datos?

En el caso de una base de datos parece claro que no es suficiente el hecho de apuntar a otra y ya está, puesto que esa otra no va a tener los datos ya registrados en las tablas…

¿Qué es una base de datos en alta disponibilidad?

Vale, acabamos de plantear una base de datos de alta disponibilidad no es algo banal y que no basta con cambiar de una instancia a otra y ya está. Entonces, ¿cómo funciona una base de datos en alta disponibilidad?

Para poder conseguir que una base de datos funcione en alta disponibilidad lo que vamos a necesitar es agrupar diferentes instancias y conseguir que se comporten como una sola (Cluster). Ciertamente como hacer esto depende mucho del tipo de motor de base de datos que estemos utilizando. Algunos motores soportan esto de manera añadida y otros fueron pensados desde el principio para poder trabajar de manera distribuida. No es lo mismo montar un Sql Server en alta disponibilidad que un MongoDb en alta disponibilidad, ni en el hardware necesario ni en el trabajo de configuración.

¿Qué es MongoDb y porque facilita la alta disponibilidad?

MongoDB es una base de datos documental NoSQL que desde el principio se pensó para trabajar de manera distribuida utilizando el modelo de maestro-esclavo con hasta 50 nodos a fecha actual. El hecho de que sea maestro-esclavo quiere decir que las operaciones de escritura se deben realizar sobre el maestro y este es el que se preocupa de replicar los datos sobre el resto de nodos del sistema. Pese a que planteado así puede parecer que si el maestro cae el sistema deja de funcionar, MongoDB está pensado para trabajar en alta disponibilidad y tiene la capacidad de que los nodos esclavos elijan un nuevo maestro en el caso de que este falle.

El diagrama de funcionamiento es este:

La imagen muestra la arquitectura de alta disponibilidad de mongoDB donde el primario replica los datos hacia los secundarios y la aplicación que consume la base de datos lo hace directamente desde el rpimario.

Si tienes interés en conocer cómo funciona en profundidad la replicación en MongoDB, te dejo un enlace a la documentación oficial.

Nuestras aplicaciones se conectarán directamente al nodo primario y este es el que se encargará de que los datos se repliquen sobre los nodos secundarios.

Para conseguir este funcionamiento de alta disponibilidad en mongoDB, existe el concepto de ReplicaSet. Este ReplicaSet es el conjunto de los diferentes nodos (instancias de mongoDB) conectados entre ellos de modo que consigamos alta disponibilidad.

¿Cómo crear un ReplicaSet en MongoDB?

Una de las ventajas de mongoDB es que ha sido pensada para trabajar de manera distribuida, de modo que si un nodo se cae el resto puedan asumir el trabajo y el sistema no se vea afectado. Aunque a día de hoy un ReplicaSet soporta hasta 50 nodos, el mínimo número de nodos para tener un mongoDB en alta disponibilidad es 2. Es por esto que es necesario que mongoDB se esté ejecutando en al menos dos servidores.

MongoDB no trabaja con ficheros de configuración por defecto, por lo que podemos indicarle las diferentes configuraciones al arrancar mediante parámetros del comando, o crear un fichero de configuración e indicarle en el arranque que queremos utilizarlo. Para ello basta con arrancar el proceso utilizando:

mongod --config <ruta al fichero de configuración>

Una vez que tenemos claro como arrancar la instancia de mongoDB para trabajar con un fichero de configuración, vamos a crearlo y a añadir en el la propiedad net.bindingIp para hacer que cada instancia escuche peticiones desde las IPs del resto de instancias. En caso de no hacerlo, solo podríamos hacer llamadas desde localhost hacia esa instancia de mongoDB y no podríamos crear el ReplicaSet.

MongoDB soporta diferentes formatos de ficheros de configuración, pero por su sencillez vamos a utilizar yaml. Por ejemplo nuestro fichero de configuración ahora mismo sería algo así:

net:
   bindIp: localhost,<ip address>,<ip address>

Podemos indicar varias IPs utilizando coma ‘,’ entre las diferentes IPs. En caso de querer permitir llamadas desde cualquier dirección, bastaría con utilizar ‘::’ para cualquier dirección IPv6 y ‘0.0.0.0’ para cualquier dirección IPv4.

Sobre ese mismo fichero, vamos a añadir el nombre del ReplicaSet al que pertenece la instancia con la propiedad replication.replSetName. En este caso yo lo he llamado rs0, por lo que mi fichero de configuración completo es así:

net:
   bindIp: ::,0.0.0.0
replication:
   replSetName: "rs0"

Estas dos propiedades de configuración tienen que estar en cada uno de los nodos que van a componer el ReplicaSet ya que todos los nodos tienen que ser accesibles entre ellos y compartir ReplicaSet. No es relevante si un nodo se configura mediante fichero y otro mediante parámetros, pero tienen que estar configuradas.

Con esto, ya estamos listos para funcionar :). Vamos a entrar en cada uno de los servidores mongoDB utilizando el cliente de mongo simplemente ejecutando desde la consola local (de cada servidor):

mongo

Y en cada uno de ellos crearemos un usuario:

use admin
db.createUser(
  {
    user: "admin",
    pwd: "password",
    roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ]
  }
)

Recapitulemos, tenemos varias instancias de mongo corriendo, todas configuradas para trabajar en alta disponibilidad. ¿Ya esta todo hecho? La verdad es que aun no… Con estos pequeños pasos hemos conseguido tener todo preparado, pero falta iniciarlo todo.

Desde cualquiera de los servidores pero solo desde uno, vamos a entrar en la consola y vamos a ejecutar rs.initiate() indicándole los datos de los servidores. Por ejemplo en mi caso:

rs.initiate(
  {
    _id : "rs0",
    members: [
      { _id : 0, host : "40.118.22.136:27017" },
      { _id : 1, host : "13.73.148.195:27017" },
      { _id : 2, host : "13.73.144.190:27017" }
    ]
  }
)

En este comando, aparte de otros datos que podríamos informar si nos hiciese falta, le estamos indicando el nombre del ReplicaSet, así como la dirección de cada uno de sus miembros. Una vez ejecutado el comando podemos utilizar rs.status() para ver el estado en el que se encuentra el cluster:

rs0:PRIMARY> rs.status()
{
        "set" : "rs0",
        "date" : ISODate("2020-03-22T00:11:25.958Z"),
        "myState" : 1,
        "term" : NumberLong(6),
        "heartbeatIntervalMillis" : NumberLong(2000),
        "optimes" : {
                "lastCommittedOpTime" : {
                        "ts" : Timestamp(1584835883, 1),
                        "t" : NumberLong(6)
                },
                "readConcernMajorityOpTime" : {
                        "ts" : Timestamp(1584835883, 1),
                        "t" : NumberLong(6)
                },
                "appliedOpTime" : {
                        "ts" : Timestamp(1584835883, 1),
                        "t" : NumberLong(6)
                },
                "durableOpTime" : {
                        "ts" : Timestamp(1584835883, 1),
                        "t" : NumberLong(6)
                }
        },
        "members" : [
                {
                        "_id" : 0,
                        "name" : "40.118.22.136:27017",
                        "health" : 1,
                        "state" : 1,
                        "stateStr" : "PRIMARY",
                        "uptime" : 1702,
                        "optime" : {
                                "ts" : Timestamp(1584835883, 1),
                                "t" : NumberLong(6)
                        },
                        "optimeDate" : ISODate("2020-03-22T00:11:23Z"),
                        "electionTime" : Timestamp(1584834202, 1),
                        "electionDate" : ISODate("2020-03-21T23:43:22Z"),
                        "configVersion" : 1,
                        "self" : true
                },
                {
                        "_id" : 1,
                        "name" : "13.73.148.195:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 1694,
                        "optime" : {
                                "ts" : Timestamp(1584835883, 1),
                                "t" : NumberLong(6)
                        },
                        "optimeDurable" : {
                                "ts" : Timestamp(1584835883, 1),
                                "t" : NumberLong(6)
                        },
                        "optimeDate" : ISODate("2020-03-22T00:11:23Z"),
                        "optimeDurableDate" : ISODate("2020-03-22T00:11:23Z"),
                        "lastHeartbeat" : ISODate("2020-03-22T00:11:25.174Z"),
                        "lastHeartbeatRecv" : ISODate("2020-03-22T00:11:25.173Z"),
                        "pingMs" : NumberLong(0),
                        "syncingTo" : "40.118.22.136:27017",
                        "configVersion" : 1
                },
                {
                        "_id" : 2,
                        "name" : "13.73.144.190:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 1679,
                        "optime" : {
                                "ts" : Timestamp(1584835883, 1),
                                "t" : NumberLong(6)
                        },
                        "optimeDurable" : {
                                "ts" : Timestamp(1584835883, 1),
                                "t" : NumberLong(6)
                        },
                        "optimeDate" : ISODate("2020-03-22T00:11:23Z"),
                        "optimeDurableDate" : ISODate("2020-03-22T00:11:23Z"),
                        "lastHeartbeat" : ISODate("2020-03-22T00:11:25.174Z"),
                        "lastHeartbeatRecv" : ISODate("2020-03-22T00:11:24.934Z"),
                        "pingMs" : NumberLong(0),
                        "syncingTo" : "13.73.148.195:27017",
                        "configVersion" : 1
                }
        ],
        "ok" : 1,
        "operationTime" : Timestamp(1584835883, 1),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1584835883, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Adicional a la información sobre el propio cluster, podemos encontrar cosas como información sobre cada nodo. Alguna información útil sobre el nodo nos dice si es primario o secundario, cuando llego el último latido de vida, o el tiempo que lleva en marcha.

¡¡Ahora sí que tenemos una base de datos mongoDB en alta disponibilidad!!

¿Cómo añadir y borrar nodos de un cluster mongoDB?

Ahora mismo tenemos un cluster mongoDB en alta disponibilidad listo y funcionando, pero… ¿qué pasa si tenemos que añadir o borrar nodos?

Imagina que por la razón X uno de los servidores de cluster se va a parar durante un tiempo para hacer ciertas labores. Para que el propio cluster sea capaz de elegir al maestro, tienen que estar disponibles la mayoría de nodos del cluster. En un cluster de 4 nodos, la mayoría son 3, si ya estamos quitando uno de los 4 para hacer mantenimientos pero el cluster no lo sabe, cuando uno falle no será capaz de elegir un nuevo maestro ya que necesita que 3 nodos estén de acuerdo, pero solo nos quedan 2… (recuerda que el cluster piensa que tiene 4).

Para esto mongoDB nos ofrece 2 sencillos comandos que podemos ejecutar desde el servidor primario y nos va a permitir añadir y eliminar nodos del cluster. Estos comandos son rs.remove() y rs.add().

Estos comandos reciben como parámetro la dirección del nodo que queremos añadir o eliminar y se encarga de hacer que todo se configure para seguir funcionando. Por ejemplo en mi caso si quiero eliminar un nodo y volverlo a añadir de que acabe las labores sería algo así:

 rs.remove("13.73.144.190:27017")
........
 rs.add("13.73.144.190:27017")

Conclusión

MongoDB es una base de datos NoSQL más que probada y muy robusta que podemos utilizar en nuestros desarrollos sin ningún problema, pero hay que tener en cuenta que no es una base de datos relacional. Esto tiene sus ventajas y sus desventajas y el hecho de que la replicación y la alta disponibilidad se consiga fácilmente no quita que sea necesario estudiar si es una solución apropiada o no. Es una base de datos pensada para trabajar de manera distribuida y eso es lo que hace que mongoDB se pueda utilizar de manera muy sencilla en alta disponibilidad.

Para acabar, en esta entrada hemos hablado por encima del funcionamiento de mongoDB y hemos hecho una configuración básica de un ReplicaSet. Lo que busco escribiendo esta entrada no es explicar al 100% los entresijos de mongoDB y las diferentes configuraciones que ofrece, ya que eso da para un libro (y de hecho los hay), sino hablar de un potente motor NoSQL y poner un ejemplo de cómo crear una alta disponibilidad.

Si tienes alguna duda concreta sobre como hacerlo o cómo diseñar el sistema en concreto, déjame un comentario o un mensaje y vemos el caso más a fondo.

Bola extra: MongoDB en alta disponibilidad utilizando Docker

Todo lo que hemos planteado está muy bien, pero la realidad es que no es práctico tener que crear un cluster de mongoDB para el desarrollo del día a día. Para poder conseguir de manera sencilla un cluster de mongoDB en local, podemos utilizar docker y crearlo de una manera muy sencilla.

Lo primero que vamos a necesitar es tener Docker instalado (por supuesto) y crear un fichero docker-compose.yml como este:

version: "3"
services:
  mongo0:
    hostname: mongo0
    container_name: mongo0
    image: mongo
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    ports:
      - 27017:27017
    restart: always
    entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "rs0" ]
  mongo1:
    hostname: mongo1
    container_name: mongo1
    image: mongo
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    ports:
      - 27018:27017
    restart: always
    entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "rs0" ]
  mongo2:
    hostname: mongo2
    container_name: mongo2
    image: mongo
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    ports:
      - 27019:27017
    restart: always
    entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "rs0" ]
    
  mongo-express:
    container_name: mongo-client
    image: mongo-express
    restart: always
    ports:
      - 8081:8081
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: eample
      ME_CONFIG_MONGODB_SERVER: "mongo0,mongo1,mongo2"

Gracias a este fichero docker-compose vamos a crear 3 instancias de mongoDB y además vamos a levantar también un cliente web donde poder ver que todo funciona.

Simplemente vamos a ejecutar:

docker-compose up -d

Y una vez que se levanten los contenedores, vamos a entrar en una instancia cualquiera de mongo y vamos a poner en marcha el Replica Set. Por ejemplo, si eligiésemos el contenedor mongo0:

docker exec -it mongo0 mongo
# Y una vez dentro de mongo
config={"_id":"rs0","members":[{"_id":0,"host":"mongo0:27017"},
{"_id":1,"host":"mongo1:27017"},{"_id":2,"host":"mongo2:27017"}]}
rs.initiate(config)

Simplemente con esto ya hemos creado el Replica Set y tenemos una base de datos mongo en alta disponibilidad corriendo todo en local sobre docker. Podemos ver que todo está funcionando si entramos en el cliente web en http://localhost:8081/ . De hecho, podemos comprobar fácilmente que las elecciones del nodo primario están ocurriendo simplemente si paramos el contenedor del nodo primario y refrescamos la web. En el apartado hostname vamos a ver que nodo está actuando como primario.

La imagen señala el apartado Hostname del cliente web donde se puede ver el calor mongo0

Gracias a docker es muy fácil jugar con mongoDB y probar que sucede cuando un nodo se cae, o montar un cluster de más de 2-3 nodos. Me ha parecido útil añadir este último apartado sobre como hacer lo mismo con docker para que puedas probar y trastear con mongoDB, ya que es una opción muy interesante. Pero recuerda, docker no es lo ideal para poner una base de datos en alta disponibilidad en entornos de producción, úsalo solo con fines de desarrollo y pruebas.

**La entrada Cómo crear una base de datos en alta disponibilidad con MongoDB se publicó primero en Fixed Buffer.**

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

Variable not found

Cómo implementar view-models de componentes Blazor en clases code-behind

marzo 24, 2020 07:05

Blazor Al comenzar con Blazor, seguro que veis en la mayoría de ejemplos que la implementación de la lógica (código C#) y de la pura interfaz de usuario (HTML) se encuentran en un bloque @code en el mismo archivo .razor, como en el siguiente bloque de código:
@* File: ~/Pages/HelloWorld.razor *@
@page "/hello"
<h1>Hello</h1>
<div>
<label for="name">Your name:</label>
<input id="name" @bind-value="Name" @bind-value:event="oninput"/>
</div>
@if (HasName)
{
<h2>Hello, @Name!</h2>
}

@code
{
public string Name { get; set; }
public bool HasName => !string.IsNullOrWhiteSpace(Name);
}
Sin embargo, esto no tiene que ser necesariamente así. En tiempo de compilación, un archivo .razor genera una clase parcial C# con su mismo nombre, lo cual nos brinda la posibilidad de mover todo el código C# a otra porción de dicha clase.
Podéis ver las clases generadas para cada archivo .razor en la carpeta del proyecto obj\debug\netcoreapp3.1\Razor\Pages.

La única precaución a tener en cuenta es que la clase debe ser parcial, llamarse exactamente igual que el archivo .razor (mayúsculas y minúsculas incluidas), y encontrarse en el mismo espacio de nombres (algo que, por ejemplo, podríamos conseguirlo rápidamente si creamos la clase en la misma carpeta).

Además, si usamos justo el mismo nombre de archivo pero con la extensión final ".cs", el editor lo mostrará por debajo del primero, muy al estilo "code-behind" al que estamos acostumbrados.

Los siguientes bloques de código muestran cómo quedaría la página anterior si realizamos esta división:
@* File: ~/Pages/HelloWorld.razor *@
@page "/hello"
<h1>Hello</h1>
<div>
<label for="name">Your name:</label>
<input id="name" @bind-value="Name" @bind-value:event="oninput"/>
</div>
@if (HasName)
{
<h2>Hello, @Name!</h2>
}
// File: ~/Pages/HelloWorld.razor.cs
namespace BlazorDemo.Pages
{
public partial class HelloWorld
{
public string Name { get; set; }
public bool HasName => !string.IsNullOrWhiteSpace(Name);
}
}
Fijaos que hemos llamado a la clase HelloWorld (usando Pascal casing) porque el archivo Razor era HelloWorld.razor. Pero si se hubiera llamado Hello-World.razor, o Hello!World.razor , dado que C# no permite nombres de clases con esos caracteres separadores, debería llamarse Hello_World.

Publicado en Variable not found.

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

Variable not found

Enlaces interesantes 396

marzo 23, 2020 07:05

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os entretengan, al menos un ratillo, durante el confinamiento :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

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

Coding Potions

Vue ≫ Cómo crear un sistema de login y registro de usuarios

marzo 22, 2020 12:00

Introducción

Hace unos meses, ya vimos un ejemplo completo para crear una web tareas en Vue. Si no lo recuerdas te dejo el link aquí: Ejemplo de todo app en Vue.

Pues hoy tenemos otro ejemplo práctico de principio a fin para que afiances conocimientos previos con vue. En este caso vamos a hacer una web que tenga un formulario de login y un formulario de registro en Vue, así repasamos las llamadas HTTP a una API con axios y de paso aprendemos a guardar información en cookies.

🗺️ Hoja de ruta

  • [ ] 🏗️ Crear y maquetar las vistas de login y registro con formularios
  • [ ] ✨ Conectar el sistema de registro con la API
  • [ ] ✨ Conectar el sistema de login con la API
  • [ ] ✨ Recordar la sesión iniciada al recargar la página

Creando las vistas. Formulario de login y registro

Lo primero que vamos a hacer es maquetar los dos formularios, sin lógica por el momento. Para ello vamos a crear dos componentes, uno para la vista de login y otro para la vista del registro.

Ambos componentes los vamos a añadir a las rutas que tengamos en el router. Si no sabes lo que es el router de Vue te dejo el artículo que escribí sobre eso: Cómo usar el router de Vue

En mi caso en el fichero router.js pongo lo siguiente:

import Vue from "vue";
import Router from "vue-router";

import App from "./App";
import Home from "./views/Home";
import Login from "./views/Login";
import Register from "./views/Register";

Vue.use(Router);

const routes = [
  { path: "/", component: Home },
  { path: "/login", component: Login },
  { path: "/register", component: Register }
];

const router = new Router({
  routes
});

Simplemente tres rutas, la de la página principal que próximamente protegeremos para que solo entren usuarios logueados, la del formulario del login y la del registro.

De tal forma que lo que hago es crear 3 archivos en la carpeta views: Home.vue, Login.vue y Register.vue

La vista de login (login.vue), la he maquetado de la siguiente forma:

<template>
  <div class="login">
    <h1 class="title">Login in the page</h1>
    <form action class="form">
      <label class="form-label" for="#email">Email:</label>
      <input class="form-input" type="email" id="email" required placeholder="Email">
      <label class="form-label" for="#password">Password:</label>
      <input class="form-input" type="password" id="password" placeholder="Password">
      <input class="form-submit" type="submit" value="Login">
    </form>
  </div>
</template>

<script>
export default {};
</script>

<style lang="scss" scoped>
.login {
  padding: 2rem;
}
.title {
  text-align: center;
}
.form {
  margin: 3rem auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 20%;
  min-width: 350px;
  max-width: 100%;
  background: rgba(19, 35, 47, 0.9);
  border-radius: 5px;
  padding: 40px;
  box-shadow: 0 4px 10px 4px rgba(0, 0, 0, 0.3);
}
.form-label {
  margin-top: 2rem;
  color: white;
  margin-bottom: 0.5rem;
  &:first-of-type {
    margin-top: 0rem;
  }
}
.form-input {
  padding: 10px 15px;
  background: none;
  background-image: none;
  border: 1px solid white;
  color: white;
  &:focus {
    outline: 0;
    border-color: #1ab188;
  }
}
.form-submit {
  background: #1ab188;
  border: none;
  color: white;
  margin-top: 3rem;
  padding: 1rem 0;
  cursor: pointer;
  transition: background 0.2s;
  &:hover {
    background: #0b9185;
  }
}
</style>

Lo único que tiene es el formulario de login con unos estilos que me he inventado, de momento no hay nada de lógica en este componente. Siempre recordad de poner los estilos con scoped para que se queden aislados del resto de componentes.

En la imagen se ve un formulario con dos inputs, uno para el email y otro para la contraseña. Además hay un botón de enviar

Ahora vamos a añadir los v-model en los campos para poder sacar el email y la contraseña que el usuario escribe, para ello:

<template>
  <div class="login">
    <h1 class="title">Login in the page</h1>
    <form action class="form">
      <label class="form-label" for="#email">Email:</label>
      <input v-model="email" class="form-input" type="email" id="email" required placeholder="Email">
      <label class="form-label" for="#password">Password:</label>
      <input v-model="password" class="form-input" type="password" id="password" placeholder="Password">
      <input class="form-submit" type="submit" value="Login">
    </form>
  </div>
</template>

<script>
export default {
  data: () => ({
    email: "",
    password: ""
  })
};
</script>

No tiene más, dos v-model apuntando a variables definidas en la sección data del componente para poder recibir los dos valores. Toca preparar el método para enviar la petición de login:

<template>
  <div class="login">
    <h1 class="title">Login in the page</h1>
    <form action class="form" @submit.prevent="login">
      <label class="form-label" for="#email">Email:</label>
      <input v-model="email" class="form-input" type="email" id="email" required placeholder="Email">
      <label class="form-label" for="#password">Password:</label>
      <input v-model="password" class="form-input" type="password" id="password" placeholder="Password">
      <input class="form-submit" type="submit" value="Login">
    </form>
  </div>
</template>

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

De momento solo se escriben los dos valores por consola para probar que se guardan bien en las variables los valores. Nada muy complicado de momento. Vamos a maquetar por último el mensaje de error con un v-if para que se muestre si el usuario ha metido mal el email o la contraseña:

<template>
  <div class="login">
    <h1 class="title">Login in the page</h1>
    <form action class="form" @submit.prevent="login">
      <label class="form-label" for="#email">Email:</label>
      <input
        v-model="email"
        class="form-input"
        type="email"
        id="email"
        required
        placeholder="Email"
      >
      <label class="form-label" for="#password">Password:</label>
      <input
        v-model="password"
        class="form-input"
        type="password"
        id="password"
        placeholder="Password"
      >
      <p v-if="error" class="error">Has introducido mal el email o la contraseña.</p>
      <input class="form-submit" type="submit" value="Login">
    </form>
  </div>
</template>

<script>
export default {
  data: () => ({
    email: "",
    password: "",
    error: false
  }),
  methods: {
    login() {
      console.log(this.email);
      console.log(this.password);
    }
  }
};
</script>

Listo, componente maquetado por el momento, vamos a maquetar el del formulario de registro para prepararlos para conectar al backend.

El componente del registro es igual solo que añadiendo un campo nuevo para que el usuario repita su contraseña:

<template>
  <div class="register">
    <h1 class="title">Sign Up</h1>
    <form action class="form" @submit.prevent="register">
      <label class="form-label" for="#email">Email:</label>
      <input
        v-model="email"
        class="form-input"
        type="email"
        id="email"
        required
        placeholder="Email"
      >
      <label class="form-label" for="#password">Password:</label>
      <input
        v-model="password"
        class="form-input"
        type="password"
        id="password"
        placeholder="Password"
      >
      <label class="form-label" for="#password-repeat">Repite la contraeña:</label>
      <input
        v-model="passwordRepeat"
        class="form-input"
        type="password"
        id="password-repeat"
        placeholder="Password"
      >
      <input class="form-submit" type="submit" value="Sign Up">
    </form>
  </div>
</template>

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

<style lang="scss" scoped>
.register {
  padding: 2rem;
}
.title {
  text-align: center;
}
.form {
  margin: 3rem auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 20%;
  min-width: 350px;
  max-width: 100%;
  background: rgba(19, 35, 47, 0.9);
  border-radius: 5px;
  padding: 40px;
  box-shadow: 0 4px 10px 4px rgba(0, 0, 0, 0.3);
}
.form-label {
  margin-top: 2rem;
  color: white;
  margin-bottom: 0.5rem;
  &:first-of-type {
    margin-top: 0rem;
  }
}
.form-input {
  padding: 10px 15px;
  background: none;
  background-image: none;
  border: 1px solid white;
  color: white;
  &:focus {
    outline: 0;
    border-color: #1ab188;
  }
}
.form-submit {
  background: #1ab188;
  border: none;
  color: white;
  margin-top: 3rem;
  padding: 1rem 0;
  cursor: pointer;
  transition: background 0.2s;
  &:hover {
    background: #0b9185;
  }
}
.error {
  margin: 1rem 0 0;
  color: #ff4a96;
}
</style>

En la imagen se ve un formulario con tres inputs, uno para el email, otro para la contraseña y el último de repetir la contraseña. Además hay un botón de enviar

🎉 Pues listo, primera tarea completada, hacemos commit y pasamos a la siguiente.

[X] 🏗️ Crear y maquetar las vistas de login y registro con formularios

Conectando el registro al servidor

Lógicamente para poder conectar el login y registro necesitamos un servidor (encargado de almacenar los usuarios en base de datos ya que desde javascript no se puede). Para ello necesitas una API, puedes crear tú mismo una API usando alguna tecnología de backend como nodejs, java o python. Como de lo que se trata es de aprender, para este ejemplo voy a usar esta API de pruebas ya creada que tú también puedes usar.

La API en cuestión es la de: https://reqres.in/. Te permite mandar peticions de login y registro además de muchas otras para hacer pruebas.

Lo primero que vamos a hacer es conectar el registro, para ello voy a crear una carpeta llamada logic dentro de la carpeta src del proyecto. Yo la he llamado logic pero la puedes llamar como quieras. Dentro de esa carpeta voy a crear un archivo llamado auth.js en el que voy a colocar la petición de registro y la de login.

Por el momento dentro del archivo de auth.js voy a crear esto:

import axios from "axios";

const ENDPOINT_PATH = "https://reqres.in/api/";

export default {
  register(email, password) {
    const user = { email, password };
    return axios.post(ENDPOINT_PATH + "regiser", user);
  }
    };

Lo primero es importar axios, si no lo tienes instalado en el proyecto ejecuta:

npm install axios --save

Lo siguiente que hago es definir una constante para endpoint de la API. Luego exporto un objeto para poder usar desde fuera este fichero con el método de registro de usuarios.

Dentro del método de registro se construye el objeto user que se enviará en la petición POST de registro de usuarios. Por último se llama a axios para que haga el POST y devuelva la promesa.

Vamos ahora a usar este fichero desde el componente de registro. Lo primero que se hace es importar el fichero encima del export default del componente:

...
import auth from "@/logic/auth";
export default {
  data: () => ({
...

¿Recuerdas el métoodo que creaste que simplemente tenía los console.log? Pues hay que cambiar eso por esto otro:

...
methods: {
  register() {
    auth.register(this.email, this.password).then(response => {
      console.log(response);
    })
  }
}
...

Como el método register del archivo auth devuelve una promesa lo que hay que hacer es crear a continuación el then para capturar la respuesta asíncrona.

Si ejecutas el código y escribes en el formulario el email eve.holt@reqres.in y la contraseña pistol y le das a registrar verás que se devuelve la respuesta de la API, en este caso un 201 created.

La forma de resolver la asincronía con el then está bien pero creo que con async/await queda todo más claro:

methods: {
  async register() {
    const response = await auth.register(this.email, this.password);
    console.log(response);
  }
}

Por último podemos crear una variable en el data para mostrar error si el usuario ha metido mal el usuario y contraseña. Para capturar el error en la petición podemos usar try/catch. De paso vamos a poner que si el registro es correcto lleve al usuario a la página de inicio. Veamos como queda todo el componente:

<template>
  <div class="register">
    <h1 class="title">Sign Up</h1>
    <form action class="form" @submit.prevent="register">
      <label class="form-label" for="#email">Email:</label>
      <input
        v-model="email"
        class="form-input"
        type="email"
        id="email"
        required
        placeholder="Email"
      >
      <label class="form-label" for="#password">Password:</label>
      <input
        v-model="password"
        class="form-input"
        type="password"
        id="password"
        placeholder="Password"
      >
      <label class="form-label" for="#password-repeat">Repite la contraeña:</label>
      <input
        v-model="passwordRepeat"
        class="form-input"
        type="password"
        id="password-repeat"
        placeholder="Password"
      >
      <input class="form-submit" type="submit" value="Sign Up">
    </form>
  </div>
</template>

<script>
import auth from "@/logic/auth";
export default {
  data: () => ({
    email: "",
    password: "",
    passwordRepeat: "",
    error: false
  }),
  methods: {
    async register() {
      try {
        await auth.register(this.email, this.password);
        this.$router.push("/")
      } catch (error) {
        console.log(error);
      }
    }
  }
};
</script>

<style lang="scss" scoped>
.register {
  padding: 2rem;
}
.title {
  text-align: center;
}
.form {
  margin: 3rem auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 20%;
  min-width: 350px;
  max-width: 100%;
  background: rgba(19, 35, 47, 0.9);
  border-radius: 5px;
  padding: 40px;
  box-shadow: 0 4px 10px 4px rgba(0, 0, 0, 0.3);
}
.form-label {
  margin-top: 2rem;
  color: white;
  margin-bottom: 0.5rem;
  &:first-of-type {
    margin-top: 0rem;
  }
}
.form-input {
  padding: 10px 15px;
  background: none;
  background-image: none;
  border: 1px solid white;
  color: white;
  &:focus {
    outline: 0;
    border-color: #1ab188;
  }
}
.form-submit {
  background: #1ab188;
  border: none;
  color: white;
  margin-top: 3rem;
  padding: 1rem 0;
  cursor: pointer;
  transition: background 0.2s;
  &:hover {
    background: #0b9185;
  }
}
.error {
  margin: 1rem 0 0;
  color: #ff4a96;
}
</style>

Listo, sistema de registro de usuarios terminado, tarea completad, toca hacer commit.

[X] ✨ Conectar el sistema de registro con la API

Sistema de login

Una vez tenemos el sistema de registro el de login debería ser más fácil porque básicamente es repetir lo mismo que antes solo que cambiando la ruta del endpoint.

En el fichero de auth.js añadimos:

import axios from "axios";

const ENDPOINT_PATH = "https://reqres.in/api/";

export default {
  register(email, password) {
    const user = { email, password };
    return axios.post(ENDPOINT_PATH + "regiser", user);
  },
  login(email, password) {
    const user = { email, password };
    return axios.post(ENDPOINT_PATH + "login", user);
  }
};

Y en el componente de login llamamos a este fichero de la misma manera que en el registro, solo que cuando haya error activamos la variable error a true para que en el formulario se muestre un error avisando de que el email o la contraseña están mal:

<template>
  <div class="login">
    <h1 class="title">Login in the page</h1>
    <form action class="form" @submit.prevent="login">
      <label class="form-label" for="#email">Email:</label>
      <input
        v-model="email"
        class="form-input"
        type="email"
        id="email"
        required
        placeholder="Email"
      >
      <label class="form-label" for="#password">Password:</label>
      <input
        v-model="password"
        class="form-input"
        type="password"
        id="password"
        placeholder="Password"
      >
      <p v-if="error" class="error">Has introducido mal el email o la contraseña.</p>
      <input class="form-submit" type="submit" value="Login">
    </form>
    <p class="msg">¿No tienes cuenta?
      <router-link to="/register">Regístrate</router-link>
    </p>
  </div>
</template>

<script>
import auth from "@/logic/auth";
export default {
  data: () => ({
    email: "",
    password: "",
    error: false
  }),
  methods: {
    async login() {
      try {
        await auth.login(this.email, this.password);
        this.$router.push("/");
      } catch (error) {
        this.error = true;
      }
    }
  }
};
</script>

<style lang="scss" scoped>
.login {
  padding: 2rem;
}
.title {
  text-align: center;
}
.form {
  margin: 3rem auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 20%;
  min-width: 350px;
  max-width: 100%;
  background: rgba(19, 35, 47, 0.9);
  border-radius: 5px;
  padding: 40px;
  box-shadow: 0 4px 10px 4px rgba(0, 0, 0, 0.3);
}
.form-label {
  margin-top: 2rem;
  color: white;
  margin-bottom: 0.5rem;
  &:first-of-type {
    margin-top: 0rem;
  }
}
.form-input {
  padding: 10px 15px;
  background: none;
  background-image: none;
  border: 1px solid white;
  color: white;
  &:focus {
    outline: 0;
    border-color: #1ab188;
  }
}
.form-submit {
  background: #1ab188;
  border: none;
  color: white;
  margin-top: 3rem;
  padding: 1rem 0;
  cursor: pointer;
  transition: background 0.2s;
  &:hover {
    background: #0b9185;
  }
}
.error {
  margin: 1rem 0 0;
  color: #ff4a96;
}
.msg {
  margin-top: 3rem;
  text-align: center;
}
</style>

Si abres la página de login y pruebas con el email: eve.holt@reqres.in y la contraseña cityslicka te debería llevar a la página principal ya que la llamada al login ha salido bien. Si pones otro email y contraseña te debería salir el mensaje de error en pantalla.

Pues otra tarea terminada. Commit y a por la siguiente:

[X] ✨ Conectar el sistema de login con la API

Recordando la sesión iniciada

Vamos con una parte fundamental en todo sistema de login, el de guardar el usuario cuando se loguea en una cookie o en el localstorage para que los componentes puedan pintar información del usuario logueado.

Para este ejemplo voy a optar por usar la librería de js cookie, para ello lo primero es decargarlo mediante npm:

npm install js-cookie --save

Ahora lo que voy a hacer es crear dos métodos más dentro del archivo de auth.js. Uno de ellos para guardar el usuario logueado y el otro para recuperarlo desde las cookies.

import axios from "axios";
import Cookies from "js-cookie";

const ENDPOINT_PATH = "https://reqres.in/api/";

export default {
  setUserLogged(userLogged) {
    Cookies.set("userLogged", userLogged);
  },
  getUserLogged() {
    return Cookies.get("userLogged");
  },
  register(email, password) {
    const user = { email, password };
    return axios.post(ENDPOINT_PATH + "regiser", user);
  },
  login(email, password) {
    const user = { email, password };
    return axios.post(ENDPOINT_PATH + "login", user);
  }
};

Lo que vamos a hacer ahora en la vista de login es que si la petición de login sale bien tenemos que guardar el usuario en la cookie. El método de login quedaría de esta forma:

...
async login() {
  try {
    await auth.login(this.email, this.password);
    const user = {
      email: this.email
    };
    auth.setUserLogged(user);
    this.$router.push("/");
  } catch (error) {
    console.log(error);
    this.error = true;
  }
}
...

En el componente de registro podemos hacer lo propio en caso de que queramos que en nuestra aplicación cuando un usuario se registre se autologuee también.

Ahora en la página principal podemos mostrar el usuario logueado, para ello:

<template>
  <div class="home">
    <navigation/>
    <h1>Home</h1>
    <p v-if="userLogged">User loggued: </p>
  </div>
</template>

<script>
import Navigation from "../components/Navigation";
import auth from "@/logic/auth";
export default {
  name: "Home",
  components: {
    navigation: Navigation
  },
  computed: {
    userLogged() {
      return auth.getUserLogged();
    }
  }
};
</script>

<style>
    </style>

Pues listo, si has hecho login y refrescas la página verás que el usuario logueado sigue estando porque se guarda en las cookies. Para terminar podemos crear en el archivo auth.js un método para cerrar sesión que simplemente borre la cookie:

deleteUserLogged() {
  Cookies.remove('userLogged');
}

ATENCIÓN: No recomiendo guardar toda la información del usuario en la cookie, pero lo hecho así en este ejemplo para demostrar cómo guardar cosas en las cookies. En estos casos lo que se suele hacer es guardar un token y no todo el usuario. Más info de esto si buscas JWT

Pues la última tarea terminada también. Commit y listo.

La página principal la he dejado sin estilos, pero puedes aprovechar para cambiarlos.

Te dejo el proyecto subido a Codesanbox para que puedas jugar con él:

Proyecto completo en codesanbox

Y para terminar te pongo deberes por si quieres seguir aprendiendo:

  • [ ] Mostrar aviso y comprobación en el registro si las dos contraseñas no coinciden
  • [ ] Deshabilitar el botón con otros estilos mientras que el usuario no meta el email y las contraseñas no coincidan
  • [ ] Meter más campos en el registro (nombre de usuario por ejemplo) y guardar más información del usuario en las cookies

Conclusiones

Como he dicho muchas veces, mira siempre la consola del navegador porque muchas veces falla algo y no nos damos cuenta hasta que leemos el mensaje ahí.

Espero que te haya gustado este ejemplo práctico y espero que te haya servido para afianzar los conocimientos que ya tenías de Vue. Con esto deberías ser capaz de crear cosas bastante interesantes a partir de aquí.

En próximos episodios veremos cómo se pueden proteger las rutas para que solo puedan acceder usuarios logueados y ademaś veremos algún truquito más de los que puede ofrecer Vue.

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

Blog Bitix

Qué es mejor para jugar y 10+ variables para elegir entre PC o consola

marzo 21, 2020 06:00

Ha una cantidad ingente de juegos nuevos y antiguos a los que jugar. Y mútiples plataformas donde jugar a juegos, las principales PC y consola pero también están los emuladores y aumentarán las personas que lo hacen mediante streaming sin necesidad de gran hardware. Para decidir que plataforma es mejor hay mútiples variables que intervienen, una importante es el precio del hardware, juegos y servicios pero hay algunas otras más. Dada la variabilidad no hay una claramente superior a las otras para todas las personas.

PlayStation

Xbox

Una de los propósitos que se puede utilizar un PC es de dedicarlo en mayor o menor medida a los juegos. Con NVIDIA y Ati, ahora AMD, y evolución de las tarjetas gráficas dedicadas y las integradas cada vez más capaces los juegos han ganado en detalles gŕaficos enormemente.

Las consolas y los PC en la actualidad utilizan la arquitectura de mismos componentes. Una consola es casi un PC especializado exclusivamente en juegos, utiliza los mismos componentes que se encuentran en el mercado de los PC, en cuanto a procesador, memoria, tarjeta gráfica y almacenamiento.

La siguiente evolución de las tarjetas gráficas es la adopción de la tecnología de trazado de rayos que mejorará aún más el realismo y detalles gráficos de los juegos. En trazado de rayos ya está disponible en los juegos que lo soporten en las tarjetas gráficas de NVIDIA de la serie RTX 2000. AMD añadirá su soporte en la siguiente generación de gráficas con arquitectura RDNA2 y aparecerá en la generación de las consolas PlayStation 5 y Xbox Series X del año 2020. Con el trazados incluso juegos con gráficos simples como Minecraft lucen excepcionalmente bien.

A la hora de elegir la mejor mejor opción para cada persona según sus necesidades, presupuesto, características o preferencias, ¿qué es mejor para jugar, PC o consola?. Para tomar esa decisión hay que evaluar un conjunto de puntos que en cada persona puede variar su importancia. La decisión final dependerá del conjunto, algunos valorarán más el precio, disponibilidad de juegos, versatilidad de PC, precio de los juegos, juego en línea, …

En mi opinión no me parece que haya una opción clara ganadora para todas las personas, la decisión depende de varios de estos factores. Para algunas personas el PC es la opción preferida pero para otras es la consola.

Versatilidad

El PC es un producto polivalente capaz de realizar múltiples funciones, una de ellas los juegos pero también tareas ofimáticas, navegación por internet, descargas y muchas otras. Las consolas aunque en realidad son como un PC son un producto que solo se pueden utilizar exclusivamente para juegos.

De modo que si una persona compra una consola es probable que también necesite comprar también un PC cosa que con únicamente el PC ya tendría cubiertas todas las necesidades informáticas que necesite. Aunque no tiene por que ser así ya que los smartphones pueden suplir algunas de las funciones de los PC.

Precio hardware

El precio es uno de los puntos más decisivos a lo hora de tomar la decisión entre PC o consola. Dado que un PC es capaz de realizar múltiples funciones sin nada más adicionalmente puede parecer que es una opción más económica pero también depende de qué componentes monte el PC.

Los juegos son posiblemente las aplicaciones más exigentes en cuanto a memoria, procesador, gráfica y almacenamiento que un usuario utiliza en su PC y los componentes para un PC gamer han de ser superiores a los que serían suficientes para un PC dedicado a tareas ofimáticas. Los componentes para un PC gamer incrementa el precio para poder jugar a los juegos.

El precio de un PC gamer puede estar entre 500 € llegando a los 1000 € e incluso llegando los 1500 € y 2000 € dependiendo del presupuesto de cada persona. Hay que tener en cuenta que un PC gamer de 500 € quizá sea capaz de jugar a los juegos actuales pero bajando los detalles gráficos más si utiliza un tarjeta gráfica integrada, más barata pero mucho menos potente y no será capaz de mover los juegos a tasas de frames por segundo altos sin bajar los niveles de detalles. Para poder jugar a los lanzamientos de juegos futuros hay que incrementar el precio para tener garantías de poder jugarlos a buenos fps o nuevamente bajar detalles. Solo la gráfica puede costar 600 € y el procesador 300 € a los que hay que sumar caja, placa base, fuente de alimentación, teclado, ratón, almacenamiento de estado sólido y memoria.

Las consolas por su parte son más económicas que un PC equivalente y tienen la garantía de que si el juego está en disponible se puede jugar a una calidad mínima aceptable ya que los juegos se adaptan para que así sea como en el caso de The Witcher 3 para Switch.

Pero una consola no cubre todas las necesidades y es posible que también sea necesario comprar un PC para complementarlo. Pero este PC al no estar dedicado a los juegos es muy posible que no necesite ser demasiado potente y por tanto será más económico. Una consola está en torno a los 200-350 € y un PC ofimático 400-500 €.

La generación de una consola está en torno a 7 años y un PC gamer puede estar en torno a eso para seguir jugando aceptablemente bien a los últimos lanzamientos. Un PC ofimático de 400 € y consola de 400 € empieza a compensar si el precio del PC gamer está a partir de 1000 €.

Precio juegos

Tanto en PC como en consola los se hacen muy frecuentemente ofertas con grandes descuentos que pueden partir del 25% hasta llegar al 90%, y un juego que cuesta 70 € quedarse en 10 € y otros quedarse en 4 €. A 4 € juegos que en su momento de lanzamiento eran de lo mejor que había y que siguen siendo juegazos como Watch Dogs y The Witcher 3.

En el PC con plataformas como Steam y Epic Games quizá algunos juegos se encuentra algo más baratos que en las consolas puede estar en torno a 3-10 € a favor del PC.

Aún con esto dependiendo del precio del PC puede seguir compensando una consola incluso si se es del tipo de jugador que juega muchos de juegos.

Tipo jugador

Hay juegos que exigen más a los PC que otros. Y hay personas que juegan exclusivamente a uno o pocos juegos y otros jugadores que juegan a muchos juegos incluídas las novedades.

Los juegos competitivos en línea no requieren potentes PC y hay personas que juegan exclusivamente a unos pocos de estos como League of Legends o Counter Strike. Si se es de este tipo de jugador un PC puede salir más económico que PC más consola ya que el mismo PC valdría para los dos casos.

Otras personas juegan a las novedades como Red Dead Redemption 2 que a medida que pasa el tiempo requieren PC más potentes. Si se es de este tipo de jugador el PC más consola puede ser más económico.

Mando o teclado y ratón

Algunos juegos se juegan mejor con teclado y ratón que con mando. Y algunas personas prefieren teclado y ratón para jugar.

Esto no influye en el precio pero sin en la decisión de una persona de elegir PC o consola independientemente del precio.

Juego en línea

Otra diferencia entre PC y consola es que en los PC no es necesario pagar por poder jugar a través de internet con otras personas. En la PlayStation si es necesario adquirir una suscripción para algunos juegos que en el caso de la PlayStation llega a los 60 € por doce meses de juego.

Sistema

La mayoría de los juegos disponibles en PC se desarrollan para el sistema Windows de modo que hace necesario tener instalado este sistema operativo para evitar problemas de compatibilidades. La licencia de Windows llega a los 100 € a incluir en el precio del PC.

En GNU/Linux también se puede jugar a muchos juegos de Window sin problemas utilizando Wine o el cliente de Steam. Aún así es posible que requiera solventar algunos problemas con los controladores gráficos, instalar una distribución GNU/Linux cosa que no todos los usuarios quieren hacer. Para estos casos optar por la opción de la consola es la opción más sencilla.

También puede influir el tipo de PC que se quiera tener, un PC compacto como los NUC o un portátil no son adecuados para los juegos aunque sean capaces de mover los juegos lo hacen manteniendo altas temperaturas de 75º lo que no es bueno para estos equipos durante periodos de varias horas.

Retrocompatibilidad

Los juegos de las consolas requieren del hardware de la consola y cambiar a la siguiente generación puede que no sea posible jugar a los juegos antiguos en ella. En los PC comprar un nuevo PC no solo es posible jugar a los juegos antiguos sino que además es posible jugarlos en mejores calidades e incluso jugar a juegos de consola antiguos con emuladores.

En el caso de la PS5 y Xbox Series X se ha anunciado que son retrocompatibles con los juegos de anteriores generaciones, en el caso de pa PS5 con la mayoría de los juegos de la PS4 y en el caso de la Xbox Series X con todo el catálogo de las anteriores.

Disponibilidad juegos

Algunos pocos juegos son exclusivos de una plataforma y para jugarlos hay que tener esa plataforma. Esto es un argumento de venta para las consolas y el hecho de que haya más exclusivos en estas.

Dependiendo si un juego al que se quiere jugar es exclusivo de esa plataforma es necesario adquirirla. Algunos juegos son exclusivos de una plataforma de forma temporal y pasado ese tiempo sale en el resto de plataformas si a uno no le importa esperar. E incluso Horizon Zero Down que era exclusivo de PS4 se ha anunciado que saldrá en PC.

Juegos en streaming

En el futuro es posible que la decisión entre elegir PC y consola se termine y se sustituya por servicios de juegos mediante en streaming que ya están disponibles com Stadia, GeForce Now o PlayStation Now.

Junto con un PC muy modesto y barato e incluso portátil o NUC lo único que será necesario es una buena conexión a internet con un ancho de banda a partir de los 35 Mbits, muchos operadores de telecomunicaciones ya ofrecen ese ancho de banda y en muchos casos superado ampliamente de 100, 200 y 600 Mbps.

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

Blog Bitix

Novedades de Java 14

marzo 19, 2020 12:00

Java

Entre las novedades más destacadas que incorpora Java 14 están los records, la incorporación definitiva de las expresiones switch o el pattern matching para el operador instanceof. Otra de las novedades más destacadas es una traza de NullPointerException más útil, también destaca la posibilidad de utilizar el recolector de basura ZGC en Windows y macOS. El resto de novedades son la eliminación de algunas funcionalidades con poco uso y la preparación marcando como desaconsejado su uso con deprecated.

Las mejoras incluídas en esta versión son:

Excepciones NullPointerException más útiles

Cuando se produce una excepción NullPointerException por usar una referencia de objeto cuyo valor es null Java emite una traza indicando la línea de código donde se ha producido, la clase y método donde se ha intentado referenciar pero no se ha podido.

1
2
Exception in thread "main" java.lang.NullPointerException
    at Prog.main(Prog.java:5)
NullPointerException-1.out

Sin embargo, hay casos en los que la trazas de NullPointerException no es lo suficientemente precisa para determinar la causa de la excepción sin usar el debugger. En los siguientes ejemplos con elementos encadenados no es posible determinar cuál es la variable que ha originado la excepción por tener valor nulo.

1
2
3
4
a.b.c.i = 99;
a[i][j][k] = 99;
a.i = b.j;
x().y().i = 99;
NullPointerException-2.out

A partir de Java 14 las excepciones NullPointerException son más útiles e indican de forma precisa cual es el miembro de la línea de código que ha producido la excepción.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Exception in thread "main" java.lang.NullPointerException:
       Cannot assign field "i" because "a" is null
   at Prog.main(Prog.java:5)

Exception in thread "main" java.lang.NullPointerException:
       Cannot read field "c" because "a.b" is null
   at Prog.main(Prog.java:5)

Exception in thread "main" java.lang.NullPointerException:
      Cannot load from object array because "a[i][j]" is null
   at Prog.main(Prog.java:5)

Exception in thread "main" java.lang.NullPointerException:
       Cannot read field "j" because "b" is null
   at Prog.main(Prog.java:5)
NullPointerException-3.out

Records

Esta es la característica más destacada añadida al lenguaje que permite reducir significativamente el código necesario para algunas clases.

Los registros son clases que no contienen más datos que los públicos declarados. Evitan mucho del código que es necesario en Java para definir los constructores, los métodos getter, los setter e implementar de forma correcta los métodos equals y hashCode.

Para reducir el código de las clases de datos los registros adquieren automáticamente varios miembros:

  • Un campo privado y final para cada componente del estado en la descripción.
  • Un método de acceso de lectura para cada componente del estado de la descripción, con el mismo nombre y tipo.
  • Un constructor público cuya firma es la misma que el estado de la descripción que inicializa cada campo de su correspondiente argumento.
  • Una implementación de equals y hashCode de tal forma que dos registros son iguales si son del mismo tipo y contienen el mismo estado.
  • Una implementación de toString que incluye una representación de todos los componentes del registro con sus nombres.

Los registros tienen algunas restricciones:

  • No pueden extender ninguna otra clase y no pueden declarar campos que no sean los privados automáticos que corresponden a los componentes de la descripción del estado en la descripción. Cualquier otro campo debe ser declarado como static. estas restricciones aseguran que la descripción del estado define su representación.
  • Los registros son implícitamente final y no pueden ser abstract. Esto significa que no pueden ser mejorados por otra clase o registro.
  • Los componentes de un registro son implícitamente final. Esta restricción hace que sean inmutables.

Más allá de esta restricciones los registros se comportan como clases normales pudiendo declararse en su propio archivo de código fuente o anidada en otra clase, pueden ser genéricos, implementar interfaces e instanciarse con la palabra clave new. Pueden declarar métodos estáticos, propiedades estáticas, inicializadores estáticos, constructores, métodos de instancia y tipos anidados. El registro y los componentes individuales de los componentes pueden ser anotados.

Para dar soporte a los records al realizar tareas de reflection se añaden los siguientes métodos en la clase Class: RecordComponent[] getRecordComponents() y boolean isRecord()

De una clase como esta.

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

public class PhoneNumber {

    private Integer lineNumber;
    private Integer prefix;
    private Integer areaCode;

    public Integer getLineNumber() {
        return lineNumber;
    }

    public Integer getPrefix() {
        return prefix;
    }

    public Integer getAreaCode() {
        return areaCode;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null)
            return false;
        if (getClass() != o.getClass())
            return false;

        PhoneNumber that = (PhoneNumber) o;
        return super.equals(that)
            && Objects.equals(this.lineNumber, that.lineNumber)
            && Objects.equals(this.prefix, that.prefix)
            && Objects.equals(this.areaCode, that.areaCode);
    }
}
Records-1.java

Con los registros se define de la siguiente forma.

1
2
3
4
package ...;

public record PhoneNumber(Integer lineNumber, Integer prefix, Integer areaCode) {
}
Records-2.java

Pattern Matching para el operador instanceof

Al usar el operador instanceOf para comprobar si un objeto es una instancia de una clase si se realiza en un if posteriormente es necesario hacer un cast del objeto a la clase.

1
2
3
4
if (obj instanceof String) {
    String s = (String) obj;
    // use s
}
IfPatternMatching-1.java

Ahora el operador instanceOf permite renombrar la variable y dentro de la rama usarla sin necesidad de realizar el cast, esto simplifica el código y evita posibles errores.

1
2
3
if (obj instanceof String s) {
    // use s
}
IfPatternMatching-2.java

En futuras proposiciones de mejoras para el lenguaje de programación Java está planificado soportar pattern matching para otras construcciones del lenguaje como expresiones switch y sentencias. La incorporación de pattern matching permitirá reducir la verbosidad del código haciéndolo más fácil de leer y modificar.

La posible implementación en Java quizá sea similar a la implementación de C# para pattern matching.

Bloques de texto

En esta nueva revisión de los bloques de texto se definen dos nuevos caracteres de escape. El terminador de línea para poder definir bloques de texto en varias líneas pero sin insertar saltos de línea en el bloque de texto y \s para evitar que los espacios en blanco sean eliminados por la operación trim.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
String text = """
               Lorem ipsum dolor sit amet, consectetur adipiscing \
               elit, sed do eiusmod tempor incididunt ut labore \
               et dolore magna aliqua.\
               """;

String colors = """
   red  \s
   green\s
   blue \s
   """;
TextBlocks.java

Expresiones switch

La características de expresiones switch introducida en modo vista previa en las versiones de Java 12 y 13 se califica como estándar.

1
2
3
4
5
6
String numericString = switch(integer) { 
   case 0 -> "zero"; 
   case 1, 3, 5, 7, 9 -> "odd"; 
   case 2, 4, 6, 8, 10 -> "even"; 
   default -> "N/A"; 
};
Switch-1.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
String numericString = switch(integer) { 
   case 0 -> {
       String value = calculateZero();
       yield value;
   } ; 
   case 1, 3, 5, 7, 9 -> {
       String value = calculateOdd();
       yield value;
   };
   case 2, 4, 6, 8, 10 -> {
       String value = calculateEven();
       yield value;
   };
   default -> {
       String value = calculateDefault();
       yield value;
   };
};
Switch-2.java

ZGC para Windows y macOS

La versión del recolector de basura ZGC que permite pausas muy reducidas en memorias de unos pocos MB hasta varios TB ahora es posible utilizarla en los sistemas operativos macOS y Windows.

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

Picando Código

[Libro] La crisis de los 38 por Ignacio Alcuri

marzo 17, 2020 02:30

El prolífico escritor uruguayo Ignacio Alcuri es conocido por -entre otras cosas- sus libros de cuentos. Lleva publicado hasta el momento 8 títulos de cuentos cortos, la mayoría de humor y con chistes de los que más me gustan: escatológicos. No he leído todos estos libros (seguro más de la mitad), pero leí la novela gráfica que hizo con Gustavo Sala (Parto de Nalgas), hice un taller de relato breve con él, fuimos compañeros de nerdeo en Multiverseros (Q.E.P.D.), lo escuchaba en su programa de radio Los Informantes (y muchos años atrás Justicia Infinita en la época de las radio series de las precuelas de Star Wars), hemos conversado sobre la vida, el universo y todo lo demás varias veces y más.Ignacio Alcuri - La crisis de los 38

Todo esto para aclarar que éste post no es una crítica 100% objetiva del libro y mis impresiones del libro están directamente sesgadas por conocer a Nacho. Pero este es mi blog personal, así que ahí va con lo que tengo ganas de escribir.

La Crisis de los 38 es la primera novela de Nacho Alcuri, y trata de esto:

Santiago parece tener la vida perfecta: un empleo público, una relación sin ataduras y un amigo con el que puede juntarse en el momento que se le ocurra. Sin embargo, un día comenzará a replantearse su existencia y decidirá que sería mejor dedicarse a las cosas que realmente le gustan. ¡Si tan solo supiera cuáles son!

Intentará cambiar de empleo, de rutina física y hasta de parejas sexuales. Todo mientras el mundo parece estar más preocupado por el nuevo papa, un adolescente capaz de mover objetos con su mente, fanático de los videojuegos y las películas de Steven Seagal.

Pocos libros me han hecho largar la carcajada, éste es uno de ellos (a pesar de ir viajando en un ómnibus interdepartamental en el momento del chiste en cuestión). Algún otro libro de Nacho me había hecho largar la carcajada antes, probablemente uno de los cuentos sobre caca.

Si ya han leído a Nacho, o basado en lo que comento en el primer párrafo, verán que tiene un “estilo”. Podemos ver cambios y evolución en lo que escribe, así como tonos y humores distintos en distintos títulos, pero hay algunas características que se mantienen o al menos eso parece pensar mi cerebro a veces al momento de leer y sugerirme “Esto lo escribió Nacho Alcuri” (la caca no es necesariamente siempre una de éstas características).

A pesar de hablar de “cosas que definen su estilo de escritura”, una de esas características es la experimentación. No sigue siempre un formato de cuento estructurado donde vemos venir lo que va a pasar. Algunos de esos experimentos pueden terminar siendo un tweet, una lista, y otras manifestaciones que difícilmente puedan catalogarse como “cuento”. No sé cuánto retorno tiene de esos experimentos como para ver qué funciona y qué no en papel (o pantalla). Pero parece que gracias a la experiencia adquirida, usó lo que -por lo menos para mí- resulta más eficiente.

Con tanto cuento publicado es obvio que hay algunos que la pegan más que otros (el kilometraje de cada lector puede variar (y uso km en vez de millas porque expresión gringa pero tampoco venderse al sistema imperialista)). En “La Crisis”, encontré que casi todo está donde debe estar y no sobra nada. Y a pesar de ser una novela, encontramos muchos de esos experimentos literarios que comento y que son bastante disfrutables.

Creo que no tiene mucho sentido comentar la trama. La introducción en la contratapa tiene suficiente material como para engancharse. Describir más sería pisar el trabajo del autor en llevar a la historia y los personajes por lugares inesperados (y esperados). Hay personajes bien definidos y eventos conectados y desconectados cuyo orden y desorden le dan una cohesión muy interesante. Al final de todo podemos adentrarnos en la mente maestra (puedo decir eso porque en el taller de relato breve era el “maestro”) del autor, con material extra para conocer cómo diagramó la obra. Siempre está bueno conocer un poco más del “detrás de cámaras”.

Se puede llegar a pensar que el proyecto de una novela podría quedarle grande después de años de escribir cuentos cortos. Pero en mi humilde opinión está sobrado. Es una historia muy entretenida y al terminar de leer quedé con ganas de leer más. Ahora que sé que puede con la novela, le pediría otra pero más extensa. Aunque de repente esa fue una de las razones por las que la disfruté tanto, no le sobra nada. Habrá que ver qué escribe Nacho después de esto…

Quedé muy contento de haberlo disfrutado tanto y sentir que con su primera novela, Nacho Alcuri crece un poco más como escritor. El objetivo de escribirla fue que no vuelvan a preguntarle: “¿Y para cuándo la novela?”. Yo propongo que a partir de ahora le preguntemos: “¿Y para cuándo la secuela?”.

Pueden encontrar o pedir La Crisis de los 38 de Ignacio Alcuri en librerías en Uruguay. Sé de fuentes confiables que en breve también va a estar disponible en formato digital en alguna(s) plataforma(s). También pueden seguir a Nacho en Twitter.

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

Variable not found

Desinstala fácilmente versiones antiguas de .NET Core con "dotnet-core-uninstall"

marzo 17, 2020 07:05

.NET Core Hace unos días han anunciado la disponibilidad de una herramienta que os puede interesar si, como un servidor, acumuláis un gran número de versiones del SDK y runtimes de .NET Core que no váis a necesitar, algo muy propio de ese Diógenes digital que todos llevamos dentro ;)

El estado actual de acumulación de SDKs podéis conocerlo muy fácilmente desde línea de comandos utilizando la orden dotnet --list-sdks:
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.507 [C:\Program Files\dotnet\sdk]
2.1.508 [C:\Program Files\dotnet\sdk]
2.1.509 [C:\Program Files\dotnet\sdk]
2.1.511 [C:\Program Files\dotnet\sdk]
2.1.602 [C:\Program Files\dotnet\sdk]
2.1.604 [C:\Program Files\dotnet\sdk]
2.1.700 [C:\Program Files\dotnet\sdk]
2.1.701 [C:\Program Files\dotnet\sdk]
2.1.800-preview-009696 [C:\Program Files\dotnet\sdk]
2.1.801 [C:\Program Files\dotnet\sdk]
2.1.802 [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]
2.2.204 [C:\Program Files\dotnet\sdk]
3.0.100-preview8-013656 [C:\Program Files\dotnet\sdk]
3.0.100-preview9-014004 [C:\Program Files\dotnet\sdk]
3.0.100 [C:\Program Files\dotnet\sdk]
3.1.100 [C:\Program Files\dotnet\sdk]
3.1.101 [C:\Program Files\dotnet\sdk]
3.1.102 [C:\Program Files\dotnet\sdk]

C:\>_
En mi caso, tengo aún por ahí el SDK de .NET Core 1.x, un par de decenas de .NET Core 2.x, y algunas previews, pero los he visto mucho peores ;) Obviamente, salvo dos o tres versiones que quizás me interesen porque tengo aplicaciones que aún no he migrado, el resto ocupan en mi equipo un espacio considerable sin motivo, más de 5GB, pues cada SDK puede pesar entre 150 y 200 Mb.

Algo parecido ocurre con las versiones preinstaladas de los runtimes del framework, que podemos consultar con la orden dotnet --list-runtimes:
C:\>dotnet --list-runtimes
Microsoft.AspNetCore.All 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.15 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.15 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.0.0-preview8.19405.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.0.0-preview9.19424.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 1.0.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 1.1.10 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.15 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.0.0-preview8-28405-07 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.0.0-preview9-19423-09 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.0.0-preview8-28405-07 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.0.0-preview9-19423-09 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.1 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

C:\>_
El espacio ocupado por los runtimes no es tan grande como en el caso de los SDKs, pero aún así, en mi equipo subían de los 1.2Gb.

Ciertamente, podríamos eliminar gran parte de esta basura acudiendo a la configuración del sistema y desinstalando versión a versión, pero puede ser una labor tediosa si tenéis mucho acumulado.

La nueva herramienta dotnet-core-uninstall

La herramienta de línea de comandos dotnet-core-uninstall permite desinstalar estos elementos de forma más sencilla y rápida. Para instalarla, lo primero es acudir al repositorio del proyecto y, en el caso de Windows, descargar el archivo .MSI que encontraréis en la release más reciente; si tenéis un Mac, debéis seguir las instrucciones que aparecen en esa misma página.
Ojo: esta herramienta no funciona en Linux :(
Tras la correspondiente instalación, ya podremos utilizar la orden desde la consola.
C:\>dotnet-core-uninstall
Required command was not provided.

dotnet-core-uninstall:
Remove specified .NET Core SDKs or Runtimes. This tool can only uninstall items that were installed
using Visual Studio, .NET Core SDK, or Runtime installers. By default, this tool does not
uninstall versions that might be needed for Visual Studio. Read the documentation for the
.NET Core Uninstall Tool at https://aka.ms/dotnet-core-uninstall.

Usage:
dotnet-core-uninstall [options] [command]

Options:
--version Display version information

Commands:
list List .NET Core SDKs or Runtimes that can be removed with this tool.
whatif, dry-run <VERSION> Display .NET Core SDKs and Runtimes that will be removed.
remove <VERSION> Remove the specified .NET Core SDKs or Runtimes.

C:\>_
La misma ayuda ya nos da una idea clara de lo que podemos hacer con esta herramienta. Si probamos con dotnet-core-uninstall list veremos la lista de SDK, runtimes y hosting bundles que pueden ser desinstaladas:
C:\>dotnet-core-uninstall list

This tool can not uninstall versions of the runtime or SDK that are 
- SDKs installed using Visual Studio 2019 Update 3 or later.
- SDKs and runtimes installed via zip/scripts.
- Runtimes installed with SDKs (these should be removed by removing that SDK).
The versions that can be uninstalled with this tool are:

.NET Core SDKs:
3.1.102 x64 [Used by Visual Studio. Specify individually or use —-force to remove]
3.0.100 x64
2.2.204 x64 [Used by Visual Studio 2019. Specify individually or use —-force to remove]
2.2.203 x64
2.2.105 x64 [Used by Visual Studio 2017. Specify individually or use —-force to remove]
2.2.102 x64
2.1.802 x64 [Used by Visual Studio 2019. Specify individually or use —-force to remove]
2.1.801 x64
2.1.800-preview-009696 x64
2.1.701 x64
2.1.700 x64
2.1.604 x64
2.1.602 x64
2.1.512 x64 [Used by Visual Studio 2017. Specify individually or use —-force to remove]
2.1.511 x64
2.1.509 x64
2.1.508 x64
2.1.507 x64
2.1.505 x64
2.1.504 x64
2.1.503 x64
2.1.202 x64 [Used by Visual Studio. Specify individually or use —-force to remove]
1.1.11 x64 [Used by Visual Studio. Specify individually or use —-force to remove]

.NET Core Runtimes:

ASP.NET Core Runtimes:

.NET Core Runtime & Hosting Bundles:

C:\>_
En mi caso, podéis ver que no aparecen runtimes ni bundles. Esto es así porque todos ellos fueron instalados con su correspondiente SDK, y no podrían ser desinstalados de forma independiente: al desinstalar el SDK, los runtimes asociados correrán la misma suerte.

Para desinstalar un SDK basta con ejecutar la orden dotnet-core-uninstall remove <VERSION> --sdk, como por ejemplo:
C:\>dotnet-core-uninstall remove 2.1.503 --sdk
The following items will be removed:
Microsoft .NET Core SDK 2.1.503 (x64)

To avoid breaking Visual Studio or other problems, read https://aka.ms/dotnet-core-uninstall.

Do you want to continue? [Y/n] y
Uninstalling: Microsoft .NET Core SDK 2.1.503 (x64).

C:\>_
Si quisiéramos eliminar otro tipo de elementos podríamos indicarlo mediante los flags --runtime o --hosting-bundle. Asimismo, el comando soporta un gran número de opciones adicionales para poder realizar algunas de estas operaciones de forma masiva:
  • --all elimina todo
  • --all-previews elimina todas las versiones preview
  • --all-previews-but-latest elimina todas las versiones preview excepto la más reciente
  • --all-but <VERSION> elimina todo excepto la versión indicada
  • --all-lower-patches elimina todas las versiones que disponen de un parche superior
  • Podéis ver muchos otros flags en la documentación de la herramienta, muchos de ellos destinados a eliminar masivamente elementos que ya no necesitemos.
Como podéis imaginar, con estas opciones destinadas a la desinstalación masiva es fácil dispararse en un pie. Para evitarlo, existe una opción llamada "whatif" o "dry-run" que muestran por consola los elementos que serían eliminados si ejecutásemos la instrucción remove. Es importante utilizarla antes de desinstalar nada para comprobar que estamos haciéndolo correctamente:
C:\>dotnet-core-uninstall whatif --all-previews --sdk
*** DRY RUN OUTPUT
Specified versions:
Microsoft .NET Core SDK 2.1.800 - preview (x64)
*** END DRY RUN OUTPUT

C:\>dotnet-core-uninstall whatif --all-but-latest --sdk
*** DRY RUN OUTPUT
Specified versions:
Microsoft .NET Core SDK 3.0.100 (x64)
Microsoft .NET Core SDK 2.2.203 (x64)
Microsoft .NET Core SDK 2.2.102 (x64)
Microsoft .NET Core SDK 2.1.801 (x64)
Microsoft .NET Core SDK 2.1.800 - preview (x64)
Microsoft .NET Core SDK 2.1.701 (x64)
Microsoft .NET Core SDK 2.1.700 (x64)
Microsoft .NET Core SDK 2.1.604 (x64)
Microsoft .NET Core SDK 2.1.602 (x64)
Microsoft .NET Core SDK 2.1.511 (x64)
Microsoft .NET Core SDK 2.1.509 (x64)
Microsoft .NET Core SDK 2.1.508 (x64)
Microsoft .NET Core SDK 2.1.507 (x64)
Microsoft .NET Core SDK 2.1.505 (x64)
Microsoft .NET Core SDK 2.1.504 (x64)
*** END DRY RUN OUTPUT

C:\>_
En la documentación de la herramienta hacen las siguientes advertencias:
  • Hay que tener en cuenta que podríais eliminar versiones requeridas de forma local por un global.json. En este caso, deberíais volver a instalar el SDK desde la página de descargas de .NET Core.
  • De la misma forma, habría que reinstalar si eliminamos runtimes requeridos por aplicaciones desplegadas en nuestro equipo como framework dependent.
  • También podríamos eliminar por error versiones requeridas por Visual Studio; en este caso, deberemos reparar la instalación.
Publicado en Variable not found.

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

Blog Bitix

Listado de programas esenciales según categoría para GNU/Linux

marzo 13, 2020 11:00

El sistema operativo solo proporciona la abstracción del hardware a los programas de usuario. Son los programas los que permiten al usuario realizar las tareas productivas. Según su categoría hay varios programas según los tipos de archivos que soportan o funcionalidad que proporcionan. Ofimática, internet y comunicaciones, fotos y gráficos, multimedia, juegos, programación y desarrollo seguridad y privacidad, …

GNU

Linux

Un sistema operativo como GNU/Linux, Windows o macOS tiene poca utilidad sin los programas de usuario que permiten realizar tareas. Para las tareas más comunes normalmente hay varios programas entre los que elegir, con una traducción al español e interfaz gráfica.

En GNU/Linux además la mayoría de los programas son software libre. Las ventajas del software libres son que el código fuente está disponible con lo que no pueden contener funciones maliciosas ocultas, está permitido modificar el código fuente y redistribuirlo bajo la misma licencia y compartir el programa con cualquiera que lo necesite. No tiene que ser así pero generalmente los programas de software libre son generalmente gratuitos ni requieren costosas licencias de uso.

En esta lista no incluye los programas básicos que integra un entorno de escritorio por defecto como GNOME sino solo los programas adicionales comunes que se suelen instalar para complementar los que ya están instalados en el sistema.

Los programas incluidos en esta lista solo son una pequeña muestra seleccionada de todos los disponibles que están entre los más usados y considerados por los usuarios.

Todos estos programas están disponibles como paquete para cada distribución GNU/Linux, si no lo estuviera quizá esté disponible como paquete Flatpak, snaps o AppImage para instalar el software independientemente de la distribución.

Índice de categorías de programas:

Ofimática, documentos y escritorio

La ofimática es la funcionalidad más común a todos los usuarios. Poder editar documentos de texto, utilizar hojas de cálculo aplicando fórmulas y crear presentaciones. Un editor de texto avanzado tiene más funcionalidad si son necesarias que el simple editor de texto incluido en los entornos de escritorio. Visualizar archivos digitalizados como libros electrónicos y archivos de cómics es necesario para aquellos usuarios que les gusta la lectura. Tomar capturas de pantalla es otra funcionalidad para que los entornos de escritorio ya incluyen algún programa pero también suelen ser simples aunque suficientes, para los usuarios que necesitan alguna funcionalidad hay algún programa más especializado.

Editor de documentos ofimáticos

LibrerOffice es el paquete ofimático más popular y completo de GNU/Linux pero su interfaz no es la más agradable visualmente y muchos usuarios no necesitan las funcionalidades más avanzadas. OnlyOffice es un paquete ofimático más sencillo y solo incluye las aplicaciones básicas editor de documentos, hoja de cálculo y presentaciones. WPS Office no es software libre pero posee una versión gratuita con limitaciones con una interfaz cuidada parecida a Microsoft Office.

Editor de textos avanzado

Visual Studio Code es un editor de texto avanzado con funcionalidades útiles para los usuarios avanzados como una terminal integrada. Se le pueden añadir complementos que casi lo convierte en un IDE sin llegar al nivel de uno dedicado. Posee muchas combinaciones de acceso rápido para aumentar la productividad al acceder a funcionalidades usadas repetidamente.

Libros electrónicos y cómics

Los libros electrónicos ya son muy asequibles y aportan comodidad en menor peso y espacio sobre los libros impresos en papel. Hay múltiples formatos de libro electrónico y los lectores soportan únicamente algunos de ellos por lo que es necesario un programa para realizar una conversión entre formato en el que se tiene el libro y el que soporta el lector. Un ejemplo es el Amazon Kindle que no soporta el formato epub y hay que realizar la conversión a azw o mobi. Un conversor de formato de libros electrónicos y organizador de la biblioteca digital es Calibre.

Otros formatos de libros son los cómics y estos por su formato gráfico son imágenes empaquetas en un único archivo. Para leer estos archivos y proporcionar una interfaz adaptada a las necesidades de los cómics hay programas para estos tipos de archivos. MComix es una aplicación dedicada a la lectura de cómics en el ordenador.

Capturador de pantalla

Hacer una captura de pantalla es muy útil para compartir la imagen de una aplicación, una parte del escritorio o el escritorio completo. Los entornos de escritorio ya suelen incluir un capturador de pantalla, las aplicaciones dedicadas incluyen alguna funcionalidad adicional como Shutter.

Otra forma de captura del escritorio es capturar un vídeo del escritorio, incluído el sonido. recordMyDesktop graba el entorno de escritorio sin necesidad de un hardware dedicado como una capturadora de vídeo.

Copias de seguridad

Es muy importante realizar copias de seguridad de la información digital para no perderla si por cualquier circunstancia el sistema de almacenamiento o dispositivo se estropea impidiendo acceder a los archivos. FreeFileSync permite hacer copias de los archivos en varios dispositivos de almacenamiento de forma rápida copiando solo los archivos que hayan sido modificados desde la fecha de la última copia de seguridad.

Internet y comunicaciones

El acceso a internet ya está disponible para mayoría de la población de las sociedades desarrolladas, ha multiplicado las posibilidades de los ordenadores y posibilitado diferentes formas de comunicación instantánea con personas que están a miles de kilómetros.

El navegador web es una pieza fundamental en el ordenador que permite acceder a las páginas web de internet, realizar descargas de archivos y otra multitud de tareas que hoy en día ya se puede realizar desde leer periódicos digitales a compras y transferencias de dinero en la banca digital.

Dada la importancia que ya se realizan con estos programas y la información de los usuarios que tratan es importante que estén actualizados a la última versión con todas las correcciones de seguridad aplicadas y que incluya funcionalidades para proteger la privacidad de la información de los usuarios. Firefox y Chromium son de los más populares.

Correo electrónico

Con el correo web como GMail o Protonamail las aplicaciones de correo electrónicos de escritorio no son imprescindibles. Sigue habiendo algunos programas dedicados que posibilitan algunas funcionalidades como firma digital o cifrado y descifrado con GnuPG. Dos aplicaciones de correo electrónico son Thunderbird y Geary.

Lector de feeds

También hay lectores de feeds web como Feedly pero los usuarios pueden preferir un programa dedicado local en el ordenador, Liferea es uno.

Descargar archivos torrent y descargas directas

Para descargar películas, series y libros electrónicos se utilizan archivos torrent un programa que descargue el contenido de los usuarios que lo tengan, es una descarga distribuida y suele ofrecer una buena velocidad de descarga si lo tiene varios usuarios, un programa para descargar archivos torrent es Transmission.

Si es posible es mejor utilizar descarga mediante torrents, si no es posible y se ofrece la descarga directa está JDownloader que posibilita detener y continuar la descarga en el punto que se quedó.

Mensajería instantánea

Telegram y Whatsapp son las mensajerías más utilizadas por su ubicuidad con el auge de los smartphones. Pidgin permite el envío de mensajes entre personas con un ordenador.

Para el trabajo en grupo Slack es una evolución de los programas como Pidgin y muy utilizado en las empresas. Es otra forma de comunicación más apropiada en ciertas circunstancias que la comunicación mediante correo electrónico. Slack ofrece además de mensajes de texto, imágenes y reacciones permite llamadas de audio y videoconferencias.

Videoconferencia

Skype permite hacer video llamadas por internet.

Nube privada

Google ofrece multitud de servicios gratuitos que poseen gran cantidad de información personal, en GMail los mensajes enviados y recibidos de otros usuarios, en Google Docs los documentos, en Calendar las citas y otra información utilizando más servicios. Para proteger la información personal Nextcloud permite sustituir varios de esos servicios en la nube por una nube privada propia.

Fotos y gráficos

El retoque fotogŕafico permite crear imágenes y retocar fotografías en formato vectorial o formada por píxeles en formato raster, permite retocar las imágenes realizadas con móvil.

Retoque fotográfico

Para un uso personal GIMP permite sustituir programas que requieren costosas licencias com Adobe Photoshop, incluso para un uso profesional salvo quizá una función muy avanzada GIMP también es una muy buena opción. En cada nueva versión se mejoran sus funcionalidades y se añaden nuevas.

Inkscape permite editar y crear imágenes en formato vectorial.

Multimedia, vídeo y audio

Reproducir vídeos, películas, archivos de audio y música son otra categoría de archivos para los que se necesitan programas específicos. Algunos programas pueden hacer todo lo anterior y otros solo se centran en la música y archivos de audio. Otros programas permite crear vídeos a partir de una colección de archivos de vídeo y audio. También suele ser necesario realizar conversiones entre formatos de archivos para reproducirlos en los que soporte el programa que los reproduce.

Reproductor de vídeo, audio, películas y música

VLC es un programa que realiza multitud de funcionalidades, sus pricipales son reproducir archivos de vídeo y audio pero también permite realizar conversiones e incluso ser el programa que capture el vídeo de una capturadora hardware. Su interfaz no es la más estéticamente bonita pero funciona realmente bien y es capaz de reproducir cualquier formato de archivo multimedia.

Para reproducir música los programas Rhythmbox y Banshee permite organizar la librería digital de archivos de música.

Editor de vídeo

Con una colección de archivos de vídeo, imágenes y archivos de audio y un programa como OpenShot permite crear un vídeo aplicando varios efectos y transiciones, el resultado es un nuevo vídeo que incluye parte o todo el contenido individual original.

Conversor de vídeo y audio

Al igual que en otros formatos de archivos es posible necesitar realizar conversiones entre formatos. Tanto de vídeo con Handbrake y audio con Soundconverter. Además de hacer conversiones es posible ajustar la calidad del nuevo vídeo para hacer que tenga menos calidad y ocupe menos.

Otros programas específicos

Audacity es un programa para la edición de audio y Blender un programa de modelado 3D. MusicBrainz Picard permite editar los metadatos de los archivos de música para que los reproductores muestren la información correta del título, álbum, grupo musical o género.

Juegos

GNU/Linux no es la opción más popular para los juegos que está dominado por Windows pero ya hay algunos programas que permiten descargar e instalar juegos de forma muy sencilla e incluso jugar a los juegos que han sido desarrollados para Windows. También hay algunos buenos juegos desarrollados para GNU/Linux.

Steam y Wine permiten juga a juegos de Windows, en Steam hay juegos compatibles de forma nativa para GNU/Linux. GOG permite jugar a juegos ya con unos años pero que fueron notables en su época. Mame, Lakka y Retroarch permite jugar hoy en día a juegos de arcade aún más antiguos, de recreativas y de la generación de las primeras consolas.

Programación y desarrollo

Si algo destaca GNU/Linux es en el aspecto de programas disponibles para tareas de programación y desarrollo, hay multitud de programas y es uno de los sistemas operativos con más variedad y mejor preparado para este fin.

Compiladores

En GNU/Linux hay compiladores para cualquier lenguaje de programación ya sea Java, C, C#, Python, Rust, Go y muchos otros. GCC permite compilar los archivos de código fuente y producir el archivo ejecutable.

Entorno integrado de desarrollo

Los entornos integrados de desarrollo o IDE ofrecen funcionalidades que permiten aumentar la productividad al escribir código como la asistencia de código y mostrar la propia documentación de cada método y nombres de los parámetros. Así como depurar el código mientras se ejecuta.

En Java los tres IDEs más populares son IntelliJ IDEA, eclipse, Apache Netbeans.

Bases de datos y software de servidor

Las aplicaciones normalmente guardan sus datos en almacenes de datos, esto son las bases de datos. Hay varios tipos de bases de datos, dos grandes grupos son las basadas en el modelo relacional como PostgreSQL y MySQL y las noSQL como Redis o MongoDB.

Hay otro tipo de programas útiles en el desarrollo como los servidores web como Nginx, mensajería como RabbitMQ o tecnología de contenedores como Docker.

Virtualización

La virtualización permite ejecutar un sistema dentro de otros sistema, por ejemplo, ejecutar Windows en GNU/Linux o una distribución GNU/Linux en Windows. También permite probar macOS en GNU/Linux o incluso alguna ditribución BSD o Minix.

Seguridad y privacidad

Un aspecto al que se le da gran importancia en GNU/Linux es a la seguridad, está construido con la seguridad como una característica importante. También la privacidad es otro aspecto destacado. Los programas son software libre y dado que el código fuente está disponible los programas no contienen funciones maliciosas ocultas que recopilan información del usuario sin su conocimiento.

Tampoco suele ser necesario instalar un antivirus ya que los programas al ser gratuitos no es necesario buscar activadores que permitan el uso de los programas sin haber adquirido la licencia previamente. Los activadores de programas son una de las principales fuentes de entradas de virus en un sistema, no solo porque son programas que se ejecutan en el sistema que pueden quedar residentes realizando funciones desconocidas pero no con buenos propósitos sino también por el tipo de páginas de baja reputación e inseguras a las que hay que acceder para descargarlos.

Gestor de contraseñas

Las contraseñas deben ser únicas para cada sitio web, tener una longitud de entre 8, 16 o más caracteres y combinar letras, números y símbolos. Estas restricciones hacen que las contraseñas sean difíciles de recordar. Precisamente por estos motivos hay programas que guardan cifradas bajo una contraseña maestra la colección de credenciales de cada sitio web. KeepassXC es una base de datos que guarda las credenciales e información adicional cifrada, también permite generar contraseñas únicas que complan los requisitos deseados de longitud y tipos de caracteres que deben incluir.

Cifrado y descifrado de archivos

GnuPG permite cifrar y firmar digitalmente archivos y contenido, esto permite comprobar que el archivo no ha sido modificado en la transmisión y que el archivo proviene de la persona que lo ha firmado. Esto es un mecanismo importante en la seguridad y las comunicaciones. GnuPG tiene complementos para integrase en Thunderbird para firmar y cifrar el correo electrónico. GnuPG Assistant es una aplicación con interfaz gráfica que posibilita hacer operaciones de firma y cifrado.

EncFS permite crear un sistema de archivo cifrado de modo que su contenido esté protegido por una contraseña.

Cortafuegos

UFW permite restringir el tráfico de red aceptado por la computadora tanto entrante como saliente, para exponer únicamente en interner los recusos imprescindibles de la computadora. Es fácil de utilizar e incluso tiene una interfaz gráfica.

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

Fixed Buffer

Herramientas de desarollo: Github Actions como CI para .Net Core

marzo 10, 2020 09:30

Tiempo de lectura: 6 minutos
Imagen ornamental con el logo de Github Actions para la entrada Herramientas de desarollo: Github Actions como CI para .Net

Después de unas semanas apasionantes hablando sobre código de alto rendimiento en .Net Core y de cómo escribir un logger de alto rendimiento acorde a las mejoras sobre el código, es hora de volver a la realidad del día a día.

Hace unos meses que quería hablar de Github Actions y .Net Core (o cualquier otro lenguaje) ya que me parece una herramienta de CI super interesante al estar integrada perfectamente en Github.

¿Qué es Github Actions?

Hace ya unos cuantos meses Github anunció que iba a introducir un sistema de integración y despliegue continuo. Unos pocos meses después se concedió acceso a una beta y en noviembre se anunció la disponibilidad general para el público.

Github Actions surgió como un ‘fork‘ de Azure Pipelines que poco a poco va evolucionando por su propio camino. Ahora mismo es como el hermano pequeño de los pipelines con una funcionalidad más «limitada» pero solo el futuro dirá en que se acaban convirtiendo.

¿Por qué es interesante Github Actions?

Aunque en esta entrada vayamos a hablar de Github Actions como herramienta para .Net Core, no está ligado a este ni mucho menos. Se puede utilizar con muchísimos lenguajes y para muchísimas finalidades.

Una pregunta que podemos plantearnos llegados a este punto es que aporta este sistema de CI/CD en concreto si tenemos ya otros que hacen la misma labor. Sin ir más lejos en este blog hemos hablado sobre cómo hacer CI/CD con Azure Pipelines, o cómo hacer integraciónes en Travis CI y Appveyor.

La respuesta al igual que en otros muchos casos es la sencillez que aporta tener todo en una herramienta unificada. Históricamente Github ha sido un repositorio público de código fuente, pero ha ido añadiendo paulatinamente como las tablas de kanban que permiten guiar el desarrollo del código con metodologías ágiles. Con esta nueva funcionalidad Github se ha convertido en una suite completa que permite la gestión integral del proyecto desde un punto unificado.

Vale vale, mucha teoría. ¿Cómo pongo en marcha un flujo de trabajo con Github Actions para .Net Core.

La verdad es que es algo extremadamente sencillo ya que Github Actions utiliza un yaml muy parecido al de Azure Pipelines, por lo que, si habitualmente trabajas con este último, no debería ser ningún esfuerzo crear un workflow de Github Actions para .Net Core.

Creando un workflow de Github Actions para .Net Core

Lo primero que vamos a tener que hacer para crear un workflow de Github Actions para .Net Core es pulsar sobre el botón ‘Actions‘ que se encuentra en el panel superior del repositorio.

La imagen señala con una flecha el botón de Actions en el panel de herramientas de Github.

Una vez hecho eso basta con que seleccionásemos el workflow de Github Actions para .Net Core (o el lenguaje que usemos).

La imagen señala el botón "set up this workflow" de .Net Core dentro del menu de Github Actions

Con este paso tan sencillo, vamos a comprobar que Github ya nos ofrece un template listo para poder funcionar:

name: .NET Core

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.1.101
    - name: Build with dotnet
      run: dotnet build --configuration Release

En fichero consta de 2 grandes secciones, las condiciones de ejecución y los jobs a ejecutar (la primera línea indica el nombre del workflow en la visualización).

La primera sección está dentro de la etiqueta on: e indica en qué casos debe ejecutarse el workflow, en este caso nos ofrece ejecutarse en cada push o pull request a la rama master, pero si queremos que se ejecute siempre en cualquier rama podríamos cambiarlo por algo como esto:

on: [push, pull_request]

Al no indicarle parámetros, se ejecutará en esos casos sin condiciones.

>Existen una gran cantidad de eventos disponibles, no solo relativos al código sino a otros muchos eventos de Github, te recomiendo leer la lista completa por si algún otro te resulta interesante.

La segunda sección son los trabajos en si que se han de realizar. En este caso estamos creando un único job que se ejecutará sobre ubuntu e instalará .Net Core 3.1.101 y después realizará la compilación.

Evidentemente este trabajo no está completo ya que faltan cosas como las pruebas, empaquetar el código para poder publicarlo, generar cobertura, y cualquier otra cosa que pudiésemos necesitar durante el proceso.

En este caso, vamos a simplemente añadir la ejecución de las pruebas añadiendo dentro de steps:

- name: Test with dotnet
  run: dotnet test --configuration Release

Simplemente estamos indicando que dentro de los pasos que tiene que ejecutar el job se ejecuten también las pruebas.

Probando nuestras Github Actions para .Net Core en múltiples plataformas

En estos momentos ya tenemos una integración perfectamente funcional con Github Actions para un proyecto .Net core (o del lenguaje que sea con unos mínimos cambios). El problema aquí es que .Net Core es multiplataforma y como tal deberíamos probarlo en varias plataformas para garantizar que funciona correctamente.

Para solventar este problema podríamos simplemente copiar y pegar el trabajo y cambiar la clave runs-on: pero se quedaría un poco feo ¿no? Aquí al igual que ocurre en otros sistemas de integración podemos aplicar matrices de variables que utilizará el trabajo para conseguir así reutilizar el código.

Vamos a definir una estrategia (strategy) dentro del job justo encima de los steps y dentro vamos a definir una matriz con una variable que indicará el tipo de agente donde debe ejecutarse el trabajo.

strategy:
  matrix:
    agent: ['windows-latest','ubuntu-latest','macos-latest']

Después, vamos a modificar la clave runs-on y vamos a hacer que, en vez de un literal, reciba esa variable. Para indicar una variable en Github Actions vamos a necesitar indicar el nombre de la variable dentro de ${{x}}.

runs-on: ${{matrix.agent}}

Con estos dos pequeños cambios hemos conseguido que nuestro trabajo de integración se ejecute tanto en Windows como en Linux como en MacOs. Basta con hacer el commit para poder encontrarnos con que ya se ejecutan los 3 trabajos.

La imagen muestra una vista de la interfaz de Github para un workflow de Github Actions para .Net Core donde se ejecutan 3 trabajos, uno en cada plataforma

Es posible si queremos clarificar los elementos utilizar la clave name para indicar un nombre especifico en vez del nombre por defecto. Por ejemplo, podemos añadir al trabajo

name: Example executed over ${{matrix.agent}}

Con ese sencillo cambio utilizando la misma variable con la que seleccionamos el agente, vamos a tener un cambio en la visualización consiguiendo poner un mensaje más específico.

La imagen muestra la visualización con el mensaje especifico "Example executed over" y el sistema operativo del agente para cada uno de los 3 casos

Por último, al igual que otros sistemas de integración, Github Actions dispone de un sistema de badges con el que poder reflejar el estado del trabajo en diferentes sitios como puede ser el readme del proyecto. Esto se puede conseguir desde el botón ‘Create status badge‘ dentro de la vista de Github Actions.

La imagen señala el botón 'Create status badge' en la parte derecha de la interfaz de usuario de Github Actions

Conclusión

Aunque Github Actions es una característica de Github muy nueva y que llega a un mercado en el que ya hay otros muchos sistemas establecidos, es una herramienta perfectamente integrada con Github. Esto facilita mucho la labor de gestión centralizada de todos los elementos de un proyecto y es algo a tener en cuenta.

Aunque ahora mismo es la versión ‘pequeña’ de Azure Pipelines, tiene multitud de acciones ya disponibles que suplen la mayoría de los casos (de hecho existe hasta un buscador). Precisamente por eso es una herramienta muy interesante a la que puede ser muy útil seguir la pista, ¿no crees?

Cómo es habitual, he creado un repositorio en Github (donde sino para usar Github Actions) con el código del proyecto para poder probarlo y trastear con el workflow.

**La entrada Herramientas de desarollo: Github Actions como CI para .Net Core se publicó primero en Fixed Buffer.**

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

Arragonán

Evaluando Consumer-Driven Contract Testing con Pact

marzo 09, 2020 12:00

Pact es una librería con soporte en múltiples lenguajes de programación para hacer Consumer-Driven Contract Testing de integraciones vía HTTP y mensajería.

Desde que hace la tira Iván Loire me descubriera en un tuit un poco de rebote su existencia, había vuelto a cruzarse por mi radar en un par de ocasiones la posibilidad de evaluarlo, pero por unas razones u otras terminaba sin priorizarlo y caía en el olvido.

Hace unas semanas Alberto Gualis andaba preparando un taller de Introducción al contract testing con Pact para hacerlo en Bilbao Software Crafters. Él estaba empezando a probarlo para testesar los contratos entre aplicaciones frontends con sus respectivos backends y a raíz de eso estaba preparando el taller.

Ahora que compartimos oficina estuvimos discutiendo sobre ello, y enfrentándolo a otro tipo de soluciones como Dredd para testear los documentos de descripción de APIs, librerías de Record & Replay como VCR o Wiremock para hacer tests de integración con servicios externos guardando peticiones reales, etc. Así que al final me lió para echarle un cable para hablar de ese tipo de herramientas en el taller y complementar un poco más el contenido.

Esta fue la excusa perfecta para forzarme a hacer un spike para entenderlo y poder evaluarlo mejor. Hace unos pocos meses ya estuve pensando en darle un tiento por ver si tenía sentido introducirlo en Devengo para enterarnos rápido de si algún cambio entre el API y las apps móviles rompe con el contrato esperado.

¿Cómo funciona Pact?

La visión de Consumer-Driven Contract Testing de Pact es que los clientes o consumers son quienes generan el contrato, y luego el servidor o provider deberá comprobar si es capaz cumplir con él.

El flujo simplificando un poco es el siguiente:

  • Escribir los tests de integración del consumer usando alguna de las librerías de Pact para hacer stubs de las respuestas que se esperarían del provider.
  • Al lanzar los tests, si pasan en verde se genera un fichero JSON que describe el contrato esperado por el consumer. He dejado un ejemplo sencillote en gist.
  • En otro momento el provider lanza con Pact la verificación. Busca que pueda cumplir con lo descrito en ese JSON simulando las peticiones que haría el consumer y comprueba que puede responder lo esperado.

Gif explicando el flujo de trabajo de Pact

Al lanzar los tests de forma independiente sobre cada artefacto frente a hacerlo end to end tenemos un feedback más rápido, nos facilita el mantenimiento de esos tests y nos da mayor estabilidad. Todo esto sin perder confianza de que hemos roto el contrato entre los artefactos.

Integración con los pipelines de CI

Evidentemente si optamos por una solución así querremos integrarla en nuestro pipeline de integración continua.

Normalmente no sólo querremos que se validen los contratos cuando se suba un cambio al repositorio del provider, si no que si cambia el fichero que representa al contrato de un consumer queremos desde el provider se compruebe que se sigue cumpliendo ese contrato (o no).

Al generarse ficheros JSON planos no suena descabellado orquestar con un puñado de scripts y hooks estas comprobaciones, pero ya hay un proyecto dentro del paraguas de la fundación Pact que nos ayuda a resolver eso: Pact Broker.

En Pact Broker se guardan los contratos de los consumers y es donde los providers irán a validarlos, nos facilita el lanzamiento de webhooks y el trabajar con distintas versiones de los contratos.

Conclusiones por el momento

Sólo he trabajado con la parte de HTTP, no he hecho ninguna prueba con el soporte de mensajería.

Para mis pruebas he usado la gema de ruby tanto para el provider como para un consumer, además de la librería de JVM para otro consumer. Inicialmente me parece que deja un pelín verboso la preparación de los tests en ambos lenguajes, pero no he hecho aún el ejercicio de intentar esconder eso en algún tipo de helpers que mejorarían la legibilidad de los tests.

El DSL de la librería de JVM me pareció que dejaba código difícil de seguir para ver las estructuras de respuesta esperadas, luego vi que existe el Lambda DSL que mejora el asunto. Esto es porque aún habiendo buena y abundante documentación y ejemplos, algunos resultaron estar algo desfasados.

La integración de Pact Broker en nuestro pipeline de CI con Travis resultó bastante sencilla, para evitarme montar infraestructura propia lo hice con Pactflow que es un Pact Broker en SaaS. Para publicar o validar con Pact Broker usé la gema pact_broker-client y el plugin de gradle. En el travis.yml tocó añadir la publicación de los contratos de los consumers y la validación de los contratos en el provider; mientras que en pactflow configué los webhooks que disparan la build de travis del provider cuando el contrato cambia.

Aunque ofrece un flujo donde el consumidor puede empezar a trabajar sin que el proveedor esté disponible, el consumidor debería tener buena comunicación e influencia sobre lo que publica el proveedor. Claramente tanto para servicios externos como equipos que trabajen aislados es una herramienta que no aportará valor.

Y definitivamente creo que cuadra genial con una aproximación API first, acordando el equipo del proveedor con los de los consumidores cómo se va a hacer esa integración para poder empezar a trabajar en paralelo, asegurándonos así que estamos cumpliendo con lo que hemos acordado. Así que inicialmente pinta genial, pero está por ver cómo afecta al flujo del trabajo en el día a día y del contexto de cada uno.

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

Blog Bitix

Desarrollar componentes React con TypeScript y sistemas de diseño con Storybook

marzo 06, 2020 08:00

Con Storybook los componentes de React, Vue o Angular es posible desarrollarlos de forma aislada sin necesidad de hacerlo una una de las aplicaciones finales donde se usen. Esto permite independizar su desarrollo de las aplicaciones finales y proporciona un entorno donde hacerlo. Con complementos permite realizar pruebas unitarias y pruebas visuales.

TypeScript

html.svg

Storybook

La web ha evolucionado enormemente desde las simples páginas estáticas con contenido HTML, imágenes y hojas de estilos. Con posterioridad se añadió un lenguaje de programación en el navegador del lado del cliente para realizar tareas en las propias páginas como validaciones de formulario. A medida que el tiempo ha pasado los navegadores han implementado nuevos estándares y a través de JavaScript ahora hay posibilidad de desarrollar tareas en el lado de cliente que rivalizan con las aplicaciones tradicionales de escritorio.

Algunas de estas nuevas capacidades de JavaScript son nuevas versiones del lenguaje con ECMAScript con soporte para módulos, WebGL o componentes de lado de cliente con Web Components. Con las nuevas capacidades de JavaScript han surgido una comunidad de JavaScript con numerosas librerías entre las que elegir para realizar tareas. Una de las áreas son los componentes de lado del cliente, el estándar que define la W3C son los Web Components pero hay algunas otras alternativas que sustituyen o complementan como React, Vue o Angular.

Para desarrollar componentes en lado del cliente se necesita la aplicación final donde se van a usar, si se está desarrollando una librería para ser usada en múltiples aplicaciones de una organización o incluso un sistema de diseño o design system para la organización es muy útil poder desarrollar, probar y ejecutar estos componentes de forma aislada de la aplicación donde se usen.

En este artículo muestro cómo utilizar Storybook, componentes React con TypeScript, pruebas unitarias con Jest y visuales con Jest Image Snapshot, archivos CSS con Less finalmente como crear un paquete de npm para utilizarlo en otra librería o proyecto.

Desarrollo aislado de componentes, sistemas de diseño y documentación con Storybook

Storybook es una herramienta que permite desarrollar los componentes de React, Vue o Angular entre otros de forma aislada, es como una caja de arena donde desarrollarlos, probarlos y además documentarlos. El desarrollo incluye las pruebas unitarias con Jest y visual testing con un complemento para Jest que permite si con algún cambio ha habido alguna variación en el aspecto visual de un componente a nivel de píxel. También permite ver el comportamiento de los componentes en diseños resposive y ver su documentación así como en diferentes configuraciones.

Storybook puede utilizarse para implementar un design system de una organización y ver los diferentes colores, estilos y componentes en ejecución y no solo como un diseño. Esto hace que el diseño y la implementación del diseño se mantengan sincronizados y no surjan inconsistencias entre ellos.

En su guía de inicio un comando permite crear la estructura de archivos para empezar a usarlo.

1
$ npx -p @storybook/cli sb init --type react
storybook-create.sh

Con otro comando se inicia un servidor que genera la página web para Storybook. Por defecto hay dos historias o stories con dos componentes de React propios de Storybook. Las stories son las definiciones de las variaciones de los componentes o del design system.

1
$ npm run storybook
storybook-run.sh

Código de definición de una historia.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import React from 'react';
import { linkTo } from '@storybook/addon-links';
import { Welcome } from '@storybook/react/demo';

export default {
  title: 'Welcome',
  component: Welcome,
};

export const ToStorybook = () => <Welcome showApp={linkTo('Button')} />;

ToStorybook.story = {
  name: 'to Storybook',
};
0-Welcome.stories.tsx

Según los parámetros de los componentes estos tiene variaciones, en el ejemplo si se indica un parámetro muestra un mensaje por defecto si se le pasa un parámetro con un nombre muestra un mensaje con ese nombre.

Historia de bienvenida Componente HelloWorld con TypeScrtipt Variación del componente HelloWorld

Historia de bienvenida y componente HelloWorld

Storybook ofrece dos formas de desarrollar las stories, en formato Component Story Format o CSF o con la sintaxis MDX que es similar a Markdown con algunas cosas adicionales para poder añadir visualizaciones de componentes. El formato MDX permite añadir texto y documentar con descripciones las stories.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react';
import HelloWorld from '../src/components/HelloWorld';

export default {
  title: 'HelloWorld',
  parameters: {
    componentSubtitle: 'Componente básico de ejemplo',
  },
  component: HelloWorld,
};

export const HelloWorldStory = () => <HelloWorld />;
export const HelloNameStory = ({ name: String }) => <HelloWorld name="picodotdev"/>;

HelloWorldStory.story = {
  name: 'HelloWorld',
};

HelloNameStory.story = {
  name: 'AnotherHelloWorld',
};

HelloWorld.stories.tsx
 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
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
import { text } from '@storybook/addon-knobs';

import HelloWorld from '../src/components/HelloWorld';

<Meta title="Components|HelloWorld" component={HelloWorld} />

# HelloWorld

With `MDX` we can define a story for `HelloWorld` right in the middle of our
markdown documentation.

<Preview>
  <Story name="HelloWorld">
    <HelloWorld />
  </Story>
</Preview>

<Preview>
  <Story name="HelloName">
    <HelloWorld name={text("name", "picodotdev")}/>
  </Story>
</Preview>

<Props of={HelloWorld} />
HelloWorld.stories.mdx

En la web se pueden consultar varios ejemplos de Storybook que han desarrollado otras organizaciones y obtener una muestra de su utilidad.

Storybook posee varios complementos que le añaden nuevas capacidades. Algunos son:

  • @storybook/addon-docs: permite desarrollar historias en formato MDX.
  • @storybook/addon-viewport: permite probar las historias aplicando diseños responsive.
  • @storybook/addon-knobs/register: permite modificar propiedades de los componentes desde Storybook y observar los cambios en tiempo real.

Para usar un complemento hay que instalar su paquete y añadirlo en el archivo de configuración.

1
$ npm install --save-dev @storybook/addon-docs @storybook/addon-storysource @storybook/addon-viewport @storybook/addon-knobs
npm-install-storybook-addons.sh
1
2
3
4
module.exports = {
  stories: ['../stories/**/*.stories.(tsx|mdx)'],
  addons: ['@storybook/addon-actions', '@storybook/addon-links', '@storybook/preset-typescript', '@storybook/addon-docs', '@storybook/addon-viewport', '@storybook/addon-knobs/register']
};
main.js

Lenguaje de programación TypeScript y TSLint

TypeScript es un superconjunto de JavaScript, su principal diferencia es que es un lenguaje tipado lo que permite descubrir en tiempo de compilación numerosos errores que se producirán en tiempo de ejecución y que los editores ofrecen soporte realizar refactors de forma rápida y segura. Los componentes de React pueden desarrollarse con TypeScript.

Para desarrollar un componente propio basta con crear el archivo del componente en la carpeta src/components con la definición del componente en este caso de React. Con React se incluye el soporte para desarrollar componentes con ES2016 y JSX de React, a continuación se muestra usando TypeScript y archivos TSX que es el equivalente de JSX en TypeScript.

Para añadir el soporte de TypeScript a Storybook hay que instalar algunos paquetes npm, crear algún archivo de configuración y realizar modificaciones en otros. Además de TypeScript se añade el paquete para utilizar el linter TSLint para este lenguaje que muestra errores en caso de no cumplir las convenciones y reglas de formateo.

1
$ npm install --save-dev @storybook/preset-typescript typescript ts-loader tslint @types/node @types/react @types/react-dom
typescript-install.sh

Este archivo de configuración especifica las opciones para la compilación entre ellas se indica el formato de la salida, los archivos de código fuente ts y tsx son compilados a JavaScript de ECMAScript 5.

 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
{
  "compilerOptions": {
    "outDir": "build/typescript",
    "module": "commonjs",
    "target": "es5",
    "lib": ["es5", "es6", "es7", "es2017", "dom"],
    "sourceMap": true,
    "allowJs": false,
    "jsx": "react",
    "moduleResolution": "node",
    "rootDirs": ["src", "stories"],
    "baseUrl": "src",
    "forceConsistentCasingInFileNames": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "strictNullChecks": false,
    "suppressImplicitAnyIndexErrors": true,
    "noUnusedLocals": true,
    "declaration": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "build", "scripts", "src/**/*.test.*", "src/**/*.test-visual.*"]
}
tsconfig.json

Este es el código de un componente propio de React sencillo programado con TypeScript.

 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
import React from 'react';
import PropTypes from 'prop-types';

import './HelloWorld.less';

interface Props {
  name?: String;
}

class HelloWorld extends React.Component<Props> {
  public static defaultProps = {
    name: "World"
  };

  public static propTypes = {
    name: PropTypes.string,
  };

  render() {
    return <h1 className="helloworld-title--red">Hello {this.props.name}!</h1>;
  }
}

export { HelloWorld as HelloWorld };

/**
 * Componente sencillo de ejemplo.
 */
export default HelloWorld;
HelloWorld.tsx
1
2
3
.helloworld-title--red {
    color: red;
}
HelloWorld.less

Para analizar y validar el formato del código fuente se suelen emplear un linter que muestra un conjunto de mensajes que el código fuente no cumple. Estos mensajes son muy útiles para mantener la uniformidad en el código fuente y una forma automatizada de comprobar las reglas.

1
2
3
4
5
6
7
8
9
{
    "defaultSeverity": "error",
    "extends": [
        "tslint:recommended"
    ],
    "jsRules": {},
    "rules": {},
    "rulesDirectory": []
}
tslint.json
1
$ npm run tslint
tslint-run.sh

Pruebas unitarias y visuales con Jest y Jest Image Snapshot

En los tiempos actuales desarrollar pruebas debería ser parte del desarrollo, Jest permite realizar pruebas unitarias y jest-image-snapshot para realizar pruebas visuales. Hay instalar los paquetes de estas herramientas y añadir varios archivos de configuración, las pruebas también pueden desarrollarse con TypeScript, hay que añadir varios archivos de configuración.

1
$ npm install --save-dev @storybook/addon-storyshots-puppeteer jest puppeteer jest-puppeteer jest-image-snapshot jest-transform-stub puppeteer-extra start-server-and-test ts-jest @types/jest @types/puppeteer @types/jest-environment-puppeteer @types/expect-puppeteer
jest-install.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
module.exports = {
    transform: {
        '.(ts|tsx)': 'ts-jest',
	    '.(less)': 'jest-transform-stub'
    },
    moduleNameMapper: {
        ".(less)": "jest-transform-stub"
    },
    testRegex: './*\\.test\\.(ts|tsx)$',
    moduleFileExtensions: ['js', 'tsx', 'json']
};
jest.config.js

Para el componente anterior la definición de la prueba unitaria es la siguiente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import React from 'react';
import ReactDOM from 'react-dom';

import { HelloWorld } from './HelloWorld';

it('has a h1 tag with text', () => {
  const div: HTMLDivElement = document.createElement('div');
  ReactDOM.render(
    <HelloWorld />,
    div
  );

  expect(div.querySelector('h1')).not.toBeNull();
  expect(div.querySelector('h1').textContent).toEqual('Hello World!');

  ReactDOM.unmountComponentAtNode(div);
});
HelloWorld.test.tsx

Algunos cambios que afectan a los componentes son simplemente visuales como color, tamaño de letra, espaciado, … estos cambios son difíciles de probarlos con pruebas unitarias de código. Para validar estos cambios la estrategia que se emplea es generar una imagen inicial del componente, cuando hay cambios visuales se genera un error y hay que validar visualmente que el cambio es correcto. Esto permite que los cambios visuales no pasen desapercibidos. Para realiza la validación jest-image-snapshot proporciona la imagen de la versión anterior, la imagen nueva y una imagen que muestra las diferencias entre ambas versiones.

Estos son archivos de configuración para Jest.

1
2
3
import { toMatchImageSnapshot } from 'jest-image-snapshot';

expect.extend({ toMatchImageSnapshot });
jest.setup.js
1
2
3
4
5
module.exports = {
    preset: 'jest-puppeteer',
    testRegex: './*\\.test-visual\\.ts$',
    setupFilesAfterEnv: ['./jest.setup.js']
};
jest.config-visual.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          node: 'current',
        }
      }
    ],
    '@babel/preset-react',
  ],
};
babel.config.js

El código de la prueba visual requiere incluir interactuar con el navegador donde está contenido el componente en la prueba y especificar el momento en el que tomar la imagen visual de componente.

1
2
3
4
5
6
7
8
9
import puppeteer from 'puppeteer-extra';

it('visually looks correct', async () => {
    await page.goto('http://localhost:6006/iframe.html?selectedKind=components-helloworld&selectedStory=hello-world');

    const image = await page.screenshot();

    expect(image).toMatchImageSnapshot();
});
HelloWorld.test-visual.ts

En la imagen a revisar se muestra a la izquierda la versión anterior válida, a la derecha la nueva imagen por cambios realizados y en el centro una imagen que resalta las diferencias entre ambas a nivel de pixel. Con estas tres imágenes la revisión es un proceso manual pero sin complicación.

Imagen válida capturda Diferencias visuales entre válida capturada anterior y nueva con cambios

Imagen válida capturada y diferencias visuales por cambios

Para lanzar las tareas de ejecución de las pruebas unitarias y visuales hay que añadir unos scripts al archivo package.json. Posteriormente con estos comandos de npm se ejecutan y se comprueba si hay cambios visuales.

1
$ npm run test
test-run.sh
1
$ npm run test:visual
test-visual-run.sh

En caso de haber diferencias visuales al ejecutar de nuevo los teses se produce un error, hay que revisar visualmente los cambios y si son correctos validar y actualizar las imágenes para ejecuciones futuras.

1
$ npm run jest:visual-update
test-visual-update.sh

Hojas de estilos CSS con Less

Las hojas de estilo CSS permite separar el formato del contenido HTML. Para facilitar el desarrollo de hojas de estilo han surgido herramientas que añaden capacidades que CSS no posee. Estas herramientas como Less permiten generar como resultado un archivo CSS. Hay múltiples herramientas Less es una de ellas que se caracteriza por su simplicidad.

Storybook permite utilizar archivos de hojas de estilo less. Para realizar la transformación de less a css Storybook utiliza Webpack, hay que instalar las dependencias que le permiten transformar los archivos y la configuración para que detecte los archivos less para transformarlos a css.

1
$ npm install --save-dev less less-loader style-loader css-loader
npm-install-webpack.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
module.exports = {
  module: {
    rules: [
      { test: /\.(ts|tsx)$/, use: 'ts-loader' },
      { test: /\.less$/, use: [
        { loader: 'style-loader' },
        { loader: 'css-loader' },
        { loader: 'less-loader' }
      ]}
    ]
  },
  resolve: {
    extensions: ['.ts', '.tsx']
  }
};
webpack.config.js

Creación del paquete NPM

El objetivo final es crear un paquete npm que incluya los componentes de React para ser utilizados en una aplicación. Para crear el paquete npm basta ejecutar el comando npm pack pero este lo crea usando la misma estructura de directorios del código fuente lo que hace que al usar los componentes los imports reflejen la estructura del código fuente. Si esto no se desea hay que crear un directorio con el contenido del paquete npm y ejecutar el comando npm pack desde él, esto es lo que hacen los diferentes scripts build.

Otros scripts contiene el comando real que se ejecuta cuando se invoca desde la linea de comandos con npm run [script], entre ellos están los de Jest y Storybook.

 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
{
  "name": "storybook",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "types": "index.d.ts",
  "scripts": {
    "build:create-package": "mkdir -p build/pack/",
    "build:copy-package": "cp package.json build/pack/",
    "build:typescript": "tsc",
    "build:copy-typescript": "cp -r build/typescript/* build/pack/",
    "build:copy-src": "(cd src/ && cp --parents `find -name \\*.less` ../build/pack/)",
    "build:pack": "(cd build/pack && npm pack)",
    "pack": "npm run build:create-package && npm run build:copy-package && npm run build:typescript && npm run build:copy-typescript && npm run build:copy-src && npm run build:pack",
    "test": "jest -c jest.config.js",
    "test:visual": "start-server-and-test storybook http-get://localhost:6006 jest:visual",
    "jest:visual": "jest -c jest.config-visual.js",
    "jest:visual-update": "npm run jest:visual -- --updateSnapshot",
    "lint": "tslint -c tslint.json 'src/**/*.ts'",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^16.13.0"
  },
  "files": [
    "**/*.js",
    "**/*.js.map",
    "**/*.d.ts",
    "**/*.less"
  ],
  "devDependencies": {
    "@babel/core": "^7.8.6",
    "@storybook/addon-actions": "^5.3.14",
    "@storybook/addon-docs": "^5.3.14",
    "@storybook/addon-knobs": "^5.3.14",
    "@storybook/addon-links": "^5.3.14",
    "@storybook/addon-storyshots-puppeteer": "^5.3.14",
    "@storybook/addon-storysource": "^5.3.14",
    "@storybook/addon-viewport": "^5.3.14",
    "@storybook/addons": "^5.3.14",
    "@storybook/preset-typescript": "^1.2.0",
    "@storybook/react": "^5.3.14",
    "@types/expect-puppeteer": "^4.4.0",
    "@types/jest": "^25.1.3",
    "@types/jest-environment-puppeteer": "^4.3.1",
    "@types/node": "^13.7.7",
    "@types/puppeteer": "^2.0.1",
    "@types/react": "^16.9.23",
    "@types/react-dom": "^16.9.5",
    "babel-loader": "^8.0.6",
    "css-loader": "^3.4.2",
    "jest": "^25.1.0",
    "jest-image-snapshot": "^2.12.0",
    "jest-puppeteer": "^4.4.0",
    "jest-transform-stub": "^2.0.0",
    "less": "^3.11.1",
    "less-loader": "^5.0.0",
    "puppeteer": "^2.1.1",
    "puppeteer-extra": "^3.1.9",
    "react-docgen-typescript-loader": "^3.6.0",
    "start-server-and-test": "^1.10.8",
    "style-loader": "^1.1.3",
    "ts-jest": "^25.2.1",
    "ts-loader": "^6.2.1",
    "tslint": "^6.0.0",
    "typescript": "^3.8.3"
  }
}
package.json

Un paquete npm es un archivo con extensión .tgz que en el ejemplo se crea en el directorio build/pack.

Una vez construido el paquete .tgz para instalarlo en los proyectos donde se quiera usar hay que utilizar el siguiente comando y hacer el import de sus recursos.

1
$ npm install --save-dev build/pack/storybook-1.0.0.tgz
npm-install-package.sh

Storybook es una gran ayuda para desarrollar componentes de lado de cliente. Otras herramienta útil es Webpack como empaquetador de módulos y recursos web de todos lo recursos que se usan en un proyecto. Al utilizar TypeScript no es necesario utilizar Babel para realizar transformaciones de los archivos a una versión de JavaScript que los navegadores soportan, el compilador de TypeScript permite compilar los archivos de TypeScript a diferentes versiones de JavaScript que son las que finalmente se ejecutan en el navegador.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando npm install && npm run storybook.

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

Coding Potions

Cómo usar axios con Vue. Todo lo que necesitas saber

marzo 05, 2020 12:00

Introducción

Como Vue no es un framework completo como Angular, no te viene con una manera estándar de crear peticiones HTTP por lo que deja al usuario que use la manera que más le guste. Usualmente se suele usar axios que es como el fetch de javascript pero más cómo de usar.

Si ya conocías axios mucho de lo vas a leer ya te sonará porque se usa de la misma manera que en otros frameworks, aunque en esta guía también enseñaré como implementarlo con Vue. Pero antes veamos qué son las peticiones HTTP por si no las conocías de antes.

Las peticiones HTTP sirven para hacer llamadas al servidor. Cuando abres una página web el navegador se encarga de realizar peticiones HTTP al servidor para que éste le mande la página a renderizar. Además de para servir estáticos (html, css, javascript, imágenes, etc) también se suele usar para consumir y generar contenido de forma dinámica, es decir, contenido que normalmente se almacena en bases de datos como usuarios, posts, etc.

En los servidores se suelen crear lo que se conoce como API REST. Una API es un conjunto de endpoints o rutas preparadas para recibir peticiones HTTP. Las peticiones HTTP pueden ser de varios tipos:

  • GET: Simplemente devuelven información.
  • POST: A estos endpoints se envía información normalmente para crear o ejecutar acciones sobre recursos en bases de datos.
  • PUT: Se envía información al endpoint y se modifica en base de datos un recurso.
  • DELETE: Para borrar recursos del servidor.

Explicación de arriba de los métodos HTTP

Entonces, en este artículo nos vamos a conectar a una API con exios en Vue. Si te quieres hacer tu mismo la API, te recomiendo este artículo para que la hagas en Express JS. https://www.callicoder.com/node-js-express-mongodb-restful-crud-api-tutorial/

Pero. si solo quieres apreder y no quieres hacer una API, puedes usar esta de ejemplo que será la que usaré yo para esta guía.

https://jsonplaceholder.typicode.com/

Axios. Conexión a un backend

Bien, lo primero que tienes que hacer instalar axios en tu proyecto. Puedes mirar en las dependencias package.json si tienes instalado axios. Si no lo tienes puedes hacer lo siguiente:

npm install axios

Una vez instalado ya podemos realizar peticiones HTTP.

Lo primero que vamos a hacer es realizar una petición GET a la API de jsonplaceholder.

Peticiones GET a una API

Como ya vimos en el ciclo de vida de los componentes, una forma usual de realizar peticiones HTTP que alimenten la vista es desde el método created, vamos con ello:

<template>
  <div class="content"></div>
</template>

<script>
import axios from "axios";
export default {
  created() {
    axios.get("https://jsonplaceholder.typicode.com/todos/1").then((result) => {
      console.log(result.data);
    })
  }
};
</script>

<style scoped lang="scss">
</style>

Si creas este componente y lo importas en la vista, cuando cargue la página, en la consola del navegador podrás ver esto:

En la consola podrás ver que se imprime un objeto con los campos de la API

Si no conocías el funcionamiento de axios te explico un poco como va. Lo que tienes que hacer primero es importar axios en el componente. Una vez importado, y sin usar el this ya que no es una variable del data, llamas a axios con el método HTTP que necesites (get, post, put, delete, etc), en este caso el get.

Dentro del paréntesis pones dirección de la api a la que quieres llamar, aquí he puesto la de ejemplo. Como los métodos de axios son asíncronos y devuelven una promesa, tienes que usar el then para poder recoger el resultado. Dentro del then se suele usa una arrow function de javascript para definir el resultado.

Dentro de la variable result hay información sobre la petición y para recuperar el resultado tenemos que acceder al campo data.

Sabiendo esto, ya puedes guardar el resultado de la API en una variable del data para poder pintarla en la vista:

<template>
  <div class="content">
    <p>User ID: {{ result.userId }}</p>
    <p>Title: {{ result.title }}</p>
  </div>
</template>

<script>
import axios from "axios";
export default {
  data: () => ({
    result: null
  }),
  created() {
    axios.get("https://jsonplaceholder.typicode.com/todos/1").then((result) => {
      this.result = result.data;
    })
  }
};
</script>

<style scoped lang="scss">
</style>

Si haces esto Vue mostrará error en la consola porque intenta renderizar la vista antes de que la petición termine (nada más abrir la página la petición puede que esté cargando) y la variable result no tiene valor todavía (por tanto no puedes acceder a sus campos).

Una forma de resolver esto es simplemente con un if en la vista:

<template>
  <div v-if="result" class="content">
    <p>User ID: {{result.userId}}</p>
    <p>Title: {{result.title}}</p>
  </div>
</template>

Como Vue es reactivo, en cuanto termine de cargar la petición y por tanto la variable result tenga información, renderizará el resultado de la vista.

En la página aparece el userID y el name que son campos que vienen de la API

Peticiones DELETE

Las peticiones DELETE tienen la misma sintaxis que las del GET, tan solo tienes que cambiar el nombre del método:

created() {
  axios.delete("https://jsonplaceholder.typicode.com/todos/1");
}

En este caso como no queremos que la API nos devuelva nada cuando termine la petición no se pone el then. Si tu API devuelve algún tipo de información y la quieres para algo puedes usar el then.

Vamos ahora a ver cómo se pasa información a la API con las peticiones POST y PUT.

Peticiones POST, Y PUT

Las peticiones POST y PUT normalmente se usan pasando información desde el frontend a la API. Afortunadamente pasar información en una de estas llamadas es tan fácil como añadir un objeto o variable como segundo parámetro del paréntesis.

created() {
  let post = {
    title: 'foo',
    body: 'bar',
    userId: 1
  };
  axios.post("https://jsonplaceholder.typicode.com/posts", post).then((result) => {
    console.log(result);
  });
}

El PUT se hace exáctamente igual:

created() {
  let post = {
    title: 'foo',
    body: 'bar',
    userId: 1
  };
  axios.put("https://jsonplaceholder.typicode.com/posts/1", post).then((result) => {
    console.log(result);
  });
}

Async / await para las llamadas

Lo que vas a ver en este apartado es simplemente una sintaxis más clara para hacer las peticiones, pero el resultado será el mismo que los ejemplos vistos antes.

Con las versiones nuevas de javascript han metido una sintaxis nueva más clara para no tener que hacer el then.

async created() {
  let response = await axios.get("https://jsonplaceholder.typicode.com/todos/1");
  this.result = response.data;
}

Con esta sintaxis ya no vas a necesitar arrow functions ni nada de eso. Como digo es una sintaxis alternativa, y queda en tu mano si usarla o no, yo te la recomiendo porque es más clara de leer.

Para usarla, tienes que inicializar la variable en la que quieres recibir la respuesta al resultado de la petición HTTP con el await delante. Es importante saber que mientras en un método haya un await, no se va a ejecutar lo que haya debajo hasta que la petición termine. Además, tienes que poner delante del nombre del método en el que realices la petición la palabra async, de lo contrario no funcionará.

Otro detalle a tener en cuenta es que no se puede guardar directamente el resultado de la petición en una variable del data, es decir, esto NO funcionaría:

async created() {
  this.result = await axios.get("https://jsonplaceholder.typicode.com/todos/1").data;
}

Siempre tienes que guardar el resultado de la petición dentro de una variable intermedia.

Realizando varias peticiones a la vez con async await

Como hemos dicho, cuando se ejecute await el código que haya por debajo no se ejecutará hasta que la petición termine. Para resolver esto lo que recomiendo es que cada petición la hagas en un método async diferente y que desde el created llames a los métodos, de esta forma:

methods: {
  async getTodos() {
    let response = await axios.get("https://jsonplaceholder.typicode.com/todos/1");
    console.log(response.data);
  },
  async modifyPost() {
    let post = {
      title: 'foo',
      body: 'bar',
      userId: 1
    };
    let response = await axios.put("https://jsonplaceholder.typicode.com/posts/1", post)
    console.log(response.data);
  }
},
created() {
  this.getTodos();
  this.modifyPost();
}

Gestión de errores en las peticiones

Como axios devuelve una promesa para las peticiones, es sencillo saber si se ha producido un error. Para cuando utilizas las promesas con el then:

axios
  .get("https://jsonplaceholder.typicode.com/todos/1")
  .then(result => {
    this.result = result.data;
  })
  .catch(error => {
    console.log(error);
  });

Cuando se produzca un error en la petición se ejecutará el catch.

Para el caso de async / await lo que tienes que hacer es usar un try catch:

try {
  let response = await axios.get("https://jsonplaceholder.typicode.com/todos/1");
  this.result = response.data;
} catch (error) {
  console.log(error);
}

Para las dos formas una vez que tengas el error ya puedes hacer con él lo que quieras. Lo más sencillo es guardar el error en una variable del data para que se lo puedas mostrar el usuario.

Estructura que yo recomiendo

Lo que yo recomiendo es tener las peticiones HTTP en archivos aparte para que sean más mantenibles y las puedas reutilizar.

En mi caso lo que hago es crear una carpeta dentro de src llamada logic en la que creo un archivo .js por cada entidad. Para mí entidad es algo que tiene razón de ser por si misma, es decir, entidades por ejemplo son: users, posts, etc. Lo que haría en este caso sería crear un archivo users.js y posts.js, aunque tu caso puede ser otro.

Dentro de estos archivos lo que hago es crear una función por cada llamada a base de datos, devolviendo simplemente la petición. Veamos un ejemplo:

// src/todos.js
import axios from "axios";
const API = "https://jsonplaceholder.typicode.com/todos";

export default {
  get() {
    return axios.get(API);
  },
  create(todo) {
    return axios.post(API, todo);
  }
};

De tal forma que en el componente importas:

import todos from "@/logic/todos";

Y lo usas teniendo en cuenta que la llamada devuelve la petición de axios y por lo tanto tendrás que usar el then o el async / await

async created() {
  let response = await todos.get();
  console.log(response.data);
}

Con esta estructura si tienes que cambiar la API porque ahora se encuentra en otra dirección solo tendrás que cambiar estos archivos y se actualizarán todas las peticiones en las que se usen.

Conclusiones

Con lo que has aprendido hoy ya puedes conectare a una API, es decir, ahora se abre un mundo de posibilidades porque ahora puedes conectarte con un servidor y por tanto a una base de datos.

Si quieres practicar, con la API que hemos usado en este artículo, la de jsonplaceholder, puedes crear una web completa para mostrar, crear y editar todos (lista de tareas).

En el próximo capítulo usaremos lo que hemos aprendido hoy para crear un sistema de login y registro de usuarios. Stay tuned!.

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

Blog Bitix

Cambiar los niveles de log de forma dinámica sin reiniciar la aplicación con Log4j

febrero 28, 2020 06:00

Ocurre un bug en producción o en un entorno de pruebas se desea obtener más información. Actualizar el archivo de trazas para obtener más información requiere modificar el archivo de configuración, desplegarlo en el entorno y reiniciar la aplicación, este proceso consume tiempo dependiendo del nivel de automatización de la organización. Para reducir el tiempo necesario para obtener la información con Log4j hay dos posibilidades para cambiar dinámicamente los niveles de trazas de la aplicación sin necesidad de reiniciarla.

Java

Las trazas son muy útiles para tener un registro de lo que ha realizado una aplicación, también son muy útiles en tiempo de desarrollo y para depurar la aplicación. Cada traza se emite con un nivel de prioridad, en el momento de desarrollo puede que nos interese las trazas de más bajo nivel de debug, trace o info, en el entorno de producción donde la aplicación debe funcionar correctamente las trazas de debug e info se omiten y las aplicaciones se suele configuran con un nivel mínimo de warn o error para que las trazas sean registradas.

Sin embargo, cuando se descubre un error en producción o se quiere obtener más información con los niveles de info de qué es lo que está ocurriendo la aplicación requiere al menos cambiar el archivo de configuración de las trazas con su commit al un repositorio de control de versiones, la actualización del archivo de configuración desplegado en el entorno de producción y un reinicio de la aplicación. Este proceso de desarrollo y operaciones requiere tiempo más o menos dependiendo del nivel de automatización que posee la aplicación, en cualquier caso consume tiempo de personas y retrasa el tiempo necesario para obtener información y por tanto para resolver el problema.

¿Te imaginas lo bueno que sería que cuando hay un bug en producción o se necesita poder reconfigurar los niveles de trazas de dinámicamente sin reiniciar la aplicación ni despliegues? Una de las librerías más populares en Java para emitir trazas Log4j permite cambiar de forma dinámica el nivel de las trazas para cada logger.

El requisito para que cambiar el nivel de las trazas sea útil es que deben estar incluidas previamente en la aplicación, con un nivel de traza y mensaje adecuado.

Hay dos posibilidades:

  • Utilizar la opción monitorInterval con la que Log4j monitoriza según el tiempo configurado el archivo de configuración de las trazas para conocer si ha tenido cambios y reconfigurar los niveles de trazas cuando detecta cambios.
  • Utilizar la clase Configurator. Esta clase no es parte de la API pública lo que implica que puede cambiar pero permite cambiar los niveles de trazas de forma programática.

En este ejemplo de código se utiliza la clase Configurator.

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

...

public class Main {

    private static final Logger logger = LogManager.getLogger(Main.class);

    public static void main(String[] args) {
        ...
        Main.changeLogLevelDynamically();
    }

    ...

    private static void changeLogLevelDynamically() {
        Configurator.setLevel(logger.getName(), Level.ERROR);
        logger.info("info trace");
        logger.error("error trace");

        logger.error("");

        Configurator.setLevel(logger.getName(), Level.INFO);
        logger.info("info trace");
        logger.error("error trace");
    }
}
Main.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
configuration:
  status: warn

  ...

  loggers:
    root:
      level: info
      appenderRef:
        ref: STDOUT
log4j2.yaml

El nivel de trazas según se inicia la aplicación para el logger es info por configuración, según se ejecuta el programa se cambia el nivel de trazas a error y finalmente se restablece el nivel de trazas a info. Se observa que cuando el nivel de las trazas está a nivel error la traza de nivel info no se emite, como es lo esperado. Al restablecer el nivel a info se emiten ambas trazas.

1
2
3
4
2020-02-28 19:43:38,465  ERROR                    io.github.picodotdev.blogbitix.log4j.Main error trace
2020-02-28 19:43:38,465  ERROR                    io.github.picodotdev.blogbitix.log4j.Main 
2020-02-28 19:43:38,465  INFO                     io.github.picodotdev.blogbitix.log4j.Main info trace
2020-02-28 19:43:38,466  ERROR                    io.github.picodotdev.blogbitix.log4j.Main error trace
System.out

Para cambiar el nivel de trazas de una aplicación de forma programática se puede ofrecer una interfaz JMX en la aplicación o si se trata de una aplicación web una página de configuración que ofrezca la funcionalidad.

La reconfiguración de los niveles de trazas deben ser temporales ya que la aplicación dependiendo de su carga emite más trazas que con los niveles warn y error. Si las trazas se guardan en un archivo, guardar mayor cantidad de ellas hace que su tamaño pueda ser significativo e incluso llenar el almacenamiento provocando malfuncionamiento en la aplicación. Para evitar que el archivo de trazas llene el almacenamiento persistente es posible limitar por tamaño, por fecha y rotar los archivos de trazas.

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

Mascando Bits

Cómo revisar y probar un Pull Request (PR) en GitHub, GitLab o Gogs

febrero 27, 2020 08:00

Hay gente que a día de hoy sigue sin entender la diferencia entre Git (herramienta de versionado) y GitHub (plataforma para alojar proyectos Git, ahora propiedad de Microsoft). Y mucho menos entiende el valor que aportan plataformas web como son GitHub, GitLab o Gogs, que añaden todo el valor social y de discusión sobre los cambios propuestos por otros desarrolladores o usuarios, permitiendo así el Desarrollo Colaborativo y auge que hoy en día presenta la filosofía de software Open Source.

Siendo así, vamos a aclarar que Git es una estupenda herramienta de versionado y sincronización de código, o cualquier fichero que no sea binario, permitiendo establecer la sucesión cronológica y relacionar los cambios entre sí para una control y revisión del avance de un proyecto.

Pero por otro lado Git solo, es bastante «cojo» por así decirlo. Las personas no nos relacionamos y trabajamos mediante protocolos o herramientas que llevamos instaladas y que nos permitan trabajar en equipo de manera eficaz 🤣. Es aquí donde entran las plataformas web que dan esa capa social que permiten a los desarrolladores y contribuidores al proyecto comunicarse, establecer criterios, marcar pautas, organizarse, revisar secciones de código… En este artículo nos vamos a centrar en el proceso de revisión de aportes de código Pull Request, abreviados y conocidos como PR.

Esquema básico del proceso de revisión:

Antes de empezar vamos a aclarar una serie de términos que creo que es conveniente definir. Lo primero explicar lo que es un Fork, que no es más que un clon de un proyecto cuya propiedad (en términos de administración) es de otro usuario u otra organización, pero permanece relacionado con su proyecto originario del que se hizo fork (normalmente se denomina al proyecto origen upstream). Y luego lo que es un Pull Request (de ahora en adelante PR), que es una petición que se hace al proyecto originario (upstream) para que incluya los cambios que tenemos en una de las ramas de nuestro repositorio «forkeado».

Entre el PR y la aceptación del mismo está lo que se conoce como discusión y revisión de los cambios propuestos. Los procesos de revisión son importantes porque permiten afinar los cambios, corregir metodologías de trabajo, realizar pruebas de integración con plataformas como AppVeyor o TravisCI… Podríamos decir que es el punto en el que se comunican las personas involucradas en la propuesta de PR.

Entrando en el terrenos práctico la discusión se genera en la sección de PR y es donde se produce toda la conversación. Aquí podéis ver un ejemplo:

https://github.com/RDCH106/pycoinmon/pull/5

Creo bastante evidente como se debate, ya que es como un foro en el que dependiendo de la plataforma (GitHub, GitLab o Gogs…) será más completo o menos.

La pregunta del millón es cómo probamos el código que está en el PR. No se encuentra en una rama del repositorio el PR, realmente se encuentra en la rama del que nos hace el PR (a donde podemos ir para probar los cambios). De hecho lo interesante es que el PR puede ir cambiando, si el autor del PR en su fork, en la rama que usó para el PR, realiza cambios. Es decir, un PR no es un elemento estático sujeto a un commit concreto, sino que es la referencia a una rama del proyecto fork que contiene cambios que el upstream no tiene. El truco reside en que comparten la misma base de código y los cambios del PR son la diferencia entre la rama del proyecto upstream al que se le propone el PR y la rama del proyecto fork que contiene los cambios.

Pero esto es una verdad a medias… y digo a medias porque un PR sí es una rama, realmente es una referencia en el repositorio remoto (donde hacemos pull y push). Y eso es bueno 😁. La referencia entre un fork y su upstream nunca se rompe de manera oficial (existen por ejemplo mecanismos en GitHub para solicitar que un fork de un proyecto deje de estar relacionado con su upstream), por lo que asumiremos que un fork es como un padre y un hijo, pueden no llevarse bien pero eso nunca hará que desaparezca la relación parental padre-hijo. Cada vez que la rama del fork desde la que se hace PR se actualiza, la del proyecto upstream también queda actualizada. Por eso resulta conveniente usar ramas para hacer PR de diferentes features (características) que vamos a desarrollar, ya que así podremos mantener de manera independiente ambos paquetes de trabajo sin que haya interferencias entre ellos, y pudiendo dejar la rama el tiempo que haga falta hasta que finalmente se incluyan los cambios al proyecto upstream.

A causa de lo comentado antes podemos revisar desde la consola las referencias remotas con las que cuenta un proyecto git con el comando «git ls-remote«. Voy a usar el proyecto pyCOINMON como ejemplo totalmente práctico:

rdch106@RDCH106 MINGW64 /d/GIT/pycoinmon (develop)
$ git ls-remote
From https://github.com/RDCH106/pycoinmon.git
be75cbb0e449383fd42c9033a8b76c844fcfef17        HEAD
d01ecf133c342f490030b56066ed8ff18b36f124        refs/heads/develop
be75cbb0e449383fd42c9033a8b76c844fcfef17        refs/heads/master
ec9c10b6d88274a1a006ae139856de478876572c        refs/pull/1/head
c0cb91e1776632494a23cf181584c1903516a758        refs/pull/11/head
46f2e6db3d22a4a48dcf3680157c95641123095d        refs/pull/2/head
035e64652c533a569c6b236f54e12aff35ad82b1        refs/pull/3/head
0f08bd644912e7de4a3bc53e3a8d0dbd64d6fc34        refs/pull/4/head
87ef8ca9af3eb5a523d3ef532fccee91aeedcd44        refs/pull/5/head
896d697e846649c7f23b9af3430067451ab5c089        refs/pull/7/head
d57455a8acf719cd3acea623f0759c6e11baada1        refs/pull/7/merge
d6f4c5a4b3e2b0025d74e9feed2609ba50835950        refs/tags/v0.1.0
be413f410cfa23e1f0f2715f2714874a22757b0d        refs/tags/v0.1.0^{}
cdc5e356b6d7f829e83a1b0a25e2a7304844223b        refs/tags/v0.1.4
4b69c059196f1f41bafc4f5970afb8a34817b509        refs/tags/v0.1.4^{}
b5649af39db2738481751f07b6ad59585144dbd0        refs/tags/v0.2.4
c3506ed1289f3dbcddb2db68201a114a9d1cf8b3        refs/tags/v0.2.4^{}
f0c564d2617d42c0fcb3cb842122a214a6021d37        refs/tags/v0.2.6
a3fe8f8b788311c1d4f5ee906cf2a4c70ffac8db        refs/tags/v0.2.6^{}
cea535e4e663531454b437ee8d7976a06e1ae415        refs/tags/v0.2.8
080192d1d7eb33feed6b92df2b389b39592545a6        refs/tags/v0.2.8^{}
9c374d2031a9da655be44e0c79944166cbf99e8a        refs/tags/v0.3.2
f76f78252863d372fde5d6ff197ba7519f7da50b        refs/tags/v0.3.2^{}
cc248bbd64f177a9b6041cad7c269b880c21b355        refs/tags/v0.3.5
fd50d344575e54def4b841221b2b5c2e8a0a74e3        refs/tags/v0.3.5^{}
7727986a93ce65c2deb905fcd92718c43f3e61ca        refs/tags/v0.4.0
aa2df260dc9c1ae9c1aa88ddd7d2498b451bf0fc        refs/tags/v0.4.0^{}
a620d5e379d495c69926d97a33803e91b3c07783        refs/tags/v0.4.4
d0b3fa9ae73a15ff17a47804fbbac80245c028c1        refs/tags/v0.4.4^{}
f12102dfacc530f90600082a97a0533ba862bd2c        refs/tags/v0.4.7
4329b9f5a9782071fe661376225df6d8d6dfa47e        refs/tags/v0.4.7^{}
1d3dbf367ab319f0d9a515c007ff07b90de6ca1c        refs/tags/v0.4.8
d01ecf133c342f490030b56066ed8ff18b36f124        refs/tags/v0.4.8^{}

Nos vamos a centrar por ejemplo en el PR #7 que es uno de los que tenemos abiertos y cuyos cambios supongamos que queremos probar:

Lo que vamos a hacer es traernos los cambios del PR basándonos en su ID (en nuestro caso #7), creando una nueva rama (PR_7) para poder probarlo por separado:

rdch106@RDCH106 MINGW64 /d/GIT/pycoinmon (develop)
$ git fetch origin pull/7/head:PR_7
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Total 10 (delta 7), reused 7 (delta 7), pack-reused 3
Unpacking objects: 100% (10/10), done.
From https://github.com/RDCH106/pycoinmon
 * [new ref]         refs/pull/7/head -> PR_7

Ahora sólo resta hacer un checkout a PR_7:

rdch106@RDCH106 MINGW64 /d/GIT/pycoinmon (develop)
$ git checkout PR_7
Switched to branch 'PR_7'

O hacerlo desde el maravilloso SourceTree el cual os recomiendo como herramienta profesional para la gestión de vuestros repositorios Git locales y remotos (si eres usuario GNU/Linux no me olvido de ti, te recomiendo GitKraken).

A partir de aquí ya es hacer lo que quieras. Puedes probar los cambios, hacer algunas pruebas, hacer un «merge» para incluir los cambios en otra rama o incluso en la propia rama master.

En lo que a mí respecta, recomiendo simplemente probar los cambios y en la zona de discusión que comentaba antes, realizar todas las apreciaciones necesarias y dejar las cosas lo más claras posible, siendo responsabilidad del desarrollador que hizo el PR realizar los cambios para evitar conflictos. Una vez esté todo correcto, también recomiendo realizar el merge desde GitHub, GitLab o Gogs

Además todo este proceso forma parte del aprendizaje y democratización de los conocimientos, haciendo de cada PR un sitio al que se puede volver para aprender y revisar lo ocurrido gracias a la trazabilidad en la discusión de los cambios.

Personalmente los procesos de revisión es una base estupenda junto al Planning Poker (prueba https://scrumpoker.online), para igualar las habilidades y competencias que permiten acortar divergencias entre las estimaciones de un grupo de desarrollo ante la planificación de las cargas de trabajo. Yo personalmente soy partidario de que el que lanzó la estimación más alta en la partida de poker realice el paquete de trabajo para que baje su estimación y aumenten sus habilidades, gracias a la supervisión (revisión) de quien dio la cifra más baja, que se asume que dio una estimación más ajustada por unas habilidades mayores.

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

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

Evitar el uso de un plugin SMTP en WordPress

febrero 25, 2020 02:24



Llevo mucho sin escribir sobre informática en este espacio. Como si los relojes se lo hubieran comido todo. En este artículo os voy a explicar como prescindir de plugins SMTP de WordPress. Son nombres que si usas WordPress conocerás: Easy WP SMTP, WP Mail SMTP, WP SMTP, … El problema con los plugins de WordPress …

Evitar el uso de un plugin SMTP en WordPress Leer más »



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

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

Fixed Buffer

Cómo escribir un logger de alto rendimiento para .Net Core

febrero 25, 2020 09:00

Tiempo de lectura: 6 minutos
Imagen ornamental para el entrada Cómo escribir un logger de alto rendimiento para .Net Core

Han pasado ya unos días desde la última entrada en la que hablamos sobre como escribir código de alto rendimiento en .Net Core. Aunque en la entrada intenté meter todo lo posible, al final quedó bastante larga. Es por eso por lo que pensé que en vez de hablar por encima sobre como escribir un logger de alto rendimiento, era mejor dedicarle una entrada en exclusiva. ¡Vamos con ello!

¿Por qué debería escribir un logger de alto rendimiento?

Aun a riesgo de parecer pesado y repetir lo mismo que en la entrada anterior, el código de alto rendimiento es menos legible y más difícil de mantener que un código normal. Antes de decidir hacer modificaciones para conseguir un alto rendimiento, es importante medir. Con los datos de las mediciones en la mano, hay que evaluar si vale la pena cambiar el código para conseguir mejor rendimiento. (Sin medir, ni siquiera se puede afirmar de donde viene el problema…)

Si tienes dudas sobre cómo hacer esto, para aquí y lee Escribiendo código de alto rendimiento en .Net Core.

La interfaz ILogger está totalmente integrada en .Net Core tanto a nivel de web como de servicio, y es la piedra angular sobre la que se sustenta todo el sistema de logging. Esta tan bien integrado que no usarlo requiere que nos preguntemos si no estaremos reinventando la rueda….

Vale, tenemos mediciones de tiempo y podemos afirmar que hay que optimizar el logger para conseguir alto rendimiento. Tenemos totalmente integrado nuestro sistema con ILogger. ¿Dónde está el problema entonces?

Si hacemos una medición con un código como por ejemplo este:

[MemoryDiagnoser]
public class ILoggerParameters
{
    private readonly ILogger _logger;
    public ILoggerParameters()
    {
        IServiceCollection serviceCollection = new ServiceCollection();

        serviceCollection.AddLogging();

        var loggerFactory = serviceCollection.BuildServiceProvider().GetService<ILoggerFactory>();

        _logger = loggerFactory.CreateLogger("TEST");
    }

    [Benchmark(Baseline = true)]
    public void LoggerWith0Parameters()
    {
        _logger.LogInformation("Mensaje de log sin parámetros");
    }

    [Benchmark]
    public void LoggerWith1Parameters()
    {
        _logger.LogInformation("Mensaje de log con un parámetro {Parametro1}", "Valor1");
    }

    [Benchmark]
    public void LoggerWith2Parameters()
    {
        _logger.LogInformation("Mensaje de log con dos parámetros ({Parametro1},{Parametro2})", "Valor1", "Valor2");
    }
}

Podemos comprobar que los resultados apuntan a que el número de parámetros influye en el rendimiento del log.

La imagen muestra los resultados del benchmark del código, donde se ve que a medida que aumentan los parámetros el tiempo de ejecución aumenta 3.6 veces con un parámetro y 4.3 veces con 2 parámetros. También se refleja que sin parámetros no hay consumo de memoria, con 1 se consumen 32B y con 2 40B.

De hecho, hay un punto interesante, y es que incluso hay un consumo de memoria al utilizar parámetros pero… Si los parámetros son string y son por referencia, ¿porque hay consumo? ¿Es esto suficiente para justificar un logger de alto rendimiento?

¿Qué hay bajo el capo de ILogger?

En los datos anteriores hemos comprobado que cuando se utiliza ILogger con parámetros, el consumo de memoria aumenta pese a que sean datos por referencia.

Esto a simple vista puede parecer raro, pero tiene todo el sentido si entramos a detalle de como esta implementado ILogger, vamos a poder comprobar que a fin de simplificar la generalización se hacen ciertas conversiones que añaden coste computacional al proceso, como por ejemplo convertir los parámetros en un «params». Esto obliga a crear un array que hay que reservar. Encima de todo eso, «params» es de tipo object[], por lo que además tenemos un boxing por el camino.

Se conoce como boxing el proceso de hacer una conversión de cualquier tipo a object. El unboxing hace referencia al proceso contrario, convertir object a cualquier otro tipo.

De hecho, basta con mirar la firma de uno de los métodos de ILogger como puede ser LogInformation (aunque sea un método de extensión, el método Log recibe los mismos args).

public static void LogInformation(this ILogger logger, string message, params object[] args)

En él, ya se puede comprobar que se está haciendo un boxing desde el tipo de dato que le pasemos como argumento hacia object y se esta convirtiendo en un array.

En el caso de trabajar con valores por referencia, el boxing y unboxing no es un problema ya que todos los datos ya están en el heap (cómo bien indica Daniel Redondo en los comentarios). En cambio si estamos haciendo boxing de un objeto por valor, la cosa es diferente tanto en tiempo como en memoria. Un código como este:

[Benchmark(Baseline = true)]
public int Boxing() => Boxing(Message);
[Benchmark]
public int NoBoxing() => NoBoxing(Message);
private int Boxing(object message) => (int)message;
private int NoBoxing(int message) => message;

Genera un resultado tan interesante como este:

La imagen muestra el resultado del benchmark anterior. En el se ven que el boxing tarda 4.23 ns y consume 24 B mientras que no haciendo boxing el resultado es 0.01 ns y 0 B. El ratio sin hacer boxing es 99,995% más eficiente.

¿Cómo se puede conseguir un logger de alto rendimiento?

Por suerte, .Net core 3 ofrece un mecanismo para crear un logger de alto rendimiento a través de LoggerMessage.Define. ¿Y cómo funciona LoggerMessage.Define para mejorar el rendimiento?

Este mecanismo va a crear una plantilla que se compilará y servirá para un mensaje concreto. Esto es un trabajo extra (como todo cuando se optimiza), pero permite que las llamadas ejecuten esa plantilla en vez de interpolar strings. Además, al ser esa plantilla un ‘Action’ no es preciso realizar un boxing ya que los tipos son estrictos.

Adicionalmente a lo anterior, este Action debería ser estático de modo que no sea necesario recrearlo cuando la clase que contiene el logger se inicialice de nuevo.

Para ello, podríamos crear una clase abstracta que almacene todas esas plantillas (delegados) de nuestro logger de alto rendimiento:

public static class OptimizedLogger
{
    public static void LogInformation(ILogger logger) => _informationSinParametros(logger, null);
    public static void LogInformation(ILogger logger, string parametro1) => _information1Parametro(logger, parametro1, null);
    public static void LogInformation(ILogger logger, string parametro1, string parametro2) => _information2Parametros(logger, parametro1, parametro2, null);


    private static readonly Action<ILogger, Exception> _informationSinParametros = LoggerMessage.Define(
        LogLevel.Information,
        Events.Evento1,
        "Mensaje de log sin parámetros");

    private static readonly Action<ILogger, string, Exception> _information1Parametro = LoggerMessage.Define<string>(
        LogLevel.Information,
        Events.Evento1,
        "Mensaje de log con 1 parámetro: {Parametro1}");

    private static readonly Action<ILogger, string, string, Exception> _information2Parametros = LoggerMessage.Define<string, string>(
        LogLevel.Information,
        Events.Evento1,
        "Mensaje de log con 2 parámetros: {Parametro1} y {Parametro2}");
}

Haciendo un resumen del código, estamos creando 3 delegados tipados con LoggerMessage para cada uno de los 3 escenarios. Hemos conseguido añadir parámetros utilizando la sobrecarga genérica de LoggerMessage.Define y ahora tenemos 3 delegados que reciben un ILogger, una excepción y entre o y 2 strings.

¿Hemos conseguido algo con esto? ¿No estamos utilizando el mismo ILogger que tenía mal rendimiento? Pues sí y no. Sí estamos utilizando el mismo ILogger, pero no estamos llamando a su método Log, sino que estamos utilizando el delegado que hemos definido previamente de modo que ahora las llamadas son fuertemente tipadas.

Vamos a comprobar si realmente hemos mejorado con un código que compare los dos loggers en el caso de tener 2 parámetros como podría ser este:

[MemoryDiagnoser]
public class ILoggerVsOptimized2Params
{
    private readonly ILogger _logger;
    private const string Parametro1 = "Parametro1";
    private const string Parametro2 = "Parametro2";

    public ILoggerVsOptimized2Params()
    {
        IServiceCollection serviceCollection = new ServiceCollection();
        serviceCollection.AddLogging();
        var loggerFactory = serviceCollection.BuildServiceProvider().GetService<ILoggerFactory>();
        _logger = loggerFactory.CreateLogger("TEST");
    }

    [Benchmark(Baseline = true)]
    public void Logger2Parametros()
    {
        _logger.LogInformation("Mensaje de log con 2 parámetros: {Parametro1} y {Parametro2}", Parametro1, Parametro2);
    }

    [Benchmark]
    public void OptimizedLogger2Parametros()
    {
        OptimizedLogger.LogInformation(_logger, Parametro1, Parametro2);
    }
}

Si nos fijamos en los resultados podemos comprobar que este cambio mejora el rendimiento tardando un 93% menos y sin consumir nada de memoria.

La imagen muestra los resultados utilizando un logger de alto rendimiento. En el se ve que es un 93% más rápido y consume 0 B respecto a los 40 B del logger standard.

Conclusiones

Hacer un logger de alto rendimiento tiene un impacto a la hora de desarrollar el proyecto. Al igual que todas las optimizaciones es necesario que exista una razón cuantificable antes de hacer un desarrollo pensado para la máquina en vez de para las personas.

El hecho de tener que definir un delegado fuertemente tipado para cada mensaje que se vaya a loguear genera una sobrecarga de trabajo muy grande en un proyecto donde hay cientos de mensajes distintos. Es por eso que una opción muy interesante es valorar que logger tienen que ser de alto rendimiento (por ejemplo, en un middleware de cabecera de ASP) y que loggers pueden utilizar la implementación estándar.

Con esas consideraciones en la mano, evidentemente hacer los cambios tiene un beneficio importante en el coste de ejecución de la aplicación.

He dejado un repositorio en GitHub para que puedas descargarte el código y probarlo, tocarlo y cambiarlo para poder ver los resultados en tu propio equipo (que es como más se aprende).

**La entrada Cómo escribir un logger de alto rendimiento para .Net Core se publicó primero en Fixed Buffer.**

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

Poesía Binaria

Limitar el uso de CPU de nuestras aplicaciones o procesos en GNU/Linux (señales, nice, cpulimit/cputool, cgroups, systemd slices)

febrero 19, 2020 09:10

Algo que nos puede traer de cabeza como administradores de sistemas y, a veces como usuarios es el hecho de que un proceso se coma, devore y trate sin piedad la CPU de nuestro sistema. Ya no solo el hecho de que un programa deje inservible el ordenador, aunque a día de hoy con tantos núcleos de procesador, casi siempre tendremos algo de CPU para enviar una señal de parada a un proceso. Un ejemplo más claro puede que tengamos dos programas en ejecución (o más), uno de ellos nos corre más prisa que el otro, ¡pero el ordenador no lo sabe! Así que, si utilizan la CPU de forma intensiva terminarán utilizando el 50% de la CPU cada uno.

Pero claro, un proceso necesitamos que esté listo antes, lo lógico es asignarle más CPU a éste. ¿Qué opciones tenemos?

La medida obvia, ejecución secuencial

Lo primero que se nos pasa por la cabeza es ejecutar primero el proceso que nos corre prisa, y cuando termine ejecutar el otro. Esto puede ser una buena solución en muchas ocasiones. Pero claro, ¿y si los procesos llevan lanzados ya un tiempo y han avanzado en su progreso? detener uno de ellos puede suponer la pérdida del estado que se ha conseguido. Puede que nos compense, o puede que no.
¿Y si estamos hablando de programas demonio? Los programas están en ejecución en segundo plano, haya momentos de gran utilización de CPU y momentos en los que no se usa, pero sí sabemos que uno tiene preferencia sobre otro. O al menos no queremos dejar sin CPU a los demás programas en ejecución.

Señales (peleando con la terminal)

Lo siguiente que se nos puede ocurrir es pausar y reanudar procesos. Si tenemos los procesos en dos terminales por separado, podemos pulsar Control+Z en el que deseemos pausar y luego escribir fg para reanudarlo. No tiene pérdida. Pero si los procesos no tienen un terminal, también podemos hacerlo enviándoles las señales SIGSTOP y SIGCONT. Para poner un ejemplo, he creado un pequeño script en Bash (muy tonto y lo he llamado consumidor.sh):

1
2
3
#!/bin/bash

while (( 1 )); do echo CONSUMO CPU; done

Ahora lo ejecuto y me olvido de esa terminal porque no puedo tocarla. Desde otra terminal ejecuto top.
En lugar de consumidor.sh podríamos utilizar la siguiente línea:

echo «scale=100000; a(1)*4» | bc -l

Que se pondrá a calcular con un solo núcleo 100000 dígitos del número Pi.

top del consumidor
Como vemos, tenemos un proceso consumidor.sh que está tirando bastante de CPU, así que, conociendo el PID (process ID o identificador de proceso) podemos ejecutar:

kill -SIGSTOP 11023

En ese momento vemos que ya no es un proceso que consuma CPU. Sigue en ejecución, pero está en pausa. Que no os asuste el comando kill, que no estamos matando a nadie, aún, sólo le decimos al sistema operativo que no le de más CPU hasta nueva orden, dicha orden es:
kill -SIGCONT 11023

Y tras ejecutar esto volveremos a ver el proceso en cuestión dar guerra.
Si no queremos utilizar el PID del proceso, podemos hacerlo por su nombre utilizando (con cuidado, si tenemos varios procesos con el mismo nombre pararemos todos):
killall -SIGSTOP consumidor.sh

O el comando pkill de la misma manera.

De todas formas, es un engorro para nosotros estar todo el rato enviando señales es demasiado trabajo. Como humanos, si tenemos muchos procesos nos podemos confundir de PID y por otro lado tenemos que estar pendientes de cuando un proceso empieza a consumir CPU para pararlo. Si lo pensamos es una tarea muy automatizable.

Nice

La llamada del sistema que nos permite establecer la prioridad de un proceso con respecto al programador de tareas es nice. Pero solo es un pequeño factor que influye en la asignación de CPU a cada proceso por parte del sistema operativo. Imaginémonos una cola de supermercado, darle más nice a un proceso sería colocarlo un poco antes o después en la cola. El problema es que esta cola se repite muchas veces porque hay muchos procesos en ejecución (si tuviéramos que esperar que se terminara un proceso para ejecutar otro, nos podríamos olvidar de la multitarea y de muchas otras cosas). Si cada vez que un proceso se coloca en la cola, no lo ponemos el último, sino que lo colocamos más cerca de la caja, al final le estaremos asignando más tiempo de CPU al proceso.
Para ver resultados al cambiar el nice del proceso. Debemos tener una CPU pequeña (de un VPS pequeño, por ejemplo), o tener muchísimas tareas en ejecución, porque si tenemos CPU de sobra, el sistema siempre va a encontrar tiempo de CPU para un proceso. Si tenemos 8 core y estamos utilizando solo 2 al 100%, ahora queremos ejecutar otra aplicación (monohilo) con baja prioridad, el sistema verá que tenemos 6 core muertos de risa, así que la aplicación se ejecutará consumiendo el 100% de uno de los core sobrantes, porque ninguna otra aplicación reclama CPU, y el sistema ve que hay recursos de sobre. Por eso notaremos estos cambios cuando tengamos menos CPU para asignar.

Podemos ejecutar un programa con un nice determinado así:

nice -n xx consumidor.sh

Donde xx es un número entre -20 (más favorable) y 19 (menos favorable). Es decir, con -20 colaremos todo lo que podamos al proceso y con 19 lo pondremos lo más lejos de la caja que podamos. Con el 19, el proceso solo obtendrá tiempo de CPU cuando ningún otro proceso reclame CPU. Nota: También podemos usar nice -10 o nice 5, quitando el -n del medio, para teclear menos.

Pero también es posible cambiar el nice, o prioridad del proceso cuando éste está en ejecución, utilizando su PID (también podemos asignar prioridad a todos los procesos de un grupo o usuario con -u y -g):

renice -n -20 -p 11023

En este caso también podremos quitar el -n si queremos.

ionice

Cuando un proceso utiliza además de CPU, entrada/salida de datos. Por ejemplo lectura y escritura de disco. Podemos acelerar o decelerar dicha operación. Normalmente un proceso cuando realiza una operación de disco, la solicita al sistema operativo. El proceso pasa a un estado de espera, porque no puede continuar sin el dato o el resultado de la operación, y cuando se hace efectiva, el sistema operativo vuelve a meter al proceso en la cola del programador de tareas para que pueda tener CPU de nuevo.
Ahora bien, si las operaciones de entrada/salida tardan poco, el proceso que las origina tardará menos en terminar. Las operaciones de I/O también van en cola y podemos hacer que éstas se realicen con prioridad máxima (tal cual entran, se realizan), con prioridad mínima (solo se llevarán a cabo cuando no haya ninguna otra operación de I/O por hacer), o en varios términos medios.
Si, por ejemplo, nuestro proceso solo usa CPU y poco I/O, cambiar esto apenas influirá.

Para cambiar el nice de IO (ionice), tenemos la orden ionice que podemos usar así:

ionice -c3 -p 10271

Para establecer prioridad mínima, el argumento -c (clase) a 3 (idle, cuando el sistema está desocupado). O para máxima prioridad,

ionice -c1 -n0 -p 10271

Siendo -c1 (clase 1, realtime o tiempo real) y -n0 (máxima prioridad dentro de la clase).

Como caso general -n obtendrá un valor entre 0 y 7 donde 0 es máxima prioridad y 7 es la mínima. Faltaría la clase 2 (best effort), que sería algo intermedio y también tendrá prioridades entre 0 y 7.

cpulimit/cputool

Ambos programas utilizan la misma técnica, y lo que hacen es automatizar el envío de señales SIGSTOP y SIGCONT a los procesos. Es decir, parar y reanudar procesos continuamente. De este modo es como si constantemente un programa estuviera mirando el porcentaje de CPU de top y si ve que un proceso se pasa de lo que hemos establecido, lo pausa, cuando ve que el porcentaje ha bajado, lo reanuda y así continuamente.
Como concepto está bien, pero claro, el propio cpulimit (cputool también) consume ya CPU, haciendo llamadas a sistema para ver el % de CPU de los procesos y luego hace más llamadas a sistema para pausar y reanudar procesos, por lo tanto estamos consumiendo CPU para ello. Es verdad que la cantidad de CPU que consumimos comparada con un proceso que devora CPU es pequeña, pero si en realidad el proceso devora CPU, la aplicación se pasa el rato pausando y reanudando.

Para probar esto, primero instalamos la utilidad cpulimit (seguro que nuestra distribución la tiene en su repositorio) y hacer lo siguiente:

cpulimit -l 10 -p 11023

Con esto le decimos al proceso con PID 11023 (el que lanzamos antes) que no consuma más de un 10% de CPU. Esto no tendrá precisión científica, como vemos en la captura de pantalla (está consumiendo 15,2%) depende de muchos factores. El primero de ellos es el algoritmo con el que se calcula el porcentaje de CPU, que puede hacer que varíe un poco. También influye que el proceso cpulimit esté corriendo con un nice alto (al estar antes en la cola de la CPU hace más frecuentemente los cálculos de porcentaje de CPU y puede actuar antes) y que el proceso utilice gran cantidad de CPU, haga o no llamadas a sistema y demás acciones que pueden hacer que el programador de tareas se detenga más o menos en el proceso en cuestión.


Para observar el comportamiento de cpulimit, debemos observar la columna S del comando top (en la captura), veremos cómo el estado del proceso va cambiando de T (stopped, parado, cuando envían SIGSTOP) a R (running, en ejecución, cuando reanudan el proceso con SIGCONT).

Cpulimit nos permite hacer algunas cosas más, como por ejemplo, con -m podemos monitorizar también todos los procesos hijos, porque muchos procesos se dividen en varios subprocesos nada más ejecutarse para trabajar.

Cputool funciona de forma parecida, en este caso, debemos utilizar -c:

cputool -c 10 -p 11023

Pero la técnica utilizada es la misma. Con el argumento -vv podremos ver cuándo cputool dice de pausar y cuándo de reanudar, es hipnótico y muy educativo, pero al final lo que hacemos es pausar y reanudar el proceso constantemente. Algo que pasa desde una aplicación, al kernel, o núcleo del sistema operativo, muchas veces.

Tanto con cputool como con cpulimit podemos ejecutar la aplicación directamente desde el comando y no especificar PID. En este caso serviría para aplicaciones que estamos ejecutando ahora y no para las que se encuentran ya en ejecución

Control Groups (cgroups)

Los grupos de control o control groups nos permiten limitar los recursos de un grupo de procesos en Linux. Estos grupos nos permiten controlar el tiempo de CPU, memoria, ancho de banda de red o de entrada/salida, etc. disponibles para ese grupo de procesos. Y en este caso es el kernel el que controla (como el nice), así que todo queda en casa, sin un proceso extra que regule y calcule y le diga al kernel lo que tiene que hacer (SIGSTOP, SIGCONT, etc). Configurar los control groups puede ser un poco más trabajoso que utilizar cpulimit, pero a la larga nos dará más alegrías. Además hablamos de grupos de procesos y no de procesos sueltos, por lo que podemos meter varios procesos en un grupo y tendrán las mismas políticas.

Los cgroups se usan muchísimo por docker, systemd, Hadoop, Kubernetes, LxC y muchos más proyectos. Son una herramienta potentísima para llevar el control de procesos.

Aunque sea el kernel el que controla y decide, necesitamos aplicaciones de espacio de usuario que definan los parámetros de control. Vamos, programas que hablen con el kernel para decir qué queremos; igual que kill, nice y demás utilidades.

Para ello podemos instalar las cgroup-toolso libcgroup-tools, dependiendo de la distribución puede tener otro nombre parecido. Una vez instaladas, nos disponemos a crear nuestro cgroup:

sudo cgcreate -g cpu:/cpulimitada

Con esto creamos un grupo dedicado a limitar CPU llamado cpulimitada. Podemos crear otro tipo de grupos que controlen memory: (memoria), cpuset: (que una aplicación use determinados núcleos), blkio: (entrada/salida), net-prio: (prioridad de red) y algunos más. Aunque este post se centrará en CPU.

Tras esto, debemos añadir nuestro proceso al cgroup (podríamos configurar el cgroup primero, pero como tenemos un terminal abierto con top, me gusta observar lo que pasa en todo momento. Para añadir nuestro proceso tenemos dos opciones, lanzar el proceso así:

sudo cgexec -g cpu:/cpulimitada consumidor.sh

Aunque, si por el contrario, el proceso ya está lanzado con anterioridad y queremos modificar su comportamiento podremos hacer lo siguiente:
sudo cgclassify -g cpu:/cpulimitada 11023 consumidor.sh

Aunque se llamen grupos, un grupo de procesos puede contener un solo proceso, para hacer un ejemplo nos viene bien, luego podemos meter más procesos si queremos.

Una vez tenemos el proceso dentro de nuestro grupo y top no ha mostrado nada diferente ni raro. Por defecto, el grupo no tiene restricciones de CPU, podemos hacer lo siguiente:

sudo cgset -r cpu.cfs_quota_us=20000 cpulimitada

La forma de dar el dato de cantidad de CPU a asignar es un poco diferente. Aquí establecemos los microsegundos de CPU que se dedicarán al proceso por intervalo de tiempo. En este caso dedicaremos 20000µs (20ms) por cada 0.1segundos (por cada 100000µs o 100ms), resultará un 20% del tiempo total. (20% de CPU)
Con cgroups estamos eliminando factores de la ecuación, por lo que no dependerá de un proceso externo, ni de la prioridad del proceso, además, no estaremos enviando señales y cambiando el estado del proceso constantemente. Será el propio kernel el que asigne CPU a un proceso o no. Por lo que todo será más limpio eficiente.

Con los cgroups podremos además definir de cuánto será el intervalo de tiempo (antes dijimos que era de 100000µs, que es el valor por defecto) de la siguiente forma y dejarlo en un segundo (1000000µs):

sudo cgset -r cpu.cfs_period_us=1000000 cpulimitada

Tenemos mucho más control sobre los procesos con cgroups. Para obtener más información podemos echar un vistazo a la documentación de RedHat. Incluso podríamos controlar los cgroups desde el sistema de archivos (en muchas distribuciones tenemos los cgroups montados en /sys/fs/cgroup/, o podemos montar el directorio virtual donde queramos, igual que /proc).

Este post pretende ser solo una pincelada, así que para curiosear con las cgroup-tools podremos investigar los comandos: lscgroup, cgget, cgset, cgcreate, cgdelete. Hay más, pero creo que estos son los más importantes.

systemd-slices


Como dijimos antes, systemd utiliza muchísimo los cgroups, generalmente tiene un cgroup para cada servicio con todos los procesos que despliegue cada uno de ellos. Podemos personalizar todo esto. Ahora, creemos un servicio de systemd, que contenga lo siguiente (yo lo he llamado /etc/systemd/system/poesiatest.service):

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=Gascripts
After=network.target

[Service]
Type=exec
ExecStart=/home/gaspy/consumidor.sh
CPUAccounting=true
CPUQuota=20%

[Install]
WantedBy=multi-user.target

Como hemos puesto al final de la etiqueta Service, utilizamos la clave CPUAccounting para indicarle a systemd que queremos limitar la CPU de ese servicio, y CPUQuota para especificar el porcentaje de CPU con el que lo vamos a limitar. Arrancamos el servicio:

sudo systemctl daemon-reload
sudo systemctl start poesiatest.service

Y cuando miremos top veremos que la CPU que consume dicho proceso está limitada al 20% (más o menos, siempre hay un poco de margen).

La gracia está en que podemos hacer que un servicio en ejecución varíe su cuota de CPU:

sudo systemctl set-property poesiatest.service CPUQuota=50%

Pero esto no está limitado a servicios de systemd, podemos ejecutar un programa tal y como lo hacíamos con cpulimit, pero utilizando un slice que limite las características de la ejecución del programa. Por detrás utilizará cgroups, pero el resultado es mucho más amigable para el usuario (o sea, nosotros). Primero creamos la slice (luego podemos modificar sus características con systemctl set-property por lo que no es algo fijo). Nuestra slice será un archivo en /etc/systemd/system/mislice.slice que contiene:

1
2
3
4
5
[Unit]
Description=Mi slice

[Slice]
CPUQuota=40%

Ahora para ejecutar un programa con esa slice hacemos:

systemd-run --user --slice=mislice.slice consumidor.sh

Utilizaremos –user para ejecutar el programa como usuario.

Si por el contrario el proceso ya está iniciado, podremos utilizar cgclassify (como antes), ya que nuestro slice al final es un cgroup:

sudo cgclassify -g cpu:mislice.slice 11023

Y, por supuesto, sin ningún problema, podremos variar la cuota de CPU para dicho grupo:

sudo systemctl set-property mislice.slice CPUQuota=90%

CPU shares

Una forma justa de asignar CPU a grupos de procesos o cgroups es a través de shares. Son como participaciones en la rifa de tiempo de CPU. Como no estamos definiendo el porcentaje de CPU que le corresponde a cada grupo o a cada proceso es algo más difícil de ver. En este caso le estamos diciendo al sistema operativo la cantidad de CPU relativa que le corresponde a cada grupo.
Por ejemplo, tenemos 100 participaciones o shares y tenemos dos procesos. Un proceso tiene 50 y el otro tiene otras 50. Este es el caso por defecto, en el que el mismo tiempo de CPU le corresponde a cada proceso. Ahora bien, si un proceso tiene 75 y el otro 25. El proceso que más participaciones tiene tendrá más tiempo de CPU asignado que el proceso que tiene 25. Hasta aquí todo es más o menos lógico. La gracia de las participaciones está en la CPU que necesitan los procesos. Si por ejemplo el proceso que tiene 75 shares está esperando que el usuario pulse una tecla, y el proceso de 25 shares está calculando dígitos del número Pi (una tarea que utiliza intensamente la CPU), nuestro sistema operativo, mientras el usuario pulsa o no la tecla que requiere el primer proceso asignará el 100% de CPU (o toda la que sea posible) al proceso que solo tiene 25 participaciones. Es decir, las participaciones tienen efecto cuando hay una disputa por la CPU. En caso que haya CPU para todos los procesos, el sistema operativo entregará los recursos sin mirar mucho.
Por ejemplo, si tenemos un servidor web, y en la misma máquina un gestor de tareas en segundo plano para extraer información, procesar ficheros pesados de usuarios, etc. Nos interesa más que haya CPU para procesar las peticiones de los usuarios con la menor demora posible, por lo que si entra una nueva tarea de un usuario, no queremos que se lleve todos los recursos del procesador. Podemos hacer que el servidor web tenga 90 participaciones y el gestor de tareas tenga 10. Eso sí, si estamos en una hora en la que no tenemos usuarios en la web o tenemos muy pocos, el gestor de tareas podrá consumir toda la CPU si lo necesita.

Para utilizar las CPU shares, podemos hacerlo tanto con cgset como con systemctl set-property, de la siguiente manera:

sudo cgset -r cpu.shares=512 cpulimitada
sudo systemctl set-property mislice.slice CPUShares=512

Teniendo en cuenta que el número de shares que tiene un grupo por defecto es de 1024. En el ejemplo, estamos reduciendo la cantidad de participaciones, por lo que le bajamos prioridad a la asignación de CPU. Si queremos podemos subirla a 2048, 4096, etc. Personalmente, suelo utilizar múltiplos y submúltiplos de 1024 para hacer rápidamente el cálculo de probabilidad de cabeza. Pero podemos utilizar otras cantidades como 100, 1000, 1234, etc.

Es una buena técnica, sobre todo si estamos experimentando, anotar el valor de shares, que tiene un grupo (si no es de 1024), para eso podemos hacer:

sudo cgget -r cpu.shares cpulimitada
cpulimitada:
cpu.shares: 512

Un detalle más sobre systemd y cgroups

Como hemos visto systemd también administra cgroups en nuestro sistema y, por defecto, crea muchos grupos y nos deja una configuración base hecha. Es cierto que solo están los procesos agrupados, pero no tenemos ninguna restricción, eso nos lo deja a nosotros. Pero si somos usuarios de una distribución que utilice systemd podemos sacar provecho de todo esto.
Solo tenemos que echar un vistazo a lo siguiente:

systemd-cgls

Nos devuelve algo como esto:

Es un listado de todos los slices de manera jerárquica. Si observamos con atención podemos ver que hay un slice para usuarios (user.slice), para el sistema (system.slice) y a veces no aparece si no está en uso, otro para máquinas virtuales y linux containers (machine.slice). Eso nos permite establecer propiedades con systemctl set-property sobre esos slices. Por ejemplo:
sudo systemctl set-property user.slice CPUQuota=30%

Para que los usuarios no puedan exceder una cuota determinada de CPU. Aunque también vemos que cada usuario tiene su slice. Por ejemplo, user-1000.slice, por lo que podríamos decir:
sudo systemctl set-property user.slice CPUQuota=40%

Para que dicho usuario no pueda exceder esa cuota de CPU. Algo que puede ser de gran utilidad si administramos un servidor con varios usuarios y no queremos que alguno de ellos se pase.

Por ejemplo, también podríamos aplicar restricciones a Apache, PHP, MySQL, postfix, o demás servicios del modo habitual. O incluso a diferentes contenedores de docker (vía containerd.service). Además, si queremos ver qué cgroups están consumiendo CPU, memoria y demás, tenemos una utilidad systemd-cgtop, que nos muestra la información como el comando top, sencilla y actualizada cada segundo.

Más información

Si quieres más información sobre prioridad de procesos y cgroups:

Hay muchas más webs que hablan de ello, pero estas son algunas que he consultado para escribir este post.

Foto principal: unsplash-logoOleg Gospodarec

Foto pizza: unsplash-logoHeather Gill

Binarideas logo

¿Necesitas un sysadmin?

Si te ha gustado el post y encuentras interesante lo que cuento en materia de sistemas. O si necesitas gestionar un servidor (o muchos), automatizar procesos o mejorar la calidad de los procesos actuales. No dudes en ponerte en contacto conmigo.

The post Limitar el uso de CPU de nuestras aplicaciones o procesos en GNU/Linux (señales, nice, cpulimit/cputool, cgroups, systemd slices) appeared first on Poesía Binaria.

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

Poesía Binaria

Reanudando la marcha después de un año

febrero 18, 2020 09:14

¡Hace poco más de un año que no publico nada! Seguro que muchos de los que leíais el blog a menudo pensabais que estaba abandonado. Lo cierto es que en este último año han ocurrido varias cosas tanto en mi vida profesional como personal que me han impedido seguir con el ritmo de publicación.
Hace muchísimo tiempo que no hablo de algún factor personal en este blog. Llevo más de 10 años con él y 10 años dan para mucho, ¡No he celebrado el aniversario del blog! ¡No tengo perdón!

Lo principal, el año pasado nació mi primer hijo, muy querido y deseado. Y claro, la cantidad de ratos libres durante el día desciende de manera implacable. Es más, cuando por fin tienes un rato libre, para ti solo, todo está hecho (cosa rara), y el pequeño está dormido, prefieres descansar un poco. Aún así, en este tiempo, he empezado cerca de diez posts que poco a poco iré revisando y sacando de los borradores.

Por otro lado, a veces he escuchado a personas decir que tener un hijo les cambia la perspectiva de manera radical. Es cierto que te planteas la vida de manera distinta. Tanto que, empujado también por otros factores, dejé mi trabajo. Y desde entonces intento salir adelante como autónomo/emprendedor/mente inquieta, ofreciendo mis servicios y experiencia como desarrollador y administrador de sistemas bajo el nombre de Binarideas.
Siempre he tenido muchas ideas, y me han gustado la programación y los retos y había muchas cosas que aprender tanto de contabilidad, organización, presupuestos, trato con clientes, fiscalidad (aunque una asesoría me lleva algunos temas, algo tiene que sonarme todo, digo yo). Además, tenía que desarrollar una serie de herramientas que me iban a ayudar a sacar adelante mis proyectos y eso me quitaba mucho más tiempo para el blog. En cualquier caso, también ofrezco servicios de desarrollo a medida y administración de sistemas. Si crees que puedo ayudarte, contacta conmigo, si vienes del blog, seguro que te hago una oferta.

Como me dijo un amigo (no recuerdo las palabras exactas), hay que estar loco para tener un hijo y dejar el trabajo. Y no le falta razón.

En cualquier caso, este blog quedará reservado para temas técnicos, aunque me haré algo de publicidad y, por supuesto, como persona comprometida con el software libre, muchas de las herramientas, scripts y técnicas que he desarrollado, las compartiré por aquí, comentadas y explicadas así como en plataformas como Github o Gitlab.

También tengo que decir que la frecuencia de publicación de posts bajará un poco. Antes, intentaba tener un artículo cada semana, incluso tenía varios programados para lanzarse los lunes que coincidía con el día de más visitas (depende de la temporada cae en lunes o miércoles), pero ahora seguramente la frecuencia bajará un poco, ¡pero este blog no está abandonado!

Eso sí, ¡mañana, toca post! Largo y muy interesante. También tengo muchos e-mails y comentarios que debo contestar. ¡Todos serán contestados!

Foto principal: unsplash-logoKai Pilger

The post Reanudando la marcha después de un año appeared first on Poesía Binaria.

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

Navegapolis

Por qué pusimos en marcha un registro de propiedad intelectual

febrero 13, 2020 06:47

safe creative cc tech summit

En 2007, cuando pusimos en marcha Safe Creative, muchos expertos dudaban si era posible abrir en internet un registro de propiedad intelectual.

El mismo Creative Commons nos invitó en el  Technology Summit de 2008,  a explicar "por qué y para qué un registro de propiedad intelectual", presentándonos con un título que parecía cuestionarlo: "Developers of digital copyright registries and similar animals" :-P 

¿Por qué un registro electrónico? ¿Por qué no seguir registrando como siempre en oficinas de registro de ministerios o consejerías de educación?

Los registros de propiedad intelectual no otorgan derechos: el autor de una obra creativa tiene todos sin necesidad de registrarlos (Convenio de Berna). Lo que ocurre es que resulta muy aconsejable que registre su obra antes de "moverla", para tener asentada una primera prueba declarativa de su autoría, para publicar su trabajo o enviar versiones previas con tranquilidad, sabiendo que dispone de la merjor prueba en el tiempo frente a quien pudiera estar tentado de atribuírselo.

Y así fue. Para subir a internet la canción que hemos compuesto, el vídeo, el libro.... con la seguridad de haberlo registrado previamente, sin tener que esperar a que abra la ventanilla del registro a las 9 de la mañana para llevarles  una copia. Para eso nació Safe Creative (y para otras muchas cosas que tampoco se pueden hacer en un registro tradicional).

... que preguntárselo hace 13 años era normal, ¡pero a estas alturas!     ;-)

 

 EN EL REGISTRO TRADICIONALEN SAFE CREATIVE
     
La prueba de derechos de autoría se basa En la presunción administrativa de veracidad que la ley del país correspondiente concede al funcionario que inscribe la manifestación del autor. En la evidencia tecnológica que constituye la identificación de la obra con 3 huellas criptográficas diferentes, y de la fecha por la aplicación de un sellado de tiempo cualificado, redundado con un proceso de auditoría diaria sobre blockchain. 
     
Validez internacional La presunción de veracidad del funcionario es válida en su propio país. La peritación de pruebas o evidencias tecnológicas es válida en todas las jurisdicciones.
     
Operativa Normalmente presencial, con formatos de obras en algunos casos definidos y restringidos por reglamentos internos.

On line 24 x 7 en cualquier formato que permita identificar a la obra.

 

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

Meta-Info

¿Que es?

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

rss subscripción

Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
planetacodigo

planetacodigo

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

Idea: Juanjo Navarro

Diseño: Albin