Translate

Mostrando las entradas con la etiqueta Java. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Java. Mostrar todas las entradas

sábado, 9 de noviembre de 2024

GraalVM + sistema operativo = GraalOS


GraalOS es una iniciativa experimental que integra la tecnología de GraalVM directamente en el sistema operativo, permitiendo que las aplicaciones, especialmente las desarrolladas en lenguajes JVM (Java, Scala, Kotlin), se ejecuten de manera más eficiente y directa sobre el hardware. GraalOS busca ser un sistema operativo minimalista y optimizado para ejecutar aplicaciones de alto rendimiento, proporcionando un entorno ideal para microservicios, procesamiento en la nube y aplicaciones en tiempo real.

Las principales características de GraalOS son: 

  1. Soporte Nativo para Lenguajes JVM: GraalOS permite ejecutar código de JVM directamente sobre el sistema operativo sin capas intermedias, ofreciendo un rendimiento nativo para lenguajes como Java, Kotlin y Scala.
  2. Integración con GraalVM: GraalOS está construido sobre la base de GraalVM, lo que permite la compilación AOT (Ahead-of-Time) y el uso de `native-image` para generar binarios nativos que corren eficientemente sobre el hardware.
  3. Ecosistema Multilenguaje: Aunque está optimizado para lenguajes de la JVM, GraalOS también soporta otros lenguajes como JavaScript, Python y R, aprovechando la compatibilidad de GraalVM.
  4. Optimización para Microservicios: GraalOS está diseñado para ejecutarse en contenedores ligeros, ideales para arquitecturas de microservicios y entornos de computación en la nube.

Uno de los puntos fuertes de GraalOS es el uso de la tecnología de compilación Ahead-of-Time (AOT) de GraalVM. La compilación AOT permite que el código de JVM se convierta en código nativo, lo cual mejora significativamente el tiempo de inicio y reduce el uso de memoria.

native-image -jar tu_aplicacion.jar

Este comando convierte un archivo JAR en un binario nativo, optimizado y listo para ejecutarse en GraalOS. Los binarios nativos generados pueden arrancar casi instantáneamente y son ideales para aplicaciones que requieren respuesta en tiempo real.

GraalOS ofrece un entorno perfecto para el despliegue de aplicaciones en la nube gracias a su integración optimizada con GraalVM. Además, permite manejar aplicaciones en tiempo real gracias a su bajo tiempo de respuesta y consumo de recursos. Su diseño minimalista y eficiente hace que sea una opción atractiva para desarrolladores que busquen optimizar costos y rendimiento en entornos de microservicios o serverless.

Aunque GraalOS es experimental, se puede probar en entornos de contenedores o como un sistema operativo en máquinas virtuales para evaluar su rendimiento en aplicaciones específicas. Para comenzar, puedes instalar GraalOS en una máquina virtual y luego utilizar GraalVM para compilar y ejecutar aplicaciones.


apt update && apt install graalos


GraalOS representa un avance en la forma en que interactuamos con el hardware a nivel de sistema operativo para ejecutar aplicaciones nativas. Aunque en sus primeras etapas, su integración con GraalVM abre la puerta a nuevas oportunidades en la ejecución de aplicaciones de alto rendimiento y microservicios en la nube.

Con una promesa de rendimiento optimizado, tiempos de respuesta ultrarrápidos y soporte multilenguaje, GraalOS podría transformar la forma en que desarrollamos e implementamos aplicaciones nativas.

Dejo like : 

https://blogs.oracle.com/java/post/introducing-graalos

https://graal.cloud/

jueves, 31 de octubre de 2024

Le pregunte a la IA : ¿Qué lenguaje tiene las mejores perspectivas de futuro: Python, Java o JavaScript?

Le pregunte a la IA : ¿Qué lenguaje tiene las mejores perspectivas de futuro: Python, Java o JavaScript? y como me gusto mucho su respuesta se las comparto: 

Para evaluar el futuro de Python, Java, y JavaScript, consideremos cómo se están usando, su versatilidad y las tendencias que los impulsan en áreas clave:


Python

   - Ventajas: Python es ampliamente valorado por su legibilidad y simplicidad, lo cual lo hace popular tanto entre principiantes como en sectores avanzados como la ciencia de datos, la inteligencia artificial (IA), y el análisis de datos. Su ecosistema ofrece un gran soporte en estas áreas, con bibliotecas como TensorFlow, Pandas, y NumPy.

   - Perspectivas de Futuro: Su relevancia en IA y ciencia de datos seguirá creciendo, dado que es el lenguaje de referencia en estos campos. Aunque tiene limitaciones de rendimiento comparado con lenguajes de bajo nivel, las mejoras continuas en sus bibliotecas y en plataformas de soporte (como el uso de PyPy y la integración con código en C++) pueden mitigar estas limitaciones. Sin embargo, en áreas que demandan alto rendimiento en tiempo real, como juegos y aplicaciones de alta concurrencia, su uso podría ser limitado.


Java

   - Ventajas: Java es conocido por su estabilidad y robustez, especialmente en entornos empresariales y sistemas a gran escala. La JVM (Java Virtual Machine) permite que las aplicaciones en Java se ejecuten de manera confiable en múltiples plataformas. Java también es ampliamente utilizado en aplicaciones de Android y sigue siendo un estándar en muchas empresas.

   - Perspectivas de Futuro: La madurez y confiabilidad de Java aseguran su presencia en grandes empresas y aplicaciones financieras, que demandan sistemas sólidos y escalables. Recientemente, con mejoras de rendimiento en las versiones más nuevas y características de programación moderna (como pattern matching y records), Java sigue evolucionando y atrayendo a una nueva generación de desarrolladores. Su uso en aplicaciones web modernas y su compatibilidad con entornos de nube son elementos clave que aseguran su vigencia en el mercado.


JavaScript

   - Ventajas: JavaScript es el lenguaje dominante en el desarrollo web, y su versatilidad ha crecido enormemente con el ecosistema Node.js, lo cual permite su uso tanto en el frontend como en el backend. Su capacidad para construir aplicaciones de una sola página (SPA), junto con frameworks como React, Vue, y Angular, le ha dado un rol esencial en la web moderna.

   - Perspectivas de Futuro: La demanda de aplicaciones web interactivas y rápidas respalda el crecimiento de JavaScript. Además, su capacidad de integrarse con **WebAssembly (Wasm)** ha ampliado su uso en aplicaciones de alto rendimiento en el navegador. Node.js sigue siendo relevante para el backend, y con la evolución continua del lenguaje (por ejemplo, ES2022), JavaScript se mantiene competitivo. 


- Python: dominará en ciencia de datos, IA y aplicaciones académicas; su simplicidad asegura su uso entre nuevos programadores.

- Java: mantiene su posición en el desarrollo empresarial y de sistemas complejos; su futuro está asegurado en estos sectores.

- JavaScript: seguirá siendo fundamental en el desarrollo web, y su interoperabilidad con WebAssembly amplía sus horizontes.


En general, la elección del lenguaje con las "mejores perspectivas" depende del campo específico. Para IA y datos, Python; para sistemas empresariales, Java; y para la web y aplicaciones de usuario, JavaScript. Cada uno tiene un rol fuerte y perspectivas sólidas en sus respectivos dominios.


Que piensan? 

miércoles, 25 de septiembre de 2024

Apache Geode: Almacenamiento de Datos Distribuido en Tiempo Real


En el mundo actual, donde las aplicaciones demandan respuestas inmediatas y alta disponibilidad de datos, es crucial contar con herramientas que permitan gestionar datos en tiempo real de manera distribuida. Apache Geode es una de esas soluciones que permiten construir aplicaciones escalables y altamente disponibles.

Apache Geode es un sistema de almacenamiento de datos en memoria distribuido que ofrece capacidades de procesamiento y almacenamiento en tiempo real. Originalmente desarrollado por GemStone bajo el nombre de GemFire, pasó a ser un proyecto de código abierto bajo el paraguas de la Apache Software Foundation. 

Apache Geode se caracteriza por su capacidad para almacenar datos en memoria distribuidos entre varios nodos, permitiendo así un acceso rápido y eficiente a grandes volúmenes de datos. Ofrece baja latencia, alta disponibilidad, y consistencia, lo que lo convierte en una opción ideal para aplicaciones críticas que requieren acceso en tiempo real a los datos.

Apache Geode distribuye los datos entre múltiples nodos (o servidores), formando un clúster donde cada nodo puede almacenar una parte de los datos. De esta forma, el sistema es capaz de escalar horizontalmente a medida que aumenta la demanda. Además, Geode puede replicar los datos entre los nodos para garantizar redundancia y alta disponibilidad.

La arquitectura de Apache Geode permite particionar y replicar datos de manera eficiente. Esto significa que cada partición de datos se almacena en un nodo del clúster, y estas particiones pueden replicarse en otros nodos para evitar pérdida de información en caso de fallos.

Además, Geode ofrece consistencia fuerte, lo que significa que los datos son siempre consistentes entre las réplicas, lo cual es fundamental en entornos de alta disponibilidad.

Características Principales de Apache Geode:

  1. Almacenamiento en Memoria: Apache Geode utiliza la memoria principal de los servidores para almacenar los datos, lo que reduce drásticamente la latencia de acceso en comparación con bases de datos tradicionales basadas en disco.
  2. Distribución y Replicación de Datos: Los datos en Geode se distribuyen entre varios nodos y pueden replicarse para garantizar redundancia y alta disponibilidad.
  3. Alta Disponibilidad y Tolerancia a Fallos: Al replicar los datos en diferentes nodos del clúster, Geode garantiza que los datos estarán disponibles incluso si uno o varios nodos fallan.
  4. Consistencia: Apache Geode asegura consistencia fuerte, es decir, cualquier cambio en los datos es inmediatamente visible en todos los nodos que almacenan copias del mismo dato.
  5. Procesamiento en Tiempo Real: Permite realizar consultas y operaciones sobre los datos en tiempo real, manteniendo la latencia baja incluso en sistemas con altos volúmenes de transacciones.
  6. Soporte para APIs de Java y Spring: Geode está profundamente integrado con Java y tiene un fuerte soporte para el ecosistema Spring, lo que facilita su integración en aplicaciones Java empresariales.
  7. Persistencia: Aunque su principal almacenamiento es en memoria, Geode permite configurar persistencia en disco para asegurar que los datos no se pierdan tras un reinicio o fallo catastrófico.

Y donde podemos utilizar Geode: 

  1. Aplicaciones Financieras: En sistemas de trading y banca, donde la baja latencia y la consistencia de datos son cruciales, Geode se utiliza para garantizar acceso rápido a los datos en tiempo real.
  2. eCommerce: Plataformas de comercio electrónico, donde es necesario manejar grandes cantidades de usuarios concurrentes y transacciones, pueden beneficiarse de la capacidad de escalado y alta disponibilidad de Geode.
  3. Sistemas de Telecomunicaciones: Las redes de telecomunicaciones requieren acceso constante a la información del usuario y deben procesar grandes volúmenes de datos en tiempo real, algo que Apache Geode maneja eficientemente.
  4. Monitorización en Tiempo Real: Para sistemas de monitoreo y análisis en tiempo real, Geode permite el procesamiento y la toma de decisiones rápidas basadas en datos en memoria, sin la necesidad de acceder a discos lentos.

Cuando comparamos a Apache Geode con otras soluciones de almacenamiento en memoria como Redis o Hazelcast, la diferencia radica en el soporte más amplio de Apache Geode para modelos de datos más complejos y la integración nativa con Spring, lo que facilita su adopción en entornos Java empresariales.

Por otro lado, en comparación con bases de datos tradicionales como MySQL o PostgreSQL, Geode ofrece una arquitectura distribuida en memoria, lo que reduce significativamente la latencia de acceso y permite una mayor escalabilidad.

Apache Geode es una herramienta poderosa para desarrollar aplicaciones críticas que requieren almacenamiento distribuido en memoria y acceso en tiempo real a los datos. Su integración con tecnologías como Java y Spring, junto con su capacidad de escalabilidad y alta disponibilidad, lo convierten en una opción excelente para sectores como finanzas, telecomunicaciones, y comercio electrónico.

Dejo link; https://geode.apache.org/

viernes, 20 de septiembre de 2024

Programación Reactiva con Spring


La programación reactiva es un paradigma orientado a manejar flujos de datos de manera asíncrona, lo cual resulta ideal para aplicaciones con alta carga de tráfico, como sistemas de microservicios. Con Spring, la programación reactiva se facilita mediante el módulo Spring WebFlux.

Spring WebFlux es el módulo reactivo de Spring que permite construir aplicaciones no bloqueantes y asíncronas. Está basado en el patrón Reactor, que sigue el estándar de Reactive Streams y utiliza Mono y Flux como las abstracciones principales para manejar uno o varios elementos, respectivamente.

  • Mono: Representa 0 o 1 elemento.
  • Flux: Representa 0 o N elementos.
  • Backpressure: Mecanismo para controlar la velocidad de emisión de los eventos.

Primero, crea un proyecto de Spring Boot en Spring Initializr. Asegúrate de agregar las dependencias de Spring Reactive Web.


<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-webflux</artifactId>

</dependency>


A continuación, implementamos un controlador que responde a peticiones HTTP de manera reactiva usando `Flux` y `Mono`.


import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;


import java.time.Duration;


@RestController

public class ReactiveController {


    // Endpoint que devuelve un solo valor (Mono)

    @GetMapping("/mono/{id}")

    public Mono<String> getMono(@PathVariable String id) {

        return Mono.just("Valor recibido: " + id);

    }


    // Endpoint que devuelve un flujo continuo de datos (Flux)

    @GetMapping("/flux")

    public Flux<Integer> getFlux() {

        return Flux.range(1, 10)

                   .delayElements(Duration.ofSeconds(1));

    }

}


Una vez que hayas implementado el controlador, ejecuta la aplicación de Spring Boot. Puedes acceder a los endpoints:

  • /mono/{id}: Devuelve un único valor. Ejemplo: http://localhost:8080/mono/5
  • /flux: Devuelve un flujo de datos continuo que se emite cada segundo. Ejemplo: http://localhost:8080/flux
Como ventajas podemos ver: 
  • Eficiencia y Escalabilidad: Ideal para manejar aplicaciones que requieren alta concurrencia y asíncronas.
  • Control del Flujo: Con Reactive Streams puedes controlar el ritmo de procesamiento y evitar la sobrecarga del sistema.
  • Menos Bloqueo: No hay hilos bloqueados mientras se esperan respuestas, lo que mejora el rendimiento en sistemas de alta demanda.


viernes, 23 de agosto de 2024

lunes, 19 de agosto de 2024

Se encuentran abiertas las inscripciones para los cursos Gugler!!!

¡Tengo grandes noticias! Estoy emocionado de anunciar que ya están abiertas las inscripciones para los tan esperados cursos Gugler. Si estás buscando avanzar en tu carrera, aprender nuevas habilidades, o simplemente profundizar tus conocimientos en áreas tecnológicas, ¡estos cursos son para ti!







Inscripciones abiertas del segundo cuatrimestre 2024. Inscripciones.gugler.com.ar

lunes, 12 de agosto de 2024

Procedimientos Almacenados y Funciones en H2


H2 es una base de datos relacional escrita en Java que es especialmente popular por su ligereza y facilidad de uso. Aunque es muy común utilizar H2 como una base de datos en memoria para pruebas, también es lo suficientemente potente para manejar operaciones más avanzadas, como procedimientos almacenados y funciones. 

Un procedimiento almacenado es un conjunto de instrucciones SQL que pueden ser almacenadas en la base de datos y ejecutadas cuando sea necesario. Los procedimientos almacenados son útiles porque permiten encapsular lógica compleja en un solo lugar, lo que facilita su mantenimiento y reutilización.

Las funciones son similares a los procedimientos almacenados, pero con algunas diferencias clave. Mientras que un procedimiento almacenado puede ejecutar una serie de comandos SQL y no necesariamente retornar un valor, una función siempre retorna un valor y está diseñada para ser utilizada en expresiones SQL.

En H2, los procedimientos almacenados se crean utilizando la palabra clave `CREATE ALIAS`. Veamos un ejemplo sencillo de cómo crear un procedimiento almacenado que inserta un nuevo registro en una tabla:


CREATE ALIAS insert_user AS $$

void insert_user(Connection conn, String username, String email) throws SQLException {

    PreparedStatement prep = conn.prepareStatement("INSERT INTO users(username, email) VALUES(?, ?)");

    prep.setString(1, username);

    prep.setString(2, email);

    prep.execute();

    prep.close();

}

$$;


Una vez que el procedimiento almacenado ha sido creado, puedes llamarlo usando `CALL`:


CALL insert_user('JohnDoe', 'john.doe@example.com');


Si necesitas que tu procedimiento devuelva resultados, puedes modificarlo ligeramente:


CREATE ALIAS get_user_by_id AS $$

ResultSet get_user_by_id(Connection conn, int id) throws SQLException {

    PreparedStatement prep = conn.prepareStatement("SELECT * FROM users WHERE id = ?");

    prep.setInt(1, id);

    return prep.executeQuery();

}

$$;


Luego puedes utilizar el procedimiento en una consulta:


CALL get_user_by_id(1);


Al igual que los procedimientos, las funciones en H2 se crean utilizando `CREATE ALIAS`, pero la diferencia es que las funciones siempre retornan un valor. Aquí tienes un ejemplo de una función que calcula el IVA de un precio dado:


CREATE ALIAS calculate_vat AS $$

double calculate_vat(double price) {

    return price * 0.21;

}

$$;


Una vez creada, puedes utilizar la función en tus consultas SQL:


SELECT calculate_vat(100) AS vat_amount;


Esto te devolverá el valor con el IVA aplicado.


H2 ofrece un soporte robusto para procedimientos almacenados y funciones, lo que lo hace muy útil incluso en aplicaciones más allá del simple almacenamiento de datos en memoria. Con la capacidad de encapsular lógica SQL compleja en procedimientos y funciones, puedes mantener tu código más limpio y organizado. 


jueves, 25 de julio de 2024

Como crear un compilador que tome un lenguaje personalizado y lo traduzca a bytecode


Crear un compilador que tome un lenguaje personalizado y lo traduzca a bytecode es una tarea compleja pero fascinante. Veamos cómo podríamos abordar este proyecto, usando herramientas y conceptos clave en el desarrollo de compiladores. Vamos a dividirlo en varias etapas:

1. Definir el Lenguaje

   Antes de empezar, necesitas definir tu lenguaje. Esto incluye:

  •    Sintaxis: La estructura del lenguaje, las reglas gramaticales, etc.
  •    Semántica: El significado de las construcciones del lenguaje.

   Por ejemplo, podríamos definir un lenguaje simple con variables, operadores y estructuras de control.


2. Crear una Gramática

Usa ANTLR o una herramienta similar para definir la gramática de tu lenguaje. Esto implica escribir un archivo `.g4` (o el formato correspondiente) que describa la sintaxis de tu lenguaje.

   Ejemplo de una gramática simple en ANTLR (`SimpleLang.g4`):


   grammar SimpleLang;


   program: statement+;

   statement: assignment | expression ';';

   assignment: ID '=' expression ';';

   expression: ID | NUMBER | '(' expression ')' | expression '+' expression | expression '-' expression;

   ID: [a-zA-Z_][a-zA-Z_0-9]*;

   NUMBER: [0-9]+;

   WS: [ \t\r\n]+ -> skip;

   

3. Generar el Lexer y el Parser

Usa ANTLR para generar el lexer y el parser a partir de tu gramática.


   antlr4 SimpleLang.g4


4. Crear un Árbol de Sintaxis Abstracto (AST)

El parser generará un árbol de sintaxis. Sin embargo, es útil convertir esto en un Árbol de Sintaxis Abstracto (AST) que simplifica el manejo de la estructura del código.


5. Transformar el AST a Bytecode

   Para convertir el AST a bytecode, necesitas:

  • Definir un formato de bytecode: Dependiendo de la máquina virtual (como la JVM para Java) o un formato personalizado.
  • Generar bytecode: Escribir código que recorra el AST y genere el bytecode correspondiente.

Aquí hay un ejemplo simple de cómo podrías generar bytecode para una calculadora en Java:


   public class BytecodeGenerator extends SimpleLangBaseVisitor<Void> {

       private final StringBuilder bytecode = new StringBuilder();


       @Override

       public Void visitAssignment(SimpleLangParser.AssignmentContext ctx) {

           String id = ctx.ID().getText();

           visit(ctx.expression());

           bytecode.append("STORE ").append(id).append("\n");

           return null;

       }


       @Override

       public Void visitExpression(SimpleLangParser.ExpressionContext ctx) {

           if (ctx.ID() != null) {

               bytecode.append("LOAD ").append(ctx.ID().getText()).append("\n");

           } else if (ctx.NUMBER() != null) {

               bytecode.append("PUSH ").append(ctx.NUMBER().getText()).append("\n");

           } else if (ctx.op != null) {

               visit(ctx.expression(0));

               visit(ctx.expression(1));

               switch (ctx.op.getType()) {

                   case SimpleLangParser.PLUS:

                       bytecode.append("ADD\n");

                       break;

                   case SimpleLangParser.MINUS:

                       bytecode.append("SUB\n");

                       break;

               }

           }

           return null;

       }


       public String getBytecode() {

           return bytecode.toString();

       }

   }


6. Compilar el Bytecode

Si estás utilizando una máquina virtual existente como la JVM, deberás generar bytecode en el formato adecuado. Si estás creando tu propia máquina virtual, tendrás que diseñar un mecanismo para ejecutar el bytecode.


7. Probar y Depurar

Pruebar el compilador con varios programas de ejemplo para asegurarte de que se comporta como se espera. Depura cualquier problema que encuentres en el proceso de generación de bytecode.


Este es un enfoque básico y simplificado para ilustrar cómo podrías comenzar a construir un compilador. En un compilador real, deberás gestionar muchas más cosas, como el manejo de errores, la optimización del bytecode y la implementación de una máquina virtual completa si no estás utilizando una existente.

jueves, 11 de julio de 2024

Como podemos manejar las referencias nulas?


El error más frecuente en Java es NullPointerException y me imagino que en otros lenguajes alguno similar...  Para abordar esto, se han introducido estructuras y operadores que ayudan a manejar la ausencia de valores de manera más segura y explícita. 

Por ejemplo en Java se introdujo la clase `Optional` en la versión 8 para manejar valores potencialmente nulos de una manera más segura. `Optional` es un contenedor que puede o no contener un valor no nulo.

import java.util.Optional;


public class OptionalExample {

    public static void main(String[] args) {

        Optional<String> optional = Optional.of("Hello, World!");

        

        // Verificar si hay un valor presente

        if (optional.isPresent()) {

            System.out.println(optional.get());

        }

        

        // Uso del método ifPresent

        optional.ifPresent(System.out::println);

        

        // Proveer un valor predeterminado

        String value = optional.orElse("Default Value");

        System.out.println(value);

        

        // Proveer un valor predeterminado usando un Supplier

        value = optional.orElseGet(() -> "Default Value from Supplier");

        System.out.println(value);

    }

}


Scala utiliza la clase `Option` para representar un valor opcional. `Option` tiene dos subclases: `Some` y `None`, lo que proporciona una forma elegante y funcional de manejar valores que pueden estar ausentes. Esta idea es similar a la monada `Maybe` en Haskell.


object OptionExample extends App {

  val someValue: Option[String] = Some("Hello, World!")

  val noneValue: Option[String] = None


  // Uso de getOrElse

  println(someValue.getOrElse("Default Value"))

  println(noneValue.getOrElse("Default Value"))


  // Uso del patrón de coincidencia (Pattern Matching)

  someValue match {

    case Some(value) => println(value)

    case None => println("No value")

  }


  noneValue match {

    case Some(value) => println(value)

    case None => println("No value")

  }

}


Scala "copio" esta forma de Haskell. Haskell utiliza el tipo de datos `Maybe` para manejar valores opcionales `Maybe` puede ser `Just` un valor o `Nothing`.


main :: IO ()

main = do

    let someValue = Just "Hello, World!"

    let noneValue = Nothing


    -- Uso de fromMaybe

    putStrLn (fromMaybe "Default Value" someValue)

    putStrLn (fromMaybe "Default Value" noneValue)


    -- Uso del patrón de coincidencia (Pattern Matching)

    case someValue of

        Just value -> putStrLn value

        Nothing -> putStrLn "No value"


    case noneValue of

        Just value -> putStrLn value

        Nothing -> putStrLn "No value"


Kotlin es similar a Scala en muchos aspectos pero no en este. Kotlin introduce el operador `?` para facilitar la gestión de valores nulos. Este operador se utiliza para declarar tipos de datos que pueden ser nulos y para realizar operaciones seguras contra nulos.


fun main() {

    var nullableString: String? = "Hello, World!"


    // Uso del operador ?. para llamadas seguras

    println(nullableString?.length)


    // Uso del operador ?: para proporcionar un valor predeterminado

    val length = nullableString?.length ?: 0

    println(length)


    nullableString = null


    // Uso de let para ejecutar código solo si el valor no es nulo

    nullableString?.let {

        println(it)

    }

}


C# ha incluido varias características para manejar valores nulos, como el operador `?`, que facilita el manejo seguro de tipos que pueden ser nulos.


using System;


class Program

{

    static void Main()

    {

        string? nullableString = "Hello, World!";

        

        // Uso del operador ?. para llamadas seguras

        Console.WriteLine(nullableString?.Length);


        // Uso del operador ?? para proporcionar un valor predeterminado

        int length = nullableString?.Length ?? 0;

        Console.WriteLine(length);


        nullableString = null;


        // Uso de pattern matching para verificar nulos

        if (nullableString is string nonNullString)

        {

            Console.WriteLine(nonNullString);

        }

    }

}


Rust maneja la ausencia de valores y los errores de una manera robusta utilizando los tipos `Option` y `Result`. `Option` puede ser `Some` o `None`, mientras que `Result` puede ser `Ok` o `Err`.


fn main() {

    let some_value: Option<String> = Some("Hello, World!".to_string());

    let none_value: Option<String> = None;


    // Uso de unwrap_or

    println!("{}", some_value.unwrap_or("Default Value".to_string()));

    println!("{}", none_value.unwrap_or("Default Value".to_string()));


    // Uso del patrón de coincidencia (Pattern Matching)

    match some_value {

        Some(value) => println!("{}", value),

        None => println!("No value"),

    }


    match none_value {

        Some(value) => println!("{}", value),

        None => println!("No value"),

    }

}


Go no tiene un tipo de datos específico para manejar valores opcionales, pero utiliza la convención de retornar múltiples valores, incluyendo un valor y un `error`. Que la verdad no me gusta, te pasas preguntando todo el tiempo si hay error o si los valores son nulos. 


package main


import (

    "errors"

    "fmt"

)


func getValue() (string, error) {

    return "Hello, World!", nil

}


func getNullableValue() (string, error) {

    return "", errors.New("no value")

}


func main() {

    value, err := getValue()

    if err != nil {

        fmt.Println("Error:", err)

    } else {

        fmt.Println("Value:", value)

    }


    nullableValue, err := getNullableValue()

    if err != nil {

        fmt.Println("Error:", err)

    } else {

        fmt.Println("Value:", nullableValue)

    }

}


Python utiliza la palabra clave `None` para representar la ausencia de valor. Aunque no tiene una estructura específica como `Optional`, los desarrolladores pueden utilizar condicionales y manejo de excepciones.


def get_value():

    return "Hello, World!"


def get_nullable_value():

    return None


value = get_value()

nullable_value = get_nullable_value()


if value is not None:

    print(value)

else:

    print("Default Value")


if nullable_value is not None:

    print(nullable_value)

else:

    print("Default Value")


Ruby utiliza `nil` para representar la ausencia de valor. Al igual que en Python, no tiene una estructura específica para valores opcionales, pero proporciona métodos para manejar `nil`.


value = "Hello, World!"

nullable_value = nil


# Uso del operador ||

puts value || "Default Value"

puts nullable_value || "Default Value"


# Uso de condicionales

puts value.nil? ? "Default Value" : value

puts nullable_value.nil? ? "Default Value" : nullable_value


C++ utiliza punteros inteligentes (`smart pointers`) para gestionar la memoria y prevenir errores relacionados con punteros nulos. Los punteros inteligentes, como `std::unique_ptr` y `std::shared_ptr`, se encargan de la gestión automática de la memoria.


#include <iostream>

#include <memory>


int main() {

    std::unique_ptr<int> uniquePtr(new int(42));

    if (uniquePtr) {

        std::cout << *uniquePtr << std::endl;

    }


    std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);

    if (sharedPtr) {

        std::cout << *sharedPtr << std::endl;

    }


    // Uso de weak_ptr para evitar ciclos de referencia

    std::weak_ptr<int> weakPtr = sharedPtr;

    if (auto lockedPtr = weakPtr.lock()) {

        std::cout << *lockedPtr << std::endl;

    }


    return 0;

}


TypeScript, un superconjunto de JavaScript, permite tipos opcionales y tiene un soporte robusto para manejar valores `null` y `undefined`.


let nullableString: string | null = "Hello, World!";


// Uso del operador ? para llamadas seguras

console.log(nullableString?.length ?? 0);


// Uso de if para asegurar valores no nulos

if (nullableString !== null) {

    console.log(nullableString);

}


TypeScript utiliza tipos opcionales para manejar valores que pueden ser `null` o `undefined`, proporcionando un enfoque seguro para evitar errores comunes relacionados con valores nulos. El operador `?.` permite realizar llamadas seguras, y el operador `??` proporciona valores predeterminados en caso de valores `null` o `undefined`.

En fin, aunque la gestión de valores nulos varía entre lenguajes, la idea subyacente es la misma: proporcionar mecanismos más seguros y expresivos para manejar la ausencia de valores. Ya sea mediante clases contenedoras como `Optional` en Java y `Option` en Scala, tipos de datos como `Maybe` en Haskell, operadores específicos como `?` en Kotlin y C#, punteros inteligentes en C++, o enfoques específicos en Rust, Go, Python y Ruby, estos enfoques ayudan a reducir los errores y a escribir un código más robusto y mantenible.


miércoles, 3 de julio de 2024

Aprende a programar en Java desde cero con Java Magician


Si empiezan en el mundo java, les quiere recomendar este sitio que contiene muy buenos tutoriales en español. Los cuales están escritos de forma divertida.  


Dejo link: https://javamagician.com/

viernes, 21 de junio de 2024

Hola mundo en Vaadin


Vamos a hacer un hola mundo en Vaadin. Yo voy a dar por sobreentendido que sabemos crear un proyecto con spring. Si no lo sabes es tan fácil como ir a https://start.spring.io/ y de ahi vas configurando las cosas y agregamos el framework vaadin. 

Luego creamos un nuevo paquete como por ejemplo : com.example.vaadin.ui. Dentro del paquete, creamos una nueva clase MainView.java


package com.example.vaadin.ui;


import com.vaadin.flow.component.button.Button;

import com.vaadin.flow.component.notification.Notification;

import com.vaadin.flow.component.orderedlayout.VerticalLayout;

import com.vaadin.flow.router.Route;


@Route("")

public class MainView extends VerticalLayout {


    public MainView() {

        Button button = new Button("Haz clic aquí",

                event -> Notification.show("¡Hola, Mundo!"));

        add(button);

    }

}


Y listo!! Vamos a localhost:8080 para chequear nuestro "Hola mundo". 

martes, 18 de junio de 2024

Inyectar condicionalmente un bean con profiles


Tengo 2 beans que implementan una interfaz, por ejemplo un repositorio jpa con una base de datos y otro con archivos o en memoria, ponele... Y quiero algunas veces utilizar un bean y otras otro dependiendo de una configuración, esto se puede resolver de 2 maneras. Se puedes utilizar la anotación @Conditional o @Profile.

Veamos ejemplo utilizando profile: 


public interface MyService {

    void performService();

}


@Service

@Profile("serviceA")

public class ServiceA implements MyService {

    @Override

    public void performService() {

        System.out.println("Service A implementation");

    }

}


@Service

@Profile("serviceB")

public class ServiceB implements MyService {

    @Override

    public void performService() {

        System.out.println("Service B implementation");

    }

}


Tenemos que configurar el perfil activo en application.properties:


# Para usar ServiceA
spring.profiles.active=serviceA

# Para usar ServiceB
#spring.profiles.active=serviceB


Y inyectamos el bean:


@RestController
public class MyController {

    private final MyService myService;

    @Autowired
    public MyController(MyService myService) {
        this.myService = myService;
    }

    @GetMapping("/perform")
    public String perform() {
        myService.performService();
        return "Service performed";
    }
}

Y listo!!

lunes, 20 de mayo de 2024

Lenguajes utilizados en los proyectos apache

Tal vez hace mucho que Apache publico este gráfico pero yo recién lo veo : 



Como se puede ver Java es el lenguaje más utilizado por los proyectos de apache, seguido de python, c++, c, javascript, scala, C#, go, perl, etc ... 




miércoles, 8 de mayo de 2024

Que es la Covarianza, Contravarianza y Invarianza?


Ya sé que estos son temas super basicos pero siempre que hablan de eso me los confundo. Por eso voy a hacer este post para ver si porfin puedo fijar las ideas. 

La varianza y la covarianza son conceptos importantes en el contexto de los tipos genérico. Estos conceptos se refieren a cómo se relacionan los tipos genéricos cuando se consideran subtipos y super tipos. Veamos una explicación de cada uno:

Covarianza: La covarianza se refiere a la relación entre tipos genéricos donde la relación de subtipos se mantiene en la misma dirección que la relación de subtipos de los parámetros de tipo. En otras palabras, si tenemos un tipo T y otro tipo U donde U es un subtipo de T, entonces podemos decir que List<U> es un subtipo de List<T>. Esto significa que podemos asignar una lista de subtipos a una lista de supertipos sin necesidad de conversión explícita. La covarianza se utiliza típicamente en situaciones donde solo se lee de una estructura de datos, como en secuencias o enumeraciones.

Veamos un ejemplo en C#: 

using System;

using System.Collections.Generic;


class Program

{

    static void Main()

    {

        // Covarianza en IEnumerable<T>

        IEnumerable<string> strings = new List<string> { "hello", "world" };

        PrintItems(strings);

    }


    static void PrintItems(IEnumerable<object> items)

    {

        foreach (var item in items)

        {

            Console.WriteLine(item);

        }

    }

}

Veamos un ejemplo en Java: 

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Covarianza en List<? extends Number>
        List<Integer> integers = new ArrayList<>();
        integers.add(1);
        integers.add(2);
        printNumbers(integers);
    }

    static void printNumbers(List<? extends Number> numbers) {
        for (Number number : numbers) {
            System.out.println(number);
        }
    }
}

Y en scala: 

class Main {
  def main(args: Array[String]): Unit = {
    // Covarianza en List[+A]
    val strings: List[String] = List("hello", "world")
    printItems(strings)
  }

  def printItems(items: List[Any]): Unit = {
    items.foreach(println)
  }
}

Contravarianza: La contravarianza es el opuesto de la covarianza. Se refiere a la relación entre tipos genéricos donde la relación de subtipos se invierte en comparación con la relación de subtipos de los parámetros de tipo. En otras palabras, si tenemos un tipo T y otro tipo U donde U es un subtipo de T, entonces podemos decir que Action<T> es un subtipo de Action<U>. Esto significa que podemos asignar una función que acepta supertipos a una variable de función que acepta subtipos. La contravarianza se utiliza típicamente en situaciones donde solo se escribe en una estructura de datos, como en consumidores de datos o comparadores.

Veamos un ejemplo en Java: 


import java.util.function.Consumer;


public class Main {

    public static void main(String[] args) {

        // Contravarianza en Consumer<? super String>

        Consumer<Object> printString = Main::printString;

        printString.accept("hello");

    }


    static void printString(Object s) {

        System.out.println(s);

    }

}


Y en Scala: 

class Main {
  def main(args: Array[String]): Unit = {
    // Contravarianza en Function1[-T, +R]
    val printString: Any => Unit = Main.printString
    printString("hello")
  }
}

object Main {
  def printString(s: String): Unit = {
    println(s)
  }
}

Invarianza: La invarianza es el tercer escenario, donde no hay una relación de subtipos entre tipos genéricos. En otras palabras, List<T> no es ni un subtipo ni un supertipo de List<U> si T y U son tipos diferentes, incluso si U es un subtipo de T o viceversa. En este caso, necesitamos una conversión explícita para asignar entre los dos tipos.

En C#, la covarianza y la contravarianza se expresan a través de modificadores como out y in en declaraciones de tipo genérico. En Java, estos conceptos se implementan a través de la notación <? extends T> para la covarianza y <? super T> para la contravarianza en tipos genéricos. Es importante entender estos conceptos para escribir código genérico seguro y comprensible.

No recuerdo haber utilizado la contravarianza pero no se me hace algo tan común. Y ustedes, han utilizado estas técnicas? 


sábado, 3 de febrero de 2024

gRPC Client


De manera similar al lado del servidor, podemos generar el código del lado del cliente utilizando la definición del servicio. El código del cliente proporciona los mismos métodos que el servidor, que su código de cliente puede invocar; el código del cliente los traduce en llamadas de red de invocación de funciones remotas que van al lado del servidor. Dado que las definiciones de servicios gRPC son independientes del lenguaje, puede generar clientes y servidores para cualquier lenguaje admitido (a través de implementaciones de terceros) de su elección. 

Entonces, veamos un ejemplo en Java. A pesar del lenguaje de programación que utilizamos, los pasos simples involucrados en una implementación del lado del cliente implican configurar una conexión con el servidor remoto, adjuntar el código auxiliar del cliente a esa conexión e invocar el método remoto usando el código auxiliar del cliente.


ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)

.usePlaintext(true)

 .build(); 


// Initialize blocking stub using the channel 

ProductInfoGrpc.ProductInfoBlockingStub stub = ProductInfoGrpc.newBlockingStub(channel); 


// Call remote method using the blocking stub 

StringValue productID = stub.addProduct( 

     Product.newBuilder() 

            .setName("Apple iPhone 11") 

            .setDescription("Meet Apple iPhone 11." + "All-new dual-camera system with " + "Ultra Wide and Night mode.") 

   .build());



lunes, 21 de agosto de 2023

Inscripciones Abiertas 2023 a cursos Gugler!!

 

PRENSA GUGLER  LAB 2023

  NOTICIAS ACTUALES

informacion  OFERTA ACADÉMICA


Estimado :

 Se encuentran abiertas las inscripciones para el segundo cuatrimestre del año 2023, para todas las capacitaciones dictadas por el Laboratorio de Investigación Gugler. Podés asegurar tu lugar en el curso y comisión que desees !!!.

Las clases inician:

  • Martes 12/09 , Miércoles 13/09, Jueves 14/09 o Sábado 16/09, según el curso que elegiste.

 

Inscribirteclic aquí

Cursos, Horarios y comisionesclic aquí.

 

 

Dictamos nuestros cursos en la Facultad de Ciencia y Tecnología, perteneciente a la Universidad Autónoma de Entre Ríos. En nuestro portafolio de capacitación encontrarás:

MODALIDAD PRESENCIAL

  • Cursos de Programación:
    Programación en PHP.
     
  • Cursos de Mantenimiento/Reparación:
    Reparación y Mantenimiento de PC.

 MODALIDAD DISTANCIA

  • Cursos de Programación:
    Programación Web Frontend.
    Programación en Java.
informacion   MÁS INFORMACIÓN 

informacion LABORATORIO DE INVESTIGACIÓN GUGLER

Si deseas comunicarte con nosotros, te recordamos que podes hacerlo a través de los siguientes portales, en los cuales encontrarás información sobre Gugler.

TEL: (0343) - 4975066 Interno 119

Sitio Oficial:  www.gugler.com.ar

Campus:  campusvirtual.gugler.com.ar

Sistema de Gestión Cursos:  sgc.gugler.com.ar

Sistema de Gestión Cursos Móvil: Aquí 

Sistema de Documentación:  sgd.gugler.com.ar

Sistema de Validación:  giua.gugler.com.ar

                   Twitter                Facebook

Laboratorio Gugler

partir del 2012, la Facultad de Ciencia y Tecnología nos declaro:  "Laboratorio de Investigación".

 

El laboratorio ha ampliado las incumbencias de Gugler, ya que además de la capacitación, la promoción y difusión del software libre, ahora abarcará actividades como publicaciones y proyectos de investigación, así como también el dictado y participación en conferencias o exposiciones de ámbitos académicos y científicos.

 

Ante cualquier duda comunicarse con nosotros a gugler_contacto@uader.edu.ar

GUGLER PRESS

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

    }

}

lunes, 22 de mayo de 2023

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. 


 

jueves, 11 de mayo de 2023

String Interpolation en Java


La interpolación de cadenas es una forma sencilla y precisa de inyectar valores variables en una cadena. Permite a los usuarios incrustar referencias de variables directamente en literales de cadena procesados. Java carece de soporte nativo para la interpolación de cadenas en comparación con lenguajes como Scala.

Sin embargo, existen algunos enfoques para lograr este comportamiento en Java.

Primero, tenemos el operador "+". Podemos usar el operador "+" para concatenar nuestras variables y valores de cadena. La variable se reemplaza por su valor, por lo que logramos la interpolación o concatenación de cadenas:


@Test

public void givenTwoString_thenInterpolateWithPlusSign() {

    String EXPECTED_STRING = "String Interpolation in Java with some Java examples.";

    String first = "Interpolation";

    String second = "Java";

    String result = "String " + first + " in " + second + " with some " + second + " examples.";

    assertEquals(EXPECTED_STRING, result);

}


Como podemos ver en el ejemplo anterior, con este operador el String resultante contiene los valores de las variables “interpolados” con otros valores de string. Dado que puede ajustarse para satisfacer necesidades específicas, este método de concatenación de cadenas se encuentra entre los más sencillos y valiosos. Cuando usamos el operador, no necesitamos poner el texto entre comillas.

Otro enfoque es usar el método format() de la clase String. Al contrario del operador "+", en este caso necesitamos usar marcadores de posición para obtener el resultado esperado en la interpolación de cadenas:

@Test

public void givenTwoString_thenInterpolateWithFormat() {

    String EXPECTED_STRING = "String Interpolation in Java with some Java examples.";

    String first = "Interpolation";

    String second = "Java";

    String result = String.format("String %s in %s with some %s examples.", first, second, second);

    assertEquals(EXPECTED_STRING, result);

}

Además, podemos hacer referencia a un argumento específico si queremos evitar repeticiones de variables en nuestra llamada de formato:

@Test

public void givenTwoString_thenInterpolateWithFormatStringReference() {

    String EXPECTED_STRING = "String Interpolation in Java with some Java examples.";

    String first = "Interpolation";

    String second = "Java";

    String result = String.format("String %1$s in %2$s with some %2$s examples.", first, second);

    assertEquals(EXPECTED_STRING, result);

}

Nuestro siguiente enfoque es la clase StringBuilder. Instanciamos un objeto StringBuilder y luego llamamos a la función append() para construir el String. En el proceso, nuestras variables se agregan a la cadena resultante:


@Test

public void givenTwoString_thenInterpolateWithStringBuilder() {

    String EXPECTED_STRING = "String Interpolation in Java with some Java examples.";

    String first = "Interpolation";

    String second = "Java";

    StringBuilder builder = new StringBuilder();

    builder.append("String ")

      .append(first)

      .append(" in ")

      .append(second)

      .append(" with some ")

      .append(second)

      .append(" examples.");

    String result = builder.toString();

    assertEquals(EXPECTED_STRING, result);

}


Como podemos ver en el ejemplo de código anterior, podemos interpolar las cadenas con el texto necesario encadenando la función de agregar, que acepta el parámetro como una variable (en este caso, dos cadenas).

El uso de la clase MessageFormat es un método menos conocido para obtener la interpolación de cadenas en Java. Con MessageFormat, podemos crear mensajes concatenados sin preocuparnos por el idioma subyacente. Es un método estándar para crear mensajes orientados al usuario. Toma una colección de objetos, formatea las cadenas contenidas dentro y las inserta en el patrón en las ubicaciones adecuadas.

El método de formato de MessageFormat es casi idéntico al método de formato de String, excepto por cómo se escriben los marcadores de posición. Índices como {0}, {1}, {2}, etc., representan el marcador de posición en esta función:


@Test

public void givenTwoString_thenInterpolateWithMessageFormat() {

    String EXPECTED_STRING = "String Interpolation in Java with some Java examples.";

    String first = "Interpolation";

    String second = "Java";

    String result = MessageFormat.format("String {0} in {1} with some {1} examples.", first, second);

    assertEquals(EXPECTED_STRING, result);

}

En cuanto al rendimiento, StringBuilder solo agrega texto a un búfer dinámico; sin embargo, MessageFormat analiza el formato dado antes de agregar los datos. Como resultado, StringBuilder supera a MessageFormat en términos de eficiencia.

Finalmente, tenemos StringSubstitutor de Apache Commons. En el contexto de esta clase, los valores se sustituyen por variables incluidas dentro de una Cadena. Esta clase toma un fragmento de texto y reemplaza todas las variables. La definición predeterminada de una variable es ${variableName}. Se pueden usar constructores y métodos de conjuntos para modificar el prefijo y los sufijos. La resolución de los valores de las variables normalmente implica el uso de un mapa. Sin embargo, podemos resolverlos utilizando los atributos del sistema o proporcionando un solucionador de variables especializado:

@Test

public void givenTwoString_thenInterpolateWithStringSubstitutor() {

    String EXPECTED_STRING = "String Interpolation in Java with some Java examples.";

    String baseString = "String ${first} in ${second} with some ${second} examples.";

    String first = "Interpolation";

    String second = "Java";

    Map<String, String> parameters = new HashMap<>();

    parameters.put("first", first);

    parameters.put("second", second);

    StringSubstitutor substitutor = new StringSubstitutor(parameters);

    String result = substitutor.replace(baseString);

    assertEquals(EXPECTED_STRING, result);

}

De nuestro ejemplo de código, podemos ver que creamos un Map. Los nombres de las claves son los mismos que los nombres de las variables que reemplazaremos en el String. Luego pondremos el valor correspondiente para cada clave en el Map. A continuación, lo pasaremos como argumento constructor a la clase StringSubstitutor. Finalmente, el objeto instanciado llama a la función replace(). Esta función recibe como argumento el texto con los marcadores de posición. Como resultado, obtenemos un texto interpolado.

Como vemos no es tarea fácil interpolar strings en java actualmente, pero tenemos la esperanza que en java 21 vendrá string template. Esta funcionalidad nos permitirá hacer lo siguiente : 

String name = "Joan";

String info = STR."My name is \{name}";

assert info.equals("My name is Joan");   // true

Otro ejemplo puede ser : 

// Embedded expressions can be strings

String firstName = "Bill";

String lastName  = "Duck";

String fullName  = STR."\{firstName} \{lastName}"; // "Bill Duck"

String sortName  = STR."\{lastName}, \{firstName}"; // "Duck, Bill"

// Embedded expressions can perform arithmetic

int x = 10, y = 20;

String s = STR."\{x} + \{y} = \{x + y}"; // "10 + 20 = 30"

Que opinan?


Dejo link: https://openjdk.org/jeps/430