Weblogs Código

Adrianistán

Usar AVA para tests en una API hecha en Node.js y Express

julio 15, 2018 07:01

Testear nuestras aplicaciones es algo fundamental si queremos garantizar un mínimo de calidad. En este breve post, explicaré como he usado AVA para crear tests para mis APIs: tanto unitarios como de integración con AVA.

AVA es una herramienta de testing, que nos permite describir los tests de forma muy sencilla. De todas las herramientas que he probado, AVA es muy preferida. Es muy sencilla, ejecuta los tests en paralelo y permite escribir código en ES6 (usa Babel por debajo). Además tiene bastante soporte siendo el framework de testing usando por muchos proyectos ya.

Instalamos AVA de la forma típica:

npm install ava --save-dev

A continuación creamos un fichero cuya terminación sea .test.js, por ejemplo, suma.test.js. El lugar da igual.

Una opción de diseño es poner los test unitarios al lado de las funciones en sí, otra opción es crear una carpeta para todos los tests, ya que los de integración van a ir casi siempre ahí. Para ejecutar los tests, simplemente:

ava

El interior de suma.test.js debe importar la función test de AVA y las funciones que quiera probar.

Los tests se definen como una llamada a la función test con la descripción del test y a continuación un callback (asíncrono si queremos) con el objeto que controla los tests (llamado t normalmente). Veamos un ejemplo simple:

import test from "ava";
import {suma} from "./operaciones";

test("Suma",t => {
    t.is(suma(1,2),3);
});

El objeto t soporta múltiples operaciones, siendo is la más básica. Is pide que sus dos argumentos sean iguales entre sí, como Assert.Equal de xUnit.

Veamos que más soporta Ava.

  • t.pass(): Continúa el test (sigue)
  • t.fail(): Falla el test (no sigue)
  • t.truthy(val): Continúa el test si val es verdaderoso (usando la lógica de JavaScript) o falla el test
  • t.true(val): Continúa el test si val es verdadero (más estricto que el anterior) o falla.
  • t.is(val1,val2): Continúa el test si val1 y val2 son iguales (superficialmente) o falla.
  • t.deepEqual(val1,val2): Continúa el test si val1 y val2 son iguales (profundamente) o falla.
  • t.throws(funcion): Ejecuta la función especificada esperando que lance una excepción. Si no lo hace, falla el test. Se puede especificar el tipo de excepción que esperamos en el segundo argumento.
  • t.notThrows(funcion): Exactamente lo contrario que la anterior.

Y algunas más, pero estas son las esenciales.

import test from "ava";

function sum(a,b){
    return a+b;
}

function login(username,password){
    if(username === null || password === null){
        throw new Error("Missing username or password");
    }
}

test("Test example: Sum",t => {
    t.is(sum(1,2),3);
});

test("Login fail username null", t => {
    t.throws(()=>{
        login(null,"123456");
    });
});
test("Login fail password null", t => {
    t.throws(()=>{
        login("username",null);
    });
});

También podemos definir funciones que se ejecuten antes y después de nuestros tests, y una sola vez o con cada test. Podemos usar test.before, test.beforeEach, test.after y test.afterEach en vez de test. Por ejemplo, si tienes una base de datos que necesita inicialización, puedes definir ese código en test.before y la limpieza en test.after.

import test from "ava";
import db from "../db";

test.before(async () => {
    // Iniciar el ORM Sequelize
    await db.sync();
});

 

Con esto ya podemos hacer tests unitarios, pero no podemos probar la aplicación web al 100%. Entra en acción supertest que nos permitirá tener un servidor Express simulado para que los tests puedan probar la aplicación al completo.

Supertest

Instalamos supertest

npm install supertest --save-dev

En el fichero de test necesitamos crear un objeto de tipo aplicación de Express. Este objeto puede ser el mismo que usas en tu aplicación real o ser una versión simplificada con lo que quieras probar.

import test from "ava";
import request from "supertest";
import auth from "http-auth";
import express from "express";

function setup(){
    const app = express();

    let basic = auth.basic({
        realm: "Upload Password"
    }, function (username, password, callback) {
        callback(username === "admin" && password === "123456");
    });

    app.get("/upload",auth.connect(basic),function(req,res){
        res.sendFile("upload.html");       
    });
    return app;
}

test("Página upload requiere autenticación HTTP Basic", async t => {
    let res = await request(setup())
        .get("/upload")
        .send();
    t.is(res.status,401);
    t.true(res.header["www-authenticate"] !== undefined);
});

Aquí la función de test es asíncrona, AVA es compatible con ambos tipos. Supertest es muy completo y permite probar APIs enteras con su sencilla interfaz que junto con AVA, se convierte en algo casi obligatorio para una aplicación que vaya a producción.

La entrada Usar AVA para tests en una API hecha en Node.js y Express se publicó primero en Adrianistán.

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

Blog Bitix

La librería log4j2 para emitir trazas en aplicaciones Java

julio 14, 2018 11:00

Java

La librería log4j2 es la librería sucesora de log4j y logback para emitir las trazas de depuración e información indispensables cuando son requeridas en una aplicación Java. Suele usarse en combinación slf4j ya que esta permite cambiar de librería de logging subyacente sin hacer ningún cambio en la aplicación.

Algunas de las nuevas características de log4j2 son:

  • Rendimiento mejorado usando funcionalidades asíncronas en los loggers.
  • Soporte para múltiples APIs como SL4J, Commongs Logging y java.util.logging (JUL).
  • Sin encadenamientos a la librería al poder en cualquier momento usar cualquier librería compatible con SLF4J.
  • Recarga automática de la configuración sin perder ninguna traza.
  • Filtrado avanzado basado en datos de contexto, marcadores, expresiones regulares y otros componentes.
  • Arquitectura basada en plugins.
  • Soporte de propiedades definidas en archivos de configuración, propiedades del sistema, variables de entorno, el mapa ThreadContext y datos del evento.
  • Soporte de lambdas de Java 8. Las expresiones lambdas no se evalúan si no está activado el nivel de log consiguiendo el mismo efecto que con una sentencia if pero en menos código.
  • Niveles de log personalizados fácilmente definibles sin necesidad de realizar subclases.
  • Recolección de basura reducida lo que evita presión al recolector de basura y mejora el rendimiento de las aplicaciones.

Por defecto, la configuración de log4j2 se describe en un archivo xml aunque también soporta definirlo en un formato menos verboso como yaml. La siguiente aplicación de Spring Boot al iniciarse emite en la consola varios mensajes usando log4j2.

Usando la clase LogManager se obtiene una referencia a la clase Logger con la que se emiten las trazas y que posee diferentes métodos para cada nivel de traza.

Una vez se ha iniciado la aplicación Spring Boot invoca el método run y se emiten las trazas propias de la aplicación después de las que también Spring Boot y otras librerías emiten., en este caso usando texto en forma de arte ascii.

En el archivo de construcción de la aplicación usando Gradle hay que incluir las dependencias de las librerías.

Las trazas son muy importantes por la valiosa información que proporcionan de lo que está sucediendo en una aplicación a los que recurriendo es posible obtener la valiosa información que permite saber que ha sucedido en una determinada acción o que permite descubrir mucho más rápidamente la causa de un error.

En otro artículo ya he comentado como usar marcadores con sl4j y logback para relacionar trazas que son emitidas en diferentes clases o módulos de la aplicación, también se puede hacer con log4j. En siguientes artículos comentaré como transformar un objeto a un mensaje sin necesidad de convertir ese objeto a un String en cada traza de la aplicación donde se use su información y otra forma de relacionar trazas muy útil en aplicaciones web asignando un indenticador única en cada petición y emitiendolo en todas sus trazas, esto permite saber todo lo que ha ocurrido en una petición entre las muchas que se ejecutan concurrentemente por todos los usuarios de la aplicación.

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 ./gradlew run.

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

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

WinAPI contra C

julio 14, 2018 09:09

Muchas veces tiendo a usar funciones implementadas en la API de Windows en vez de las nativas de C/C++. El motivo es que al estar disponibles en el propio sistema evitamos que se enlacen en nuestra aplicación, y así el ejecutable resultante sea más compacto. El inconveniente es que probablemente sean más lentas, particularmente porque […]

La entrada WinAPI contra C aparece primero en Bitácora de Javier Gutiérrez Chamorro (Guti).

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

Variable not found

El filtro [ApiController] en ASP.NET Core MVC 2.1

julio 13, 2018 07:13

ASP.NET Core MVCUno de los objetivos de ASP.NET Core ha sido siempre servir como infraestructura para la creación de servicios HTTP o APIs web, por lo que ya desde las primeras versiones se incluían funcionalidades específicamente diseñadas para facilitar esta tarea.

ASP.NET Core 2.1 continúa profundizando en esa línea e incluye entre sus novedades el nuevo atributo [ApiController], un decorador aplicable a controladores que los identifica como puntos de entrada de APIS, aplicando de forma automática una serie de convenciones bastante útiles a la hora de crear este tipo de componentes:
[ApiController]
[Route("api/[controller]")]
public class ValuesController : ControllerBase
{
...
}
Fijaos que, a diferencia de ASP.NET Web API (.NET Framework), se ha optado por utilizar un atributo en lugar de emplear herencia (en aquél framework existía la clase ApiController).
A continuación veremos qué debemos tener en cuenta a la hora de aplicar este atributo a nuestros controladores y qué convenciones son las que estaremos asumiendo al utilizarlo.

Obligatoriedad de usar rutas explícitas

El atributo [Route] es obligatorioLa aplicación de [ApiController] a un controlador hace obligatorio el uso de rutado por atributos hacia sus acciones. Es decir, los controladores decorados con [ApiController] no serán accesibles utilizando rutas por convenciones, como las definidas en el método Configure() de la clase Startup. De no ser así, se lanzará una excepción durante el arranque de la aplicación y no se podrá ejecutar.

Por tanto, este atributo vendrá siempre acompañado del correspondiente [Route], ya sea a nivel de controlador, o en sus acciones.
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
...
}

Inferencia del origen de parámetros

Al decorar un controlador con [ApiController], el binder usará el siguiente criterio, en orden de aplicación, para determinar desde dónde obtener los valores para los parámetros de las acciones:
  • Los parámetros de tipo complejo (clases, básicamente) se obtendrán desde el cuerpo de la petición. Sería equivalente a decorar el parámetro con [FromBody] en versiones anteriores a ASP.NET Core 2.1, por lo que, como ocurría anteriormente, sólo puede existir un parámetro de este tipo en las acciones.
     
  • Los valores para los parámetros de tipo IFormFile e IFormFileCollection serán tomados desde los valores recibidos como campos de formulario. Equivalente a usar [FromForm] en versiones anteriores.
     
  • Los parámetros cuyo nombre coincida con un valor presente en la ruta de la petición serán tomados desde ella, es decir, como si estuvieran decorados con [FromRoute].
     
  • El resto de parámetros se asumirá que proceden de la query string, equivalente a aplicar FromQuery] sobre ellos.
Por ejemplo, en una acción código como la siguiente, el valor del parámetro id se obtendrá desde la ruta de la petición, Invoice desde el cuerpo de la petición, y returnUrl desde la query string, todo ello sin necesidad de especificarlo expresamente:
[ApiController]
[Route("api/[controller]")]
public class InvoiceController: ControllerBase
{
[HttpPost("update/{id}")]
public ActionResult<InvoiceCreationResult> Update(int id, InvoiceInfo invoice, string returnUrl)
{
...
}
}

Errores HTTP 400 automáticos cuando el estado del modelo es inválido

Cuando entra una petición dirigida hacia una acción perteneciente a un controlador decorado con [ApiController], el binder cargará sus parámetros y aplicará automáticamente las validaciones asociadas a los datos de entrada. Como es habitual, el resultado de dicha validación lo tendremos en la propiedad ModelState.IsValid.

Pues bien, por defecto, el framework comprobará todos los objetos recibidos, y retornará un error HTTP 400 cuando se produzcan errores de validación. A continuación puedes ver un ejemplo de acción que devuelve el mismo objeto que recibe como parámetro, y el resultado obtenido de algunas peticiones:
public class Friend
{
[Required]
public string Name { get; set; }
[Range(0, 120)]
public int Age { get; set; }
}

[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpPost]
public ActionResult<Friend> Friend(Friend f)
{
return f;
}
}
PeticiónRespuesta
Enviamos un objeto correcto:
POST https://localhost:44399/api/test/ HTTP/1.1
Host: localhost:44399
Content-Length: 34
Content-Type: application/json

{
   name: "John",
   age: 34
}
HTTP 200, con el mismo objeto recibido:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Server: Kestrel
Content-Length: 24

{"name":"John","age":34}
Objeto con contenidos inválidos en los campos:
POST https://localhost:44399/api/test/ HTTP/1.1
Host: localhost:44399
Content-Length: 31
Content-Type: application/json

{
   name: "",
   age: 200
}
HTTP 400 con el resultado de las validaciones incorrectas:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Server: Kestrel
Content-Length: 91

{
   "Age":["The field Age must be between 0 and 120."],
   "Name":["The Name field is required."]
}
Ni siquiera enviamos un objeto:
POST https://localhost:44399/api/test/ HTTP/1.1
Host: localhost:44399
Content-Length: 0
HTTP 400 indicando un error en el objeto:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Server: Kestrel
Content-Length: 33

{"":["The input was not valid."]}

Personalización o desactivación de algunos comportamientos

Aunque las convenciones asociadas al uso de [ApiController] son bastante razonables, si por cualquier motivo queremos desactivar alguno de los comportamientos por defecto, podemos hacerlo desde el método ConfigureServices() de la clase Startup.

En el siguiente código muestra cómo desactivarlas todas (ya, mucho sentido no tendría, pero me vale como ejemplo ;))
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = false;
options.SuppressInferBindingSourcesForParameters = false;
options.SuppressModelStateInvalidFilter = false;
});
Ojo, que estas líneas deben incluirse después de haber añadido los servicios de MVC con services.AddMvc(), porque de lo contrario no funcionará.
También podemos configurar el tipo de resultado que se retornará cuando se detecte que el estado del modelo es inválido. Por ejemplo, en el siguiente código se establece como respuesta un resultado del tipo NotFoundResult(), lo que provocará que nuestros clientes obtengan un HTTP 404 como respuesta a una petición con datos inválidos:
services.Configure<ApiBehaviorOptions>(options =>
{
...
options.InvalidModelStateResponseFactory = context => new NotFoundResult();
});
¡Y eso es todo! Espero que lo que hemos visto os sea de utilidad, y gracias a estas pequeñas novedades os resulte más sencillo el desarrollo de APIs HTTP con ASP.NET Core :)

Publicado en Variable not found.

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

Variable not found

Task.Factory.StartNew(EnjoySummer, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);

julio 12, 2018 06:55

Pues eso, que parecía que no llegaba y las semanas se hacían cada vez más cuesta arriba, pero por fin es el momento de descansar un poco e intentar conseguir un merecido reinicio mental.

Durante las próximas semanas dejaré el blog a merced de las olas, pues estaré disfrutando con la familia en las maravillosas playas de Cádiz: sol, paseos, refrescantes bañitos en las aguas del Atlántico y chiringuitos playeros con aroma a pescado fresco. Un buen plan, sin duda ;)

Costa Ballena, Cádiz
Imagen original

Nos vemos a la vuelta, ya en el mes de septiembre, con la batería al 100% y listos para seguir buscando la variable ;)

¡Felices vacaciones a todos!

Publicado con ánimo vacacional en Variable not found.

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

Adrianistán

La perlificación de Python

julio 10, 2018 09:59

You know, FOX turned into a hardcore sex channel so gradually I didn’t even notice

Marge Simpson, Los Simpson

Recientemente ha salido Python 3.7, con interesantes novedades. También han salido los primeros artículos hablando de las novedades que podrá traer Python 3.8. Como muchos ya conoceréis, y si no es así explico, Python funciona a base de PEPs (Python Enhancement Proposal). Cualquier persona puede abrir un PEP, que es un documento que describe la funcionalidad que se quiere añadir/modificar/eliminar. Estas PEPs se discuten y finalmente Guido, creador de Python, las aprueba y se codifican.

Dentro de las PEP relacionadas con Python 3.8 hay algunas bastante controvertidas que han hecho saltar la voz de alarma. No ha faltado gente que ha opinado que cada vez Python se parece más a Perl. Este proceso habría empezado con Python 3 pero se habría ido haciendo más evidente hasta llegar a hoy. Cada vez con más sintaxis poco utilizada, con más elementos, más cómodo de escribir para el experimentado aunque menos legible si no dominas el lenguaje.

Y resulta curioso, porque Python es en parte una respuesta a la excesiva complejidad que podían tener los programas hechos en Perl. Su popularidad se debe a que es fácil de aprender y eso parece que ya no está tan claro.

Con la introducción de operadores como := o ?? o anotaciones como @dataclass se va, en cierta medida, perdiendo el espíritu original de Python. Y es cierto que otros lenguajes tienen algo similar, pero precisamente Python había sido muy selecto en incluir un conjunto bastante reducido de características, que todo el mundo pudiese dominar. Al final se sacrifica legibilidad y facilidad de aprendizaje por una ergonomía que beneficia a unos pocos en unos casos concretos.

Lenguajes de programación barrocos

Universidad de Valladolid, ejemplo de barroco civil. Foto: https://artevalladolid.blogspot.com

Python lleva un tiempo entrando en un proceso de perlificación pero en mi opinión no es el único lenguaje que ha entrado en una espiral parecida. Cada vez más lenguajes han pasado del renacimiento, donde se buscaba la consistencia, la simetría, la sencillez sin perder la funcionalidad, hacia el barroco, donde los lenguajes son más recargados, con más elementos sintácticos, que cubren mejor casos concretos, pero que de por sí no son tan esenciales, no cambian aspectos fundamentales del lenguaje y normalmente introducen diversas formas de hacer algo en un mismo lenguaje.

Veamos más ejemplos: en C++20 se propuso añadir funcionalidad de dibujado 2D a la librería estándar (propuesta que fue rechazada en una historia bastante larga para este post) y se han propuesto conceptos, módulos, comparación de tres vías, reflexión, metaclases,… En C# 8.0 se han propuesto también bastantes cosas como records, tipos non-nullable, interfaces con métodos ya implementados (traits) y rangos. Y eso sin contar con las características que estos dos lenguajes ya tienen, que son bastante más extensos que Python.

Retablo lateral de la Iglesia de San Miguel y San Julián (Valladolid). Barroco puro. Foto: https://commons.wikimedia.org/wiki/File:San_Miguel_-_retablo_relicario.jpg

Hay un horror vacui, horror al vacío, a funcionalidades. Siempre se añade y casi nunca se elimina. ¿Pero es realmente necesario? Es evidente que durante mucho tiempo, los lenguajes evolucionaban de forma muy lenta y muchos de los cambios que han hecho eran necesarios. Desde hace unos años, se ha aumentado la velocidad de los cambios, pero esto no puede seguir así eternamente, porque el ritmo que llevan muchas veces es superior al de los avances en lenguajes de programación y la retrocompatibilidad impide ir quitando cosas al mismo ritmo que se añaden. De este modo, todos los lenguajes que entran en esta espiral crecen y crecen. No se llegan a pulir, simplemente crecen.

La perfección no se alcanza cuando no hay nada más que añadir, sino cuando no hay nada más que quitar

Antoine de SaintExupéry

Uno podría comparar esto con lenguajes naturales, como el español o el inglés. En estos idiomas, pese a existir reglas, existen numerosas excepciones. Es posible que lenguajes como Python se estén viendo influenciados por las mismas mecánicas humanas que rigen los lenguajes naturales y que han hecho que existan excepciones. Tendría bastante sentido que ocurriese así. Pero personalmente, me gustaría que aprender Python no costase tanto como aprender alemán o francés.

Los procesos comunitarios

Para mí, gran parte de esta sobrecarga viene dada por los procesos comunitarios. En un proceso comunitario como PEP, comité de C++ o similares es mucho más fácil añadir que eliminar. En C++ la situación ha llegado a un punto en el que Bjarne Stroustrup, creador de C++, ha pedido que se relajen con las propuestas en Remember the Vasa!, en honor a un bonito barco sueco que se hundió por exceso de carga. No tanto por su peso, sino por su disposición y las reformas que hubo que hacer para que todo encajase.

El Vasa fue recuperado después de su naufragio y se expone en Estocolmo. Foto: https://rachelannsblog.wordpress.com/2016/08/03/set-sail-set-at-the-bottom-of-the-sea/

Es por ello que las personas encargadas de elegir que se añade al lenguaje o no deben de ser muy conscientes de lo que supone, ya que una vez se introduzca, va a ser casi imposible eliminarlo.

No estoy en contra de añadir nuevas características (¡al contrario!) pero se debe respetar la visión de conjunto del lenguaje, que todo cuadre y esté cohesionado. No siempre tener más es mejor.

¿Te ha gustado el artículo?

Si es así, puedes ayudar al blog. Dentro de unos días es el Amazon Prime Day. Como muchos de vosotros seguro que os compraréis algo, no quiero dejar la oportunidad de deciros que este blog tiene enlace de referidos y que por cada cosa que compréis con el enlace, me llevo una pequeña parte (a vosotros no os va a costar más caro).

Enlace Amazon.es

Será muy bien recibido

 

La entrada La perlificación de Python se publicó primero en Adrianistán.

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

Variable not found

Problem Details: una forma estándar de retornar errores desde APIs HTTP (y cómo usarlo desde ASP.NET Core)

julio 10, 2018 06:55

ASP.NET Core MVCLos que lleváis tiempo programando APIs HTTP, ya sea con ASP.NET Core o con otras tecnologías, seguro que en muchas ocasiones habéis tenido que decidir cómo retornar al cliente, de forma más o menos normalizada, los errores producidos en el lado servidor.

Lo habitual es echar mano de los status code de HTTP para indicar problemas en el proceso de una petición; de hecho, este protocolo dispone de un rico conjunto de códigos que en principio parecen cubrir todas nuestras necesidades.

Pero no siempre es así. Por ejemplo, si tenemos un servicio que permite a los clientes de una empresa formalizar un pedido a través de un API y una llamada a este servicio retorna un error HTTP 403 (forbidden), claramente estamos indicando que el solicitante no tiene permisos para hacer un pedido. Sin embargo, no tenemos una forma clara de indicar cuál es la causa de esta prohibición (¿quizás las credenciales no son correctas? ¿o quizás el cliente no tiene crédito en la empresa? ¿o puede ser que el administrador lo haya denegado expresamente?)

Para aportar más detalles sobre el problema, normalmente necesitaremos retornar en el cuerpo de la respuesta información extra usando estructuras o formatos personalizados, probablemente distintos de una aplicación a otra, y documentarlos apropiadamente para que los clientes puedan entenderlos. Y aquí es donde entra en juego el estándar “Problem details”.

El estándar Problem details (RFC7807)

Consciente de la necesidad de normalizar este tipo de escenarios, la Internet Engineering Task Force (IEFT) propuso hace unos años el estándar Problem Details for HTTP APIs (RFC7807), promoviendo el uso de una estructura JSON normalizada en la que es posible ampliar información sobre un error producido durante el proceso de la petición a la API.

Estas respuestas, empaquetadas con un content type “application/problem+json” (aunque también podrían ser “application/problem+xml”), tienen una pinta como la siguiente:
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Content-Language: en

{
"status": 403,
"type": "https://www.b2bcommerce.com/orders/problems/disabled-customer",
"title": "You can't submit orders",
"detail": "You must finish your registration process before submitting orders",
"instance": "/orders/12/123456",
}
En la estructura anterior se observan varias propiedades que forman parte del estándar:
  • status, si existe, debe contener el código HTTP original del error. Puede ser interesante cuando intermediarios como proxies u otros middlewares pueden alterar el código original.
     
  • type es una URI absoluta o relativa, por defecto “about:blank”, que permite identificar cuál es el problema exacto y su posible solución. Según la especificación, el contenido de esa URI debería ser una descripción del problema legible por personas (por ejemplo, escrita usando HTML).
     
  • title puede contener un texto breve que describa el problema, sobre todo destinados a aquellos consumidores que no sean capaces de interpretar correctamente el significado del campo type.
     
  • En detail podemos especificar información adicional sobre el problema, pero más enfocada a su resolución. Como en el caso anterior, debe ser legible por humanos, y no estructuras de datos que deban ser procesadas por el consumidor.
     
  • Por último, instance es una URL, absoluta o relativa, que puede apuntar a la instancia protagonista del problema.
Aparte de estos campos, el estándar permite el uso de extensiones, es decir, de campos personalizados que aporten aún más información al cliente. El siguiente bloque muestra, por ejemplo, el resultado de una petición con valores incorrectos en sus parámetros:
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
Content-Language: en

{
"type": "https://mysite.com/validation",
"title": "Invalid request parameters",
"invalid-params":
[
{
"name": "displayName",
"reason": "This field is required"
},
{
"name": "age",
"reason": "Must be a integer between 0 and 120"
},
{
"name": "email",
"reason": "Must be a valid email address"
}
]
}
Es muy recomendable leer la especificación completa en https://tools.ietf.org/html/rfc7807

Problem details en ASP.NET Core 2.1

ASP.NET Core 2.1 incluye un soporte aún muy básico para facilitar la adopción de este estándar en nuestros servicios HTTP, pero al menos nos facilita la tarea en algunos escenarios.

En primer lugar, en el espacio de nombres Microsoft.AspNetCore.Mvc encontramos la clase ProblemDetails, que modela la estructura de datos definida por la especificación:
public class ProblemDetails
{
public string Type { get; set; }
public string Title { get; set; }
public int? Status { get; set; }
public string Detail { get; set; }
public string Instance { get; set; }
}
Esto ya nos da la posibilidad de utilizarla directamente para retornar al cliente problem details, por ejemplo como sigue:
[HttpPost]
public ActionResult Submit(Order order)
{
const int maxProducts = 10;
if (order.ProductsCount > maxProducts)
{
var details = new ProblemDetails()
{
Type = "https://www.b2bcommerce.com/orders/too-many-products",
Title = "The order has too many products",
Detail = $"You can't submit orders with more than {maxProducts} products",
Instance = Url.Action("Get", "Orders", new { id = order.Id }),
Status = 403
};
return new ObjectResult(details)
{
ContentTypes = {"application/problem+json"},
StatusCode = 403,
};
}
...
}
Una secuencia de petición y respuesta para comprobar el funcionamiento del código anterior podría ser:
========================================================================
POST https://localhost:44399/api/orders HTTP/1.1
Host: localhost:44399
Content-Length: 37
Content-type: application/json

{
id: 19,
productsCount: 22
}

========================================================================
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json; charset=utf-8
Server: Kestrel
X-Powered-By: ASP.NET
Content-Length: 208

{
"type":"https://www.b2bcommerce.com/orders/too-many-products",
"title":"The order has too many products",
"status":403,
"detail":"You can't submit orders with more than 10 products",
"instance":"/api/Orders/19"
}
Aunque crear el ObjectResult de forma manual es algo farragoso, sería bastante sencillo crear un helper o action result que nos ayudara a construir y retornar este tipo de objetos.
Tenemos también la clase ValidationProblemDetails, que, heredando de ProblemDetails, está diseñada expresamente para retornar errores de validación estructurados. Esta clase añade la propiedad Errors que es un diccionario en el que las claves son los nombres de las propiedades validadas con error, y el valor contiene un array de strings describiendo los problemas encontrados.

De hecho, esta clase está tan enfocada a los errores de validación que incluso podemos instanciarla enviándole un ModelState:
[HttpPost]
public ActionResult Submit([FromBody]Order order)
{
if (!ModelState.IsValid)
{
var details = new ValidationProblemDetails(ModelState);
return new ObjectResult(details)
{
ContentTypes = {"application/problem+json"},
StatusCode = 400,
};
}
return Ok(order);
}
Para simplificar aún más este escenario, también se ha añadido en ControllerBase el método ValidationProblem(), que automáticamente retornará un error 400 con la descripción de los errores:
[HttpPost]
public ActionResult Submit([FromBody]Order order)
{
if (!ModelState.IsValid)
{
return ValidationProblem();
}
...
}
Misteriosamente, el retornar el resultado de la invocación a ValidationProblem() no establecerá el content type a "application/problem+json", cuando esto tendría bastante sentido. Supongo que será para evitar que exploten clientes que no estén preparados para recibir ese tipo de contenido.
En cualquier caso, el resultado devuelto por el servicio anterior podría ser el siguiente:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-Powered-By: ASP.NET
Content-Length: 188

{
"errors": {
"ProductsCount": ["The field ProductsCount must be between 1 and 10."]
}
,
"type":null,
"title":"One or more validation errors occurred.",
"status":null,
"detail":null,
"instance":null
}

¿Y cómo se integra esto con el filtro [ApiController]?

Hace unos días hablábamos del nuevo filtro [ApiController], que permite identificar un controlador como endpoint de un API, y aplicarle automáticamente usa serie de convenciones, como el retorno automático de errores 400 cuando se detectan errores de validación.

También veíamos que, jugando un poco con su configuración, era posible establecer la factoría de resultados para las respuestas a peticiones con problemas de validación, por lo que resulta sencillo utilizar este punto de extensión para crear y configurar la respuesta bad request alineada con el estándar:
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var problemDetails = new ValidationProblemDetails(context.ModelState)
{
Instance = context.HttpContext.Request.Path,
Status = 400,
Type = "https://myserver.com/problems/validation",
Detail = "Invalid input data. See additional details in 'errors' property."
};
return new BadRequestObjectResult(problemDetails)
{
ContentTypes = { "application/problem+json", "application/problem+xml" }
};
};
});
¡Y eso es todo, al menos de momento! Espero que os haya resultado interesante, y útil para conocer este nuevo estándar y su, aunque aún básica, integración en ASP.NET Core.

Publicado en Variable not found.

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

Variable not found

Enlaces interesantes 329

julio 09, 2018 06:55

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

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Otros

Publicado en Variable not found.

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

Koalite

APIs con Node, TypeScript, Koa y TypeORM

julio 09, 2018 05:06

En mi (parsimoniosa) búsqueda de plataformas alternativas a .NET más de una vez me he llegado a plantear el uso de NodeJS + TypeScript. A fin de cuentas NodeJS es multiplataforma, ya tiene cierta madurez y es supuestamente estable. Con TypeScript llevo ya un par de años y, pese a sus “cosillas”, estoy razonablemente contento con él.

Por eso cuando hace unos días contactó conmigo Javier Avilés para hablarme sobre un repositorio que ha montado como plantilla para crear APIs con Node, TypeScript, Koa y TypeORM me pareció interesante echarle un vistazo.

OJO: Si has llegado aquí buscando un tutorial sobre cómo crear APIs con Node y TypeScript, mejor léete directamente la documentación de la plantilla creada por Javier. Ahí encontrarás información más útil que en este post. Aquí me limitaré a hacer un repaso somero a lo que me ha parecido la plantilla.

Las acciones web

De NodeJS no hay mucho que contar a estas alturas, y sobre TypeScript ya he escrito suficiente, por lo que intentaré centrarme en otras cosas. Puesto que el proyecto es para montar un API Web, parece razonable empezar por ver qué tipo de servidor web y framework se está usando.

Como framework para crear el API se está usando koa. Sin estar muy metido en node, diría que es una de las librerías/frameworks más populares para servidores web junto con express (del que incluso escribí un tutorial cuando hacía ese tipo de cosas). Conocía koa un poco por sus primeras versiones y recuerdo que hacía un uso llamativo de los generadores para huir del callback hell. En las versiones actuales se aprovecha async/await para conseguir dar un aspecto líneal al código asíncrono haciéndolo bastante claro y legible.

Al igual que la mayoría de frameworks web de lenguajes que no viven encorsetados en clases, la gestión de peticiones y respuestas se realiza mediante funciones que reciben la petición y generan la respuesta, en este caso ambas empaquetas en un objeto context. Lo bueno de este enfoque es que evita código repetitivo y ayuda a que todo sea más fácil de componer:

app.use(async ctx => {
  ctx.body = 'Hello World';
});

Es un estilo que me gustó mucho cuando conocí node, y más aún cuando jugué con él en clojure. Lo cierto es que la filosofía de la web, basada en petición/respuesta, se ajusta muy bien a ser modelada con funciones en lugar de con clases. Además, eso hace que todo sea muy homogéneo, ya conceptos típicos de frameworks webs como middlewares se reducen a crear decoradores sobre funciones.

Precisamente esta es una de las cosas que no me convencen de la plantilla que estamos analizando: el uso de clases estáticas para agrupar estas funciones que sirven de manejadores de peticiones:

export default class UserController {
  public static async getUsers (ctx: BaseContext) {
    // ...
  }
}

El uso de una clase para encapsular en método estático no aporta nada (a menos que me esté perdido algo) y en lenguajes como TypeScript lo consideraría un code smell.

Las rutas

En cualquier aplicación web acaba siendo necesario gestionar rutas, y en este caso se está usando koa-router. No tengo ni idea de si hay más routers para koa o de lo bueno o malo que es éste, pero parece fácil de manejar con el típico API basado en métodos con los nombres de los verbos:

var app = new Koa();
var router = new Router();

router.get('/', (ctx, next) => {
  // ctx.router available
});

app
  .use(router.routes())
  .use(router.allowedMethods());

Entre las librerías que conozco hay varias opciones para gestionar el registro de rutas. Algunas se basan puramente en convenciones (al estilo RoR), otras permiten utilizar metainformación en forma de atributos o anotaciones (uno de los varios estilos soportados ASPNET MVC), y las hay que registran las rutas explícitamente.

En el caso de la plantilla se está haciendo un registro explícito de las rutas y además se hace todo en un único fichero:

// routes.ts

router.get('/users', controller.user.getUsers);
router.get('/users/:id', controller.user.getUser);
// ...

Personalmente me gusta ese estilo. Creo que es cómodo tener centralizada la definición de rutas y poder comprobar de un vistazo qué rutas existen en la aplicación y qué formato siguen. En el caso de que el número de rutas creciese mucho siempre se podría partir en varios ficheros por áreas de aplicación.

La contrapartida que tiene es que cuando añades un nuevo manejador de rutas necesitas tocar en dos sitios: el fichero donde defines la función y el fichero que contiene el registro de rutas. Para mi es un mal menor, pero no deja de ser un incordio y puede dar lugar a fallos si te olvida registrar la ruta del manejador que acabas de crear.

Una cosa que me gusta de la plantilla es que no se usa un contenedor de inversión de control. He visto otras implementaciones de este estilo que tratan de copiar demasiado los patrones de uso de plataformas como .NET y Java, y acaban replicándolas pieza a pieza con componentes que no son realmente necesarios.

Los datos

Para tratar con los datos se está usando TypeORM, una librería que se define a si misma como un ORM que soporta los patrones de Active Record y Data Mapper.

Para mi eso se queda un poco lejos de lo que espero de un ORM. Como Data Mapper estaría al nivel de micro ORMs tipo Dapper o Massive. Como Active Record… bueno, la verdad es que excepto las inspiradas en Rails, no conozco muchas librerías que hayan triunfado con ese modelo.

En muchos casos la mejor opción es usar un Data Mapper que te simplifique la generación de consultas y el mapeo de resultados a objetos. Pero hay que tener en cuenta las partes que te estás perdiendo por uno usar un ORM completo, y que van mucho más allá de generar automáticamente SQL y materializar objetos: identity map, unit of work, persistencia por alcance, etc.

El uso de TypeORM parece sencillo, aunque si eres de los que piensa que tu modelo debe ignorar la persistencia, vete olvidando. Todo se basa en el uso de decoradores sobre tus entidades:

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column({
        length: 80
    })
    @Length(10, 80)
    name: string;

    @Column({
        length: 100
    })
    @Length(10, 100)
    @IsEmail()
    email: string;
}

No me apasiona la idea de contaminar mi modelo de dominio con cosas relacionadas con la base de datos, pero lo cierto es que en el momento en que decides usar TypeORM me da la sensación de que tampoco es que vayas a tener un modelo de dominio muy rico y vas a usar otro tipo de diseño, así que no es tan grave.

En la plantilla se utiliza también class-validator, una librería para incluir reglas de validación de forma declarativa en nuestras “entidades”. De ahí sale por ejemplo el decorador IsEmail del ejemplo anterior.

Hace muchos años escribí en contra de mezclar la validación con las entidades y sigo pensando lo mismo. Una entidad por definición debería mantener sus invariantes y no puede encontrarse en un estado inválido. Sin embargo, por mucho que aquí las llamemos entidades no estamos trabajando más que con DTOs, por lo que tiene más sentido unirlos a las reglas de validación.

Actualización: Me explica Javier que realmente TypeORM sí que tiene persistencia por alcance (usando los decoradores correspondientes) e incluso un cierto soporte para UoW a través de transacciones.

El proyecto

El proyecto usa las herramientas típicas de node y TypeScript. Además permite usar docker para facilitar las pruebas usando una máquina ya preparada con postgreSQL.

No sabía que se podía usar la propiedad engines en el fichero package.json para fijar las versiones de node y npm que requiere el proyecto, pero me ha parecido muy útil teniendo en cuenta lo (no) estable que es todo el ecosistema de Javascript.

Para la parte de TypeScript usa ts-node, que permite ejecutar TypeScript en node compilándolo al vuelo. Lo conocía por haberlo usado con alguna librería de testing. Aun así, para la compilación de producción se está generando javascript, imagino que para evitar tener que pagar el coste de la compilación. No sé si realmente es necesario (supongo que una vez compilado queda todo en memoria y no hay que estar recompilando nada en cada petición), pero parece más limpio y evitas tener la dependencia sobre TypeScript en el entorno de producción.

El código está organizado en carpetas basadas en roles (controllers, entities, …). Nuevamente es cuestión de gustos, pero prefiero organizarlo en base a funcionalidades porque creo que es mejor tener cerca físicamente los ficheros que vas a tener que tocar a la vez.

Echo de menos algunos tests, aunque sean de ejemplo. Cuando desarrollas un API Web muchas veces no tiene mucho sentido recurrir a tests unitarios, y montar test de integración supone más esfuerzo, por lo que tener un ejemplo de cómo se podrían escribir vendría bien. Teniendo además la imagen de docker para levantar la base de datos imagino que no debería ser demasiado complicado preparar el entorno.

En resumen

Me ha gustado la plantilla que ha preparado Javier para este tipo de proyectos. Puede servir como base para desarrollar un API Web basada en TypeScript y Koa, y es lo bastante pequeña como para que pueda leerte todo el código en un rato y entenderlo sin problemas. Entre las cosas que me han gusto un poco menos está el uso (para mi innecesario) de métodos estáticos y la organización basada en roles en lugar de funcionalidades, pero son detalles menores.

Se agradece que no haya intentado meter demasiadas cosas innecesarias y que no sea una copia directa de un proyecto en Sprint o ASP.NET MVC, como pasa en otras plantillas basadas en TypeScript que tiene un aspecto demasiado enterprise/javero/nettero para mi gusto.

Posts relacionados:

  1. TypeScript, un lenguaje diseñado para las herramientas
  2. Extender tipos existentes en TypeScript
  3. Mantenimiento de invariantes en TypeScript

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

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

C++ Builder in the requirements

julio 08, 2018 11:43

Como integrante del programa MVP de Embarcadero fui invitado para confeccionar a un call for papers acerca de C++ Builder. El tiempo apremiaba, así que opté por desarrollar con mayor formalisto, que no rigurosidad, lo que os explicaba en ¿Por qué C++ Builder. De esa manera es como surgió mi autoría en el memorando (whitepaper) […]

La entrada C++ Builder in the requirements aparece primero en Bitácora de Javier Gutiérrez Chamorro (Guti).

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

Navegapolis

Scrum no se logra con pensamiento de grupo, sino con inteligencia colaborativa

julio 07, 2018 04:29

Un equipo autogestionado no tiene por qué ser ágil, ya que al igual que ocurre con los individuos, los equipos se conducen por su propia inteligencia —inteligencia colectiva—(1) que puede ser colaborativa, o simplemente pensamiento de grupo.

Aplicar prácticas ágiles en equipos autogestionados no es suficiente. Se necesita además inteligencia colaborativa.

Los equipos ágiles actúan con inteligencia colaborativa, no con pensamiento de grupo.

Inteligencia colaborativa:

Es una forma de inteligencia emergente de la acción de muchos individuos que interactúan entre sí en el contexto de la cultura digital. En ella cada individuo actúa de manera autónoma desde su posición, participando en una red para crear algo o solucionar algún problema.

Wikipedia-es "Inteligencia colaborativa" 8-jul-2018

Pensamiento de grupo:

Pensamiento de grupo (groupthink en inglés) es una expresión acuñada por el psicólogo Irving Janis en 1972 para describir el proceso por el cual un grupo puede tomar decisiones malas o irracionales. En una situación de pensamiento en grupo, cada miembro del grupo intenta conformar su opinión a la que creen que es el consenso del grupo. Esto parece ser una manera muy racional de afrontar la situación. Sin embargo, resulta en una situación en la cual el grupo en definitiva se pone de acuerdo en determinada acción que cada miembro individualmente considera desaconsejable.

La definición original de Janis era: «Un modo de pensamiento que las personas adoptan cuando están profundamente involucradas en un grupo cohesivo, cuando los esfuerzos de los miembros por unanimidad hacen caso omiso de su motivación para valorar realísticamente cursos de acción alternativos» ... El pensamiento de grupo tiende a ocurrir en comités y en grades organizaciones.

Wikipedia-es "Pensamiento de grupo" 8-jul-2018

 

 

pensamiento de grupo

(1) Referencias - MIT - Anita Williams.

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

Blog Bitix

Pruebas funcionales con Geb en una aplicación web Java

julio 06, 2018 10:30

Geb
Java

Las pruebas automatizados permiten comprobar que una aplicación se comporta de la manera esperada en los casos probados, creando pruebas automatizadas se evita hacer las aburridas y repetitivas pruebas manuales que consumen gran cantidad de tiempo. Hay diferentes categorías de pruebas: unitarias, de integración y las funcionales. Para las pruebas unitarias y de integración en la plataforma Java unas de las más populares herramientas son JUnit y Spock, para las pruebas funcionales están Selenium y la herramienta que explicaré en este artículo Geb que puede usarse junto con Spock haciendo que el código de los teses sea muy descriptivo.

Geb soporta una APi similar a la que emplea jQuery para la interacción con los elementos de la página bajo pruebas. Este ejemplo prueba a ir a la página del buscador Google y que el resultado es el esperado usando alguna de las múltiples formas de interacción con el contenido de la página.

Para hacer más sencillos las pruebas se pueden describir las páginas, sus URLs y los elementos que contienen. Para ello hay que crear una clase que extienda de Page y definir propiedades como url, at y content, se pueden incluso definir métodos. En la documentación de Geb está más detallado la definición de las páginas. También se pueden crear módulos para definir elementos comunes a varias páginas.

La automatización de las pruebas además de comprobar que la página devuelva es la esperada, en el caso de estos ejemplo con una propiedad meta o con el título de la página, consiste en realizar las interacciones que un usuario realizaría como introducir datos en formularios, hacer clic en elementos de una página y comprobar la presencia de elementos que valide la prueba. Con esta herramienta se pueden probar los casos y flujos funcionales más importantes de la aplicación como sería el proceso de compra en una aplicación de comercio electrónico y la búsqueda, alta y modificación de un registro en la aplicación de gestión.

En este ejemplo se realiza una búsqueda en Google y se comprueba el resultado devuelto. Primero se accede a ella con la sentencia to, se introduce el texto de búsqueda Chuck Norris y se pulsa el botón de búsqueda. La comprobación consiste en asegurar que la página devuelta es la página de resultados de búsqueda con la sentencia at y que el primer resultado de la búsqueda contiene la palabra Chuck, GoogleHomePage define la página de búsqueda, GoogleResultsPage la página de resultados y GoogleSearchSpec contiene la interacción de la prueba.

Las dependencias de Geb necesarias a incluir en el archivo de configuración de la herramienta de gestión del proyecto usando Gradle son las siguientes:

Usando una aplicación con Spring Boot para ejecutar los teses de integración se debe iniciar la aplicación web previamente con la anotación SpringBootTest mostrado en el caso IndexSpec además de excluir los teses de integración y los unitarios de los funcionales. En el ejemplo con la tarea de Gradle integrationTest se ejecutan los teses de funcionales de Geb.

Con Gradle el informe de resultados de las pruebas se genera en build/reports/tests/integrationTest en una colección de páginas HTML con el siguiente aspecto. Si hubiese algún error se mostraría un mensaje descriptivo del fallo ocurrido. La prueba IndexSpec tarda medio minuto ya que previamente ha de iniciar el servidor de aplicaciones con la aplicación, en este caso usando Spring Boot.

Informe de las pruebas funcionales

Algunas otras herramientas que son ampliamente usadas en proyectos Java son las que comentaba en el artículo Nueva visita a herramientas para un proyecto Java.

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 ./gradlew integrationTest.

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

Poesía Binaria

Píldora: Utilizar arrays con nombre dinámico en Bash

julio 04, 2018 07:34

Arrays con nombre dinámico en Bash

Una de las grandes ventajas de los lenguajes interpretados es que podemos almacenar en una variable el nombre de otra variable y trabajar con su valor. ¿Punteros? Bueno, al final es parecido a la utilización de punteros en C, aunque aquí podemos hacer referencia por el mismo nombre de la variable incluso, previo filtrado y con mucho cuidado, preguntarle al usuario el de la variable para trabajar con ella.

Esto nos puede dar mucha flexibilidad en nuestro código. Por ejemplo, yo lo he utilizado para perfeccionar mi script de lectura de ficheros de configuración al que le podemos pasar también el nombre de la variable donde almacenar la configuración.

Arrays con nombre dinámico

En Bash, acceder a una variable cuyo nombre está contenido dentro de otra variable es sencillo, podemos hacerlo llamando a ${!NOMBREDEVARIABLE}. Podemos ver un ejemplo en el post referenciado.
El problema es que cuando son arrays no es tan inmediato. El objetivo es que, teniendo una variable que contiene el nombre del array podamos acceder a su información de una forma sencilla. Y El problema aquí viene en la versión de Bash que estemos utilizando. En este caso debemos hacerlo de una forma u otra dependiendo de si utilizamos una versión inferior a la 4.3 (más o menos salió en 2014) o si tenemos una versión posterior.

Ejemplo para Bash < 4.3

Lo siento, mira que no me gusta utilizar eval, pero claro, para estas versiones del intérprete necesitamos crear una cadena de caracteres con la llamada que vamos a hacer y luego evaluar dicha cadena de caracteres. Esto nos dará el valor que buscamos. Por ejemplo:

1
2
3
4
5
6
MIARRAY=("cero" "uno" "dos" "tres")
REFERENCIA="MIARRAY"

# Lo que necesitamos es acceder a cada uno de los elementos de MIARRAY a través de REFERENCIA.

eval echo \${$REFERENCIA[2]}

Si os fijáis, al construir la cadena. El primer símbolo de dólar ($) lo escapamos con la barra para que no se interprete. El que sí tiene que interpretar es el segundo, para que $REFERENCIA se transforme en el nombre del array.

Lo podemos hacer con arrays asociativos también sin problema, pero es algo complicado y realizamos demasiadas tareas para hacer referencia a una variable. Además no queda muy elegante o intuitivo tener que escapar un dólar y otro no.

Ejemplo para Bash >= 4.3

Para estas versiones, disponemos de un modificador en la orden declare (también llamada typeset) a través del cual podemos crear una variable de referencia que se comportará exactamente igual que nuestro array:

1
2
3
4
MIARRAY=("cero" "uno" "dos" "tres")
declare -n REFERENCIA="MIARRAY"

echo ${REFERENCIA[2]}

Además, de esta forma, el nombre de la variable puede venir dado en otra variable, con lo que podemos permitir que el usuario especifique el nombre de variable. Eso sí, con mucho cuidado.

Foto principal: unsplash-logoRuben Ortega

The post Píldora: Utilizar arrays con nombre dinámico en Bash appeared first on Poesía Binaria.

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

Blog Bitix

Qué es un SUT, fake, Stub, Mock y Spy en las pruebas con un ejemplo

julio 04, 2018 08:00

Las pruebas unitarias y de integración tratan de probar que el comportamiento del sujeto bajo prueba es el esperado. Este sujeto bajo prueba usará colaboradores que en las pruebas deben ser reemplazados por fakes para probar las condiciones deseadas del de sujeto bajo prueba. Depdendiendo del comportamiento asignado al colaborador tenemos varios tipos: Stub, Mock o Spy.

Al desarrollar pruebas unitarias o de integración se emplean varios términos que no son imprescindibles conocer para hacer una prueba pero si es recomendable conocerlos para saber que diferencias hay entre ellos y saber que posibilidades tenemos para construir la prueba. Los términos básicos son sujeto bajo prueba o subject under test, fake, mock, stub y spy.

En una prueba el objeto bajo prueba es el elemento del que se quiere probar su comportamiento y que las pruebas verificarán que para los casos de prueba funciona según lo esperado. Dado que raramente el sujeto bajo prueba funciona de forma aislada sino que que se comunica con otros elementos estos colaboradores han de reemplazarse preprogramado su comportamiento, a los colaboradores se les denomina impostores o fakes ya que no son los objetos reales que se usan en el programa en ejecución pero simulan su comportamiento.

Según el tipo de fake que sea un objeto están los siguientes tipos:

  • Stub: es un fake al que se le reprograman sus valores de retorno y se proporciona al objeto bajo prueba teniendo control sobre cuales son los valores que usa el sujeto bajo control.
  • Mock: es un fake que después de terminada la prueba puede ser examinado para comprobar si las interacciones con el sujeto bajo prueba han sido las correctas, se puede probar si un método ha sido llamado o cuántas veces ha sido llamado junto con sus parámetros. Adicionalmente al igual que los Stub puede preprogramarse su comportamiento. Si solo se necesita preprogramar los resultados se suele utilizar un Stub y solo cuando además se necesitan verificar las interacciones se usa un Mock.
  • Spy: es un fake en el que solo un subconjunto de métodos son fake y a menos que explícitamente sean mockeados el resto de métodos son los reales.

Los colaboradores permiten hacer los casos de prueba deterministas haciendo que siempre produzcan el mismo resultado y las pruebas no se vean afectados por condiciones externas, los colaboradores evitan efectos colaterales, evitan depender del entorno de computación, hacen que las pruebas sean rápidas por no necesitar de sistemas externos como una base de datos o servicio de red y permiten probar comportamientos inusuales en un entorno real.

En el ejemplo se quiere probar un sistema de alarma que cuando detecte en tres mediciones consecutivas que la temperatura está por encima de cierto valor suene una alarma. Los elementos de este sistema serán una clase Monitor que obtiene las temperaturas de un sensor y si detecta la condición de una temperatura elevada hace sonar una alarma. El monitor será el sujeto bajo prueba y el sensor y la alarma los colaboradores. El sensor será un fake de tipo Stub ya que solo se necesita preprogramar sus valores de retorno de temperaturas y la alarma un fake de tipo Mock ya que se debe comprobar que el monitor ha llamado una vez al método que haría sonar la alarma, esto es se necesitan verificar las interacciones.

Sistema de control de temperatura simulado

Se pueden hacer más casos de prueba como por ejemplo probar que tras dos temperaturas altas siendo la siguiente baja y posteriormente otra alta la alarma no es disparada.

En el libro Java Testing with Spock explican toda esta teoría básica de pruebas además de explicar bastante detalladamente todas las posibilidades de la herramienta de testing Spock.

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 ./gradlew test.

Referencia:

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

Variable not found

Enlaces interesantes 328

julio 02, 2018 02:00

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

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

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

Variable not found

¡Microsoft MVP 2018-2019!

julio 02, 2018 06:58

MVP Award ProgramEn esta ocasión sólo quería compartir con vosotros la gran alegría que supone ser honrado de nuevo con el galardón Microsoft MVP por las contribuciones y aportaciones realizadas a la comunidad de desarrolladores a lo largo del año pasado.

Son ya ocho años consecutivos y debería haberme acostumbrado a este subidón de primeros de julio, pero, como no suelo ser consciente de que ha llegado el día de los nombramientos, sigo recibiendo el conocido email de nombramiento casi con la misma emoción que aquella primera vez en que me pilló completamente desprevenido.

Muchas gracias a todos vosotros, amigos y amigas de Variable not found, porque si estoy aquí es porque me habéis regalado vuestro incondicional apoyo durante todos estos años.

Muchas gracias también al equipo del programa MVP en Microsoft y, en particular, a nuestra querida MVP lead Cristina González, por el gran trabajo que realizan.

Muchas gracias todos los que dedicáis vuestro tiempo la comunidad, seáis MVP o no, porque hacéis posible que todos aprendamos y mejoremos un poco más cada día.

Y, por supuesto, muchas gracias también a mi mujer e hijas, Inma, Inmita y María, porque ellas son las que permiten, con su infinita paciencia y comprensión, que pueda dedicar a mi pasión por el software más de lo que estrictamente requieren mis obligaciones profesionales.

Aprovecho también para enviar mi enhorabuena a los MVP renovados recientemente, y un caluroso abrazo de bienvenida a los que lo han sido por primera vez (es un momento muy bonito, ¡disfrutadlo!)

Sólo espero estar a la altura de esta distinción, compartida con gente de todo el mundo a la que admiro tanto personal como profesionalmente, y entre los que me enorgullezco de contar con grandes amigos. Y ahora, con esta dosis extra de motivación, ¡a seguir trabajando!

Desarrollador supermotivado
Source: Giphy

Publicado en Variable not found.

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

Blog Bitix

Decidido a comprar un Intel NUC entre las opciones que he evaluado

julio 01, 2018 05:30

Intel

El ordenador portátil que tenía sigue siendo perfectamente útil, sin embargo, en algunos casos concretos de mi uso la cantidad de memoria que tenía se me estaba quedando corta y cuando me pasa esto suelo aprovechar si alguien de mi entorno necesita ordenador traspasarlo y yo comprarme otro nuevo.

El ordenador que tenía era un Sony VAIO de finales del 2012 con un procesador Intel i3 3210M con gráfica integrada HD 4000, 8 GiB de memoria, almacenamiento de estado sólido Samsung 840 EVO de 256 GiB con interfaz SATA y una pantalla de resolución 1600x900. Durante todo este tiempo me ha funcionado a la perfección, incluso con su gráfica me fue suficiente para un entorno de escritorio GNOME con GNU/Linux y he acabado muy contento con la marca Sony al contrario de mi anterior portátil Dell XPS 1530 de la que no pienso comprar nunca nada de esta marca, quizá tuve mala suerte pero el portátil Dell no fue barato, no acabé contento con la calidad del producto pasado el encantamiento inicial y al final acabó totalmente muerto sin ni siquiera encenderse.

Con lo que desde hace unos meses he empezado a barajar opciones para comprar equipo con los requisitos que tengo de ser lo más pequeño posible, silencioso, con 32 GiB de memoria, procesador con mínimo 4 cores y 8 hilos, no necesito tarjeta gráfica dedicada ya que no jugaré a juegos o una integrada con bajar calidad de detalles me servirá a modo ocasional. También quería una pantalla externa de 25” o 27” con resolución de 2K para mayor comodidad y evitar problemas por estar con la cabeza medio agachada durante bastante tiempo. Habiendo pasado 5 años desde mi anterior portátil Sony quiero que el ordenador sea un salto significativo en lo que tenía por eso el requerimiento de los 4 cores, los 32 GiB de memoria y la pantalla 2K. La memoria a pesar del caro precio de la DDR4 en el momento de este artículo. El SSD es la mejor inversión para mejorar el rendimiento que uno puede hacer, más que el procesador. El SSD probablemente elija uno en formato M.2 con interfaz NVMe que tienen una tasa de transferencia 3 veces superior a los SSD con interfaz SATA que ya son rápidos, los NVMe son algo más caros pero no mucho más.

He barajado varias opciones principalmente ordenadores en factor de forma pequeños como los Intel NUC, Gigabyte Brix, Zotac, MSI Cubi, Slimbook ONE, Slimbook PRO2 o AMD Ryzen montando un mini-ITX. Ya estoy casi decidido a lo que compraré y no quiero esperar mucho más a tener equipo propio de cuando se comercialicen y aunque estoy aguantando con un Mac del trabajo comentando Tu con tu macOS yo con mi GNU/Linux. Será con mucha probabilidad un Intel NUC Bean Canyon que se lanzarán en la segunda mitad del 2018.

A pesar de saber que todos los procesadores Intel de la última década está afectados por los importantes problemas de seguridad Meltdown y Spectre que se están mitigando por software, de momento no se ha descubierto una forma de aprovecharlos pero quizá en un futuro se pueda y afecte de forma importante al rendimiento para corregirlos si es que se puede. Pero es que el resto de opciones he ido descartando por otros motivos.

Meltdown Spectre Logotipos de Meltdown y Spectre

El precio que calculo tendrá el Intel NUC Bean Canyon con procesador Intel i5 8250U (4 núcleos, 8 hilos, 6 MiB cache) estará en el momento de su salida entre los 350 y 450€ sin incluir la memoria y el SSD, respecto al modelo con el Intel i3 7100U (2 núcleos, 4 hilos, 3 MiB cache) que cuesta unos 250€ se puede dudar si esos dos núcleos adicionales y 3 MiB de caché más en el procesador merecen la pena la diferencia de precio. En mi caso estoy estoy dispuesto a pagarlo queriendo tener algo significativamente con mejor rendimiento teórico que lo que tenía.

He revisado algunos análisis de los NUC y me han gustado, en este por ejemplo se explica cómo instalar la memoria, el SSD, como es la BIOS/UEFI y sus opciones de configuración como por ejemplo el ring que puede mostrar la actividad del SSD y se pueden personalizar los colores, también muestra el rendimiento en algunos juegos. Y en otro la gráfica HD 620 es suficiente para jugar a Diablo 3 a 30 fotogramas por segundo en un resolución de 1080p bajando detalles, esta gráfica integrada equivale a una dedicada NVIDIA GeForcd 920m laptop o GeForce 730 desktop que no está del todo mal para un jugador ocasional si no importa jugar bajando algunos detalles de calidad. En GNU/Linux es posible jugar a juegos destinados a Windows con PlayOnLinux.

Investigando más he visto también el Intel i5 8259U, solo cambia el último número pero la diferencia es importante en el apartado gráfico ya que este en vez de una UHD 620 lleva una Iris Pro 655 que es nada más y nada menos el doble de potente en teoría gracias a que tiene el doble de execution units, shading units y memoria dedicada de 128 MiB (que la UHD 620 no tiene). No llegará al nivel de una gráfica dedicada de NVIDIA o AMD pero es suficiente para el jugador ocasional.

El uso que le daré es el de programación, virtualización, contenedores Docker, navegación, ofimática, leer PDF, tal vez algún juego ocasional, quizá Diablo 3 si pudiese, y usando GNU/Linux con la distribución Arch Linux. En un futuro lo quiero como equipo servidor dedicado personal con por ejemplo Nextcloud.

Los Gigabyte Brix ya hay algunos modelos con los Intel i5 8250U a un precio de unos 440€, sin embargo, no me gusta que no tienen luz indicador de actividad de SSD y creo que su BIOS/UEFI es en modo texto. La luz de actividad de SSD me parece importante para saber si en algún momento el ordenador se queda bloqueado o por algún tipo de problema lee, o peor escribe, constantemente al almacenamiento persistente cuando no debería. Los Zotac están dotados de una buena cantidad de puertos USB, incluso algunos tiene dos conexiones de red pero son algo caros y la mayoría de sus modelos están orientados a gamers, no son fáciles de encontrar ni siquiera en Amazon ni en PC Componentes que es donde compraré finalmente el equipo. De MSI Cubi no hay muchos modelos y aún no he visto ninguno anunciado con un Intel de 4 núcleos. Los Slimbook ONE me gustaban pero los modelos con los Intel de 4 núcleos solo tienen una ranura de memoria, se les pueden instalar 32 GiB… cuando haya módulos de memoria de 32 GiB, ahora me tendría que conformar con 16 GiB.

También he seguido los NUC con un Intel Core i3 7100U sin embargo tiene solo 2 cores y 4 hilos con 3 MiB de memoria será algo mejor que el Intel i3 3210M que tenía pero no me parece una mejora significativa habiendo pasado 5 años y a pesar de que el 7100U está fabricado a 14 nanómetros y el 3210M a 22 nanómetros, los NUC con Intel i7 8650U son más caros, sobre los 580€ y teniendo en cuenta que la memoria y SSD hay que comprarlos aparte. Los NUC más económicos sobre los 175€ con Intel Pentium J5005 solo admiten 8 gigas como máximo y me parece que están limitados artificialmente a ese tamaño máximo de memoria. En Aliexpress hay productos similares a los NUC con procesador i5 8250U pero a ver quien se la juega a importar desde China por aduanas y con la incertidumbre de lo que llega y que tal funciona, también he estado mirando placas ARM aunque no con el objetivo que sea mi equipo principal, 7+ computadoras baratas del tamaño de una tarjeta de crédito basadas en GNU/Linux y ARM, como la Rock64 que tiene 4 gigas pero siguiendo su desarrollo y foros no son pocos los problemas que la gente crea en su repositorio de Github con lo que me da que pensar que la experiencia podría ser algo frustrante, la arquitectura x86/x64 es mucho más compatible, en ARM cada placa es un mundo aparte de que su rendimiento es mucho más limitado.

Preferiría un microprocesador AMD ya que solo están afectados por algunas variantes de Spectre, pero AMD no tiene tanta diversidad de productos como tiene Intel, no tienen nada parecido a los Intel NUC y en portátiles prácticamente todos los modelos equipan un Intel, quizá cambia ahora algo con los Ryzen. Por rendimiento y precio posiblemente elegiría un AMD Ryzen 2400G pero de momento las placas con el chipset B450 para sacarle el máximo rendimiento aún no están disponibles, en la generación 1xxx de los Ryzen no hay modelos con gráfica integrada, tendría que ser un mini-ITX y el equipo me lo tendría que montar yo, no es difícil pero si tuviese algún imprevisto o incompatibilidad sería un problema por tener que devolver piezas y buscar otras, además no estoy seguro del ruido que haría, lo quiero muy silencioso al menos en tareas ofimáticas, navegación por internet y cosas simples, no estoy seguro de que lo sea con el ventilador de la CPU y fuente de alimentación. Ocupa más que un NUC para mi importante si en un futuro le quiero dar uso como ordenador secundario, servidor personal o quisiera traspasarlo. Pero por 400€ se tiene un ordenador muy decente en plataforma AMD Ryzen a falta de memoria y SSD.

La pantalla que estoy siguiendo es una 2K de BenQ PD2700Q en tamaño de 27”, 4K me parece excesiva resolución para ese tamaño de pantalla, a su máxima resolución en una 27” 4K las cosas ven muy pequeñas y tendría que escalarla a 2K, busco que sea IPS ya que los colores en este tipo de paneles se ven significativamente mejor que en un panel TN, uno al lado del otro la diferencia es clara. Apreciaré una mejora notable en la calidad de la pantalla de la que tenía en el portátil Sony. No es barata pero es algo que no necesitan renovarse tan a menudo como el ordenador seguramente pueda tener un uso durante una década si no presenta ningún problema de funcionamiento. Otros aspectos interesantes son ser regulable en altura y horizontalmente, colocable en posición vertical y puertos USB, HDMI y DisplayPort por otro lado una información en pantalla o OSD aceptable y de fácil configuración.

Entre otras cosas están el teclado y ratón que me conformo con un modelo básico y barato como el Logitech MK120 y una alfombrilla que cubra la mesa.

Otras personas no se lo piensan tanto pero en un área del que más o menos entiendo algo me gusta saber con mucho detalle lo que estoy comprando. Una vez que uno comprende las especificaciones técnicas de los productos, que diferencias hay entre cada una de ellas, se entiende las diferencias de precios y se compra con criterio.

No es lo mismo comprar un NUC7i3BNH con un i3-7100 que tiene una gráfica HD Graphics 620 con un precio de unos 250€ que un NUC7i5BNH con un i5-7260U que tiene un gráfica Iris Plus 640 con un precio de unos 360€.

La diferencia del precio está en que el procesador i5-7260U que tiene 4 MiB de cache por 3 MiB del i3-7100 que le proporciona algo más de rendimiento y la gráfica Iris del primero es el doble de potente con 48 execution units, 384 shading units, con memoria gráfica dedicada de 64 MiB eDRAM para proporcionar unos 700800 GFLOPS de rendimiento contra la gráfica HD Graphics 620 que tiene 24 execution units, 192 shading units, 0 MiB eDRAM (sin memoria dedicada) con unos 400 GFLOPS. Aún siendo ambas gráficas integradas para jugadores ocasionales en todo caso mucho menos capaces que una dedicada con su memoria más veloz, la Iris Plus 640 del i5-7260U es el doble de potente que la HD Graphics 620 del i3-7100. Por este motivo se justifica la diferencia de precio entre los modelos. Dependiendo del uso que se le dé al NUC será preferible un modelo u otro.

En estas dos páginas se pueden conocer muchos detalles de los procesadores y gráficas de Intel.

Cuando realmente compre el equipo publicaré varios artículos con el unboxing, instalación, análisis, uso y opinión personal de varios de ellos.

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

Blog Bitix

Hemeroteca #13

junio 30, 2018 08:00

El primer semestre del 2018 ya ha pasado. Estos primeros meses he publicado 38 artículos sobre la temática habitual de esta bitácora como programación, software libre, GNU/Linux algunos de opinión relacionados con la tecnología. Las visitas e ingresos de AdSense no han crecido tanto como en ocasiones anteriores pero se han mantenido a un nivel similar sobre los 30K páginas vistas y los 30€ de ingresos en AdSense que no están nada mal.

Dado que desde hace unos cuantos meses no tengo ordenador propio y solo dispongo de el del trabajo normalmente solo los fines de semana no he podido investigar, probar tantas cosas que me gustaría y he publicado más artículos de opinión que de software libre y GNU/Linux. Como comento en algunos de los artículos publicados más o menos ya tengo decidido que mi siguiente equipo será un Intel NUC pero estoy esperando a que salgan esta segunda mitad los Bane Canyon para que el salto que experimente sea notable tanto en el microprocesador con un Intel Core i5 8250U o 8259U, 4 núcleos y 8 hilos, 6 MiB de caché y 32 GiB de memoria sobre lo que tenía antes un Intel Core i5 3210M, 3 MiB de caché, 2 núcleos, 4 hilos y 8 GiB de memoria. Con tal cantidad de memoria podré hacer algunas pruebas con contenedores y virtualización que con solo 8 GiB de memoria ya incluso se me quedaba escaso. Cuando adquiera este nuevo equipo publicaré varios artículos con el desempaquetado o unboxing y el análisis de varios de esos componentes.

Evolución Analytics y AdSense

Entre los artículos sobre programación han estado los habituales sobre Java, destacando las novedades de Java 10, un par sobre Apache Tapestry. Como resolver varios de los problemas clásicos de sincronización, programación concurrente en Java, Webjars, …

En la categoría de software libre o GNU/Linux como tener una consola de juegos retro con una Raspberry Pi, varias computadoras similares a la Raspberry Pi.

En la categoría de opinión están el importante problema de seguridad que se hacía público a principio de año y que afectaba a todos los microprocesadores de Intel fabricados desde hace una década con Meltdown. Intel, AMD y ARM afectados otras variantes denominadas Spectre. Como el hardware estos microprocesadores es defectuoso ni solucionable por microcódigo solo se ha podido mitigar mediante parches de software en los nucleos de los sistemas operativos y compiladores que en cualquier caso pueden suponer una merma en el rendimiento. Aún no se conoce un software capaz de aprovechar estos fallos de seguridad pero no quiere decir que no se descubran. Afecta aún más si cabe a entornos de computación en la nube al compartirse los recursos de computación, en estos entornos es especialmente grabe por seguridad ya que el fallo permite leer la memoria del kernel, aplicaciones, contenedores, datos, claves, …

También publiqué un artículo el ordenador con las opciones que barajaba, una opinión sobre el Slimbook AIO Curve y cómo internet está transformando el sector del comercio y distribución o mi opinión de que me quedo GNU/Linux sobre un Mac. También los artículos con las opciones que estoy tomando para comprar equipo ya no a mucho tiempo tardar.

Para finalizar otro artículo fuera de tema del habitual de este blog con los plantones de roble que tengo, como han crecido y como están desde que los planté el año pasado. Aunque aún no lo he publicado algunos están mostrando síntomas de fallecimiento y lo que es peor no tengo claro por que si por la cantidad de agua, excesiva luz directa del sol, humedad, calor, …

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

Blog Bitix

Decidido a comprar un Intel NUC Bean Canyon entre las opciones que he evaluado

junio 24, 2018 07:30

Intel

El ordenador portátil que tenía sigue siendo perfectamente útil, sin embargo, en algunos casos concretos de mi uso la cantidad de memoria que tenía se me estaba quedando corta y cuando me pasa esto suelo aprovechar si alguien de mi entorno necesita ordenador traspasarlo y yo comprarme otro nuevo.

El ordenador que tenía era un Sony VAIO de finales del 2012 con un procesador Intel i3 3210M con gráfica integrada HD 4000, 8 GiB de memoria, un disco duro de estado sólido Samsung 840 EVO de 256 GiB con interfaz SATA y una pantalla de resolución 1600x900. Durante todo este tiempo me ha funcionado a la perfección, incluso con su gráfica me fue suficiente para un entorno de escritorio GNOME con GNU/Linux y he acabado muy contento con la marca Sony al contrario de mi anterior portátil Dell XPS 1530 de la que no pienso comprar nunca nada de esta marca, quizá tuve mala suerte pero el portátil Dell no fue barato, no acabé contento con la calidad del producto pasado el encantamiento inicial y al final acabó totalmente muerto sin ni siquiera encenderse.

Con lo que desde hace unos meses he empezado a barajar opciones para comprar equipo con los requisitos que tengo de ser lo más pequeño posible, silencioso, con 32 GiB de memoria, procesador con mínimo 4 cores y 8 hilos, no necesito tarjeta gráfica dedicada ya que no jugaré a juegos o una integrada con bajar calidad de detalles me servirá a modo ocasional. También quería una pantalla externa de 25” o 27” con resolución de 2K para mayor comodidad y evitar problemas por estar con la cabeza medio agachada durante bastante tiempo. Habiendo pasado 5 años desde mi anterior portátil Sony quiero que el ordenador sea un salto significativo en lo que tenía por eso el requerimiento de los 4 cores, los 32 GiB de memoria y la pantalla 2K. La memoria a pesar del caro precio de la DDR4 en el momento de este artículo. El SSD es la mejor inversión para mejorar el rendimiento que uno puede hacer, más que el procesador. El SSD probablemente elija uno en formato M.2 con interfaz NVMe que tienen una tasa de transferencia 3 veces superior a los SSD con interfaz SATA que ya son rápidos, los NVMe son algo más caros pero no mucho más.

He barajado varias opciones principalmente ordenadores en factor de forma pequeños como los Intel NUC, Gigabyte Brix, Zotac, MSI Cubi, Slimbook ONE, Slimbook PRO2 o AMD Ryzen montando un mini-ITX. Ya estoy casi decidido a lo que compraré y no quiero esperar mucho más a tener equipo propio de cuando se comercialicen y aunque estoy aguantando con un Mac del trabajo comentando Tu con tu macOS yo con mi GNU/Linux. Será con mucha probabilidad un Intel NUC Bean Canyon que se lanzarán en la segunda mitad del 2018.

A pesar de saber que todos los procesadores Intel de la última década está afectados por los importantes problemas de seguridad Meltdown y Spectre que se están mitigando por software, de momento no se ha descubierto una forma de aprovecharlos pero quizá en un futuro se pueda y afecte de forma importante al rendimiento para corregirlos si es que se puede. Pero es que el resto de opciones he ido descartando por otros motivos.

Meltdown Spectre Logotipos de Meltdown y Spectre

El precio que calculo tendrá el Intel NUC Bean Canyon con procesador Intel i5 8250U (4 núcleos, 8 hilos, 6 MiB cache) estará en el momento de su salida entre los 350 y 450€ sin incluir la memoria y el SSD, respecto al modelo con el Intel i3 7100U (2 núcleos, 4 hilos, 3 MiB cache) que cuesta unos 250€ se puede dudar si esos dos núcleos adicionales y 3 MiB de caché más en el procesador merecen la pena la diferencia de precio. En mi caso estoy estoy dispuesto a pagarlo queriendo tener algo significativamente con mejor rendimiento teórico que lo que tenía.

He revisado algunos análisis de los NUC y me han gustado, en este por ejemplo se explica cómo instalar la memoria, el SSD, como es la BIOS/UEFI y sus opciones de configuración como por ejemplo el ring que puede mostrar la actividad del SSD y se pueden personalizar los colores, también muestra el rendimiento en algunos juegos. Y en otro la gráfica HD 620 es suficiente para jugar a Diablo 3 a 30 fotogramas por segundo en un resolución de 1080p bajando detalles, esta gráfica integrada equivale a una dedicada NVIDIA 920m o GeForce 730 que no está del todo mal para un jugador ocasional si no importa jugar bajando algunos detalles de calidad. En GNU/Linux es posible jugar a juegos destinados a Windows con PlayOnLinux.

El uso que le daré es el de programación, virtualización, contenedores Docker, navegación, ofimática, leer PDF, tal vez algún juego ocasional, quizá Diablo 3 si pudiese, y usando GNU/Linux con la distribución Arch Linux. En un futuro lo quiero como equipo servidor dedicado personal con por ejemplo Nextcloud.

Los Gigabyte Brix ya hay algunos modelos con los Intel i5 8250U a un precio de unos 440€, sin embargo, no me gusta que no tienen luz indicador de actividad de SSD y creo que su BIOS/UEFI es en modo texto. La luz de actividad de SSD me parece importante para saber si en algún momento el ordenador se queda bloqueado o por algún tipo de problema lee, o peor escribe, constantemente al disco duro cuando no debería. Los Zotac están dotados de una buena cantidad de puertos USB, incluso algunos tiene dos conexiones de red pero son algo caros y la mayoría de sus modelos están orientados a gamers, no son fáciles de encontrar ni siquiera en Amazon ni en PC Componentes que es donde compraré finalmente el equipo. De MSI Cubi no hay muchos modelos y aún no he visto ninguno anunciado con un Intel de 4 núcleos. Los Slimbook ONE me gustaban pero los modelos con los Intel de 4 núcleos solo tienen una ranura de memoria, se les pueden instalar 32 GiB… cuando haya módulos de memoria de 32 GiB, ahora me tendría que conformar con 16 GiB.

También he seguido los NUC con un Intel Core i3 7100U sin embargo tiene solo 2 cores y 4 hilos con 3 MiB de memoria será algo mejor que el Intel i3 3210M que tenía pero no me parece una mejora significativa habiendo pasado 5 años y a pesar de que el 7100U está fabricado a 14 nanómetros y el 3210M a 22 nanómetros, los NUC con Intel i7 8650U son más caros, sobre los 580€ y teniendo en cuenta que la memoria y SSD hay que comprarlos aparte. Los NUC más económicos sobre los 175€ con Intel Pentium J5005 solo admiten 8 gigas como máximo y me parece que están limitados artificialmente a ese tamaño máximo de memoria. En Aliexpress hay productos similares a los NUC con procesador i5 8250U pero a ver quien se la juega a importar desde China por aduanas y con la incertidumbre de lo que llega y que tal funciona, también he estado mirando placas ARM aunque no con el objetivo que sea mi equipo principal, 7+ computadoras baratas del tamaño de una tarjeta de crédito basadas en GNU/Linux y ARM, como la Rock64 que tiene 4 gigas pero siguiendo su desarrollo y foros no son pocos los problemas que la gente crea en su repositorio de Github con lo que me da que pensar que la experiencia podría ser algo frustrante, la arquitectura x86/x64 es mucho más compatible, en ARM cada placa es un mundo aparte de que su rendimiento es mucho más limitado.

Preferiría un microprocesador AMD ya que solo están afectados por algunas variantes de Spectre, pero AMD no tiene tanta diversidad de productos como tiene Intel, no tienen nada parecido a los Intel NUC y en portátiles prácticamente todos los modelos equipan un Intel, quizá cambia ahora algo con los Ryzen. Por rendimiento y precio posiblemente elegiría un AMD Ryzen 2400G pero de momento las placas con el chipset B450 para sacarle el máximo rendimiento aún no están disponibles, en la generación 1xxx de los Ryzen no hay modelos con gráfica integrada, tendría que ser un mini-ITX y el equipo me lo tendría que montar yo, no es difícil pero si tuviese algún imprevisto o incompatibilidad sería un problema por tener que devolver piezas y buscar otras, además no estoy seguro del ruido que haría, lo quiero muy silencioso al menos en tareas ofimáticas, navegación por internet y cosas simples, no estoy seguro de que lo sea con el ventilador de la CPU y fuente de alimentación. Ocupa más que un NUC para mi importante si en un futuro le quiero dar uso como ordenador secundario, servidor personal o quisiera traspasarlo. Pero por 400€ se tiene un ordenador muy decente en plataforma AMD Ryzen a falta de memoria y SSD.

La pantalla que estoy siguiendo es una 2K de BenQ PD2700Q en tamaño de 27”, 4K me parece excesiva resolución para ese tamaño de pantalla, a su máxima resolución en una 27” 4K las cosas ven muy pequeñas y tendría que escalarla a 2K, busco que sea IPS ya que los colores en este tipo de paneles se ven significativamente mejor que en un panel TN, uno al lado del otro la diferencia es clara. Apreciaré una mejora notable en la calidad de la pantalla de la que tenía en el portátil Sony. No es barata pero es algo que no necesitan renovarse tan a menudo como el ordenador seguramente pueda tener un uso durante una década si no presenta ningún problema de funcionamiento. Otros aspectos interesantes son ser regulable en altura y horizontalmente, colocable en posición vertical y puertos USB, HDMI y DisplayPort por otro lado una información en pantalla o OSD aceptable y de fácil configuración.

Entre otras cosas están el teclado y ratón que me conformo con un modelo básico y barato como el Logitech MK120 y una alfombrilla que cubra la mesa.

Cuando realmente compre el equipo publicaré varios artículos con el unboxing, instalación, análisis, uso y opinión personal de varios de ellos.

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

Adrianistán

¿Cómo funcionan los sistemas basados en inodos?

junio 14, 2018 10:18

Después de ver como funcionan de forma genérica los sistemas FAT, saltamos a los sistemas de inodos. Estos se han usado tradicionalmente en sistemas UNIX (UFS, ext2), así que tradicionalmente ha existido una cierta rivalidad  en las redes entre FAT e inodos similar a la de Windows/Linux. Lo cierto es que a nivel técnico cada uno tiene fortalezas y debilidades.

Partición

Tomando la base de FAT, una partición de un sistema basado en inodos también contiene un sector de arranque y un superbloque con metadatos. También es necesario un bloque dedicado al directorio raíz presente en el disco. Además es necesario espacio para almacenar todos los inodos y un mapa de bits de espacio libre que en FAT no hacía falta, ya que la propia tabla nos indicaba que bloques del disco estaban libres.

Los inodos

¿Qué es inodo te podrás preguntar? Es una estructura de datos, su nombre proviene de index node y es que los inodos no son más que índices, que almacenan los números de bloque de las diferentes partes del archivo. Además, contienen metadatos como permisos, propietario, tamaño, fecha de modificación, referencias, tipo de fichero (directorio, archivo, enlace duro, enlace blando,…) salvo el nombre del propio archivo, que en ningún sitio del inodo aparece.

Este sistema tiene una ventaja de rendimiento respecto a FAT en cuanto al acceso aleatorio a los archivos, ya que es mucho más rápido de esta forma que con FAT. En FAT para hacer lo mismo tenemos que ir recorriendo la tabla arriba y abajo siguiendo los números de bloque hasta encontrar el bloque deseado.

Normalmente un inodo tiene un tamaño fijo, lo que implica que el índice no se puede alargar hasta el infinito. Esto hace que haya un número limitado de bloques que puede usar un archivo y por ende, que haya un tamaño máximo de archivo que no es muy elevado. Para solventar este problema hay varias soluciones. El enfoque de UFS y de ext2/3/4 consiste en múltiples niveles de indexado progresivo.

Esto significa que los primeros bloques son números de bloque directos pero los siguientes son números de bloque que llevan a tablas de inodo secundarias que ya sí, hacen referencia al archivo real. Más adelante los números de bloque hacen referencias a tablas de inodo secundarias que a su vez llaman a tablas de inodos terciarias.

Esto provoca algo que en principio puede parecer paradójico y es que es más lento leer una zona final de un archivo que una zona del principio, aunque en una lectura secuencial no se nota demasiado.

Otra solución a esto es enlazar los inodos a modo de lista enlazada añadiendo al final de cada inodo un número de inodo al que continuar la lectura de índices.

Localización de los inodos

Los inodos se crean cuando se formatea el disco y permanecen en un estado libre. No se pueden añadir más inodos ni quitar inodos y no puede haber más archivos y directorios que inodos por esta misma razón. Esto es una desventaja respecto a FAT, ya que en FAT puede haber tantos archivos como bloques haya/entradas tenga la tabla. En sistemas de inodos como ext2/3/4 puede ocurrir que no queden inodos libres pero haya todavía bloques libres, dejando todo ese espacio inutilizado (aunque en realidad lo podrían usar los archivos existentes si creciesen).

Los inodos se pueden ubicar de dos formas distintas. Un enfoque consiste en ponerlos al principio del disco todos juntos. Otro enfoque, el que sigue ext3, consiste en dividir el disco en 4 zonas y ubicar inodos en cada inicio de zona. La idea es que los inodos de esa zona usen bloques de esa zona y de esta forma reducir los desplazamientos de las cabezas lectoras del disco (en unidades SSD esto da completamente igual como podréis suponer).

Gestión del espacio libre

Una de las grandes ventajas de FAT era que la tabla podía mantener a la vez un listado de bloques libres, listos para ser usados. Con inodos no tenemos esa facilidad y tenemos que recurrir a otros tipos de estructura. Aquí hay muchos planteamientos siendo el más común el mapa de bits. El mapa de bits es una estructura que se compone de un listado de bits. Cada bit corresponde a un bloque y dependiendo de si el bit es 1 o 0 podemos saber si el bloque está libre u ocupado.

Como veis, los sistemas basados en inodos tienen que mantener varias estructuras de datos de forma paralela. Esto aumenta las probabilidades de inconsistencias en el sistema. Por este motivo, muchos sistemas más avanzados como ext4 mantienen un journal o diario.

Fragmentación

Veamos como se comportan los sistemas de inodos ante los tres tipos de fragmentación. Respecto a la fragmentación interna, que es aquella que sucede cuando asignamos espacio de más a archivos que no necesitan tanto, tiene los mismos valores que FAT, ya que los bloques siguen pudiendo pertenecer únicamente a un archivo. Realmente, para mejorar la fragmentación interna tenemos que saltar a sistemas como XFS o JFS.

La fragmentación externa de los sistemas basados en inodos es la misma que la de FAT, 0, ya que todo se asigna mediante bloques del mismo tamaño.

Respecto a la fragmentación de datos, históricamente las implementaciones como UFS o ext2/3 se han comportado relativamente bien, aunque a nivel teórico nada impide que se comporten igual de mal que FAT, no existen mecanismos preventivos. Ext4 por contra, sí que tiene mecanismos de prevención (delayed allocation).

Directorios y enlaces duros

En los sistemas basados en inodos los directorios son más sencillos que en FAT, ya que no tienen que almacenar los metadatos del archivo en cuestión. En un fichero de directorio simplemente se almacena el nombre y el número de inodo correspondiente.

Aquí surge un concepto muy interesante, el de enlaces duros. Un enlace duro no es más que otra entrada de directorio que apunta a un mismo inodo. Realmente desde el punto de vista del sistema de archivos no existe un fichero original y su enlace. Simplemente dos entradas diferentes apuntando al mismo inodo. ¿Y cómo reacciona esto a los borrados? Imagina la siguiente situación: tengo un fichero y creo un enlace duro. Borro el fichero original y su inodo se libera. Ahora creo otro archivo y reutiliza ese mismo inodo que estaba libre. ¡Ahora el enlace duro apunta a un contenido totalmente distinto sin darse cuenta! Para prevenir esto, los inodos no se liberan hasta que no quede ninguna entrada de directorio apuntando a ellos. Para eso sirve el campo referencias dentro del inodo, para llevar la cuenta de cuántas veces se hace referencia al inodo en el sistema de archivos. Cuando el valor de referencias llega a cero, se puede liberar sin problema.

Conclusiones

Los sistemas basados en inodos son conceptualmente algo más complejos que los basados en FAT. Comparten además limitaciones comunes, usan más espacio de disco, aunque suelen ser más rápidos.

Actualmente existen sistemas basados en inodos mucho más avanzados y complejos que UFS y ext2/3/4 y que mejoran este sistema de forma sustancial. Por ejemplo en XFS los inodos tienen una estructura totalmente distinta donde se indica un bloque de inicio y una longitud en número de bloques contiguos que pertenecen al archivo. Muchos sistemas además usan estructuras de árbol B+ como pueden ser ZFS o Btrfs.

La entrada ¿Cómo funcionan los sistemas basados en inodos? se publicó primero en Adrianistán.

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

Adrianistán

¿Cómo funcionan los sistemas de archivos basados en FAT?

junio 11, 2018 06:18

Voy a dedicar unas entradas en el blog a hablar del funcionamiento de los sistemas de archivos, un componente fundamental en la gran mayoría de sistemas informáticos. Voy a empezar con los basados en FAT sin centrarme en ninguno en concreto (FAT16, FAT32, exFAT,…). Intentaré escribir también sobre inodos y mecanismos más raros y avanzados.

En esencia un sistema de archivos es un método ordenado que permite guardar datos sobre un soporte físico para luego poder acceder a ellos. Históricamente ha habido muchos enfoques a este problema: los sistemas más usados usan archivos, directorios y enlaces.

Bloques y sectores: la división del disco

Esta parte es común a muchos sistemas de archivos, tanto FAT como inodos, como otros. A nivel físico los dipositivos están divididos. En el caso del disco duro, el dispositivo de almacenamiento más común, los discos se dividen en sectores de N bytes, según parámetros de la fabricación. Estos sectores cuentan con código de control de errores incorporado y todo ello es manejado por la controladora de disco que opera ajena al sistema operativo. Los sistemas de archivos dividen a su vez el disco en bloques. Es importante dejar claro que bloque no es igual a sector. El bloque es una división que hace el sistema de archivos y los sectores los hace la controladora de disco. Usualmente un bloque equivale a varios sectores, aunque la equivalencia real depende del sistema de archivos en concreto.

Algunos bloques especiales: boot y superbloque y raíz

Antes de entrar en el mecanismo específico de FAT es necesario comentar que en un disco existirán dos bloques de vital importancia ajenos a FAT. El primero es el bloque de boot o arranque (también llamado MBR). Normalmente situado en el sector 0 del disco duro, contiene código para iniciar el sistema operativo. El superbloque suele ser el bloque siguiente, contiene metadatos del sistema de archivos (por ejemplo, puede decir que usamos FAT con bloques de 32KiB, etc). Además en FAT es necesario que exista un fichero siempre, el fichero del directorio raíz.

Directorios

Los directorios o carpetas son archivos como otros cualquiera, solamente que en sus metadatos se indica que es un directorio y no un fichero corriente. Los ficheros de directorios son los encargados de almacenar los metadatos de los ficheros (paras saber si son ficheros, otros directorios, su fecha de modificación, propietario y tamaño entre otros) que se encuentran en el directorio así como una referencia al bloque.

Un fichero en FAT

Vamos al asunto. Supongamos que en un determinado bloque N tenemos un fichero. Este bloque es de 64 KiB. Si un fichero ocupa menos de 64 KiB, perfecto, todos los datos entran en el bloque. Simplemente ajustamos los metadatos de tamaño con el valor correcto y el resto del espacio que queda en el bloque queda inutilizado.

Este espacio perdido se denomina fragmentación interna y dependiendo de los datos que se almacenen en un disco duro, el porcentaje de pérdida puede ser mayor o menor. Evidentemente si tenemos bloques muy grandes y ficheros muy pequeños perderemos mucho espacio debido a la fragmentación interna. Tener bloques muy pequeños y ficheros muy grandes también es problemático pero por otros motivos.

Tipos de fragmentación

En un sistema de archivos existen 3 tipos de fragmentación: interna, externa y de datos. La interna se refiere al espacio perdido en bloques asignados a ficheros que no están llenos por completo. La externa se refiere al espacio que perdemos por no tener un espacio libre contiguo lo suficientemente grande como para guardar el fichero allí. Ningún sistema FAT o de inodos tiene fragmentación externa al usar todos bloques de tamaño predefinido. Por último la fragmentación de datos, o fragmentación a secas, se refiere a que los bloques asignados estén contiguos o no. Esto tiene implicaciones a nivel de rendimiento pero no al número de bytes que se vuelven inútiles como los otros dos tipos de fragmentación.

¿Pero qué pasa si el contenido de nuestro fichero no puede entrar en el tamaño de bloque? Aquí viene la gracia de FAT, la File-Allocation-Table. La FAT es una tabla compuesta por entradas que indican el siguiente bloque del archivo. La tabla está indexada por bloque y además de indicar cuál es el siguiente bloque del archivo también indica si el archivo acaba ahí o si ese bloque está libre y puede usarse para un archivo nuevo.

En la foto el archivo /home/user/hola.txt tiene una longitud menor al tamaño de bloque. Así que miramos en la FAT la entrada 150 y efectivamente comprobamos que no hay bloque siguiente ya que es un End-of-File.

Pongamos un ejemplo con un archivo largo. Cada celda de la tabla correspondiente al índice del bloque actual indica el siguiente bloque a leer. Estos bloques pueden estar en cualquier parte. Si en un disco duro muchos ficheros tienen los bloques muy dispersos, se dice que necesita ser desfragmentado.

Conclusiones

FAT es muy rápido si la tabla FAT consigue ser cargada en memoria, ya que obtener los datos necesarios de ella será muy rápido. Desgraciadamente, dependiendo del tamaño del disco duro y del tamaño de los bloques, esta tabla puede ocupar mucho y ser impráctico.

El mecanismo FAT como vemos es simple pero efectivo. Dispone de una fragmentación interna limitada y carece de fragmentación externa. Un inconveniente es que FAT cuando busca un archivo necesita empezar siempre desde el directorio raíz. Esto implica accesos cada vez más lentos a medida que vayamos haciendo carpetas, aunque con sistemas de caché se puede reducir.

El esquema FAT tampoco impone restricciones propiamente dichas al número archivos de un sistema ni a su tamaño. Sin embargo en la práctica suelen existir límites.

Edito: He explicado mejor que significa la fragmentación y sus tipos

La entrada ¿Cómo funcionan los sistemas de archivos basados en FAT? se publicó primero en Adrianistán.

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

Picando Código

Lanzamiento ATuNombre.uy 2.0 👩👧👱👵

junio 11, 2018 12:00

DATA presenta una versión renovada de ATuNombre: la aplicación que señala el problema de la desigualdad de género en el nomenclator urbano:

ATuNombre.uy

Hoy les escribimos para contarles del re-lanzamiento, en una versión completamente renovada, de ATuNombre.uy. Un proyecto que toma los nombres de las calles de Montevideo, para hablar sobre la desigualdad de género.

El objetivo de este mapa es dar visibilidad a los aportes que hacen las mujeres a nuestra sociedad, promover su visibilidad y reconocimiento público a través de los nombres de calles de Montevideo. Queremos ver en qué medida estos aportes son valorados, reconocidos y qué hacían esas mujeres para que “pasen a la historia” otorgándoles el nombre de una calle, pasaje o avenida.
¿Cómo lo hicimos?

Este proyecto nace de una propuesta de DATA Uruguay, que invitó a la comunidad a desarrollar esta idea a través de nuestros encuentros regulares llamados Café de DATA (reuniones para trabajar en Datos Abiertos y herramientas sociales).

La primera versión de este sitio se comenzó a trabajar junto a un equipo voluntario (¡gracias!) el 21 de febrero de 2015 (Día Mundial de los Datos Abiertos) y en 2017, empezamos a trabajar nuevamente en renovar la propuesta a lo largo de varios encuentros y con trabajo remoto. Conocé al equipo que hizo esto posible y tené en cuenta que este proyecto va a seguir desarrollándose y mejorando.

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

Poesía Binaria

Programación de tareas en segundo plano para nuestras aplicaciones web (Introducción. Parte 1 de 3)

junio 11, 2018 08:32

Tareas programadas en segundo plano

Las aplicaciones web se van complicando cada vez más. Es un hecho y es algo bueno. Los ordenadores tienen más potencia, y los creadores cada vez más opciones e imaginación para destacar sobre la competencia. Una práctica interesante es la posibilidad de realizar tareas en segundo plano o en un momento concreto del tiempo sin que exista una mediación por parte del usuario.

Tenemos varias posibilidades y en estos posts vamos a centrarnos en cada una de ellas. Por otro lado, aunque este post es más general e independiente del lenguaje, quiero centrarme en PHP. Por un lado, es un lenguaje con el que he trabajado mucho, por otro, aunque es posible que diferentes partes de un proyecto estén realizadas en diferentes lenguajes, y PHP no sea un lenguaje pensado a priori para esto, en ocasiones, es más fácil y barato para una empresa pequeña recolocar a alguien en otro módulo de la apliación si todo está desarrollado en el mismo lenguaje. Como quiero hacer este post más o menos completo y relatar mi experiencia en cada uno de los puntos, se me ha alargado mucho, por eso he decidido dividirlo en tres partes, incluso puede que algún ejemplo dé para una cuarta.

¿Qué podemos hacer con estas tareas?

Bueno, ¿para qué vale todo esto? Es cierto que una web que tenga sus usuarios, envíe correos y sirva cierta información al usuario no tiene por qué ejecutar nada en segundo plano. Todo se servirá y se realizará al ritmo que los usuarios lo pidan. Eso está bien, en principio. Pero claro, imaginad que la web empieza a ganar visitantes. Lo primero que puede darnos problema es el envío de correo.

Puede que si se registran 10 usuarios por hora no pase nada pero, ¿10 usuarios por segundo? A los que debemos enviar confirmaciones de registro (no son muchos), pero luego enviaremos alertas para ciertos eventos, recordatorios de contraseña, avisos de que están intentando entrar con esa cuenta de correo… en definitiva, nuestra aplicación deberá establecer varias conexiones por segundo con el sistema de correo, sistema que muchas veces es externo y seguro que se cabrea (y nos expulsa por establecer tantas conexiones). Seguro que estaría bien coger cada 10, 15 o 20 segundos, y ejecutar una tarea que envíe todos los mensajes que tenga pendiente, lo que se llama un spool de correo, incluso enviar todos los correos pendientes aprovechando una misma conexión al servidor de correo, de manera mucho más eficiente y ordenada.

Supongamos que queremos tener un control de la llegada de dichos mensajes, o al menos comprobar que el e-mail sigue siendo válido para determinar si nuestros usuarios son legítimos o tenemos algún problema con sus datos. En este caso, cuando el servidor de correo de destino está recibiendo el mail normalmente contestará. Puede que el correo se entregue bien, o puede que estemos en una lista negra, el servidor de destino puede aplicar listas grises, nuestro correo puede ser tachado como Spam, o incluso que el servidor de destino no funcione ahora mismo. En caso de fallo, nuestro servidor de correo nos enviará un mensaje con el motivo por el que no se ha podido entregar el correo. Dichos mensaje podemos leerlos periódicamente y asignar un código de estado a los mensajes que hemos enviado a modo de diagnóstico, o para hacer un reintento pasados unos minutos, desactivar usuarios, etc.

Una web actual, normalmente también descarga contenidos de otros lados, y los procesa. Puede que descarguemos tweets, una captura de una web, extraigamos una imagen o una descripción de un post, la cotización del euro o de alguna criptomoneda. Esto son tareas que no dependen de nosotros. Así que no podemos controlar que el tiempo que tardamos en ellas sea fijo, o que nos pueda echar por tierra una página. También tenemos que pensar en que si cada vez que un visitante requiere alguno de estos datos desde nuestro sitio tenemos que acceder a otro lado para sacarlo, tal vez el otro lado se harte de nosotros, o que no responda en algún momento debido a una caída. Lo que podemos hacer es acceder periódicamente a esos sitios sin que exista mediación por parte de nuestros usuarios y guardar esos contenidos en una memoria o base de datos, de modo que nosotros podamos acudir a esos datos de forma muy rápida. Nuestro dato tal vez no sea el más actualizado, en el caso de las cotizaciones, tal vez tengamos un cierto desfase (aunque podemos aplicar otras técnicas), pero si se trata de tweets, posts y demás, esos contenidos no van a variar con el tiempo.

Otro uso muy útil es la importación, exportación y cálculo de datos. En sistemas de estadística, puede que nuestra aplicación deba importar gran cantidad de datos procedente de diversas fuentes (webs externas, ficheros de usuario, APIs, etc). Esta tarea puede ser muy rápida o llegar a ser muy lenta en su ejecución. ¿Por qué no hacerla en segundo plano y avisar al usuario cuando terminemos? Por un lado, evitamos que el usuario esté eternamente esperando una respuesta (y que se estrese poniéndose a pedir los mismos datos una y otra vez), por otro, si la tarea hace un uso muy intensivo de CPU, podemos limitarlo (la tarea puede realizarse de forma más lenta, pero al menos no nos tira el servicio). Tanto si estamos introduciendo datos, generando un fichero de reporte, realizando cálculos, que también puede ser convirtiendo un vídeo u obteniendo una miniatura de un vídeo, son tareas que pueden estresar al usuario y no deberían influir en la carga de nuestras páginas.

También puede que nos interese eliminar sesiones de usuarios inactivos, invalidar o generar cachés de contenidos complicados. El objetivo es que el usuario final de nuestra página tenga que esperar lo menos posible para la realización de las tareas antes de ver un resultado en la página web. Por supuesto, luego podemos consultar por Ajax si una tarea ha finalizado para avisar al usuario o, como es el caso del envío de correos, es algo que al usuario no le importa, pero a nosotros sí.

Escalabilidad

Algo que tenemos que tener en cuenta cuando estamos inmersos en un proyecto es su escalabilidad. En un primer momento, y si no nos queda otra, cuando empezamos a tener visitas a nuestros servicios podremos optar por una escalabilidad vertical. Es decir, utilizar máquinas más potentes para servir nuestros contenidos. Aunque pronto empiezan a surgir más problemas si hacemos de esta práctica algo común. Los costes suelen dispararse, es más barato comprarse 4 ordenadores pequeños que uno cuya potencia englobe a los cuatro. Vale, no siempre es así, pero si nos ponemos a mirar presupuestos de ordenadores llega un momento en el que sí se cumple. Por otro lado, si tenemos nuestro servicio alojado únicamente en una máquina, sólo hace falta que se rompa una máquina para echar todo al traste. Parece una frase tonta pero es así, mientras que si tenemos nuestro servicio replicado en cuatro máquinas, si se rompe una nos fastidia, pero no nos pillamos los dedos. Bueno, tras todo esto tendríamos que pensar qué parte debemos escalar y cómo, porque puede que escalar toda una aplicación mastodóntica no sea la solución, debemos analizar los cuellos de botella y optar por una solución inteligente.

El caso es que muchas veces este tipo de tareas pueden pasarse a otra máquina. Es decir, nuestra máquina principal (o máquinas), como punto de entrada servirán webs y punto. Pero para las tareas programadas, que puede que algunas requieran mucha CPU vamos a utilizar otra máquina, que incluso será privada y funcionará de forma independiente, incluso si vemos que las tareas son muy exigentes podemos dividirlas en varias máquinas a nuestro gusto. De esta manera podremos hacer crecer nuestra aplicación.

Informes de errores

Aunque muchos programadores dejan esto para el final, en mi opinión es lo primero que deberíamos implementar. Cada vez que ejecutamos una de estas tareas deberíamos escribir en algún lado (base de datos, fichero log, servicio de logs externo, etc) que estamos empezando a realizar la tarea. Deberíamos escribir los errores que nos encontramos y los pasos que damos a la hora de ejecutar tareas, así como su finalización. Con esto tendremos un seguimiento que nos puede ayudar a corregir errores o saber si alguna tarea se ha quedado bloqueada. Más tarde podremos desactivar mensajes para que solo muestre inicio de tarea, fin de tarea y errores, así sabemos que nuestro programa hace su trabajo.

Tareas tras la carga de la web

Una técnica que se ha utilizado durante mucho tiempo es que tras la carga completa de la web se ejecute cierto código de mantenimiento. Es cierto que durante mucho tiempo, una vez se ha cargado la web, se comprueba si se deben realizar tareas y se realizan en caso afirmativo. Muchos sitios lo hacen cuando carga una web, otros sitios hacen una petición Ajax para ejecutar tareas… el problema es que en cualquier caso, estamos haciendo que el usuario intervenga en el disparo de dichas tareas. Eso puede causar que la página cargue más lenta, que haya demasiadas peticiones innecesarias al sistema de tareas, que si la tarea tarda mucho en ejecutarse se cancele debido a los timeouts de PHP o del servidor web, o que, si ningún usuario entra a la web en un tiempo, no se lance ninguna tarea. En cualquier caso, yo soy de la idea de que “son cosas que al usuario no le interesan“, incluso en algunos sistemas, podemos ver tras la petición (sobre todo las Ajax), si se ha ejecutado la tarea y cómo ha ido dicha ejecución, cosa que interesa mucho menos al usuario.

El origen de todo esto es porque muchos servidores (sobre todo los compartidos), históricamente no nos dejaban hacer llamadas a otras aplicaciones de gestión de tareas o incluso la creación de tareas programadas (cron jobs). Actualmente, hasta los hospedajes compartidos nos dejan hacer cosas así, de todas formas, yo te recomiendo montarte por lo menos un VPN para tener más libertar con estas cosas. Aún así, las necesidades de ejecución de tareas en nuestras aplicaciones están creciendo.

Cron jobs

Reloj
Son las tareas programadas de toda la vida, de esta forma le podemos decir al sistema ejecuta este programa cada x días, h horas y m minutos. Normalmente un servidor ejecuta muchas tareas en segundo plano como pueden ser mantenimiento de logs, sincronización del reloj, comprobación de actualizaciones, envío de informes de salud, generación de estadísticas y algunas más. Así que, ¿por qué no introducir nuestras propias tareas?

Por un lado podríamos utilizar un script para cada tarea, por ejemplo invalidación de cachés o limpieza de base de datos, consulta de webs externas y envío de e-mails incluso introducir diferente periodicidad para cada una de ellas. Pero en muchas ocasiones podemos incluso centralizarlo todo en una única llamada. Si hablamos de PHP, podremos ejecutar un único archivo PHP o Python, o Java y que éste se encargue de revisar las tareas pendientes y ejecutar las que crea pertinentes. Habrá ejemplos en PHP en el siguiente post. Incluso si lo hacemos de forma centralizada, podremos introducir tareas que solo deban lanzarse una vez en el juego. Por ejemplo cuando exista una tarea que no tiene por qué ser periódica, como puede ser la obtención de los últimos posts de un blog que acaban de introducir en nuestra web (no es periódico, se realiza cuando un usuario da de alta su blog). Luego este cron job lo podemos ejecutar cada minuto o cada dos minutos, como lo creamos necesario. Basta con ejecutar:

crontab -e

Y luego añadir
1
2
# m h  dom mon dow   command
*/2 * * * * php /ruta/del/archivo/archivo.php

Con esto ejectaremos la tarea cada dos minutos. Seguro que buscando por la red encontramos mucha información sobre los cron jobs.

En nuestro script podremos hacer una consulta a base de datos para determinar las tareas que hay pendientes (periódicas o no) y ejecutar la que toque, incluso cada tarea podrá realizar llamadas a otros procesos, y todo sin que el usuario tenga que intervenir y sin costarle tiempo a él.

Lo malo es que los cron jobs de Unix no pueden alcanzar una resolución de segundos por lo que si queremos lanzar una tarea cada segundo, cada dos, o cada quince segundos tenemos que andar ejecutando tareas con sleep delante, aunque podríamos tener problemas si una tarea tarda más de lo normal en ejecutarse.

Programador de tareas en ejecución

Otra opción es crear nosotros un programador de tareas en el lenguaje en el que estemos trabajando. Por un lado, si utilizamos una base de datos para el control de nuestras tareas no tendremos que iniciar conexiones cada vez que ejecutamos la tarea, lo que gasta tiempo y recursos, sino que podremos tener una conexión abierta todo el tiempo. Y, por otro lado, podemos mirar si tenemos una tarea pendiente cada menos tiempo, por ejemplo cada dos segundos. En este caso, si, en el caso de una tarea de envío de correo electrónico, comprobamos si tenemos que enviar e-mails cada minuto, en el peor de los casos, el mensaje tardará un minuto (más o menos en salir). Mientras que si comprobamos cada dos segundos, al usuario prácticamente le da tiempo a cambiar de ventana, acceder a su correo y comprobar si tiene algo nuevo cuando ya ha recibido el mensaje. Parece que no, pero da una mejor sensación a los usuarios.

Por otro lado, si utilizamos un programador de tareas de este estilo podremos tener varias instancias del programador corriendo en una misma máquina. En caso de que no sean tareas muy intensas computacionalmente, como son enviar e-mails, descargar información de webs externas, etc podríamos tener varias tareas en ejecución a la vez. Eso sí, deberíamos controlar muy bien que no se lance la misma tarea por dos programadores a la vez, lo que puede ser un problema. Así como determinar qué tareas pueden ejecutarse en varios sitios a la vez y cuáles no.

Como el artículo me ha quedado muy largo, he decidido dividirlo en tres. Así que para el próximo incluiré ejemplos de código fuente. La segunda parte estará disponible el día 18 de Julio.

Foto principal: unsplash-logoEstée Janssens

Foto (reloj): unsplash-logoÁlvaro Bernal

The post Programación de tareas en segundo plano para nuestras aplicaciones web (Introducción. Parte 1 de 3) appeared first on Poesía Binaria.

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

Koalite

Microsoft, GitHub, unicornios y apuestas

junio 11, 2018 05:06

OJO: Éste es un post de tertuliano de medio pelo de esos que salen en televisión hablando de cosas sin tener ni idea. No le hagas mucho caso.

La noticia de la semana pasada en el mundo tecnológico fue la compra de GitHub por parte de Microsoft. A estas alturas ya no es noticia, pero éste tampoco ha sido nunca un blog de actualidad, así que no tenía ninguna prisa por escribir sobre el tema.

Las reacciones a la noticia han sido variadas y, en muchas ocasiones, claramente sesgadas en función del amor/odio de quien las emitiera hacia Microsoft. O Micro$oft, que todavía hay quien lo ve así.

Las reticencias de siempre

Entre los que se llevan las manos a la cabeza por la adquisición, encontramos los que creen que Microsoft se cargará GitHub y lo llenará de características enterprise de dudosa utilidad para integrarlo con sus otros productos. Supongo que es un temor razonable viendo la evolución que ha tenido Skype. Por lo que se ha comunicado hasta ahora, no parece que eso vaya a ocurrir y GitHub seguirá operando de forma independiente, aunque sólo el tiempo dirá como acaba la cosa.

Otros hablan de la inseguridad que genera almacenar repositorios privados a los que puede tener acceso Microsoft y robarte el código fuente. Esto no hay por donde cogerlo, la verdad. Para empezar, el valor de la mayoría del código fuente es mucho menor del que pensamos, y en los casos en que realmente es tan importante, seguramente esté alojado on-premise y no en un servicio en la nube.

Además, si Microsoft estuvieran tan interesado en alguna tecnología, probablemente le resultaría más rentable comprar la empresa entera que andar robando su código fuente. Y por último, no olvidemos que uno de los principales competidores de GitHub está alojado en Azure, por lo que si Microsoft fuese ese ente maligno robacódigo podría también obtenerlo por ahí.

Suspicacias aparte, podemos también encontrar argumentos a favor de la compra. Puestos a confiar tus repositorios a alguien, parece mejor Microsoft, que tradicionalmente ha mantenido sus productos durante mucho tiempo, que una startup de incierto futuro (y no lo digo sólo por GitHub, sino por algunas alternativas que se han planteado los que huyen ahora de allí).

El modelo actual

Con todo lo exitoso que nos pudiera parecer GitHub a nosotros como desarrolladores, desde el punto de vista de empresa su única posibilidad de supervivencia era ser comprada.

Y puestos a ser comprada, Microsoft no parece la peor opción (imagínate que hubiera sido Oracle).

Lo malo es que se han pagado por él 7.500 millones de dólares y ¿cuántas empresas pueden pagar eso? Sólo las más grandes, lo que implica que cada vez se concentran más las herramientas de desarrollo en unas pocas grandes empresas. No tengo nada en contra del capitalismo y el libre mercado, pero como usuario no me gusta que todo quede en unas pocas manos porque acaba limitando mi libertad de elección.

Uno de los motivos que intuyo influyen en todo esto es la cantidad de capital riesgo que hay en circulación. Algunos hablan de burbuja (yo no sé tanto del tema como para afirmarlo), pero lo que está claro es que hay mucho dinero en circulación para financiar startups. Muchas de estas startups tienen un modelo de negocio más que cuestionable (si es que lo tienen), y su único objetivo es “ponerse guapas” y esperar a ser compradas por una empresa grande.

Como desarrollador, esto tiene varias implicaciones directas. Por una parte, al haber más dinero en circulación es más fácil conseguir buenos sueldos, y eso obviamente se agradece, pero son sueldos que no están mantenidos por el modelo productivo de la empresa, sino por financiación externa. Si te cortan el grifo, se acabó. A esto se le une que el objetivo de la empresa es ser apetecible para que te compren, por lo que es fácil ver empresas que queman rápido la inversión externa recibida para que las curvas de crecimiento de los KPIs de turno queden bien en un Powerpoint, pero el crecimiento no es todo lo sólido, ni técnica ni económicamente, que debería.

Se podría pensar que la existencia de estas empresas que dilapidan financiación sin conseguir triunfar (o sea, ser compradas) es algo terrible para los inversores, pero lo cierto es que es algo que se da por descontado. Este tipo de inversión funciona al modo de apuestas: meto dinero en 100 startups asumiendo que voy a perder lo invertido en muchas, recuperar lo de algunas, ganar con las menos y, si tengo suerte, dar el pelotazo con algún unicornio. Mientras se mantenga el ratio de éxito los inversores estarán contentos. Otra cosa son los compradores de estos unicornios.

Eso deja en una posición complicada a las empresas que quieren crecer sin financiación externa, de una forma orgánica y sostenible, manteniéndose con sus propios beneficios. Cuando las empresas que hay a tu alrededor reciben rondas de financiación que se gastan en dos años en aras de conseguir ese crecimiento explosivo que las haga apetecibles a sus compradores, captar y retener talento no es sencillo y seguir el ritmo del mercado a nivel de sueldos se hace francamente complicado.

Por mi trabajo en el Mundo Real™ tengo contacto con bastantes startups que desarrollan productos para nuestro sector y nos plantean llegar a alianzas o integraciones con nuestos productos. Son empresas financiadas por capital riesgo, que consiguen montar equipos de desarrollo mucho más grandes que el nuestro en poco tiempo y que (parecen) avanzar muy rápido, pero en la mayoría de los casos su plan de negocio no soporta el primer contacto con el mundo real.

En definitiva (y como ya decía al principio, este no es un tema que domine) me cuesta pensar que una situación así sea sostenible. Inflar los precios de la tecnología a base de financiación externa sin un modelo de negocio real (más allá de la propia inversión y compraventa de empresas) me parece una situación arriesgada para todo el sector y, como dice David Heinemeier Hansson el mundo necesita más empresas modestas de crecimiento lineal.

Posts relacionados:

  1. Open Source en un mundo Microsoft

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

Adrianistán

Bindings entre Rust y C/C++ con bindgen

junio 07, 2018 10:30

Rust es un lenguaje con muchas posibilidades pero existe mucho código ya escrito en librerías de C y C++. Código que ha llevado mucho tiempo, que ha sido probado en miles de escenarios, software maduro y que no compensa reescribir. Afortunadamente podemos reutilizar ese código en nuestras aplicaciones Rust a través de bindings. Los bindings no son más que trozos de código que sirven de pegamento entre lenguajes. Esto es algo que se ha podido hacer desde siempre pero dependiendo de la librería podía llegar a ser muy tedioso. Afortunadamente tenemos bindgen, un programa que permite generar estos bindings de forma automática analizando el código de la librería de C o C++.

En este post veremos como usar SQLite desde Rust usando bindgen.

Instalando bindgen

En primer lugar necesitamos tener instalado Clang 3.9 o superior. En Ubuntu o Debian necesitamos estos paquetes:

sudo apt install llvm-3.9-dev libclang-3.9-dev clang-3.9

Para el resto de plataformas puedes descargar el binario desde la página de descargas de LLVM.

Bindgen permite dos modos de uso: línea de comandos o desde el código Rust. El más habitual es desde código Rust pero antes veremos el modo en línea de comandos.

Modo línea de comandos

Para bindings sencillos podemos usar el modo línea de comandos. Instalamos binden con Cargo:

cargo install bindgen

Su uso es muy sencillo:

bindgen /usr/include/sqlite3.h -o sqlite.rs

Simplemente indicamos el fichero de cabecera que queremos traducir y su correspondiente fichero de salida en Rust. Este fichero será el pegamento. Vamos a crear un programa que use este pegamento:

mod sqlite;

use sqlite::{sqlite3_open, sqlite3_exec, sqlite3_close, SQLITE_OK};
use std::ffi::CString;
use std::ptr::{null_mut,null};

fn main(){
    let mut db = null_mut();
    let database_name = CString::new("test.db").unwrap().into_raw();
    let sql = CString::new("
    CREATE TABLE contacts (name TEXT, tel TEXT);
    INSERT INTO contacts VALUES ('Adrian','555-555-555');").unwrap().into_raw(); 
    let mut error_msg = null_mut();
    unsafe{
        sqlite3_open(database_name,&mut db);
        let rc = sqlite3_exec(db,sql,None,null_mut(),&mut error_msg);
        if rc != SQLITE_OK as i32 {
            let error = CString::from_raw(error_msg);
            println!("ERROR: {}",error.into_string().unwrap());
        }
        sqlite3_close(db);
    }
}

Como se puede apreciar, las llamadas al módulo de pegamento de hacen desde un bloque unsafe ya que se van a usar punteros al estilo C, de forma insegura. Hace tiempo escribí sobre ello así que voy a saltarme esa parte.

Compilamos enlazando de forma manual libsqlite3 de la siguiente forma:

rustc main.rs -lsqlite3

Si todo va bien, compilará aunque con numerosos warnings. En principio no son importantes.

Ahora si ejecutamos el programa resultante debería crear una base de datos nueva con una tabla contacts y los datos insertados.

¡Hemos conseguido llamar a una librería de C desde Rust y no hemos escrito ningún binding!

Build.rs

El sistema anterior funciona, pero no es lo más práctico, además no usa Cargo que es el sistema estándar de construcción de programas y crates un Rust. Lo habitual es dejar este proceso automatizado en el fichero build.rs que se ejecuta con Cargo.

Lo primero es añadir la siguiente línea al fichero Cargo.toml:

[build-requires]
bindgen = "0.26.3"

El siguiente paso consiste en crear un archivo cabecera de C que a su vez haga referencia a todos los archivos de cabecera que necesitamos. En el caso de SQLite es bastante simple.

#include <sqlite3.h>

Y lo llamamos wrapper.h

Ahora viene lo interesante. Dentro de build.rs creamos un programa que gracias a la API de bindgen haga lo mismo que la línea de comandos.

extern crate bindgen;

use std::env;
use std::path::PathBuf;

fn main() {
    // indicamos al linker que necesitamos sqlite3
    println!("cargo:rustc-link-lib=sqlite3");


    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .generate()
        .expect("Unable to generate bindings");

    // escribir los bindings en $OUT_DIR/bindings.rs
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}

El archivo build.rs debe estar en la misma carpeta que Cargo.toml para que funcione.

Finalmente para hacer accesible nuestros bindings creamos un módulo llamado sqlite.rs con el siguiente contenido.

#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

Lo único que hace es desactivar unos warnings molestos e incluir el texto de bindings.rs al completo.

Una vez hecho esto podemos usar desde el programa principal la librería de la misma forma que hemos visto antes.

Ahora podríamos usar estos bindings directamente en nuestro programa o rustizarlos (darles una capa segura alrededor e idiomática) y subirlo a Crates.io.

El código del post está en GitHub

La entrada Bindings entre Rust y C/C++ con bindgen se publicó primero en Adrianistán.

» 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

Sponsors

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