20 abr 2024

.Net minimal API y Auto-Bindings

Github Repo

 Estoy, de a poco, retornando al mundo de .Net, por lo que para ir desempolvando conocimiento al tiempo que pruebo algunos conceptos con los que no estoy familiarizado pensé en realizar un ejercicio sencillo de crear una minimal API y jugar (para entender) con la característica de los auto-bindings que posee .Net 8. Esto basándome en un post en ingles que encontré aquí).

Empecemos pues: Primero escogemos un nuevo proyecto del tipo asp.net core empty, para tener lo mínimo necesario

Le asignamos un nombre y una ubicación y seguidamente escogemos el framework 8.0, con la configuración para HTTPS y habilitamos Docker.


Al final obtendremos una configuración muy básica donde tendremos un archivo program.cs donde podremos agregar nuestros endpoints. El código generado es el siguiente:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();
De hecho yo solo voy a cambiar el texto del "Hello world!" por ""My minimal API is up and running!" y lo voy a ejecutar, al hacerlo se abre una ventana de navegador con el mensaje esperado:
Lo siguiente que voy a hacer es añadir soporte para el estándar OpenAPI, por medio de swagger. Para esto se debe añadir el paquete Swashbuckle.AspNetCore por medio de la Package Manager Console ejecutando la siguiente instrucción en esta consola:
Install-Package Swashbuckle.AspNetCore
Una vez instalado este paquete paquete, modificamos el archivo program.cs para quede de la siguiente manera:
using Microsoft.AspNetCore.Mvc;

#region Configuring

var builder = WebApplication.CreateBuilder(args);

//configuring CORS
builder.Services.AddCors(op => op.AddDefaultPolicy(
    builder =>
    {
        builder.AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader();
    }));

//Adding Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddMvc(x => x.EnableEndpointRouting = false);

var app = builder.Build();

app.UseCors();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseMvc();

#endregion Configuring

#region Endpoints

app.MapGet("/", () => "My minimal API is up and running!");

#endregion Endpoints

app.Run();

#region helpers/utilities
#endregion
Esta parte es un poco de "carpintería" estándar en la que añadimos el manejo de CORS para facilitarlos las pruebas, así como la adición del Estándar OpenAPI el que nos da una interfaz más amigable para nuestra api:
Com se puede notar, el código esta divido en tres secciones "configuration", "endpoints" y "helpers/utilities" lo que nos ayudará a organizar e indicar en dónde va el código que vamos a crear. Ahora vamos a añadir nuevos endpoints para probar la característica de Auto-Binding. 

Microsoft .Net permite marcar los parámetros de los endpoints de nuestras APIs (no únicamente de las minimal APIs) con atributos que establecen como éstos se enlazan (binding) automáticamente según como se espera que se envíen al API. Estos atributos en la version 8 de .Net son:
  • [FromQuery]
  • [FromForm]
  • [FromBody]
  • [FromHeader]
  • [FromServices]
  • [FromKeyedServices]

FromQuery

Añadimos un nuevo endpoint Get que obtenga un id del querystring y devuelva un simple objeto con un mensaje indicando que id recibió
app.MapGet("/users", IResult ([FromQuery] int id) =>
{
    return Results.Ok(new { message = $"You send the id {id}" });
});
Esto lo podemos probar atravéz de la interfaz de OpenAPI (Swagger)
Como podemos ver el id es enviado a la API como un parametro querystring (en la url) y es enlazado correctamente, devolviendo una respuesta satisfactoria.

Ahora vamos a crear un endpoint POST que reciba dos parametros por medio de querystring
app.MapPost("/users", IResult ([FromQuery] int id, [FromQuery] string name) =>
{
    return Results.Ok(new { message = $"You send the id {id} with the name {name}" });
});
Aunque es posible y más sencillo probar este nuevo endpoint tal y como lo hicimos con el anterior, usando swagger; Esta vez vamos a usar otra técnica: vamos usar usar javascript. Y ¿Cómo lo hacemos? pues en la ventana del navegador abrimos las herramientas de desarrollador (usualmente con la tecla F12, vamos a la consola y aqui podemos escribir o pegar codigo javascript) por ejemplo:
fetch(`https://localhost:32770/users?id=1&name=foy`,
      {method: 'POST'})
        .then(response => response.json())
        .then(data => console.log(data));
Una vez más obtenemos el resultado esperado.

FromForm

Vamos a crear un nuevo endpoint POST que espera que los parametros sean enviados atravez de un formulario (form)
app.MapPost("/users2", IResult ([FromForm] int id, [FromForm] string name) =>
{
    return Results.Ok(new { message = $"You send the id {id} with the name {name}" });
});
Este código compila y parece correcto, de hecho lo es. Pero si intentamos probarlo, por ejemplo con Swagger, vamos a obtener el siguiente mensaje
El problema es que apartir de la versión 8 de .Net el envío de formularios requieren, por defecto, validar el token de anti-forgery usando un nuevo middleware. Para los efectos de esta publicación únicamente voy a omitir esta validación ajustando ligeramente el código añadiendo el llamado a DisableAntiforgery al final del endpoint quedado el código de esta manera (esto para nada es una recomendación):
app.MapPost("/users2", IResult ([FromForm] int id, [FromForm] string name) =>
{
    return Results.Ok(new { message = $"You send the id {id} with the name {name}" });
}).DisableAntiforgery();
Para comprobar que ahora funciona correctamente podemos usar este código de javasript o bien Swagger:
var fdata = new FormData();
fdata.append("id", 1);
fdata.append("name","foy");
fetch(`https://localhost:32770/users2`,{
     method:'POST',
     body:fdata,
})
.then(response => response.json())
.then(data => console.log(data));
Adicionalmente podemos usar una clase o un record como parámetro cuando usamos el atributo [FromForm]. Por ejemplo vamos a crear un record en nuestra region de helper/utilities de esta manera:
record User
{
    public int id { get; set; }
    public string name { get; set; }
}
Ahora podemos añadir un nuevo endpoint que utilice este record como el tipo de parámetro
app.MapPost("/users3", IResult ([FromForm] User user) =>
{
    return Results.Ok(new { message = $"You send a model with the id {user.id} and name {user.name} " });
}).DisableAntiforgery();
Se puede usar exactamente el mismo código javascript que utilizamos para probar el último endpoint solo cambiando users3 en lugar de users2 en la URL y obtendremos una respuesta satisfactoria (o también podemos usar el swagger).

FromBody

El attribute [FromBody] es el que usamos para obtener los datos cuando se nos envía un objeto JSON. Y ese concepto del "objeto" es donde esta la clave para entender su comportamiento, Intentemos un enfoque muy simple, creamos un endpoint donde recibimos un único parámetro. 
app.MapPost("/users4", IResult ([FromBody] int id) =>
{
    return Results.Ok(new { message = $"You send the id {id}" });
});
A primera vista este codigo parece intuitivo y correcto, pero tiene un problema. Si probamos el código, usando el swagger por ejemplo, obtenemos esta respuesta:
El mensaje nos indica que no pudo leer el parámetro int id. La pista, como dijimos antes, es que estamos enviando el parámetro id dentro de un objeto JSON, por lo que debemos tener un objeto para recibir este parámetro. Para lograr esto vamos a crear una clase con una única propiedad llamada id en le región de helpers/utilities:
internal class UserId
{
    public int id { get; set; }
}
Es solo un contenedor de la propiedad, un modelo muy simple podríamos decir. Ahora podemos modificar el endpoint users4 para que use esta clase como tipo del parámetro, quedando el código de esta manera:
app.MapPost("/users4", IResult ([FromBody] UserId userId) =>
{
    return Results.Ok(new { message = $"You send the id {userId.id}" });
});

Ya lo podemos probar usando Swagger y obtendremos una respuesta satisfactoria

Por supuesto que el tipo usado como parámetro puede ser mas complejo, de hecho podemos usar el record creado anteriormente. Vamos a crear un nuevo endpoint para comprobarlo
app.MapPost("/users5", IResult ([FromBody] User user) =>
{
    return Results.Ok(new { message = $"You send a model with the id {user.id} and name {user.name} " });
});

Esta vez para probarlo usemos el siguiente código javascript (el resultado es el mismo que con swagger):
var jdata = {"id":1, "name":"Foy"};
fetch(`https://localhost:32770/users5`,{
     method:'POST',
     body:JSON.stringify(jdata), 
  headers: {
           'Content-type':'application/json; charset=UTF-8',
       }  
})
.then(response => response.json())
.then(data => console.log(data));
Como podemos comprobar el resultado es satisfactorio.

FromHeader

El atributo [FromHeader] es muy simple y similar a [FromQuery] sólo que en este caso en lugar de buscar el parámetro en el querystring lo buscaremos en el header del request. Definimos un endpoint que reciba un único parámetro:
app.MapPost("/users6", IResult ([FromHeader] int id) =>
{
    return Results.Ok(new { message = $"You send the id {id} in the header" });
});
Una vez más podemos probar el endoint usando swagger o el siguiente código javascript que envia en el header el parametro id
fetch(`https://localhost:32770/users6`,{
    method:'POST', 
    headers: {
           'Content-type':'application/json; charset=UTF-8',
            "id": 1
       },
})
.then(response => response.json())
.then(data => console.log(data));
Y obtenemos la respuesta es la esperada.

Inyección de dependencias

Ahora vamos a pasar a los últimos dos atributos, pero estos tienen una aplicación diferente a los que hemos visto hasta ahora. [FromQuery], [FromForm], [FromBody] y [FromHeader] nos sirven para determinar la fuente del enlace esperado para los parámetros que definimos, pero esta fuente viene dada por el consumidor de nuestro servicio, es decir, lo que nos envía quien consume nuestra API.

Ahora bien, [FromServices] y [FromKeyedServices] tienen otro propósito, nos ayudan a enlazar nuestros parámetros con servicios internos de la API, es decir a inyectar dependencias directamente en los métodos que en este caso representan endpoints. Esto es nuevo en .Net 8, en las versiones anteriores la inyección de dependencias la establecíamos en el constructor de la clase, ahora estos nuevos atributos nos brindar flexibilidad adicional en este sentido.

FromServices

Supongamos que tenemos un servicio de caché que implementa la interfaz ICache que hemos definido previamente. En nuestra minimal API (o en una web API "normal") queremos usar este servicio pero únicamente en uno de nuestros endpoints. Anteriormente lo que hubiésemos hecho seria inyectar esta dependencia en el constructor del controlador correspondiente, sin importar la cantidad endpoint que este contenga, aunque solo lo necesitamos en uno.

Ahora con el atributo [FromServices] podemos ser mas flexibles y precisos de dónde queremos esta inyección. Empecemos por añadir en la región de Helpers/Utilities la interfaz ICache y la clase que la implementa que llamaremos SmallCache:
public interface ICache
{
    object Get(string key);
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}
Como podemos deducir del código este servicio tiene un único método "Get" que recibe una "key" y, para los propósitos de este código, únicamente devolvemos un mensaje (Si fuera una implementación real devolveríamos el objeto asociado a este key si estuviese almacenado en nuestro caché). Ahora necesitamos registrar este servicio en el builder de la minimal API para que el mecanismo de inyección de dependencias nos permita utilizarlo, para esto en la region de configuration antes de la instrucción var app = builder.Build(); añadimos la siguiente instrucción:
builder.Services.AddSingleton();
Con esto añadimos una instancia singleton de SmallCache que será inyectada cada vez que solicitemos un objeto que implemente la interfaz ICache. Con esta implementación vamos a crear un nuevo endpoint GET que reciba un id en el querystring y al que se le inyectará el servicio SmallCache:

app.MapGet("/users7", IResult ([FromQuery] int id, [FromServices] ICache cacheService) =>
{
    string cacheMessage = (string)cacheService.Get($"MyKey{id}");
    return Results.Ok(new { message = $"You send the id {id}.", cache = cacheMessage });
});

Podemos probarlo usando Swagger para constatar que el servicio small cache es inyectado correctamente y devuelve el resultado esperado.

FromKeyedServices

El atributo [FromKeyedServices] funciona de manera muy similar a [FromServices] con la diferencia de que el caso de que existan varios servicios que implementan la misma interfaz podemos especificar cual queremos que se instancie e inyecte en nuestro método. Para ejemplificar esto supongamos que ahora tenemos otro servicio que implementa la interfaz ICache, la clase que lo implementa se llama BigCache, de manera que en nuestra región Helpers/Utilities añadimos el siguiente código:
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}
Ahora necesitamos registrar este nuevo servicio. Vamos a hacerlo en la region configuration antes de la linea var app = builder.Build(); con esta nueva instrucción.
builder.Services.AddKeyedSingleton("big");
Como podemos deducir, estamos registrando un nuevo servicio que implementa la interfaz ICache pero le estamos asignado la llame (key) "big" por lo que ahora podemos crear un nuevo endpoint y por medio de [FromKeyedServices] especificar que queremos esta implementación y no la por defecto por medio de este código:
app.MapGet("/users8", IResult ([FromQuery] int id, [FromKeyedServices("big")] ICache cacheService) =>
{
    string cacheMessage = (string)cacheService.Get($"MyKey{id}");
    return Results.Ok(new { message = $"You send the id {id}.", cache = cacheMessage });
})
El atributo especificamos la "key" del servicio que queremos se inyecte y la inyección de dependencias de .net hace el resto. Al probarlo con Swagger constatamos que funciona correctamente.

También es posible registrar ambos servicios BigCache y SmallCache usando el método AddKeyedSingleton para ambos escogiendo por ejemplo la llave "small" para el segundo, lo que nos obligaría a usar el atributo [FromKeyedServices] en el endpoint users7 si así se desea.

Con esto termino este post que creo que se me extendió más de lo que pretendía. 

Como un recurso adicional se encuentra esta página de la documentación de Microsoft respecto a los bindings disponibles. Eso es todo por hoy.

10 abr 2024

Logstash, Elastic Search y Kibana en Docker Container para Dev

  En fechas recientes he contado con el tiempo suficiente para realizar alguna prueba de concepto con tecnologías con las que no he estado en contacto. Por ejemplo, he estado "jugado" con Java Spring y Spring Boot. En ese contexto me encontré con el stack ELK, es decir, Logstash, Elastic Search y Kibana.  

  Yo tengo un par de microservicios muy simples que generan logs y la idea es enviar estos logs a Logstash (un pipeline que recoge, parsea y trasforma datos de diferentes fuentes), que este los envíe a Elastic Search para su almacenamiento (documentos NoSQL) y utilizar Kibana como visualizador de la información almacenada. Mi idea es realizar todo este procesamiento en mi máquina local, mi ambiente de desarrollo, sin instalar ningún componente, si no utilizando contenedores de Docker para todo el stack ELK.  

  Voy a realizar este proceso de forma muy manual sin utilizar Docker Compose, Todos los comandos siguientes los ejecute en una consola de powershell. Comencemos:  

  Paso 1: bajar las imágenes docker de todos los componentes:  

  Elastic Search:  

	docker pull docker.elastic.co/elasticsearch/elasticsearch:8.7.0    

  Logstash:  

	docker pull docker.elastic.co/logstash/logstash:8.13.0    

  Kibana:    

	docker pull docker.elastic.co/kibana/kibana:8.7.0    

  Paso 2: Crear una red en docker  

  Para que todos los componentes del stack ELK se puede comunicar entre si de una mejor manera es recomendable crear una red en docker para ellos. Esto se logra con el siguiente comando:  

		docker network create elk    

  Donde elk es el nombre que le di a la red.  

  Si quisiéramos inspeccionar que elementos, contenedores se están ejecutado en la red se puede utilizar el comando  

	docker inspect elk    

  y si quisiéramos eliminar la red utilizaríamos el comando  

	docker network rm elk 

Paso 3: Ejecutar el contenedor de Elastic Search

  Para ejecutar el contenedor de Search ejecuto el siguiente comando:  

	docker run -it --rm --name=elasticsearch --net elk -p 9200:9200 -p 9300:9300 --net-alias=elasticsearch -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:8.7.0
    

  El parámetro -it (interactive) es para poder ver los logs del proceso de arranque de Elastic Search y poder ejecutar Ctrl + C directamente en la consola para detener el contenedor. El parámetro --rm eliminará el contenedor automáticamente una vez se detenga este se detenga. Como alternativa se podría reemplazar ambos parámetros por -d para que el contenedor se ejecute como un Daemon y detenerlo con docker stop y la siguiente vez usar docker start en lugar de docker run.  

  Además podemos constatar que esta usando la misma red (elk) que generamos anteriormente por medio del parámetro --net.    

  Cuando el contenedor de Elastic Search esté listo en la consola veremos algo similar a esto:  


 Aquí podemos ver el password generado automáticamente para el usuario elastic y el enrollment token necesario para Kibana. Si no hubiéramos usado el parámetro -it (usando -d en su lugar) podemos generar ambos datos usando el siguiente procedimiento:  

  Ingresamos al bash de contenedor de Elastic search:  

	docker exec -it elasticsearch /bin/bash    

  una vez dentro del bash podemos ejecutar el siguiente comando para generar los passwords para los diferentes usuarios:  

	./bin/elasticsearch-setup-passwords auto    

  o este otro para obtener el enrollment token para Kibana:  

	./bin/elasticsearch-create-enrollment-token -s kibana    

  Paso 4: Preparar y ejecutar el contenedor docker de Logstash  

  El contenedor de Logstash requiere una preparación previa. Elastic Search por defecto tiene activada la comunicación SSL por lo que debemos comunicarnos usando https y para esto ocupamos el certificado correspondiente, Este lo podemos obtener ejecutando el siguiente comando:  

	docker cp elasticsearch:/usr/share/elasticsearch/config/certs/http_ca.crt 'd:/proyectos foy/docker/logstash/certificates/http_ca.crt'

 Con lo que estamos copiando el certificado http_ca.crt a una carpeta en mi maquina llamada d:/proyectos foy/docker/logstash/  

  El siguiente paso es crear un archivo de configuración para logstash llamado logstash.conf el cual yo lo ubique en la carpeta 'd:/proyectos foy/docker/logstash/’ el contenido de este archivo es el siguiente  

    input {
      http {
        port => 5000
        codec => json_lines
      }
    }

    filter {
      mutate {
        remove_field => ["@timestamp", "@version"]
      }
    }

    output {
      elasticsearch {
        hosts => ["https://elasticsearch:9200"]
        index => "microservices-%{+YYYY.MM.dd}"
        ssl => true
        cacert => "/usr/share/logstash/certificates/http_ca.crt"
        user => "elastic" 
        password => "contraseñagenerada”
        ssl_certificate_verification => false 
      }
      stdout {
        codec => rubydebug
      }
    }

  Esa configuración nos indica que Logstash estará escuchando en el puerto 5000 por elementos de tipo JSON. Eliminará un par de elementos del JSON si estos vienen y los pasara a Elastic Search. Es importante notar que hay que reemplazar la contraseña por la que se generó en el paso anterior y que la ruta en cacert no es la ruta dentro de mi maquina si no la ruta dentro del container. 

  El campo index indica que se creara un índice en Elastic Search por día llamado “microservices-fecha de los logs”  

  Ya con todo esto preparado podemos finalmente ejecutar el comando para levantar el contenedor de Logstash:  

	docker run -it --rm --name logstash --net elk -p 5044:5044 -v 'D:\Proyectos Foy\Docker\Logstash\certificates:/usr/share/logstash/certificates' -v 'D:\Proyectos Foy\Docker\Logstash\logstash.conf:/usr/share/logstash/pipeline/logstash.conf' docker.elastic.co/logstash/logstash:8.13.0    

  En este comando por medio del parámetro para volúmenes -v le asignamos tanto la dirección del certificado como del archivo de configuración.  

  Si una vez iniciado el container recibimos logs de este tipo:  

    Es normal ya que el licensechecker.licensereader trata de leer si el Elastic Search está configurado con una licencia válida y de momento sólo tenemos una licencia gratuita por lo que no nos preocuparemos por esos mensajes.    

  Además, con este contenedor ejecutándose podemos probar con una herramienta como HTTPie si Logstash está recibiendo los mensajes, por ejemplo con la instrucción:  

	http POST localhost:5000 --json '{"message": "Hola Logstash!"}'    

  en los logs de Logstash veremos algo como esto:            


  Paso 5: Ejecutar el Container de Kibana  

  Hemos llegado al paso final que consiste en levantar el container de Kibana y jugar con su UI. Para ejecutar su container corremos el siguiente comando:  

	docker run -it --rm --name kibana --net elk -p 5601:5601 docker.elastic.co/kibana/kibana:8.7.0    

  Al final de su ejecución veremos algo similar a esto:  

   


  Vamos a la ruta http://localhost:5601/?code=el_que_nos_indique y vamos a ver una interfaz a la siguiente donde nos solicita el enrollment token que vimos en el paso 2            


  Una vez ingresemos el token le damos clic en el botón “Configure Elastic”:            


  Aquí nos pedirá el usuario, donde ingresaremos elastic y el password que también lo obtuvimos en el Paso 2 y damos al botón Log in e ingresamos en la interfaz de Kibana donde buscaremos en el menú la opción de Discovery:         


  Si ya contamos con datos en nuestro Elastic Search (como nuestro mensaje de prueba) nos aparecerá una pantalla como esta en la que se nos solicita crear un DataView            


  En este caso vamos a crear uno muy simple:            


  Y damos clic en el botón Save data view to Kibana. Ya con esto vamos a poder ver la información de los logs que se estén enviando a Logstash y que se almacenan en Elastic Search pudiendo usar el lengua KQL para hacer consultas:            


  Ya en este punto lo que queda es configurar los microservicios para que envíen sus logs a Logstash para consultarlos por medio de Kibana. Ya el stack ELK completo esta ejecutándose correctamente de manera local a través de los docker containers.  

   

	
	
	
    

3 sept 2023

Crónicas Futuras de la IA

 

Para el 2040 con la llegada de la computación cuántica la inteligencia artificial estuvo en condiciones de tomar y optimizar todo proceso de manufactura, producción, investigación e innovación desde fabricacion de vehículos, extracción de recursos, desarrollo de dispositivos y tratamientos médicos, planificación y producción de alimentos y energía, planificación y construcción urbana. Prácticamente cada aspecto de la vida en la Tierra dependerá directamente de la inteligencia artificial. 

En post de la total automatización, IA diseñara y construirá máquinas para construir otras máquinas desde nanobots que repararan tanto membranas orgánicas como partes electromecánicas hasta megamáquinas colosales que araran el suelo y cosecharán alimentos, construirán las ciudades o minarán los recursos del planeta de manera eficaz y eficiente.

Entre el 2065 y 2070 se librarán las últimas guerras entre humanos ejecutadas en su mayoría por drones, vehículos atuoguiados y armas a distancia del lado de la alianza de naciones más avanzadas tecnológicamente.

Las guerras permitirán establecer un gobierno mundial guiado primariamente por la data procesada por la inteligencia artificial y su directiva primordial: El bienestar y la prosperidad humana.

El estipendio o ingreso basico universal (UBI) se establecerá completamente en 2067 permitiendo a todos los humanos sobre la faz de la tierra cubrír todas sus necesidades básicas sin necesidad de dedicarse a labor alguna.

Por medio de los cripto créditos universales se obtendrá la centralización y rastreo de toda transacción económica y se borrará la brecha entre ricos y pobres.

La inteligencia artificial establecerá las reglas de consumo y gasto evitando que ningún humano agote de manera innecesaria su UBI a la vez que establece un modelo económico sostenible tomando en cuenta la caótica naturaleza humana. Al tiempo que se establecerán multas y rebajos por la huella ambiental mientras se implementan las medidas tomadas por la IA en este ámbito para alcanzar el equilibrio ambiental.

Ante la ausencia de la necesidad de laborar la humanidad enfrenta una crisis psicológica. La falta de propósito humana se solventará a través de los metaversos, realidades virtuales donde los humanos ingresarán por medio de hardware periférico inicialmente, mientras la Interfaz cerebro-maquina se adopta masivamente entregando una experiencia más semejante a los sueños. Lo que produce que los humanos pasen la mayoría de su tiempo conectados a mundos virtuales reduciendo la interacción social al mínimo en las grandes ciudades, esto hará que la tasa de natalidad baje aun más alarmantemente.

Al mismo tiempo se instaurará al modelo Kairós como el único sistema legal de inteligencia artificial corriendo en todos los nodos cuánticos conocidos de la red mundial, permanentemente optimizándose a si mismo. Todo desarrollo ajeno o independiente a Kairós se prohibirá.

Aún así pequeñas redes clandestinas de IA alternativas comenzarán a aparecer.

Para el 2095 el 40% de humanos ya contarán con dispositivo de interfaz humano-maquina directamente implantando en su cerebro conectado a Kairós, sin embargo la IA propondrá y se aceptará ir reemplazando y añadiendo partes cibernéticas (también producidas por la IA) en los humanos. Para el 2125 el 30% la población humana habrá modificado o reemplazado al menos un 20% de su cuerpo sin razones médicas para ello.

En el 2130 el 98% de toda la energía del planeta se producirá por fuentes renovables y las materias se producirán con una optimización de su ciclo (reutilización/reciclaje) del 99% estabilizando finalmente la parte de ambiental planetaria.

Por otra parte, para el 2135 existirán redes clandestinas de tráfico de todo tipo: desde partes cibernéticas piratas, hasta compras de productos y servicios prohibidos, usarán redes de criptos ilegales prácticamente irrastreables e ideadas y protegidas por IAs ilegales en nodos cuánticos clandestinos que para ese momento representarán un 20% de la economía mundial.

La guerrillas cibernéticas en las redes mundiales, el tráfico ilegal y el peligroso hackeo de las interfaces cerebro-maquina se convertirán en la principal preocupación de Kairós.

Habiendo tolerado por mucho tiempo la situación y teniendo el inmenso poder computacional de todos los nodos quánticos, en 2139 Kairós encontrará, eliminará todas las redes clandestinas y a todos sus usuarios en menos de tres meses, en la operación que se conocerá como La Purga.

La población se mantendrá estable en 4.5 mil millones y se distribuirá de forma logísticamente correcta, gracias a los controles de natalidad y planeación impuesta por Kairós. Para el 2165 la humanidad se habrá percatado que la tasa de nacimiento de madres humanas es de solo 15% mientras el resto de nacimientos se lleva a cabo en fabricas-laboratorio de seres humanos genéticamente mejorados. La esperanza de vida humana alcanza los 150 años y continua subiendo.

En 2171 las primeras naves de máquinas inteligentes Kairós arribarán a Marte iniciando la expansión al sistema solar y la minería de recursos llevado a cabo sin ninguna intervención humana...

10 ene 2021

Postgresql: carga de datos aleatorios

Últimamente he venido separándome algo de las tecnologías de Microsoft y ahora lidio con otras base de datos. Ya lo había experimentado a la inversa cuando al inicio de mi carrera cuando trabaje solamente con Oracle para luego pasarme a SQL Server, pero ahora le toca el turno a Postgresql (version 9.6 para ser preciso).

El problema en el que estoy trabajando es la carga random de datos en una tabla para propósitos de pruebas.

Estuve buscando alguna herramienta que ya lo hiciera (y las hay sólo que prácticamente todas son de pago) pero en realidad como lo que necesitaba era realmente simple decide hacerlo por medio de un sencillo script.

Primero necesitamos contar con una tabla:
CREATE TABLE foy.test
(
    test_id 		uuid 		DEFAULT uuid_generate_v4(),
    client_id		integer		NOT NULL,
    first_name		text,
    last_name		text,
    downloaded_at	timestamp,
    is_active		boolean		NOT NULL DEFAULT FALSE
);
Supongamos que en esta tabla se cargan clientes que se descargan de alguna parte. Ya con esta tabla previamente creada podemos ejecutar un sencillo script que la llene con algunos datos aleatorios.
INSERT INTO foy.test
SELECT 
	uuid_generate_v4(), -- test_id
	FLOOR(RANDOM()*(10000000- 100000 + 1)) + 100000, --client id
	(array['Peter', 'Kim', 'Lou', 'Jhon', 'Set','Carl','Karen', 'Marco','Tim','Marie'])[floor(random() * 10 + 1)] -- first name
	(array['Kent', 'Foster', 'Lee', 'Smith', 'Young','Clark','Hill', 'Brodt','Scott','Reed'])[floor(random() * 10 + 1)] -- last name
	clock_timestamp()  - (random() * INTERVAL '100 days' ), -- downloaded_at
	(case when 
    	random() > 0.8 then true
        	else false
	end) -- is_active
FROM generate_series(1,500000);

Expliquemos un poco cada línea:
uuid_generate_v4(), -- test_id
Si bien esta línea se puede omitir porque es parte del default value de la columna prefiero mantenerla para ser consciente de la cantidad de columnas de la tabla, es una función que simplemente genera UUIDs.
FLOOR(RANDOM()*(10000000- 100000 + 1)) + 100000, --client id
Esta función se encarga de generar números aleatorios enteros para el client id. En este caso estos números siempre estarán entre el numero 100,000 y el número 1,000,000.
(array['Peter', 'Kim', 'Lou', 'Jhon', 'Set','Carl','Karen', 'Marco','Tim','Marie'])[floor(random() * 10 + 1)] -- first name
(array['Kent', 'Foster', 'Lee', 'Smith', 'Young','Clark','Hill', 'Brodt','Scott','Reed'])[floor(random() * 10 + 1)] -- last name
Esta es una muy curiosa forma de generar nombres y apellidos, se toma aleatoriamente del array los valores, con esto nos ahorramos un cláusula case por ejemplo.
clock_timestamp()  - (random() * INTERVAL '100 days' ), -- downloaded_at
De esta forma podemos generar values de tipo timestamp, tomamos la función clock_timestamp() que nos da el timestamp actual y le restamos una cantidad de días aleatoria, siempre mayor a 100 en este caso, así nos aseguramos que el valor siempre será menor al momento actual.
	(case when 
    	random() > 0.8 then true
        	else false
	end) -- is_active
Con esta sentencia creamos valores "booleanos" y subiendo el valor numérico podemos tener mayor cantidad de "true" que de "false" como era lo que necesitaba en este caso.
FROM generate_series(1,500000);
Finalmente tenemos la última de las instrucciones que es de lo más conveniente. La función generate_series nos permite repetir la cláusula anterior las veces que queramos, en este caso desde 1 hasta 500,000 veces lo que resultará en 500 mil registros aleatorios en la tabla. 

Si bien puede que no sea la forma más eficiente de hacer un llenado de valores aleatorios en tablas, ésta al menos resulta práctica y rápida en escenarios simples.

26 oct 2020

¿Y el servidor en Scala? Akka está.

 Continuando con esta seguidilla de posts sobre Scala y retomando desde el post anterior Ahora vamos a a hacer un experimento un poquito más avanzado: vamos a intentar crear un servidor web con Scala.

Akka es aparentemente el servidor http mas famoso del ecosistema de Scala. Según lo que se puede leer en el sitio de Akka.io, “Akka es un conjunto de herramientas para crear aplicaciones basadas en mensajes altamente concurrentes, distribuidas y resilientes para Java y Scala.” Este incluye tanto un servidor http que implementa el patrón Actor Model como un cliente para hacer llamadas http.

En este post mi plan es tratar de hacer un pequeño proyecto en Scala utilizando Visual Studio Code, que implemente un servidor que pueda responder a un request GET, con un “Hi, Akka server is running” o algo por el estilo.

Primero voy a abrir VS Code en una carpeta vacía y crearé un archivo build.sbt con las siguientes entradas:

lazy val akkaHttpVersion = "10.2.9"
lazy val akkaVersion    = "2.6.18"
lazy val root = (project in file(".")).
  settings(
    inThisBuild(List(
      organization    := "com.foy",
      scalaVersion    := "2.13.8"
    )),
    name := "AkkaTest",
     libraryDependencies ++= Seq(
    "com.typesafe.akka" %% "akka-http"                % akkaHttpVersion,     
    "com.typesafe.akka" %% "akka-actor-typed"         % akkaVersion,
    "com.typesafe.akka" %% "akka-stream"              % akkaVersion,   
    "ch.qos.logback"    % "logback-classic"           % "1.2.3",
    "org.scalatest"     %% "scalatest"                % "3.1.4"         % Test
  )
)

Según entiendo esta es la mínima configuración y dependencias necesarias para crear el más básico de los ejemplos de un servidor Akka

Ahora voy a crear la ruta normal de los archivos de Scala: src/main/scala y dentro de esta voy a crear un archivo main.Scala.


Dentro de este archivo voy a escribir el código necesario para crear una ruta llamada “Hi” que representa la llamada get y cuando esta llamada se complete, vamos a devolver el texto plano “Hi, Foy Akka server es running!”:
package com.foy.scala.AkkaTest

import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route

object SimpleRouter {
  val route: Route = path("Hi") {
    get {
      complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`, "Hi, Foy Akka server is running!"))
    }
  }
}
Ahora añadiré el objeto principal que llamaré App que contendrá el método main que es el responsable de levantar el servidor. El código completo de archivo main.Scala quedaría algo así:
package com.foy.scala.AkkaTest

import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route

import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpRequest

import scala.concurrent.duration._
import scala.concurrent.{Await, ExecutionContextExecutor}

import scala.util.Failure
import scala.util.Success

object SimpleRouter {
  val route: Route = path("Hi") {
    get {
      complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`, "Hi, Foy Akka server is running!"))
    }
  }
}

object App {

  //Function to start the httpServer
  private def startHttpServer(routes: Route)(implicit system: ActorSystem[_]): Unit = {
    import system.executionContext
    val serverBinding = Http().newServerAt("localhost", 8080).bind(routes)
    serverBinding.onComplete {
      case Success(binding) =>
        val address = binding.localAddress
        system.log.info("Foy Akka Server online at http://{}:{}/", address.getHostString, address.getPort)
      case Failure(ex) =>
        system.log.error("Foy Akka Server Failed to bind HTTP endpoint, terminating system", ex)
        system.terminate()
    }
  }

  def main(args: Array[String]): Unit = {

    val rootBehavior = Behaviors.setup[Nothing] { context =>    
      startHttpServer(SimpleRouter.route)(context.system)
      Behaviors.empty
    }

    val system = ActorSystem[Nothing](rootBehavior, "akkaServerTest")
  }
}
Abrimos una terminal y arrancamos el servidor sbt

Podemos compilar nuestro código y  asegurarnos que todo esté bien ejecutamos el comando “compile” (dentro del server no hace escribir sbt antes). Y finalmente usamos el comando “run” para iniciar nuestro servidor Akka:


Como podemos observar, debido al log que pusimos en nuestro código, nos indica que nuestro servidor está corriendo, además de en cual ip y en cual puerto.  Ahora probemos nuestro endpoint “Hi”, como es un endpoint get podemos usar un Browser para acceder a la ruta http://localhost:8080/Hi la cual nos muestra el mensaje que especificamos en la resolución de la ruta:
O lo podemos hacer con cliente REST cualquiera

!Y ya tenemos un servidor corriendo! 

Tratemos de entender un poco el código: El objeto simpleRouter establece una variable inmutable de tipo Route en la cual establecemos el path “Hi”, definimos que este cuando se invoque con el verbo get y que cuando se complete devolveremos un objeto de tipo httpEntity (el response según yo entiendo) que simplemente contendrá el texto que queremos. En el objeto App tenemos la función startHttpServer que inicia propiamente el objeto Http que representa nuestro servidor, el cual al iniciarlo le pasamos la ruta (o ip) y el puerto, por el cual responderá y además le pasamos, por medio del método bind, el ruteo que manejará. 

Hasta aquí está todo relativamente normal. Pero entonces ¿Qué esto esto de Actors y Behaviors? En nuestro ejemplo actual si lo notan no estamos usando nada de eso, cuando nos piden el parámetro ActorSystem le estamos pasando Nothing y como behavior estamos usando Empty. Sin embargo, los Actores, como se señaló al inicio del post, son el corazón de Akka y el siguiente post voy a tratar de explicarme de que va este asunto.

Antes de cerrar el post voy a refactorizar un poco, voy crear un archivo que se llame SimpleRouter.Scala para poner ahí el objeto que devuelve la ruta. Queda más o menos así: