Translate

miércoles, 1 de julio de 2026

Informe M-Trends 2026: los nuevos métodos de los atacantes

 

Programación funcional en PHP


La programación funcional no es exclusiva de lenguajes como Haskell, Elixir o Scala. PHP también permite adoptar muchos de sus principios para escribir código más limpio, expresivo y fácil de mantener.

Veamos algunas de las herramientas que ofrece PHP.


Funciones como ciudadanos de primera clase

Las funciones pueden almacenarse en variables, pasarse como argumentos y devolverse desde otras funciones.


<?php

$greet = fn(string $name) => "Hello $name!";

echo $greet("Emanuel");


Salida:

Hello Emanuel!


Arrow Functions

Introducidas en PHP 7.4, las Arrow Functions permiten escribir funciones pequeñas de manera mucho más concisa.


$numbers = [1, 2, 3, 4];


$squares = array_map(

    fn($n) => $n * $n,

    $numbers

);


print_r($squares);


Resultado:

Array

(

    [0] => 1

    [1] => 4

    [2] => 9

    [3] => 16

)


map()


Aunque PHP no posee un método map() sobre los arrays, dispone de array_map().


$names = ["john", "mary", "alice"];


$upper = array_map(

    "strtoupper",

    $names

);


print_r($upper);


Resultado:

JOHN

MARY

ALICE


filter()

Para filtrar colecciones se utiliza `array_filter()`.


$numbers = [1,2,3,4,5,6];


$even = array_filter(

    $numbers,

    fn($n) => $n % 2 === 0

);


print_r($even);


Resultado:

2

4

6


reduce()

array_reduce() permite reducir una colección a un único valor.


$numbers = [1,2,3,4,5];


$sum = array_reduce(

    $numbers,

    fn($acc, $n) => $acc + $n,

    0

);


echo $sum;


Resultado

15


Funciones puras

Una función pura siempre devuelve el mismo resultado para la misma entrada y no produce efectos secundarios.


function add(int $a, int $b): int

{

    return $a + $b;

}



En cambio, esta función no es pura:


$total = 0;


function addToTotal(int $n): void

{

    global $total;

    $total += $n;

}


Inmutabilidad

PHP no posee estructuras inmutables por defecto, pero es posible trabajar evitando modificar los datos originales.

$numbers = [1,2,3];


$newNumbers = [...$numbers, 4];


print_r($numbers);

print_r($newNumbers);


Resultado:

Original:

1 2 3

Nuevo:

1 2 3 4


Composición de funciones


Podemos construir funciones más complejas combinando funciones pequeñas.


$trim = fn($s) => trim($s);

$upper = fn($s) => strtoupper($s);


$normalize = fn($s) => $upper($trim($s));


echo $normalize("   hello   ");

Resultado

HELLO


Closures

Las closures permiten capturar variables del contexto.


function multiplier(int $factor)

{

    return fn($n) => $n * $factor;

}


$double = multiplier(2);

$triple = multiplier(3);


echo $double(10);

echo $triple(10);


Resultado

20

30


Encadenando operaciones


Una secuencia típica de programación funcional consiste en filtrar, transformar y reducir datos.


$numbers = [1,2,3,4,5,6];


$result = array_reduce(

    array_map(

        fn($n) => $n * $n,

        array_filter(

            $numbers,

            fn($n) => $n % 2 === 0

        )

    ),

    fn($acc, $n) => $acc + $n,

    0

);


echo $result;


Proceso:

  • Filtra los números pares.
  • Calcula el cuadrado de cada uno.
  • Suma todos los cuadrados.


Resultado:

56


Aunque PHP sigue siendo un lenguaje predominantemente orientado a objetos, incorpora suficientes herramientas para adoptar un estilo funcional cuando resulta conveniente. El uso de funciones puras, array_map(), array_filter(), array_reduce(), closures y Arrow Functions permite escribir código más declarativo, reutilizable y sencillo de razonar.

No reemplaza a lenguajes puramente funcionales como Haskell o Elixir, pero demuestra que la programación funcional es un paradigma que puede aplicarse con éxito en prácticamente cualquier lenguaje moderno.


jueves, 25 de junio de 2026

@MockitoSpyBean en Spring Framework


Si trabajás con pruebas en Spring Boot, seguramente utilizaste @SpyBean para observar el comportamiento de un bean real y verificar sus interacciones.

Sin embargo, a partir de las versiones más recientes de Spring, @SpyBean fue reemplazado por @MockitoSpyBean, una nueva anotación que forma parte del soporte oficial de Mockito en Spring Framework.

¿Por qué aparece @MockitoSpyBean?

Durante años Spring Boot ofreció:
  • @MockBean
  • @SpyBean

para integrar Mockito con el contexto de Spring.

Con la evolución del framework, estas anotaciones fueron reemplazadas por:
  • @MockitoBean
  • @MockitoSpyBean

que proporcionan una integración más consistente y alineada con Mockito.

Un Spy es una instancia real envuelta por Mockito.

Esto significa que:
  • Los métodos reales se ejecutan normalmente.
  • Podemos verificar invocaciones.
  • Podemos modificar comportamientos específicos cuando sea necesario.

Por eso un Spy resulta ideal cuando queremos observar el comportamiento de un bean sin reemplazarlo completamente.

Supongamos el siguiente servicio:


@Service
public class EmailService {

    public void send(String to, String message) {
        System.out.println("Sending email to " + to);
    }
}


Y un servicio que lo utiliza:

@Service
public class UserService {

    private final EmailService emailService;

    public UserService(EmailService emailService) {
        this.emailService = emailService;
    }

    public void register(String email) {
        emailService.send(email, "Welcome!");
    }
}

Utilizando @MockitoSpyBean


@SpringBootTest
class UserServiceTest {

    @Autowired
    private UserService userService;

    @MockitoSpyBean
    private EmailService emailService;

    @Test
    void shouldSendWelcomeEmail() {

        userService.register("john@example.com");

        verify(emailService)
                .send("john@example.com", "Welcome!");
    }
}

La prueba utiliza el bean real de Spring, pero Mockito registra las interacciones permitiéndonos verificar llamadas y argumentos.

Aunque el Spy ejecuta la implementación real, podemos modificar métodos específicos:

doNothing()
    .when(emailService)
    .send(anyString(), anyString());


De esta manera evitamos ejecutar la lógica real mientras seguimos verificando las invocaciones.

En la mayoría de los proyectos Spring Boot basta con incluir:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Si usas gradle:

testImplementation 'org.springframework.boot:spring-boot-starter-test'

Esta dependencia incorpora Mockito y las utilidades de testing de Spring.

@MockitoBean vs @MockitoSpyBean ¿Cuándo conviene usarlo?

@MockitoSpyBean resulta útil cuando:
  • Queremos verificar interacciones sobre un bean real.
  • Necesitamos conservar parte de la lógica original.
  • Queremos evitar crear mocks complejos.
  • Estamos realizando pruebas de integración con Spring.

Los Spies son poderosos, pero también pueden volver las pruebas más sensibles a cambios internos de implementación.

Como regla general:
  • Utilizá @MockitoBean para aislar dependencias.
  • Utilizá @MockitoSpyBean cuando necesites observar el comportamiento real de un bean.

@MockitoSpyBean representa la evolución natural de @SpyBean y es la alternativa recomendada en las versiones actuales de Spring.

Permite combinar la ejecución real de un componente con todas las capacidades de verificación de Mockito, logrando pruebas más expresivas y fáciles de diagnosticar cuando algo falla.

Y si no entienden la imagen es Mr Bean de espía ;) 

martes, 23 de junio de 2026

Bases de Datos Vectoriales: la memoria de las aplicaciones de IA


Cuando hablamos de Inteligencia Artificial y modelos de lenguaje (LLMs), solemos pensar en el modelo en sí. Sin embargo, gran parte de las aplicaciones modernas de IA dependen de otro componente fundamental: las bases de datos vectoriales.

¿Por qué son necesarias?

Los LLMs poseen conocimiento general, pero no conocen nuestros documentos, manuales, correos o información específica de una empresa.

Si queremos construir un chatbot que responda preguntas sobre nuestra propia información, necesitamos una forma eficiente de almacenar y buscar ese conocimiento.

Aquí es donde entran las bases de datos vectoriales.


¿Qué es un vector?

Un vector es una representación numérica de un texto, una imagen o cualquier otro dato.

Por ejemplo:

"Java es un lenguaje orientado a objetos"

[0.24, -0.81, 0.53, 0.17, ...]


Este proceso se realiza mediante un modelo de embeddings.

La idea es que textos con significados similares tendrán vectores cercanos entre sí.


Por ejemplo:

"¿Cómo crear una API REST con Spring?"

"Tutorial de Spring Boot para servicios REST"


Sus vectores estarán próximos en el espacio vectorial, aunque las frases sean distintas.


¿Cómo funciona una búsqueda vectorial?

Supongamos que almacenamos miles de documentos.

Cuando el usuario realiza una pregunta:

¿Cómo conectarse a una base de datos con Spring Boot?


1. La pregunta se convierte en un vector.

2. Se buscan los vectores más cercanos.

3. Se recuperan los documentos más relevantes.

4. Esa información se envía al LLM.

5. El modelo genera una respuesta basada en esos documentos.


Este patrón se conoce como RAG (Retrieval-Augmented Generation).


Usuario

   ↓

Pregunta

   ↓

Embedding

   ↓

Base de datos vectorial

   ↓

Documentos relevantes

   ↓

LLM

   ↓

Respuesta


¿Cómo se mide la similitud?

Las bases de datos vectoriales utilizan diferentes métricas:


Similitud del coseno

Mide qué tan parecidos son dos vectores según el ángulo entre ellos.


Coseno = 1

→ Muy similares


Coseno = 0

→ Sin relación


Coseno = -1

→ Opuestos


Es una de las métricas más utilizadas en NLP.


Distancia Euclidiana

Mide la distancia entre dos puntos.


Menor distancia

→ Mayor similitud


Producto escalar (Dot Product)

Evalúa la relación entre vectores considerando tanto dirección como magnitud.


¿Por qué no usar una base de datos relacional?

En una base de datos tradicional podríamos hacer:

SELECT *

FROM documentos

WHERE contenido LIKE '%Spring%'


Pero esto presenta limitaciones:

  • Solo encuentra coincidencias literales.
  • No comprende el significado.
  • No detecta sinónimos.
  • Escala mal con millones de embeddings.


Una búsqueda vectorial permite consultas semánticas:

Pregunta:

¿Cómo consumir una API REST?


Documento encontrado:

Guía para invocar servicios web HTTP con Spring.

Aunque las palabras sean distintas, el significado es similar.


Comparar un vector con millones de otros puede ser costoso.

Por ello, las bases de datos vectoriales utilizan algoritmos como:

  • HNSW (Hierarchical Navigable Small World)
  • IVF (Inverted File Index)
  • PQ (Product Quantization)


Estos algoritmos permiten encontrar los vecinos más cercanos de manera eficiente sin tener que recorrer todos los vectores.


Bases de datos vectoriales populares

Pinecone

Servicio administrado diseñado específicamente para IA.

Características:

  • Escalabilidad automática.
  • API sencilla.
  • Alta disponibilidad.


Milvus

Proyecto open source muy popular.

Características:

  • Alto rendimiento.
  • Soporte para miles de millones de vectores.
  • Compatible con GPU.


Qdrant

Base de datos open source escrita en Rust.

Características:

  • Excelente rendimiento.
  • Filtros avanzados.
  • API REST y gRPC.


Weaviate

Incluye capacidades de búsqueda híbrida y soporte para GraphQL.


Chroma

Muy utilizada en proyectos pequeños y prototipos.


PostgreSQL + pgvector

Permite agregar capacidades vectoriales a PostgreSQL.

Ideal cuando ya se utiliza PostgreSQL y no se necesita una solución especializada.


¿Siempre necesito una base de datos vectorial?

No necesariamente.


Para muchos proyectos pequeños, PostgreSQL con la extensión pgvector puede ser suficiente.

Una base vectorial especializada comienza a mostrar ventajas cuando:

  • Existen millones de embeddings.
  • Se requieren búsquedas muy rápidas.
  • Hay múltiples usuarios concurrentes.
  • Se necesita alta escalabilidad.


Las bases de datos vectoriales se han convertido en uno de los componentes fundamentales de las aplicaciones modernas de IA.

Su objetivo no es reemplazar a las bases de datos tradicionales, sino complementar a los LLMs proporcionando una forma eficiente de almacenar y recuperar información basada en significado y no únicamente en palabras exactas.

En otras palabras, si los LLMs son el cerebro de una aplicación de IA, las bases de datos vectoriales actúan como su memoria externa, permitiendo implementar asistentes, chatbots y sistemas RAG capaces de trabajar con conocimiento actualizado y específico.

lunes, 22 de junio de 2026

ANTLR: El primer paso para construir un lenguaje de programación


Cuando usamos Java, Kotlin o Scala, normalmente escribimos código sin pensar demasiado en lo que ocurre detrás de escena.


int result = 10 + 20;


Sin embargo, antes de que la JVM pueda ejecutar ese programa, alguien tuvo que diseñar un compilador capaz de entender ese texto y transformarlo en bytecode.

En esta serie vamos a construir nuestro propio lenguaje para la JVM. No pretendemos competir con Kotlin o Scala, pero sí recorrer muchos de los mismos conceptos que utilizan estos lenguajes internamente.


Y el primer paso es aprender una herramienta fundamental: ANTLR.


¿Qué es ANTLR?

ANTLR (ANother Tool for Language Recognition) es una herramienta que permite generar analizadores léxicos y sintácticos a partir de una gramática.

En lugar de escribir manualmente todo el código necesario para interpretar un lenguaje, simplemente describimos las reglas del mismo y ANTLR genera automáticamente gran parte de la infraestructura necesaria.


Por ejemplo, podemos definir una regla como:


expression

    : expression '+' expression

    | NUMBER

    ;


A partir de ella ANTLR podrá reconocer expresiones como:

1 + 2 + 3


Un compilador suele dividirse en varias etapas:


Código Fuente

      │

      ▼

 Lexer

      │

      ▼

 Tokens

      │

      ▼

 Parser

      │

      ▼

 AST

      │

      ▼

 Análisis Semántico

      │

      ▼

 Generación de Código


ANTLR participa principalmente en las dos primeras:

  • Lexer
  • Parser


¿Qué hace el Lexer?

El Lexer toma una secuencia de caracteres y la divide en tokens.


Por ejemplo:

let age = 42


se transforma en:

LET

IDENTIFIER(age)

ASSIGN

NUMBER(42)


El Lexer no entiende el significado del programa. Solo identifica patrones.


¿Qué hace el Parser?

El Parser toma los tokens generados por el Lexer y verifica que respeten las reglas definidas por la gramática.


Por ejemplo:

LET IDENTIFIER ASSIGN NUMBER


podría convertirse en:

Assignment

 ├── Variable(age)

 └── Number(42)


Esta estructura representa la forma del programa y servirá como base para las siguientes etapas.


¿Qué es una gramática?

Una gramática define qué construcciones son válidas dentro de un lenguaje.


Por ejemplo:

variableDeclaration

    : 'let' IDENTIFIER '=' expression

    ;


Esta regla indica que una declaración válida debe contener:

  • La palabra reservada let
  • Un identificador
  • El símbolo =
  • Una expresión


¿ANTLR genera compiladores?

No.


ANTLR resuelve principalmente:

  • Análisis léxico
  • Parsing


Todavía debemos implementar:

  • Árboles sintácticos abstractos (AST)
  • Tablas de símbolos
  • Sistema de tipos
  • Generación de bytecode JVM


Por eso ANTLR es una herramienta muy importante, pero representa solo una parte del compilador completo.


viernes, 19 de junio de 2026

Java 26: LazyConstant


Una de las novedades de Java 26 es LazyConstant, una API experimental que permite definir constantes inicializadas bajo demanda (*lazy initialization*) de forma segura y eficiente.


Supongamos que tenemos un objeto costoso de crear:


public class Config {

    private static final ExpensiveObject INSTANCE =

            new ExpensiveObject();

}


El objeto se crea cuando la clase es cargada, aunque nunca llegue a utilizarse.

Una solución clásica es utilizar el patrón Initialization-on-demand holder:


public class Config {


    private static class Holder {

        static final ExpensiveObject INSTANCE =

                new ExpensiveObject();

    }


    public static ExpensiveObject instance() {

        return Holder.INSTANCE;

    }

}


Funciona, pero es un patrón poco evidente y algo verboso.

Con Java 26 podemos declarar un valor que será calculado únicamente la primera vez que se necesite:


private static final LazyConstant<ExpensiveObject> INSTANCE =

        LazyConstant.of(ExpensiveObject::new);


y obtenerlo mediante:


ExpensiveObject obj = INSTANCE.get();


La instancia se crea una sola vez y es segura para múltiples hilos.


Veamos un ejemplo:


class DatabaseConnection {


    private static final LazyConstant<ConnectionPool> POOL =

            LazyConstant.of(ConnectionPool::new);


    static ConnectionPool pool() {

        return POOL.get();

    }

}


La conexión se abrirá únicamente cuando alguien invoque:


DatabaseConnection.pool();


¿Qué ventajas tiene?

  • Inicialización diferida (lazy initialization).
  • Thread-safe.
  • Evita implementar patrones complejos.
  • Más legible que usar synchronized, volatile o double-checked locking.
  • Puede reemplazar muchos usos de AtomicReference.


LazyConstant está pensado para recursos costosos o poco utilizados, donde tiene sentido retrasar su creación.

Durante años, Java obligó a utilizar patrones como el Holder idiom o double-checked locking para conseguir inicialización perezosa segura. Java 26 introduce LazyConstant para convertir ese patrón en una característica del lenguaje y la biblioteca estándar, haciendo el código más simple, legible y menos propenso a errores.


miércoles, 17 de junio de 2026

¿Por qué Clojure compila diferente? Ventajas y desventajas de ser un "Hosted Language" en la JVM


Cuando pensamos en lenguajes para la JVM, solemos meter en la misma bolsa a Java, Scala, Kotlin y Clojure. Después de todo, todos terminan ejecutándose sobre la misma máquina virtual.


Pero hay una diferencia fundamental en cómo están construidos.


Scala y Kotlin generan bytecode JVM de manera bastante directa. Clojure, en cambio, adopta una filosofía distinta: es un hosted language, es decir, un lenguaje "hospedado" sobre la plataforma Java.


¿Y qué significa eso? ¿Tiene ventajas? ¿Tiene costos?

Lenguajes como Java, Scala o Kotlin siguen aproximadamente este esquema:


Código fuente

      ↓

Compilador

      ↓

Bytecode JVM (.class)

      ↓

JVM


Muchas características del lenguaje se traducen directamente en clases, métodos y bytecode especializado.


Por ejemplo, una case class de Scala:

case class Person(name: String)

genera automáticamente métodos como:

  • equals()
  • hashCode()
  • copy()
  • toString()


Todo esto queda representado directamente en bytecode.


Clojure también produce bytecode JVM, pero gran parte del comportamiento del lenguaje vive en su runtime:


Código Clojure

      ↓

Compilador

      ↓

Bytecode + Runtime de Clojure

      ↓

JVM


Por eso Rich Hickey describe a Clojure como un hosted language.


La plataforma Java no es solamente un destino de compilación: es el ecosistema sobre el cual está construido el lenguaje.


Ventaja: Aprovecha toda la plataforma Java


Desde Clojure podemos usar directamente cualquier clase Java:


(import java.time.LocalDate)

(LocalDate/now)


No hay puentes especiales ni adaptadores.

Clojure reutiliza:

  • la JVM;
  • el recolector de basura;
  • las bibliotecas Java;
  • los hilos;
  • las excepciones;
  • las herramientas de profiling;
  • todo el ecosistema existente.


En vez de reinventar la rueda, se apoya en ella.


Ventaja: Un compilador relativamente pequeño


Muchas características importantes de Clojure viven en bibliotecas y no en el compilador:

  • secuencias perezosas;
  • colecciones persistentes;
  • STM;
  • transducers.


Esto hace que el compilador sea considerablemente más sencillo que los de Scala o Kotlin.


Ventaja: Desarrollo interactivo y REPL


Una de las mayores fortalezas de Clojure es su modelo de desarrollo interactivo.

Podemos definir una función:


(defn cuadrado [x]

  (* x x))


y cargarla inmediatamente en la REPL, sin recompilar todo el proyecto.


Este enfoque favorece:

  • feedback rápido;
  • hot reloading;
  • experimentación;
  • desarrollo incremental.


Ventaja: Evolución mediante librerías


Muchas características avanzadas de Clojure se agregaron sin modificar demasiado el lenguaje:

  • transducers;
  • core.async;
  • spec;
  • STM.


Al depender más del runtime y menos del compilador, la evolución del ecosistema suele ser más flexible.


Las desventajas

Menos optimizaciones

Scala y Kotlin generan bytecode más especializado.

En Clojure, muchas operaciones pasan por componentes del runtime como:

  • IFn;
  • PersistentVector;
  • PersistentMap;
  • RT.


Esto introduce niveles adicionales de indirección y, en ciertos casos, puede afectar el rendimiento.


Más reflexión

Si escribimos:

(defn largo [s]

  (.length s))


el compilador puede recurrir a reflexión.

Podemos ayudarlo con type hints:


(defn largo [^String s]

  (.length s))


pero esto requiere intervención del programador.


Menos comprobaciones en tiempo de compilación


Scala y Kotlin verifican muchas cosas antes de ejecutar:

  • tipos;
  • nulabilidad;
  • exhaustividad del pattern matching;
  • restricciones genéricas.


Clojure, al ser dinámico, detecta muchos errores recién en tiempo de ejecución.


Interoperabilidad asimétrica

Desde Clojure usar Java es muy fácil.

Pero desde Java consumir código Clojure no es tan natural:


IFn plus = Clojure.var("myns", "plus");


Mientras que una clase escrita en Scala o Kotlin suele verse como una clase Java convencional.


Dos filosofías distintas

No es que una aproximación sea mejor que la otra.

Scala y Kotlin intentan enriquecer la JVM mediante compiladores sofisticados y bytecode especializado.

Clojure adopta otra filosofía: construir un lenguaje pequeño y dinámico que reutilice al máximo la plataforma existente.

Al final, la diferencia más interesante no es a qué compilan, porque todos terminan ejecutándose en la JVM.

La verdadera diferencia es: ¿Cuánto del lenguaje vive en el compilador y cuánto vive en el runtime?

Y en esa decisión de diseño está gran parte de la personalidad de Clojure.


martes, 16 de junio de 2026

¿Cuáles son las tendencias en la infraestructura de IA?

 

lunes, 15 de junio de 2026

JDBI en Java: acceso a datos simple


Cuando hablamos de acceso a bases de datos en Java, solemos pensar en JDBC puro o en frameworks pesados como Hibernate.

Pero existe una opción intermedia muy interesante: Jdbi.


JDBI busca mantener la simplicidad y el control de SQL, pero eliminando gran parte del código repetitivo de JDBC.


¿Qué es JDBI?

JDBI es una librería que se construye sobre JDBC y agrega:

  • Mapeo automático de objetos
  • Queries más limpias
  • Manejo simplificado de parámetros
  • Integración con transacciones
  • APIs modernas y funcionales


La idea es poder escribir SQL real sin tener que pelear con ResultSet, PreparedStatement y toneladas de try/catch.


Dependencia Maven:

<dependency>

    <groupId>org.jdbi</groupId>

    <artifactId>jdbi3-core</artifactId>

    <version>3.49.6</version>

</dependency>


Si usamos una base de datos específica, agregamos el driver correspondiente.


Por ejemplo para MySQL:

<dependency>

    <groupId>com.mysql</groupId>

    <artifactId>mysql-connector-j</artifactId>

</dependency>


Crear una conexión:

Jdbi jdbi = Jdbi.create(

    "jdbc:mysql://localhost:3306/test",

    "root",

    "password"

);


Ejecutar una consulta


Supongamos esta tabla:

CREATE TABLE users (

    id BIGINT PRIMARY KEY,

    name VARCHAR(100),

    age INT

);


Y esta clase:


public class User {

    private Long id;

    private String name;

    private Integer age;


    // getters y setters

}


Ahora podemos consultar así:


List<User> users = jdbi.withHandle(handle ->

    handle.createQuery("SELECT * FROM users")

          .mapToBean(User.class)

          .list()

);


Fijate que desaparece todo el manejo manual de ResultSet.


Insertar datos:


jdbi.useHandle(handle ->

    handle.createUpdate("""

        INSERT INTO users(id, name, age)

        VALUES(:id, :name, :age)

    """)

    .bind("id", 1L)

    .bind("name", "Emanuel")

    .bind("age", 30)

    .execute()

);


Parámetros nombrados

Una de las mejores cosas de JDBI es evitar los clásicos:


statement.setString(1, ...);

statement.setInt(2, ...);


En su lugar:

.bind("name", "Juan")


Mucho más legible.


JDBI también permite definir interfaces tipo DAO.


public interface UserDao {


    @SqlQuery("SELECT * FROM users WHERE id = :id")

    User findById(@Bind("id") Long id);


    @SqlUpdate("""

        INSERT INTO users(id, name, age)

        VALUES(:id, :name, :age)

    """)

    void insert(

        @Bind("id") Long id,

        @Bind("name") String name,

        @Bind("age") Integer age

    );

}


Y luego:


UserDao dao = jdbi.onDemand(UserDao.class);

User user = dao.findById(1L);


¿Por qué mucha gente lo elige?

Porque queda en un punto muy cómodo. No es tan automático como JPA y permite hacer optimizaciones fácilmente, pero no es tan trabajoso como JDBC. 


JDBI se integra muy bien con Spring Framework.

Dependencia:

<dependency>

    <groupId>org.jdbi</groupId>

    <artifactId>jdbi3-spring5</artifactId>

    <version>3.49.6</version>

</dependency>


Configuración:


@Configuration

public class JdbiConfig {


    @Bean

    public Jdbi jdbi(DataSource dataSource) {

        Jdbi jdbi = Jdbi.create(dataSource);

        jdbi.installPlugin(new SqlObjectPlugin());

        return jdbi;

    }

}


Crear el DAO como Bean


@Configuration

public class DaoConfig {


    @Bean

    public UserDao userDao(Jdbi jdbi) {

        return jdbi.onDemand(UserDao.class);

    }

}


Usarlo desde un Service


@Service

public class UserService {

    private final UserDao userDao;

    public UserService(UserDao userDao) {

        this.userDao = userDao;

    }


    public User getUser(Long id) {

        return userDao.findById(id);

    }

}


¿Cuándo conviene usar JDBI?

JDBI suele ser una muy buena opción cuando:

  • querés escribir SQL manualmente
  • necesitás buena performance
  • no querés la complejidad de Hibernate
  • trabajás con consultas complejas
  • querés algo más moderno que JDBC puro


Especialmente en microservicios o aplicaciones donde el modelo relacional es importante, JDBI puede resultar mucho más simple y transparente que un ORM completo.


Dejo link: https://jdbi.org


domingo, 14 de junio de 2026

Mundial de Lenguajes de Programación



Le pregunte a la IA que onda si hacemos partidos de diferentes tecnologías con diferentes lenguajes, y este fue el resultado : 



Partido 1: Aplicaciones Web

Java vs C#


🇯🇵 Java sale con Spring Boot.

🇺🇸 C# responde con ASP.NET Core.


Ambos tienen experiencia internacional, planteles maduros y una enorme cantidad de títulos.

Resultado: Empate 1-1.


> Si tenés que hacer una API corporativa, cualquiera de los dos te lleva a octavos.


 🏟️ Partido 2: Concurrencia

Elixir vs Go


🇧🇷 Elixir apuesta por la posesión y el juego asociado con actores y supervisores.

🇩🇪 Go juega más directo con goroutines y channels.


Elixir domina el mediocampo cuando aparecen millones de procesos concurrentes.

Resultado: Elixir 2 - Go 1.

La BEAM sigue siendo una de las ligas más difíciles de enfrentar.


 🏟️ Partido 3: Aplicaciones Standalone

C++ vs Rust


🇮🇹 C++ es el veterano con ocho mundiales encima.

🇳🇱 Rust llega con una generación joven y una defensa impecable.


Rust reduce errores, pero C++ todavía tiene una experiencia enorme.

Resultado: C++ 1 - Rust 1.

La leyenda sigue vigente, pero la promesa pide pista.


 🏟️ Partido 4: Cloud

Java vs Node.js


🇦🇷 Java viene con GraalVM y Spring Boot.

🇪🇸 Node.js juega rápido y con poco peso.


En microservicios y aplicaciones cloud modernas, ambos se sienten cómodos.

Resultado: Empate.

El VAR determina que la elección depende más del equipo que del lenguaje.


🏟️ Partido 5: Inteligencia Artificial

Python vs el resto del mundo


🇫🇷 Python entra a la cancha.

Resultado: Python 5 - 0.

Hubo partido durante cinco minutos.


 🏟️ Partido 6: Programación Funcional

Scala vs Haskell

🇺🇾 Scala combina técnica y pragmatismo.

🇭🇷 Haskell juega un fútbol hermoso que pocos entienden.


Resultado: Scala 2 - Haskell 1.

La posesión fue 90% para Haskell, pero Scala convirtió las que tuvo.


🏟️ Partido 7: Frontend

TypeScript vs JavaScript


🇵🇹 JavaScript es el capitán histórico.

🇵🇹 TypeScript es el mismo equipo, pero con preparador físico.

Resultado: TypeScript 3 - JavaScript 1.

 Los errores en tiempo de compilación fueron la figura del partido.


 🏟️ Partido 8: Bases de Datos

PostgreSQL vs MySQL


🇨🇴 PostgreSQL juega con mucha técnica.

🇲🇽 MySQL apuesta por la experiencia.


Resultado: PostgreSQL 2 - MySQL 1.

PostgreSQL ganó por posesión y calidad de juego.


Y listo, no hice mucho, ni dice mucho pero es divertido :P 

FFM API en Java: Accediendo a Memoria Nativa y Código Nativo sin JNI parte 2

 


Seguimos viendo FFM API

Dónde podemos usar FFM API:

Librerías de machine learning

Integración con:

  • CUDA,
  • TensorRT,
  • ONNX Runtime.


Motores gráficos

Bindings contra:

  • Vulkan,
  • OpenGL,
  • SDL.


Bases de datos

Drivers nativos de:

  • SQLite,
  • RocksDB.


Sistemas embebidos

  • Acceso a APIs del sistema operativo.
  • Upcalls: cuando C llama a Java


La FFM API también soporta: upcalls

Es decir:

  • código nativo llamando código Java.
  • Algo parecido a callbacks.


Veamos un ejemplo práctico: strlen


import java.lang.foreign.*;

import java.lang.invoke.MethodHandle;

import static java.lang.foreign.ValueLayout.*;


public class Main {

    public static void main(String[] args) throws Throwable {

         Linker linker = Linker.nativeLinker();

         MemorySegment strlenAddr =

                linker.defaultLookup()

                        .find("strlen")

                        .orElseThrow();

        MethodHandle strlen = linker.downcallHandle(

                strlenAddr,

                FunctionDescriptor.of(JAVA_LONG, ADDRESS)

        );


        try (Arena arena = Arena.ofConfined()) {

            MemorySegment text =

                    arena.allocateFrom("Hola Mundo");

            long size = (long) strlen.invoke(text);

            System.out.println(size);

        }

    }

}


FFM API esta disponible desde Java 21


La FFM API es probablemente uno de los cambios más importantes del ecosistema Java moderno.

Porque permite:

  • interoperabilidad nativa,
  • manejo eficiente de memoria,
  • reemplazar gran parte de JNI,
  • acceso a APIs del sistema,
  • y programación de bajo nivel.


Todo usando una API moderna y mucho más amigable.


Project Panama abre la puerta a una nueva generación de aplicaciones Java capaces de integrarse con el mundo nativo sin el dolor histórico de JNI.

FFM API en Java: Accediendo a Memoria Nativa y Código Nativo sin JNI


La FFM API (Foreign Function & Memory API) es una de las incorporaciones más interesantes de Java moderno.


Su objetivo es permitir que una aplicación Java pueda:

  • Llamar funciones escritas en C.
  • Acceder a memoria fuera del heap de la JVM.
  • Reemplazar gran parte del uso de JNI.
  • Interactuar con librerías nativas de forma más segura y eficiente.


La FFM API forma parte del proyecto Project Panama.

Pero demos un paso para atras,  ¿Por qué existía JNI?


Durante años, si una aplicación Java necesitaba:

  • acceder a una librería en C,
  • usar código del sistema operativo,
  • trabajar con drivers,
  • o ejecutar operaciones de bajo nivel,

la única opción era usar JNI (Java Native Interface)


El problema es que JNI:

  • es complejo,
  • verboso,
  • inseguro,
  • difícil de debuggear,
  • y propenso a crashes de la JVM.


Además, obliga a escribir:

  • código Java,
  • código C,
  • headers,
  • compilación nativa,
  • glue code.


Puff si no habre puteado con JNI.


¿Qué propone la FFM API?

La FFM API permite hacer esto:

printf("Hola desde C!\n");


...directamente desde Java.


Y además:

  • manejar memoria nativa,
  • mapear estructuras,
  • trabajar con punteros,
  • invocar funciones dinámicamente.


Todo usando una API moderna.


La FFM API tiene dos pilares:

1. Foreign Function


Permite invocar funciones nativas.


Por ejemplo:

  • printf
  • strlen
  • malloc
  • funciones de librerías C


2. Foreign Memory

Permite manejar memoria fuera del heap de Java.


Esto es importante porque:

  • la JVM normalmente controla toda la memoria,
  • pero muchas librerías nativas usan memoria propia.


La FFM API permite trabajar con esa memoria de manera segura.


Primer ejemplo: llamar a printf


import java.lang.foreign.*;

import java.lang.invoke.MethodHandle;

import static java.lang.foreign.ValueLayout.*;


public class Main {


    public static void main(String[] args) throws Throwable {

        Linker linker = Linker.nativeLinker();

        SymbolLookup stdlib = linker.defaultLookup();


        MemorySegment printfAddress =

                stdlib.find("printf")

                      .orElseThrow();


        FunctionDescriptor printfSignature =

                FunctionDescriptor.of(JAVA_INT, ADDRESS);


        MethodHandle printf = linker.downcallHandle(

                printfAddress,

                printfSignature

        );


        try (Arena arena = Arena.ofConfined()) {

            MemorySegment text =

                    arena.allocateFrom("Hola desde C!\n");


            printf.invoke(text);

        }

    }

}


¿Qué está pasando acá?


Linker linker = Linker.nativeLinker();

Obtiene un linker capaz de interactuar con funciones nativas.


SymbolLookup stdlib = linker.defaultLookup();

Busca símbolos exportados por librerías nativas.


stdlib.find("printf")

Obtiene la dirección de memoria de la función.

Muy parecido a: void* ptr = dlsym(...)


FunctionDescriptor.of(JAVA_INT, ADDRESS)

Describe la firma de la función.

En este caso: int printf(char*)


linker.downcallHandle(...)

Crea un MethodHandle que permite invocar la función nativa.


Manejo de memoria con Arena

Uno de los conceptos más importantes es:

Un Arena administra memoria nativa.


try (Arena arena = Arena.ofConfined()) {


}


Cuando el bloque termina:

  • la memoria se libera automáticamente.
  • Esto evita muchísimos memory leaks.


La memoria nativa se representa con: MemorySegment


Es básicamente:

  • un bloque de memoria,
  • con límites,
  • seguro,
  • y controlado.


Y para reservar memoria hacemos: 

MemorySegment segment = arena.allocate(4);

Por ejemplo escribir y leer un entero: 

segment.set(JAVA_INT, 0, 42);

int value = segment.get(JAVA_INT, 0);


Supongamos esta estructura:

struct Point {

    int x;

    int y;

};


Podemos modelarla en Java.

StructLayout POINT = MemoryLayout.structLayout(

        JAVA_INT.withName("x"),

        JAVA_INT.withName("y")

);


Y para reservar memoria hacemos:

MemorySegment point = arena.allocate(POINT);


Escribir los campos:


point.set(JAVA_INT, 0, 10);

point.set(JAVA_INT, 4, 20);


¿Qué ventajas tiene?

Menos complejidad


No hace falta:

  • generar headers,
  • compilar JNI,
  • usar glue code.


¿Puede FFM API reemplaza completamente JNI?

Todavía no en todos los casos.

Hay escenarios avanzados donde JNI sigue siendo necesario.


Pero para muchísimos casos:

  • bindings simples,
  • acceso a librerías,
  • llamadas nativas,
  • estructuras,


la FFM API es mucho mejor.