Translate

lunes, 5 de junio de 2023

El ecosistema de Rust


El ecosistema de Rust consta de una serie de herramientas, de las cuales las principales son:

  • rustc: el compilador de Rust que convierte los archivos .rs en binarios y otros formatos intermedios.
  • cargo: el administrador de dependencias de Rust y la herramienta de compilación. Cargo sabe cómo descargar dependencias alojadas en https://crates.io y las pasará a rustc cuando construyas el proyecto. Cargo también viene con un runtime de prueba incorporado que se utiliza para ejecutar pruebas unitarias.
  • rustup: el instalador y actualizador de la cadena de herramientas de Rust. Esta herramienta se usa para instalar y actualizar rustc y cargo cuando se lanzan nuevas versiones de Rust. Además, rustup también puede descargar documentación para la biblioteca estándar. Puede tener varias versiones de Rust instaladas a la vez y rustup le permitirá cambiar entre ellas según sea necesario.


domingo, 4 de junio de 2023

Primeros pasos con ZIO parte 3


Vamos a crear efectos funcionales de ZIO, y podemos hacerlo a partir de valores, cálculos y tipos de datos comunes de Scala.

Usando el método ZIO.succeed, se puede crear un efecto que, cuando se ejecute, tendrá éxito con el valor especificado:

val s1 = ZIO.succeed(42)

El método de succeed toma un parámetro, que garantiza que si le pasa al método algún código para ejecutar, este código se almacenará dentro del efecto ZIO para que ZIO pueda administrarlo y beneficiarse de características como reintentos, tiempos de espera y registro automático de errores.

Usando el método ZIO.fail, puede crear un efecto que, cuando se ejecuta, fallará con el valor especificado:

val f1 = ZIO.fail("Uh oh!")

Para el tipo de datos ZIO, no hay restricción en el tipo de error. Se puede usar cadenas, excepciones o tipos de datos personalizados.

Podemos usar excepciones :

val f2 = ZIO.fail(new Exception("Uh oh!"))


La biblioteca estándar de Scala contiene varios tipos de datos que se pueden convertir en efectos ZIO.

Un Option se puede convertir en un efecto ZIO usando ZIO.fromOption:


val zoption: IO[Option[Nothing], Int] = ZIO.fromOption(Some(2))


Option[Nothing], lo que significa que si dicho efecto falla, fallará con el valor Nothing (que tiene el tipo Opción[Nothing]).

Puede transformar una falla en algún otro valor de error usando orElseFail, uno de los muchos métodos que proporciona ZIO para la gestión de errores:


val zoption2: ZIO[Any, String, Int] = zoption.orElseFail("It wasn't there!")


ZIO tiene una variedad de otros operadores diseñados para facilitar el manejo de Option. En el siguiente ejemplo, los operadores Some y asSomeError se utilizan para facilitar la interfaz con los métodos que devuelven Option, similar al tipo OptionT en algunas bibliotecas de Scala.


val maybeId: ZIO[Any, Option[Nothing], String] = ZIO.fromOption(Some("abc123"))

def getUser(userId: String): ZIO[Any, Throwable, Option[User]] = ???

def getTeam(teamId: String): ZIO[Any, Throwable, Team] = ???


val result: ZIO[Any, Throwable, Option[(User, Team)]] = (for {

  id   <- maybeId

  user <- getUser(id).some

  team <- getTeam(user.teamId).asSomeError 

} yield (user, team)).unsome 


Un Some se puede convertirse en un efecto ZIO usando ZIO.fromEither:


val zeither: ZIO[Any, Nothing, String] = ZIO.fromEither(Right("Success!"))


El tipo de error del efecto resultante será el del caso Izquierdo (Left), mientras que el tipo de éxito será el del caso Derecho (Right).

Un valor Try se puede convertir en un efecto ZIO usando ZIO.fromTry:


import scala.util.Try


val ztry = ZIO.fromTry(Try(42 / 0))


El tipo de error del efecto resultante siempre será Throwable porque Try solo puede fallar con valores de tipo Throwable.


Un Scala Future se puede convertir en un efecto ZIO usando ZIO.fromFuture:


import scala.concurrent.Future


lazy val future = Future.successful("Hello!")

val zfuture: ZIO[Any, Throwable, String] =  ZIO.fromFuture { implicit ec =>

    future.map(_ => "Goodbye!")

  }


La función pasada a fromFuture recibe un ExecutionContext, que permite a ZIO administrar dónde se ejecuta Future (por supuesto, puede ignorar este ExecutionContext).

El tipo de error del efecto resultante siempre será Throwable, porque los valores Future solo pueden fallar con valores de tipo Throwable.






Vamos a instalar Cargo para programar en Rust


No se si da para un post porque tengo linux, y es solo ejecutar : 

$ sudo apt install cargo rust-src rustfmt

y listo!! 

Si todo salio bien, van a hacer : 

$ cargo --version
cargo 1.65.0

Y se imprime la versión de cargo. 

Vamos a hacer un hola mundo, creemos un proyecto: 

cargo new hello_cargo
cd hello_cargo

En Cargo.toml podemos ver datos del proyecto y las dependencias : 

$ ls
Cargo.toml  src

$ cat Cargo.toml 
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

Y podemos ver el código rust en src/main.rs

$ cat src/main.rs
fn main() {
    println!("Hello, world!");
}

Y con cargo build compilamos y cargo run corremos : 

$ cargo build
   Compiling hello_cargo v0.1.0 (...rust/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.87s

$ cargo run 
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/hello_cargo`
Hello, world!

Y listo!! 



viernes, 2 de junio de 2023

La potencia de la programación funcional en Java con Vavr

 


En el mundo del desarrollo de software, los lenguajes funcionales están ganando popularidad debido a su enfoque en la inmutabilidad, la programación declarativa y las operaciones funcionales. Java, siendo un lenguaje orientado a objetos por naturaleza, carece de muchas características propias de los lenguajes funcionales. Sin embargo, gracias al framework Vavr, los desarrolladores de Java pueden aprovechar al máximo los conceptos y las ventajas de la programación funcional. 

Vavr es un framework funcional para Java 8 y superior que proporciona una biblioteca de clases y funciones para facilitar la programación funcional en Java. Ofrece una amplia gama de características, incluyendo tipos inmutables, operaciones funcionales, manejo de errores y mucho más. Vavr se basa en el paradigma funcional y promueve el uso de funciones puras, evitando los efectos secundarios y facilitando la escritura de código más limpio y conciso.

Características principales de Vavr:

  • Tipos inmutables: Vavr proporciona una serie de tipos inmutables, como List, Set, Option, Try, etc. Estos tipos aseguran que los objetos no puedan ser modificados una vez creados, lo que ayuda a prevenir errores y garantiza la consistencia del estado.
  • Operaciones funcionales: Vavr ofrece una amplia gama de operaciones funcionales, como map, filter, reduce, flatMap, etc. Estas operaciones permiten manipular y transformar colecciones de datos de manera elegante y expresiva.
  • Patrones de concurrencia: Vavr ofrece una API concisa y segura para trabajar con concurrencia en Java. Proporciona constructores de hilos, promesas y otras estructuras de datos para facilitar la programación concurrente.
  • Manejo de errores: Vavr ofrece una forma más robusta y funcional de manejar errores en Java. El tipo Try captura excepciones y permite manejarlas de manera más elegante y comprensible.
  • Validaciones: Vavr proporciona una API para realizar validaciones de manera concisa y declarativa. Permite combinar múltiples validaciones y proporciona mensajes de error claros y comprensibles.


Entre las ventajas de usar Vavr tenemos :

  • Código más legible y conciso: Las características funcionales de Vavr permiten escribir código más legible y conciso. El uso de funciones puras y tipos inmutables reduce la complejidad y mejora la mantenibilidad del código.
  • Programación segura: Al evitar los efectos secundarios y promover la inmutabilidad, Vavr ayuda a prevenir errores y garantizar la integridad del código.
  • Compatibilidad con Java existente: Vavr se integra perfectamente con el código Java existente, lo que permite aprovechar las ventajas de la programación funcional sin necesidad de cambiar todo el código base.
  • Curvas de aprendizaje suaves: Si estás familiarizado con conceptos de programación funcional, la curva de aprendizaje de Vavr es bastante suave. Vavr proporciona una API coherente y bien documentada que facilita su adopción.

Vavr es un framework funcional poderoso que proporciona a los desarrolladores de Java las herramientas necesarias para aprovechar al máximo los conceptos de programación funcional. Con su amplia gama de características y su fácil integración con el código Java existente, Vavr puede ayudar a mejorar la calidad del código, la legibilidad y la mantenibilidad de las aplicaciones Java. 


Veamos un ejemplo de lo que podemos hacer con Vavr: 


import io.vavr.collection.List;

import io.vavr.control.Option;


public class VavrExample {


    public static void main(String[] args) {

        // Tipos inmutables

        List<Integer> numbers = List.of(1, 2, 3, 4, 5);

        List<Integer> squaredNumbers = numbers.map(n -> n * n);

        System.out.println(squaredNumbers); // Imprime: List(1, 4, 9, 16, 25)


        // Operaciones funcionales

        int sum = squaredNumbers.reduce((a, b) -> a + b);

        System.out.println("Suma: " + sum); // Imprime: Suma: 55


        // Option: manejo de valores nulos

        Option<String> name = Option.of("John");

        String upperCaseName = name.map(String::toUpperCase)

                                   .getOrElse("N/A");

        System.out.println(upperCaseName); // Imprime: JOHN


        Option<String> nullName = Option.of(null);

        String nullSafeName = nullName.map(String::toUpperCase)

                                      .getOrElse("N/A");

        System.out.println(nullSafeName); // Imprime: N/A

    }

}

miércoles, 31 de mayo de 2023

Primeros pasos con ZIO parte 2


ZIO es framework para crear aplicaciones nativas en la nube. Con un núcleo funcional amigable para principiantes pero poderoso, ZIO permite a los desarrolladores crear rápidamente aplicaciones con las mejores prácticas que son altamente escalables, comprobables, robustas, resistentes, seguras para los recursos, eficientes y observables.

En el corazón de ZIO se encuentra un poderoso tipo de datos llamado ZIO, que es el bloque de construcción fundamental para cada aplicación ZIO.

El tipo de datos ZIO se denomina efecto funcional y representa una unidad de cálculo dentro de una aplicación ZIO. Al igual que un modelo o un flujo de trabajo, los efectos funcionales son planes precisos que describen un cálculo o una interacción. Cuando se ejecuta una aplicación ZIO, un efecto funcional fallará con algún tipo de error o tendrá éxito con algún tipo de valor.

Al igual que el tipo de datos List, el tipo de datos ZIO es un tipo de datos genérico y usa parámetros de tipo para mejorar la seguridad de tipos. El tipo de datos de Lista tiene un solo parámetro de tipo, que representa el tipo de elemento que se almacena en la Lista. El tipo de datos ZIO tiene tres parámetros de tipo: ZIO[R, E, A].

Los parámetros de tipo del tipo de datos ZIO tienen los siguientes significados:

  • R - Tipo de entorno. El parámetro de tipo de entorno representa el tipo de datos contextuales que requiere el efecto antes de que se pueda ejecutar. Por ejemplo, algunos efectos pueden requerir una conexión a una base de datos, mientras que otros pueden requerir una solicitud HTTP y otros pueden requerir una sesión de usuario. Si el parámetro de tipo de entorno es Any, entonces el efecto no tiene requisitos, lo que significa que el efecto se puede ejecutar sin proporcionarle primero un contexto específico.
  • E - Tipo de falla. El parámetro tipo de falla representa el tipo de error con el que el efecto puede fallar cuando se ejecuta. Aunque Exception o Throwable son tipos de falla comunes en las aplicaciones ZIO, ZIO no impone ningún requisito sobre el tipo de error y, a veces, es útil definir tipos de error comerciales o de dominio personalizados para diferentes partes de una aplicación. Si el parámetro de tipo de error es Nothing, significa que el efecto no puede fallar.
  • A - Tipo de éxito. El parámetro de tipo de éxito representa el tipo de éxito con el que el efecto puede tener éxito cuando se ejecuta. Si el parámetro de tipo de éxito es Unit, significa que el efecto no produce información útil (similar a un método de devolución de vacío), mientras que si es Nothing, significa que el efecto se ejecuta para siempre, a menos que falle.

Como varios ejemplos de cómo interpretar los tipos de efectos ZIO:

  • Un efecto de tipo ZIO[Any, IOException, Byte] no tiene requisitos y, cuando se ejecuta, dicho efecto puede fallar con un valor de tipo IOException o puede tener éxito con un valor de tipo Byte.
  • Un efecto de tipo ZIO[Connection, SQLException, ResultSet] requiere una conexión y, cuando se ejecuta, dicho efecto puede fallar con un valor de tipo SQLException o puede tener éxito con un valor de tipo ResultSet.
  • Un efecto de tipo ZIO[HttpRequest, HttpFailure, HttpSuccess] requiere una HttpRequest y, cuando se ejecuta, dicho efecto puede fallar con un valor de tipo HttpFailure o puede tener éxito con un valor de tipo HttpSuccess.

El parámetro de tipo de entorno es un parámetro de tipo compuesto porque, a veces, un solo efecto puede requerir varios valores de diferentes tipos. Si ve que un efecto tiene un tipo de ZIO[UserSession with HttpRequest, E, A] (Scala 2.x) o ZIO[UserSession & HttpRequest, E, A] (Scala 3.x), significa que el efecto requiere múltiples valores contextuales antes de que pueda ejecutarse.

Aunque esta analogía no es precisa, se puede pensar en un efecto ZIO como una función:


R => Either[E, A]


Esta función requiere una R y produce una falla de tipo E o un valor de éxito de tipo A.

Los efectos ZIO no son en realidad funciones, por supuesto, porque modelan cálculos e interacciones complejos, que pueden ser asincrónicos, concurrentes o ingeniosos.

El tipo de datos ZIO es el único tipo de efecto en ZIO. Sin embargo, hay una familia de alias de tipo que reducen la necesidad de escribir:

  • UIO[A]: un alias de tipo para ZIO[Any, Nothing, A], que representa un efecto que no tiene requisitos, no puede fallar y puede tener éxito con una A.
  • URIO[R, A]: un alias de tipo para ZIO[R, Nothing, A], que representa un efecto que requiere una R, no puede fallar y puede tener éxito con una A.
  • Task[A]: un alias de tipo para ZIO[Any, Throwable, A], que representa un efecto que no tiene requisitos, puede fallar con un valor Throwable o tener éxito con una A.
  • RIO[R, A]: un alias de tipo para ZIO[R, Throwable, A], que representa un efecto que requiere una R, puede fallar con un valor Throwable o tener éxito con una A.
  • IO[E, A]: un alias de tipo para ZIO[Any, E, A], que representa un efecto que no tiene requisitos, puede fallar con una E o tener éxito con una A.

Si es nuevo en los efectos funcionales, le recomendamos que comience con el tipo de tarea, que tiene un solo parámetro de tipo y se corresponde más con los tipos de datos futuros integrados en las bibliotecas estándar de Scala y Java.

Si está utilizando bibliotecas Cats Effect, puede encontrar útil el tipo RIO, ya que le permite enhebrar el contexto a través de bibliotecas de terceros.

Independientemente del tipo de alias que utilice en su aplicación, UIO puede ser útil para describir efectos infalibles, incluidos los que resultan del manejo de todos los errores.

Finalmente, si es un programador funcional experimentado, se recomienda el uso directo del tipo de datos ZIO, aunque puede resultarle útil crear su propia familia de alias de tipo en diferentes partes de su aplicación.

Si se siente cómodo con el tipo de datos ZIO y su familia de alias de tipo, el siguiente paso es aprender a crear efectos ...

lunes, 29 de mayo de 2023

Primeros pasos con ZIO


Empecemos desde el principio, incluyamos ZIO en nuestro proyecto agregando la siguiente dependencia en el build.sbt:


libraryDependencies += "dev.zio" %% "zio" % "2.0.13"


Para hacer una aplicación ZIO podemos hacer que nuestra aplicación extienda de ZIOAppDefault, que permite escribir todo el programa usando ZIO:


import zio._

import zio.Console._


object MyApp extends ZIOAppDefault {


  def run = myAppLogic


  val myAppLogic =

    for {

      _    <- printLine("Hello! What is your name?")

      name <- readLine

      _    <- printLine(s"Hello, ${name}, welcome to ZIO!")

    } yield ()

}


El método run debemos devolver un valor ZIO que tiene todos sus errores manejados, que, en la jerga de ZIO, es un valor ZIO no excepcional.

Una forma de hacer esto es invocar un pliegue sobre un valor ZIO, para obtener otro valor ZIO no excepcional. Eso requiere dos funciones de controlador: de E => B (el controlador de errores) y de A => B (el controlador de éxito). Si myAppLogic falla, habrá un 1; si tiene éxito, habrá un 0.

Si la aplicación que queremos hacer es una aplicación existente, utilizando inyección de dependencia o no controla su función principal, entonces podemos crear un sistema para ejecutar sus programas ZIO:

import zio._


object IntegrationExample {

  val runtime = Runtime.default


  Unsafe.unsafe { implicit unsafe =>

    runtime.unsafe.run(ZIO.attempt(println("Hello World!"))).getOrThrowFiberFailure()

  }

}

Idealmente, la aplicación debería tener un solo runtime, porque cada runtime tiene sus propios recursos (incluido el grupo de subprocesos y el informador de errores no controlados).

ZIO proporciona un módulo para interactuar con la consola. Si necesita imprimir texto en la consola, puede usar print e printLine:

import zio._


// Print without trailing line break

Console.print("Hello World")

// Print string and include trailing line break

Console.printLine("Hello World")


Si necesita leer la entrada desde la consola, puede usar readLine:

import zio._

val echo = Console.readLine.flatMap(line => Console.printLine(line))



martes, 23 de mayo de 2023

¡Aprende un poco de Erlang por el bien de todos!

 


Quiero recomendarles este libro, es un poquito viejo pero es gratuito escrito por Fred Hebert. Este libro es ampliamente reconocido como una excelente introducción al lenguaje de programación Erlang. Proporciona una cobertura completa de los conceptos fundamentales de Erlang, desde los conceptos básicos de programación funcional hasta temas más avanzados como concurrencia y distribución.

El libro está estructurado de manera didáctica y utiliza ejemplos prácticos para ayudar a los lectores a comprender los conceptos. También aborda temas importantes en el desarrollo de software en Erlang, como la creación de aplicaciones OTP (Open Telecom Platform) y el manejo de errores.

Dejo link: https://learnyousomeerlang.com/

lunes, 22 de mayo de 2023

Getting Started with Go on Google Cloud


Google Cloud y Go deben ser de los temas más buscados en estos tiempos y podes aprender en este curso que te estoy pasando! 

"Getting Started with Go on Google Cloud" es un curso gratuito de google cloud utilizando Go como lenguaje de programación. 


Dejo link: https://www.cloudskillsboost.google/quests/129

Comprehensive Rust 🦀


Quiero recomendarles un sitio para aprender rust, que se llama Comprehensive Rust

El sitio nos dice : 

"Este es un curso de Rust de tres días desarrollado por el equipo de Android. El curso cubre el espectro completo de Rust, desde la sintaxis básica hasta temas avanzados como los genéricos y el manejo de errores. También incluye contenido específico de Android en el último día.

El objetivo del curso es enseñarte Rust. Suponemos que no sabe nada sobre Rust y esperamos:

  • Brindarle una comprensión integral de la sintaxis y el lenguaje de Rust.
  • Le permite modificar programas existentes y escribir nuevos programas en Rust.
  • Mostrar expresiones idiomáticas comunes de Rust."

Test de arquitectura con Roslyn parte 2

Siguiento la idea del post anterior, de testear cosas con roslyn. Vamos a testear que en todo el proyecto, todos los atributos privados comiencen con _ (guión bajo) 

Bueno para esto primero necesitamos 3 dependencias más : 

    <PackageReference Include="Microsoft.Build.Locator" Version="1.5.5" />

    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.5.0" />

    <PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.5.0" />


Locator es para localizar el sdk y las librerías, pienso no estoy muy seguro que tenemos que hacer esto porque estamos en un test (pero corrijanme si estoy equivocado) 

Workspace porque vamos abrir un workspace y msbuild para compilar. (esto esta explicado en este post)

Entonces nuestro test nos queda de la siguiente manera : 

    [Test]

    public async Task The_Field_Private_Should_Start_With_Underscore()

    {

        // Arrange

        string projectPath = @"C:\projects\hat\ArchTestWithRoslyn\test\test.csproj";

        MSBuildLocator.RegisterDefaults();

        using (var workspace = MSBuildWorkspace.Create())

        {

            var project = await workspace.OpenProjectAsync(projectPath);

            var compilation = await project.GetCompilationAsync();

            foreach (var syntaxTree in compilation.SyntaxTrees)

            {

                var nodeRoot = syntaxTree.GetRoot();

                var fields = nodeRoot.DescendantNodes()

                    .OfType<FieldDeclarationSyntax>()

                    .Where(field => field.Modifiers

                                        .Any(modify => 

                                            modify.Kind().Equals(SyntaxKind.PrivateKeyword))

                    && field.Declaration.Variables

                        .Any(aVar => !aVar.Identifier.ValueText.StartsWith("_"))

                    );

                // Assert

                Assert.IsTrue(!fields.Any());

            }

        }

    }


Este test lo que hace es crear un workspace, importar el proyecto (podemos importar soluciones si quisieramos) y luego obtiene el o los arboles sintacticos y luego busca si existe algun atributo privado que no comience con "_".  


Y listo!! 

Por qué son importantes los test de arquitectura.


En el desarrollo de aplicaciones de software, la arquitectura juega un papel fundamental. Define la estructura y el diseño de la aplicación, proporcionando una base sólida sobre la cual se construyen todas las funcionalidades. A medida que las aplicaciones se vuelven más complejas, es esencial garantizar que la arquitectura sea sólida y cumpla con los requisitos deseados. Aquí es donde entran en juego los test de arquitectura.  

¿Qué son los test de arquitectura? Los test de arquitectura son una práctica que busca evaluar la robustez y la calidad de la arquitectura de una aplicación. Se centran en verificar que los componentes clave de la arquitectura funcionen correctamente y cumplan con los requisitos esperados. A diferencia de los test unitarios, que se centran en probar unidades individuales de código, los test de arquitectura se enfocan en la estructura global de la aplicación y en cómo interactúan sus diferentes componentes. 

¿Porque es importante verificar la arquitectura? Principalmente porque a medida que desarrollamos podemos no respetar los principios que definimos desde la concepción de la aplicación. Los test de arquitectura nos permiten mantener esos principios a lo largo del tiempo verificando si se cumplen commit a commit.  

Además, permiten:  

  • Identificación temprana de problemas: Los test de arquitectura permiten identificar problemas potenciales en la fase inicial del desarrollo. Al evaluar la arquitectura antes de implementar todas las funcionalidades, es posible detectar problemas de diseño, cuellos de botella de rendimiento, dependencias no deseadas, entre otros. Esto ayuda a reducir el costo y el esfuerzo necesario para corregir problemas en etapas más avanzadas del desarrollo. 
  • Mejora de la calidad y mantenibilidad: Una arquitectura sólida es fundamental para garantizar la calidad y mantenibilidad a largo plazo de una aplicación. Los test de arquitectura ayudan a validar que la estructura de la aplicación cumpla con principios de diseño y buenas prácticas. Esto facilita la comprensión del código, la reutilización de componentes y la realización de cambios sin afectar negativamente otras partes del sistema. 
  • Alineación con los requisitos: Los test de arquitectura permiten asegurar que la arquitectura esté alineada con los requisitos del negocio y las necesidades de los usuarios. Al realizar pruebas exhaustivas de la arquitectura, se pueden identificar brechas en los requisitos y asegurar que todas las funcionalidades esperadas estén cubiertas. Esto ayuda a evitar costosos cambios posteriores debido a requisitos omitidos o malinterpretados. 
  • Rendimiento y escalabilidad: Las aplicaciones modernas deben ser capaces de manejar grandes volúmenes de datos y un alto número de usuarios concurrentes. Los test de arquitectura pueden ayudar a identificar cuellos de botella de rendimiento y evaluar la escalabilidad de la aplicación. Esto permite tomar decisiones informadas sobre el uso de tecnologías adecuadas, la optimización de consultas y la distribución de la carga de trabajo, asegurando un rendimiento óptimo incluso en condiciones de alta demanda. 
  • Gestión de riesgos: Los test de arquitectura ayudan a mitigar los riesgos asociados con el desarrollo de aplicaciones complejas. Permiten identificar y abordar problemas de seguridad, integridad de datos y fiabilidad del sistema. Al probar y validar la arquitectura en diferentes escenarios y condiciones, se pueden detectar posibles vulnerabilidades o debilidades y tomar medidas para corregirlas antes de que se conviertan en problemas reales. 

¿Y cómo podemos probar nuestra arquitectura fácilmente? Para ello existen varios frameworks y herramientas disponibles para realizar test de arquitectura en las plataformas Java y .NET algunos de los más utilizados son: 

Java: 

  • Arquillian: Es un framework de pruebas de integración que se enfoca en la creación de escenarios de prueba realistas para aplicaciones Java. Proporciona soporte para la ejecución de pruebas en diferentes entornos y contenedores. 
  • JUnit: Aunque principalmente utilizado para test unitarios, JUnit también puede ser utilizado para realizar pruebas de integración y pruebas de arquitectura. Permite la ejecución de pruebas en paralelo y la creación de casos de prueba complejos. 

.NET: 

  • NUnit: Similar a JUnit, NUnit es un framework de pruebas para la plataforma .NET. Ofrece una amplia gama de funcionalidades para realizar pruebas de arquitectura, incluyendo aserciones, configuraciones y la ejecución de pruebas en paralelo. 
  • SpecFlow: Esta herramienta se basa en el concepto de BDD (Behavior-Driven Development) y permite escribir pruebas de aceptación en un lenguaje natural fácilmente comprensible. Es especialmente útil para probar la arquitectura desde la perspectiva del comportamiento esperado del sistema. 

ArchUnit es otro framework relevante para realizar test de arquitectura en aplicaciones Java y .NET. ArchUnit(en java) y NArchiUnit(en .NET) es un framework de código abierto que permite definir y verificar reglas arquitectónicas en el código fuente de una aplicación. Proporciona una forma declarativa de definir restricciones sobre la estructura y el diseño de la arquitectura, como reglas de dependencia entre paquetes, convenciones de nomenclatura, restricciones de visibilidad, entre otros aspectos.  

Al utilizar ArchUnit, los desarrolladores pueden escribir pruebas que verifiquen automáticamente si la arquitectura de la aplicación cumple con las reglas definidas. Esto permite garantizar la coherencia y la integridad de la arquitectura, evitando que se introduzcan violaciones arquitectónicas en el código. Además, ArchUnit ofrece una sintaxis expresiva y flexible que facilita la definición de reglas específicas para cada proyecto.  

Para que los test unitarios sean útiles es importante, correrlos regularmente y para esto herramientas como un servidor de integración continua son fundamentales.  

Los servidores de integración continua desempeñan un papel importante en la ejecución automatizada de los test de arquitectura. Estos servidores, como Jenkins, Bamboo o Azure DevOps, permiten la configuración y ejecución de pipelines de integración continua, que incluyen la compilación, pruebas y despliegue automatizado de la aplicación. Al utilizar un servidor de integración continua, los test de arquitectura se pueden ejecutar de manera regular y sistemática, lo que garantiza que cualquier cambio o actualización en la aplicación se someta a pruebas de arquitectura antes de ser desplegado en producción. 

Conclusión: Los test de arquitectura son fundamentales para garantizar una base sólida y robusta en el desarrollo de aplicaciones de software. Ayudan a identificar problemas tempranamente, mejorar la calidad y mantenibilidad, alinear la arquitectura con los requisitos, garantizar el rendimiento y escalabilidad, y mitigar riesgos. Utilizar frameworks específicos y aprovechar los servidores de integración continua facilita su implementación y automatización. No programar estos test puede tener consecuencias negativas, como problemas de escalabilidad, falta de mantenibilidad, vulnerabilidades de seguridad y dificultad para cumplir requisitos. En resumen, los test de arquitectura son una práctica esencial para asegurar el éxito y la calidad de las aplicaciones. 


 

viernes, 19 de mayo de 2023

Emit API de Roslyn

Hasta ahora, hemos analizado principalmente cómo podemos usar Roslyn para analizar y manipular el código fuente. Ahora veremos cómo finalizar el proceso de compilación emitiéndolo en el disco o en la memoria. Para comenzar, solo intentaremos emitir una compilación simple al disco y verificar si tuvo éxito o no.

var tree = CSharpSyntaxTree.ParseText(@"

using System;

public class C

{

    public static void Main()

    {

        Console.WriteLine(""Hello World!"");

        Console.ReadLine();

    }   

}");


var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);

var compilation = CSharpCompilation.Create("MyCompilation",

    syntaxTrees: new[] { tree }, references: new[] { mscorlib });


//Emitting to file is available through an extension method in the Microsoft.CodeAnalysis namespace

var emitResult = compilation.Emit("output.exe", "output.pdb");


//If our compilation failed, we can discover exactly why.

if(!emitResult.Success)

{

    foreach(var diagnostic in emitResult.Diagnostics)

    {

        Console.WriteLine(diagnostic.ToString());

    }

}


Después de ejecutar este código, podemos ver que nuestro ejecutable y .pdb se han emitido a Debug/bin/. Podemos hacer doble clic en output.exe y ver que nuestro programa se ejecuta como se esperaba. El archivo .pdb es opcional. Escribir el archivo .pdb en el disco puede llevar bastante tiempo y, a menudo, vale la pena omitir este argumento a menos que realmente se necesite.

A veces es posible que no queramos emitir a disco. Es posible que solo queramos compilar el código, enviarlo a la memoria y luego ejecutarlo desde la memoria. Para esto, la API de script probablemente tenga más sentido de usar. Aún así, vale la pena conocer nuestras opciones.

var tree = CSharpSyntaxTree.ParseText(@"

using System;

public class MyClass

{

    public static void Main()

    {

        Console.WriteLine(""Hello World!"");

        Console.ReadLine();

    }   

}");


var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);

var compilation = CSharpCompilation.Create("MyCompilation",

    syntaxTrees: new[] { tree }, references: new[] { mscorlib });


//Emit to stream

var ms = new MemoryStream();

var emitResult = compilation.Emit(ms);


//Load into currently running assembly. Normally we'd probably

//want to do this in an AppDomain

var ourAssembly = Assembly.Load(ms.ToArray());

var type = ourAssembly.GetType("MyClass");


//Invokes our main method and writes "Hello World" 🙂

type.InvokeMember("Main", BindingFlags.Default | BindingFlags.InvokeMethod, null, null, null);


Finalmente, ¿qué pasa si queremos influir en cómo se compila nuestro código? Es posible que queramos permitir código no seguro, marcar advertencias como errores o retrasar la firma del ensamblado. Todas estas opciones se pueden personalizar pasando un objeto CSharpCompilationOptions a CSharpCompilation.Create(). Echaremos un vistazo a cómo podemos interactuar con algunas de estas propiedades a continuación.


var tree = CSharpSyntaxTree.ParseText(@"

using System;

public class MyClass

{

    public static void Main()

    {

        Console.WriteLine(""Hello World!"");

        Console.ReadLine();

    }   

}");


//We first have to choose what kind of output we're creating: DLL, .exe etc.

var options = new CSharpCompilationOptions(OutputKind.ConsoleApplication);

options = options.WithAllowUnsafe(true);                                //Allow unsafe code;

options = options.WithOptimizationLevel(OptimizationLevel.Release);     //Set optimization level

options = options.WithPlatform(Platform.X64);                           //Set platform


var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);

var compilation = CSharpCompilation.Create("MyCompilation",

    syntaxTrees: new[] { tree },

    references: new[] { mscorlib },

    options: options);   


En total hay unas veinticinco opciones diferentes disponibles para la personalización. Básicamente, cualquier opción que tiene la página de propiedades del proyecto de Visual Studio debería estar disponible aquí.

Hay algunos parámetros opcionales disponibles en Compilation.Emit() que vale la pena analizar. 

xmlDocPath: genera automáticamente documentación XML basada en los comentarios de documentación presentes en sus clases, métodos, propiedades, etc.

manifestResources: le permite incrustar manualmente recursos como cadenas e imágenes dentro del ensamblaje emitido. 

win32ResourcesPath: ruta del archivo desde el que se leerán los recursos Win32 de la compilación (en formato RES). 


miércoles, 17 de mayo de 2023

SymbolVisitor

SymbolVisitor es el análogo de SyntaxVisitor, pero se aplica a nivel de símbolo. Desafortunadamente, a diferencia de SyntaxWalker y CSharpSyntaxRewriter, cuando usamos SymbolVisitor debemos construir el código de andamiaje para visitar todos los nodos.

Para enumerar simplemente todos los tipos disponibles para una compilación, podemos usar lo siguiente.


public class NamedTypeVisitor : SymbolVisitor

{

    public override void VisitNamespace(INamespaceSymbol symbol)

    {

        Console.WriteLine(symbol);

        

        foreach(var childSymbol in symbol.GetMembers())

        {

            //We must implement the visitor pattern ourselves and 

            //accept the child symbols in order to visit their children

            childSymbol.Accept(this);

        }

    }


    public override void VisitNamedType(INamedTypeSymbol symbol)

    {

        Console.WriteLine(symbol);

        

        foreach (var childSymbol in symbol.GetTypeMembers())

        {

            //Once againt we must accept the children to visit 

            //all of their children

            childSymbol.Accept(this);

        }

    }

}


//Now we need to use our visitor

var tree = CSharpSyntaxTree.ParseText(@"

class MyClass

{

    class Nested

    {

    }

    void M()

    {

    }

}");


var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);

var compilation = CSharpCompilation.Create("MyCompilation",

    syntaxTrees: new[] { tree }, references: new[] { mscorlib });


var visitor = new NamedTypeVisitor();

visitor.Visit(compilation.GlobalNamespace);


Para visitar todos los métodos disponibles para una compilación dada podemos utilizar los siguientes:


public class MethodSymbolVisitor : SymbolVisitor

{

    //NOTE: We have to visit the namespace's children even though

    //we don't care about them. 😦

    public override void VisitNamespace(INamespaceSymbol symbol)

    {

        foreach(var child in symbol.GetMembers())

        {

            child.Accept(this);

        }

    }

    

    //NOTE: We have to visit the named type's children even though

    //we don't care about them. 😦

    public override void VisitNamedType(INamedTypeSymbol symbol)

    {

        foreach(var child in symbol.GetMembers())

        {

            child.Accept(this);

        }

    }


    public override void VisitMethod(IMethodSymbol symbol)

    {

        Console.WriteLine(symbol);

    }

}

Es importante tener en cuenta cómo  se debe estructurar el código para poder visitar todos los símbolos que interesan. Si estoy interesado en visitar símbolos de métodos, no quiero tener que escribir código que visite espacios de nombres y tipos.

Con suerte, en algún momento obtendremos una clase SymbolWalker que podamos usar para separar nuestra implementación del código transversal. 

¿Cómo obtenemos una lista de todos los tipos disponibles para una compilación? así :


public class CustomSymbolFinder

{

    public List<INamedTypeSymbol> GetAllSymbols(Compilation compilation)

    {

        var visitor = new FindAllSymbolsVisitor();

        visitor.Visit(compilation.GlobalNamespace);

        return visitor.AllTypeSymbols;

    }


    private class FindAllSymbolsVisitor : SymbolVisitor

    {

        public List<INamedTypeSymbol> AllTypeSymbols { get; } = new List<INamedTypeSymbol>();


        public override void VisitNamespace(INamespaceSymbol symbol)

        {

            Parallel.ForEach(symbol.GetMembers(), s => s.Accept(this));

        }


        public override void VisitNamedType(INamedTypeSymbol symbol)

        {

            AllTypeSymbols.Add(symbol);

            foreach (var childSymbol in symbol.GetTypeMembers())

            {

                base.Visit(childSymbol);

            }

        }

    }

}

La clase SymbolVisitor probablemente sea apropiada para usos únicos durante la compilación o para visitar un subconjunto de símbolos disponibles. Por lo menos, vale la pena ser consciente de ello.


viernes, 12 de mayo de 2023

La Guía de Google Cloud que te ayudará a innovar y crecer.

 

jueves, 11 de mayo de 2023

Scripting API de Roslyn

Para instalar la API de Scripting en el proyecto simplemente hay que ejecutar:

Install-Package Microsoft.CodeAnalysis.Scripting -Pre

CSharpScript.EvaluateAsync es probablemente la forma más sencilla de empezar a evaluar expresiones. 


var result = await CSharpScript.EvaluateAsync("5 + 5");

Console.WriteLine(result); // 10


result = await CSharpScript.EvaluateAsync(@"""sample""");

Console.WriteLine(result); // sample


result = await CSharpScript.EvaluateAsync(@"""sample"" + "" string""");

Console.WriteLine(result); // sample string


result = await CSharpScript.EvaluateAsync("int x = 5; int y = 5; x"); //Note the last x is not contained in a proper statement

Console.WriteLine(result); // 5

No todos los scripts devuelven un único valor. Para secuencias de comandos más complejos, es posible que deseemos realizar un seguimiento del estado o inspeccionar diferentes variables. CSharpScript.RunAsync crea y devuelve un objeto ScriptState que nos permite hacer exactamente esto. 


var state = CSharpScript.RunAsync(@"int x = 5; int y = 3; int z = x + y;""");

ScriptVariable x = state.Variables["x"];

ScriptVariable y = state.Variables["y"];


Console.Write($"{x.Name} : {x.Value} : {x.Type} "); // x : 5

Console.Write($"{y.Name} : {y.Value} : {y.Type} "); // y : 3


También podemos mantener el estado de nuestro script y continuar aplicándole cambios con ScriptState.ContinueWith():


var state = CSharpScript.RunAsync(@"int x = 5; int y = 3; int z = x + y;""").Result;

state = state.ContinueWithAsync("x++; y = 1;").Result;

state = state.ContinueWithAsync("x = x + y;").Result;


ScriptVariable x = state.Variables["x"];

ScriptVariable y = state.Variables["y"];


Console.Write($"{x.Name} : {x.Value} : {x.Type} "); // x : 7

Console.Write($"{y.Name} : {y.Value} : {y.Type} "); // y : 1



Podemos agregar referencias a los archivos DLL que nos gustaría usar. Usamos ScriptOptions para proporcionar nuestro script con las MetadataReferences adecuadas.

ScriptOptions scriptOptions = ScriptOptions.Default;

//Add reference to mscorlib
var mscorlib = typeof(System.Object).Assembly;
var systemCore = typeof(System.Linq.Enumerable).Assembly;
scriptOptions = scriptOptions.AddReferences(mscorlib, systemCore);
//Add namespaces
scriptOptions = scriptOptions.AddNamespaces("System");
scriptOptions = scriptOptions.AddNamespaces("System.Linq");
scriptOptions = scriptOptions.AddNamespaces("System.Collections.Generic");

var state = await CSharpScript.RunAsync(@"var x = new List(){1,2,3,4,5};", scriptOptions);
state = await state.ContinueWithAsync("var y = x.Take(3).ToList();");

var y = state.Variables["y"];
var yList = (List)y.Value;
foreach(var val in yList)
{
  Console.Write(val + " "); // Prints 1 2 3
}

Este material es sorprendentemente amplio. El espacio de nombres Microsoft.CodeAnalysis.Scripting está lleno de tipos.