Tags (71)
- Agile
- Alta disponibilidad
- Alternativas cloud
- Aop
- Arquitectura
- Arquitectura distribuida
- Automatizacion
- Azure devops
- Base de datos
- Buenas practicas
- Cloud
- Colas
- Competing consumers
- Convenciones
- Copilot
- Diseno
- Docker
- Docker compose
- Documentacion
- Eda
- Equipos
- Escalabilidad
- Flujo de negocio
- Flujo de trabajo
- Flyway
- Git
- Gradle
- Herramientas digitales
- Ia
- Iam
- Infraestructura
- Java
- Jerarquia tecnica
- Jpa
- Jsonb
- Kafka
- Kubernetes
- Liderazgo en software
- Lineamientos
- Log
- Logging
- Microservicios
- Mongodb
- Monitoreo
- Nosql
- Observabilidad
- Open source
- Plugins
- Postgresql
- Privacidad
- Programacion funcional
- Programacion reactiva
- Rabbitmq
- Rotacion de talento
- Saga
- Scrum
- Security
- Seguridad
- Self hosting
- Sistemas legados
- Spring boot
- Spring mvc
- Sql
- Streams
- Threadlocal
- Trazabilidad
- Versionado
- Web
- Webflux
- Websockets
- Zero trust
Optimizando el Acceso a Datos: La Importancia de las Proyecciones JPA en Spring Boot
- Mauricio ECR
- Persistencia
- 23 Apr, 2025
El Costo Oculto de Traer Demasiada Información En el desarrollo de aplicaciones que interactúan con bases de datos, una tarea fundamental es la recuperación de datos. Al usar Object-Relational Map
Optimizando el Acceso a Datos: La Importancia de las Proyecciones JPA en Spring Boot
- Mauricio ECR
- Persistencia
- 23 Apr, 2025
El Costo Oculto de Traer Demasiada Información
En el desarrollo de aplicaciones que interactúan con bases de datos, una tarea fundamental es la recuperación de datos. Al usar Object-Relational Mapping (ORM) como JPA (Java Persistence API), es común y tentador mapear nuestras tablas a entidades Java completas y, por defecto, recuperar estas entidades enteras cada vez que realizamos una consulta. Por ejemplo, si tenemos una entidad Usuario con 20 atributos (id, nombre, email, dirección, fecha de registro, último login, preferencias, etc.), una consulta simple como findById(1L) o findByEmail("[email protected]") a menudo se traduce, detrás de escena, en un SELECT u.* FROM usuario u WHERE ....
Si bien esto simplifica el desarrollo inicialmente, presenta un problema significativo a medida que la aplicación crece o cuando solo necesitamos una pequeña porción de esa información: el sobrecoste de datos (over-fetching).
¿Qué problemas concretos genera esto?
- Consumo de Ancho de Banda: Transferir columnas innecesarias entre la base de datos y la aplicación consume más ancho de banda de red.
- Uso de Memoria: La aplicación necesita más memoria para mantener en el Heap objetos más grandes de lo necesario.
- Rendimiento de la Base de Datos: La base de datos tiene que leer más datos del disco (potencialmente) y procesar más información.
- Latencia: La serialización/deserialización de objetos más grandes toma más tiempo, aumentando la latencia de las respuestas.
- Carga en el Garbage Collector: Objetos más grandes y potencialmente más numerosos (si se traen listas) ponen más presión sobre el recolector de basura de la JVM.
En resumen, no seleccionar específicamente los datos que necesitamos es ineficiente y puede degradar significativamente el rendimiento y la escalabilidad de nuestras aplicaciones, especialmente en escenarios de alta concurrencia o con tablas muy anchas (muchas columnas) o largas (muchas filas).
Posibles Soluciones para Optimizar la Recuperación de Datos
Ante el problema del over-fetching, existen varias estrategias que podemos emplear:
- Recuperar Entidades Completas (El Anti-Patrón): Como ya mencionamos, es la opción por defecto pero la menos eficiente si no necesitas toda la información.
- Consultas Nativas (Native Queries): Escribir SQL directamente. Permite un control total y seleccionar exactamente las columnas deseadas. Sin embargo, se pierde la portabilidad entre bases de datos, la seguridad de tipos en tiempo de compilación (parcialmente) y puede mezclar lógica SQL con el código Java de forma menos elegante.
- Criteria API de JPA: Una forma programática y type-safe de construir consultas. Es potente y flexible, permitiendo seleccionar atributos específicos. Su principal desventaja es que puede volverse bastante verbosa y compleja para consultas sencillas.
- Proyecciones (El Enfoque Recomendado): Utilizar las características de JPA y extensiones (como las de Spring Data JPA) para definir explícitamente qué atributos de una entidad queremos recuperar. Ofrece un excelente equilibrio entre eficiencia, legibilidad y seguridad de tipos.
Nos centraremos en esta última: las proyecciones.
Proyecciones JPA al Rescate
Una proyección en el contexto de JPA y Spring Data JPA es una técnica que nos permite definir una "vista" o subconjunto de los atributos de una entidad que deseamos recuperar de la base de datos. En lugar de traer el objeto completo, le indicamos al framework que solo queremos ciertos campos.
Spring Data JPA facilita enormemente el uso de proyecciones mediante dos mecanismos principales:
Proyecciones Basadas en Interfaces (Interface-based Projections)
Defines una interfaz Java que declara métodos get() para los atributos que deseas seleccionar. Los nombres de los métodos deben coincidir con los nombres de las propiedades de la entidad.
Spring Data JPA genera automáticamente la consulta SQL necesaria (SELECT columna1, columna2 FROM ...) y crea una instancia proxy de esa interfaz en tiempo de ejecución, rellenándola con los datos recuperados.
Es la forma más común y recomendada por su simplicidad y claridad.
Proyecciones Basadas en Clases (Class-based Projections - DTOs)
Creas una clase (típicamente un DTO - Data Transfer Object) con los campos que necesitas y un constructor que acepte esos campos como parámetros.
En tu consulta (usando @Query con JPQL), utilizas la sintaxis SELECT NEW com.tu.paquete.TuDTO(e.atributo1, e.atributo2) FROM Entidad e WHERE ....
JPA ejecutará la consulta seleccionando solo las columnas necesarias y las usará para instanciar tu DTO.
Es útil cuando necesitas más lógica en el objeto proyectado o si prefieres trabajar con clases concretas.
Ventajas Clave de Usar Proyecciones
- Eficiencia: Reduce drásticamente la cantidad de datos transferidos y procesados.
- Rendimiento: Consultas más rápidas y menor consumo de memoria y CPU.
- Claridad: El código (interfaces de proyección o DTOs) documenta explícitamente qué datos se esperan para un caso de uso específico.
- Seguridad (con interfaces): Mantiene la seguridad de tipos en gran medida.
Ejemplo Práctico con Spring Boot y JPA
Imaginemos una aplicación de e-commerce con una entidad Producto.
1. Entidad Producto:
package com.miblog.proyecciones.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Lob; // Para campos grandes
@Entity
public class Producto {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nombre;
@Lob // Indica que puede ser un objeto grande (TEXT, CLOB, BLOB)
private String descripcionDetallada; // Campo potencialmente pesado
private double precio;
private int stock;
private String categoria;
// Constructores, Getters y Setters (Omitidos por brevedad)
// Lombok @Data, @NoArgsConstructor, @AllArgsConstructor puede ser útil aquí
}
Supongamos que en una vista de listado rápido solo necesitamos mostrar el nombre y el precio de los productos con stock disponible. Traer descripcionDetallada sería un desperdicio.
2. Proyección Basada en Interfaz:
Creamos una interfaz que defina la vista que necesitamos:
package com.miblog.proyecciones.projection;
public interface ProductoResumen {
String getNombre();
double getPrecio();
// También puedes tener valores calculados con SpEL:
// @Value("#{target.nombre + ' (' + target.categoria + ')'}")
// String getNombreConCategoria();
}
3. Repositorio Spring Data JPA:
Modificamos nuestro repositorio para usar la proyección:
package com.miblog.proyecciones.repository;
import com.miblog.proyecciones.entity.Producto;
import com.miblog.proyecciones.projection.ProductoResumen;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProductoRepository extends JpaRepository<Producto, Long> {
// Spring Data JPA detecta que el tipo de retorno es una interfaz
// y automáticamente aplica la proyección.
List<ProductoResumen> findByStockGreaterThan(int stockMinimo);
// Ejemplo con DTO (requiere definir la clase ProductoDTO)
/*
@Query("SELECT NEW com.miblog.proyecciones.dto.ProductoDTO(p.nombre, p.precio) FROM Producto p WHERE p.stock > :stockMinimo")
List<ProductoDTO> findDtoByStockGreaterThan(@Param("stockMinimo") int stockMinimo);
*/
// También es posible usar proyecciones dinámicas:
// <T> List<T> findByCategoria(String categoria, Class<T> type);
// Al llamar: productoRepository.findByCategoria("Electrónicos", ProductoResumen.class);
// O productoRepository.findByCategoria("Electrónicos", Producto.class); // Trae la entidad completa
}
4. Uso en un Servicio (Ejemplo):
package com.miblog.proyecciones.service;
import com.miblog.proyecciones.projection.ProductoResumen;
import com.miblog.proyecciones.repository.ProductoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ProductoService {
@Autowired
private ProductoRepository productoRepository;
public List<ProductoResumen> obtenerResumenProductosEnStock() {
// Solo se traerán las columnas 'nombre' y 'precio' de la BD
return productoRepository.findByStockGreaterThan(0);
}
}
Al ejecutar obtenerResumenProductosEnStock(), Spring Data JPA generará una consulta SQL similar a:
SELECT p.nombre AS nombre, p.precio AS precio
FROM producto p
WHERE p.stock > 0; -- O el valor pasado como parámetro
Como puedes ver, la columna descripcionDetallada (y las demás no incluidas en ProductoResumen) ni siquiera se mencionan en el SELECT, logrando nuestro objetivo de eficiencia.
Conclusión:
No recuperar datos innecesarios de la base de datos es fundamental para construir aplicaciones performantes y escalables. Las proyecciones en JPA, especialmente con las facilidades que ofrece Spring Data JPA, son una herramienta poderosa y elegante para lograr este objetivo.
Adoptar el uso de proyecciones (ya sea basadas en interfaces o DTOs) siempre que no necesites la entidad completa debería considerarse una buena práctica estándar. Te permite:
- Minimizar la carga en la red y la base de datos.
- Reducir el consumo de memoria en tu aplicación.
- Acelerar los tiempos de respuesta.
- Escribir código más claro respecto a los datos requeridos para cada caso de uso.
La próxima vez que escribas una consulta, pregúntate: "¿Realmente necesito todos los atributos de esta entidad?". Si la respuesta es no, considera seriamente usar una proyección. Tu aplicación (y tus usuarios) te lo agradecerán.
Observabilidad de Servidores y Contenedores Docker: Una Mirada Práctica con Prometheus, Grafana y cAdvisor
- Mauricio ECR
- DevOps
- 22 Apr, 2025
En el mundo de la infraestructura moderna, especialmente con la creciente adopción de contenedores y arquitecturas distribuidas, entender qué está sucediendo dentro de nuestros sistemas en tiempo real
Observabilidad de Servidores y Contenedores Docker: Una Mirada Práctica con Prometheus, Grafana y cAdvisor
- Mauricio ECR
- DevOps
- 22 Apr, 2025
En el mundo de la infraestructura moderna, especialmente con la creciente adopción de contenedores y arquitecturas distribuidas, entender qué está sucediendo dentro de nuestros sistemas en tiempo real se ha vuelto fundamental. Ya no basta con saber si un servidor está "encendido"; necesitamos comprender su comportamiento interno, cómo interactúan sus componentes y predecir posibles problemas antes de que afecten a los usuarios. Aquí es donde entra el concepto de
Observabilidad.
¿Qué es la Observabilidad?
La observabilidad es la capacidad de inferir el estado interno de un sistema midiendo sus salidas externas. En términos prácticos, se trata de recopilar y analizar datos de nuestro sistema para poder hacer preguntas arbitrarias sobre su comportamiento sin necesidad de conocer previamente todas las posibles fallas o estados. A diferencia del monitoreo tradicional, que a menudo se centra en métricas conocidas y umbrales predefinidos para alertar sobre problemas conocidos, la observabilidad nos permite explorar el sistema para diagnosticar problemas desconocidos o inesperados.
Los Tres Pilares de la Observabilidad
La observabilidad se construye típicamente sobre tres tipos principales de datos o "pilares":
- Monitoreo (Metrics): Consiste en la recopilación de datos numéricos agregados a lo largo del tiempo (series temporales). Estas son las métricas de rendimiento como uso de CPU, memoria, latencia de red, errores por segundo, etc. El monitoreo nos da una vista de alto nivel del rendimiento y salud del sistema y sus componentes. Es excelente para detectar tendencias, identificar cuellos de botella y disparar alertas basadas en umbrales.
- Logging (Logs): Son registros de eventos discretos que ocurren dentro de una aplicación o sistema. Los logs proporcionan información detallada sobre lo que sucedió en un momento específico. Son cruciales para la depuración, el análisis de causa raíz de problemas y la auditoría.
- Trazabilidad (Tracing): Permite seguir el camino de una solicitud a medida que atraviesa los diferentes servicios en un sistema distribuido. El tracing es vital para comprender las interacciones entre microservicios, identificar la latencia en flujos de trabajo complejos y depurar problemas de rendimiento en arquitecturas distribuidas.
Aunque los tres pilares son esenciales para una observabilidad completa, el monitoreo a menudo constituye la base inicial, proporcionando la visibilidad en tiempo real necesaria para identificar rápidamente cuándo y dónde podría estar ocurriendo un problema.
Enfocándonos en el Monitoreo
El monitoreo nos proporciona la capacidad de responder preguntas como:
- ¿Cuánta CPU está usando mi servidor?
- ¿Cuánta memoria libre tiene un contenedor Docker específico?
- ¿Cuántas solicitudes por segundo está manejando mi aplicación?
- ¿Cuál es la latencia promedio de las respuestas de mi API?
- ¿Está aumentando el número de errores HTTP en mi servicio web?
Tener acceso a estas métricas en tiempo real y a lo largo del tiempo nos permite no solo reaccionar a los problemas, sino también anticiparlos, optimizar recursos y planificar la capacidad.
Herramientas Clave para el Monitoreo
Existen numerosas herramientas para implementar soluciones de monitoreo. Para monitorear servidores y, crucialmente, los recursos y el rendimiento a nivel de contenedor en Docker, una pila muy popular y efectiva es la compuesta por Prometheus y Grafana, complementada con Exporters como Node Exporter y cAdvisor. En algunos setups, herramientas como Redis pueden usarse como soporte (aunque no es estrictamente parte del pipeline de métricas principal en este contexto).
Prometheus: Es un sistema de monitoreo y alerta basado en series temporales. Prometheus recolecta métricas de diversos orígenes (endpoints HTTP que exponen métricas en un formato específico) mediante un modelo "pull" (Prometheus va y "raspa" los datos de los targets configurados). Es la base de nuestra recopilación y almacenamiento de métricas.
Grafana: Es una plataforma de código abierto para la visualización y el análisis de métricas. Grafana se conecta a diversas fuentes de datos, incluyendo Prometheus, y permite crear dashboards personalizables con gráficos, tablas y otros paneles para visualizar las métricas recopiladas de forma intuitiva. Es la interfaz principal para que los humanos interactúen con los datos de monitoreo. 📝 Nota: Una vez que Grafana esté funcionando, puedes importar dashboards prediseñados desde Grafana Labs. Por ejemplo, si estás monitoreando un servidor como una Raspberry Pi, puedes utilizar el dashboard con el ID 15120, que está optimizado para mostrar métricas clave de un sistema Linux. Solo necesitas ir a “+ / Import” dentro de Grafana, ingresar el número del panel (15120) y seleccionar Prometheus como fuente de datos. Esto te permitirá visualizar de inmediato un conjunto de gráficos útiles sin tener que construirlos desde cero.
Node Exporter: Es un "exporter" oficial de Prometheus que se instala en servidores Linux para exponer métricas a nivel del sistema operativo (CPU, memoria, disco, red, etc.). Esencial para entender el estado de la máquina host donde se ejecutan los contenedores.
cAdvisor (Container Advisor): Es otra herramienta de código abierto (originalmente de Google) que monitorea el uso de recursos y el rendimiento de los contenedores en ejecución. cAdvisor recopila métricas como uso de CPU, memoria, E/S de red y sistema de archivos para cada contenedor. Es indispensable para tener visibilidad del consumo de recursos por contenedor.
Redis: Aunque no es una herramienta de monitoreo per se, a veces se incluye en setups (como parece insinuar tu depends_on en cAdvisor, aunque no es el uso más común hoy en día) potencialmente como una caché o base de datos auxiliar para ciertas herramientas de monitoreo o sus componentes. En el contexto de este setup, su papel específico no es central para la recopilación de métricas por parte de Prometheus, sino quizás una dependencia para la versión o configuración específica de cAdvisor que se está utilizando.
Implementando la Pila de Monitoreo con Docker Compose
Docker Compose nos permite definir y ejecutar aplicaciones multi-contenedor con un solo comando. El archivo docker-compose.yml que proporcionaste orquesta la implementación de Prometheus, Grafana, Node Exporter, cAdvisor y Redis.
Aquí está el contenido del archivo docker-compose.yml:
services:
grafana:
image: grafana/grafana:latest
container_name: grafana_monitoring
restart: unless-stopped manualmente.
volumes:
- /home/dev/docker/monitoring/grafana/data:/var/lib/grafana
ports:
- '3000:3000'
networks:
- monitoring_net
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
volumes:
- /home/dev/docker/monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- /home/dev/docker/monitoring/prometheus/data:/prometheus
ports:
- '9090:9090'
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--web.enable-lifecycle'
networks:
- monitoring_net
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
expose:
- 9100
networks:
- monitoring_net
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
restart: unless-stopped
ports:
- '8080:8080'
volumes:
- /:/rootfs:ro
- /var/run:/var/run:rw
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
depends_on:
- redis
networks:
- monitoring_net
redis:
image: redis:latest
container_name: redis
expose:
- 6379
networks:
- monitoring_net
networks:
monitoring_net:
external: true
Explicación del Archivo Docker Compose:
El archivo define varios services, cada uno representando un contenedor:
- grafana: Configura el contenedor de Grafana, mapeando su puerto web (3000) al host y persistiendo sus datos en un volumen del host. Se une a la red monitoring_net.
- prometheus: Configura el contenedor de Prometheus, montando su archivo de configuración (prometheus.yml) y volumen de datos en el host. Su puerto web (9090) se mapea al host. También se une a la red monitoring_net y especifica argumentos de comando para su inicio.
- node-exporter: Configura el contenedor de Node Exporter. Crucialmente, monta directorios del sistema operativo host (/proc, /sys, /) en modo lectura (ro) para poder acceder a las métricas del sistema. Especifica los paths correctos en su comando de inicio. Expone su puerto por defecto (9100) internamente en la red monitoring_net.
- cadvisor: Configura el contenedor de cAdvisor. Mapea su puerto web (8080) al host y monta varios directorios (/, /var/run, /sys, /var/lib/docker) que necesita para acceder a la información de los contenedores y el sistema Docker. Depende del servicio redis para iniciar y se une a la red monitoring_net.
- redis: Configura el contenedor de Redis, exponiendo su puerto por defecto (6379) internamente en la red monitoring_net. Su inclusión aquí es principalmente como dependencia para cAdvisor en este setup específico.
Finalmente, la sección networks define la red monitoring_net como external: true. Esto significa que Docker Compose buscará una red existente con ese nombre en lugar de crear una nueva. Debes crear esta red manualmente antes de ejecutar el docker-compose utilizando el comando: docker network create monitoring_net
Configuración de Prometheus (prometheus.yml)
El archivo prometheus.yml le dice a Prometheus qué objetivos (targets) debe "raspar" (scrape) para obtener métricas y con qué frecuencia debe hacerlo.
Aquí está el contenido del archivo prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'prometheus'
scrape_interval: 15s
static_configs:
- targets: ['prometheus:9090']
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
Explicación del Archivo de Configuración de Prometheus:
- global: Establece el intervalo de raspado por defecto (scrape_interval) en 15 segundos.
- scrape_configs: Define una lista de trabajos (job_name). Cada trabajo especifica un conjunto de targets que Prometheus debe monitorear.
- El trabajo 'prometheus' se configura para raspar las métricas del propio servidor Prometheus en su puerto 9090. Esto es útil para monitorear la salud y el rendimiento del servidor de monitoreo.
- El trabajo 'cadvisor' se configura para raspar las métricas de cAdvisor en el puerto 8080. Gracias a la red Docker, Prometheus puede referirse al contenedor cAdvisor simplemente por su nombre de servicio (cadvisor).
- El trabajo 'node-exporter' se configura para raspar las métricas de Node Exporter en el puerto 9100, utilizando el nombre del servicio Docker (node-exporter).
Este archivo de configuración le indica a Prometheus que debe conectarse a los servicios prometheus, cadvisor y node-exporter dentro de la red monitoring_net (Docker maneja la resolución de nombres) en sus respectivos puertos para recolectar métricas cada 15 segundos.
Conclusión
Implementar una estrategia de observabilidad robusta es esencial para gestionar eficazmente infraestructuras basadas en servidores y Docker. La pila Prometheus, Grafana, Node Exporter y cAdvisor proporciona una base sólida para el monitoreo, permitiéndonos recopilar, almacenar y visualizar métricas cruciales sobre el rendimiento del sistema host y el consumo de recursos a nivel de contenedor. Al configurar estas herramientas mediante Docker Compose y definir correctamente los trabajos de raspado en Prometheus, podemos obtener la visibilidad necesaria para mantener nuestros sistemas saludables, identificar problemas rápidamente y optimizar nuestra infraestructura de manera proactiva.
Este setup es un excelente punto de partida. Para una observabilidad completa, se deberían integrar soluciones de logging (como ELK stack o Loki) y tracing (como Jaeger o Zipkin) para complementar la información proporcionada por el monitoreo.
Cuándo Usar Colas de Mensajes en el Desarrollo de Software
- Mauricio ECR
- Arquitectura
- 18 Apr, 2025
Las colas de mensajes son herramientas clave para construir sistemas distribuidos, escalables y tolerantes a fallos. En este artículo te comparto una guía con situaciones comunes donde su uso es altam
Cuándo Usar Colas de Mensajes en el Desarrollo de Software
- Mauricio ECR
- Arquitectura
- 18 Apr, 2025
Las colas de mensajes son herramientas clave para construir sistemas distribuidos, escalables y tolerantes a fallos. En este artículo te comparto una guía con situaciones comunes donde su uso es altamente recomendable. Esto puede servirte como referencia rápida para decidir si una cola puede ser útil en tu arquitectura.
1. Procesamiento Asíncrono de Tareas Pesadas
Descripción de la situación
Una aplicación web necesita procesar tareas pesadas (como enviar correos, generar PDFs o hacer procesamiento de imágenes) después de una solicitud del usuario.
Dificultades
- Alta latencia si se procesa todo en la misma petición HTTP.
- Posibles timeouts en el servidor.
- Experiencia de usuario lenta y frustrante.
Por qué se solucionaría con colas de mensajes
Separar el procesamiento de la respuesta al usuario permite responder rápido y delegar la tarea a un worker. La cola actúa como puente entre el sistema que genera la tarea y el que la ejecuta.
Características típicas de la cola
- Persistencia para no perder mensajes si algo falla.
- Retries automáticos para tareas fallidas.
- Delay opcional para tareas programadas.
- Visibilidad de mensajes en procesamiento.
2. Comunicación Entre Microservicios
Descripción de la situación
Una arquitectura basada en microservicios donde varios servicios necesitan intercambiar información o coordinar acciones.
Dificultades
- El acoplamiento entre servicios crece si se comunican de forma directa (HTTP sincrónico).
- Si un servicio está caído, puede romper toda la cadena.
- Difícil escalar servicios de forma independiente.
Por qué se solucionaría con colas de mensajes
Las colas desacoplan los servicios, permitiendo que uno publique mensajes sin depender del estado del consumidor. Esto permite una comunicación más resiliente y escalable.
Características típicas de la cola
- Entrega garantizada (at-least-once).
- Soporte para múltiples consumidores.
- Escalabilidad horizontal.
- Opcional: orden garantizado de mensajes.
3. Picos de Carga Temporales
Descripción de la situación
Una aplicación recibe picos de tráfico (por ejemplo, durante una campaña de marketing o un evento en vivo).
Dificultades
- El sistema puede saturarse si intenta procesar todo al instante.
- Riesgo de perder solicitudes o fallar por falta de recursos.
Por qué se solucionaría con colas de mensajes
Las colas permiten "almacenar" las tareas y procesarlas a medida que los workers tienen capacidad. Se convierte una carga variable en una carga continua.
Características típicas de la cola
- Alta capacidad de buffer.
- Procesamiento en paralelo (workers escalables).
- Métricas para monitorear backlog.
- Integración con sistemas de auto-escalado.
4. Integración con Sistemas Externos o APIs Lentas
Descripción de la situación
Tu sistema necesita integrarse con APIs de terceros (por ejemplo, pasarelas de pago, servicios de envío, etc.) que pueden ser lentas o poco confiables.
Dificultades
- Timeouts frecuentes.
- Limitaciones de tasa (rate limiting).
- Caídas del servicio externo afectan el sistema completo.
Por qué se solucionaría con colas de mensajes
Poner las llamadas a servicios externos en una cola permite controlar el ritmo, manejar reintentos, y evitar sobrecargar al proveedor.
Características típicas de la cola
- Retries con backoff.
- Soporte para Dead Letter Queues (DLQ).
- Capacidad de definir prioridades o tasa de procesamiento.
- Persistencia y durabilidad.
5. Auditoría y Logging Centralizado
Descripción de la situación
Se requiere capturar eventos del sistema (como accesos, cambios de estado, errores) en un sistema central para auditoría o análisis.
Dificultades
- El logeo en tiempo real puede bloquear procesos principales.
- Si el sistema de auditoría cae, se pierden los eventos.
Por qué se solucionaría con colas de mensajes
Las colas permiten enviar eventos de forma asincrónica y confiable a un sistema de almacenamiento o procesamiento.
Características típicas de la cola
- Alta velocidad de escritura.
- Orden garantizado (opcional, según la necesidad).
- Múltiples consumidores (ej. para alertas, dashboards).
- Baja latencia.
6. Workflows Distribuidos (Orquestación de Procesos)
Descripción de la situación
Un proceso complejo requiere que varias acciones ocurran en orden y/o condicionalmente, como un onboarding de usuario o procesamiento de pagos.
Dificultades
- Difícil mantener el estado y coordinación entre servicios.
- Problemas de sincronización y gestión de errores.
Por qué se solucionaría con colas de mensajes
Las colas permiten implementar orquestadores que gestionan los pasos del workflow como eventos, con flexibilidad para manejar errores y lógica condicional.
Características típicas de la cola
- Soporte para enrutamiento de mensajes.
- Integración con motores de orquestación.
- Baja latencia y confiabilidad.
- Opcional: soporte para eventos tipo pub/sub.
🧪 Ejemplo: Generación Asíncrona de PDF usando una Cola
Este ejemplo representa un caso real y común: un usuario solicita la generación de un PDF. En lugar de procesarlo en la misma solicitud (lo cual puede tardar), se encola la tarea y un worker la procesa de forma asíncrona.
🧍♂️ Usuario solicita un PDF desde el Frontend
El usuario hace una solicitud para generar un PDF. Este proceso es controlado desde el frontend, donde el usuario envía su solicitud.
// Envío de solicitud desde el cliente (frontend)
// Este llamado puede estar en un botón: "Generar PDF"
fetch('/generate-pdf', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 123 })
})
.then(res => res.json())
.then(data => {
// El usuario recibe un mensaje indicando que la tarea ha sido encolada.
console.log(data.status); // "Tarea encolada correctamente"
console.log("ID de la tarea:", data.jobId); // El ID para consultar el estado
});
🧠 Backend (API) recibe la solicitud y encola la tarea
El backend recibe la solicitud del frontend y encola la tarea en una cola de trabajo para ser procesada en segundo plano. La API responde inmediatamente al usuario con la confirmación de que la tarea se ha encolado.
# Supongamos un backend en Flask (Python)
@app.route('/generate-pdf', methods=['POST'])
def generate_pdf():
data = request.get_json()
user_id = data['userId']
# Genera un identificador único para la tarea
job_id = str(uuid.uuid4())
# Se encola una tarea para procesar luego
enqueue_task('generate_pdf', {'user_id': user_id, 'job_id': job_id})
# Responde al usuario con la confirmación de la tarea encolada
return jsonify({
'status': 'Tarea encolada correctamente',
'jobId': job_id, # ID de la tarea para que el usuario pueda consultar el estado
'message': 'Te notificaremos cuando tu PDF esté listo para descargar.'
})
¿Qué hace enqueue_task?
La función enqueue_task empuja la tarea a una cola (como Redis, RabbitMQ, AWS SQS, etc.). El jobId se guarda para poder referenciar la tarea.
def enqueue_task(task_name, data):
task = {
'name': task_name,
'data': data
}
redis.rpush('pdf_tasks', json.dumps(task)) # Ejemplo con Redis
⚙️ Worker que consume tareas y las ejecuta
El worker es un proceso que corre en segundo plano y escucha la cola para procesar las tareas en el momento adecuado. Una vez que el PDF esté generado, puede guardarlo o enviarlo al usuario.
# Un worker que corre en segundo plano y escucha la cola
def worker():
while True:
raw_task = redis.blpop('pdf_tasks', timeout=0) # Espera indefinidamente
if raw_task:
task = json.loads(raw_task[1])
handle_task(task)
def handle_task(task):
if task['name'] == 'generate_pdf':
user_id = task['data']['user_id']
job_id = task['data']['job_id']
generate_pdf_for_user(user_id, job_id)
def generate_pdf_for_user(user_id, job_id):
# Aquí iría la lógica real de generación del PDF
print(f"Generando PDF para el usuario {user_id}")
# Simulación: se genera el PDF y se guarda con el ID de tarea
filename = f"{job_id}.pdf"
with open(filename, "w") as f:
f.write(f"PDF generado para usuario {user_id}")
# Aquí podrías guardar el resultado en una BD o subirlo a un almacenamiento
# Además, actualizamos el estado de la tarea en la base de datos o en el sistema de colas
redis.set(f"job:{job_id}:status", "completado")
📥 Consulta del estado de la tarea (opcional)
El usuario puede consultar el estado de la tarea en cualquier momento utilizando el jobId que se le proporcionó cuando la tarea fue encolada. Esto permite saber si la tarea está aún en proceso o si ya ha sido completada.
@app.route('/job-status/<job_id>', methods=['GET'])
def job_status(job_id):
status = redis.get(f"job:{job_id}:status") # Recupera el estado desde Redis
return jsonify({'jobId': job_id, 'status': status or 'pendiente'})
En este ejemplo, si el jobId existe en el sistema, el usuario recibirá el estado de la tarea. De lo contrario, puede devolver el estado como "pendiente" si la tarea aún no se ha completado.
📧 Notificación cuando la tarea se complete (opcional)
Además de permitir que el usuario consulte el estado, puedes configurar una notificación para cuando el trabajo esté listo. Esto podría ser una notificación en la web, un correo electrónico, o incluso un SMS.
Ejemplo de función de notificación:
def notify_user(user_id, job_id):
# Esta función podría enviar un email, SMS o una notificación web
# Aquí simplemente imprimimos un mensaje de ejemplo
print(f"Notificando al usuario {user_id} que su PDF con jobId {job_id} está listo para descargar.")
Puedes llamar a esta función después de que la tarea haya sido procesada y el PDF esté disponible.
💡 Ventajas de este enfoque
- ✅ Respuesta inmediata: El usuario no espera bloqueado mientras se genera el PDF.
- 🕐 Asincronía: El trabajo pesado se maneja en segundo plano, sin afectar la experiencia del usuario.
- 🔔 Notificación opcional: El usuario puede ser notificado cuando la tarea esté lista.
- 🧱 Escalabilidad: Puedes agregar más workers si la carga aumenta, o priorizar tareas según la necesidad.
- 🔗 Desacoplamiento: El frontend no está directamente vinculado al procesamiento pesado.
Conclusión
Las colas no son solo una herramienta de "alto nivel empresarial", sino una solución práctica para muchos retos comunes en el desarrollo moderno. Identificar los síntomas típicos —como latencia, acoplamiento, o pérdida de datos— puede ayudarte a decidir cuándo usarlas.
Guía Rápida de Comandos y Cláusulas SQL
- Mauricio ECR
- Persistencia
- 15 Apr, 2025
SQL (Structured Query Language) es el lenguaje estándar para gestionar y manipular bases de datos relacionales. A continuación, encontrarás una guía rápida con los comandos y cláusulas más utilizados,
Guía Rápida de Comandos y Cláusulas SQL
- Mauricio ECR
- Persistencia
- 15 Apr, 2025
SQL (Structured Query Language) es el lenguaje estándar para gestionar y manipular bases de datos relacionales. A continuación, encontrarás una guía rápida con los comandos y cláusulas más utilizados, ejemplos prácticos y el orden de ejecución en una consulta SQL.
🛠️ Comandos Básicos de SQL
- SELECT: Selecciona datos de una tabla.
- FROM: Indica la tabla desde la cual se obtendrán los datos.
- WHERE: Filtra los resultados según una condición.
- AS: Asigna un alias a una columna o tabla.
- JOIN: Combina filas de dos o más tablas.
- AND: Une condiciones, todas deben cumplirse.
- OR: Une condiciones, al menos una debe cumplirse.
- LIMIT: Limita la cantidad de filas devueltas.
- IN: Filtra por varios valores posibles en una condición.
- CASE: Devuelve un valor basado en condiciones.
- IS NULL: Devuelve solo las filas con valores nulos.
- LIKE: Busca patrones dentro de una columna.
- COMMIT: Guarda los cambios de una transacción.
- ROLLBACK: Revierte una transacción.
🔧 Modificación de Tablas
- ALTER TABLE: Agrega o elimina columnas.
- UPDATE: Modifica datos existentes.
- CREATE: Crea una tabla, base de datos, índice o vista.
- DELETE: Elimina filas de una tabla.
- INSERT: Agrega una fila nueva.
- DROP: Elimina una tabla, base de datos o índice.
📊 Funciones de Agregación
- GROUP BY: Agrupa datos en conjuntos lógicos.
- ORDER BY: Ordena los resultados (usar
DESCpara descendente). - HAVING: Similar a WHERE pero se aplica a grupos.
- COUNT(): Cuenta el número de filas.
- SUM(): Suma los valores de una columna.
- AVG(): Calcula el promedio de una columna.
- MIN(): Devuelve el valor mínimo.
- MAX(): Devuelve el valor máximo. `
🔗 Tipos de JOIN
- INNER JOIN: Devuelve solo las coincidencias en ambas tablas.
- LEFT JOIN: Devuelve todos los registros de la tabla izquierda y coincidencias de la derecha.
- RIGHT JOIN: Devuelve todos los registros de la tabla derecha y coincidencias de la izquierda.
- FULL OUTER JOIN: Devuelve todos los registros con coincidencias en cualquiera de las tablas.
🔄 Orden de Ejecución en una Consulta SQL
- FROM – Se identifican las tablas.
- WHERE – Se filtran las filas.
- GROUP BY – Se agrupan los datos.
- HAVING – Se filtran los grupos.
- SELECT – Se seleccionan las columnas.
- ORDER BY – Se ordenan los resultados.
- LIMIT – Se limita la cantidad de filas.
💡 Ejemplos de SQL
Consultas Básicas
-- Seleccionar todas las columnas con filtro
SELECT * FROM tabla WHERE columna > 5;
-- Seleccionar primeras 10 filas de dos columnas
SELECT col1, col2 FROM tabla LIMIT 10;
-- Múltiples filtros con OR
SELECT * FROM tabla WHERE col1 > 5 OR col2 < 2;
-- Ordenar resultados
SELECT col1, col2 FROM tabla ORDER BY 1;
Funciones de Agregación
-- Contar filas
SELECT COUNT(*) FROM tabla;
-- Sumar valores
SELECT SUM(col1) FROM tabla;
-- Valor máximo
SELECT MAX(col1) FROM tabla;
-- Promedio agrupado
SELECT AVG(col1) FROM tabla GROUP BY col2;
Consultas Avanzadas
-- LEFT JOIN con alias
SELECT * FROM tabla AS t1 LEFT JOIN tabla2 AS t2 ON t2.col1 = t1.col1;
-- Agregación con filtro de grupo
SELECT col1, COUNT(*) AS total FROM tabla GROUP BY col1 HAVING COUNT(*) > 10;
-- Uso de CASE
SELECT col1,
CASE
WHEN col1 > 10 THEN 'más de 10'
WHEN col1 < 10 THEN 'menos de 10'
ELSE 'es 10'
END AS NuevaColumna
FROM tabla;
🧱 Lenguaje de Definición de Datos (DDL)
-- Crear base de datos y tabla
CREATE DATABASE MiBase;
CREATE TABLE MiTabla (id INT, nombre VARCHAR(18));
-- Crear índice
CREATE INDEX IndiceNombre ON MiTabla(col1);
-- Alterar tabla
ALTER TABLE MiTabla ADD col5 INT;
ALTER TABLE MiTabla DROP COLUMN col5;
-- Eliminar base de datos o tabla
DROP DATABASE MiBase;
DROP TABLE MiTabla;
✍️ Lenguaje de Manipulación de Datos (DML)
-- Insertar fila
INSERT INTO MiTabla (col1, col2) VALUES ('valor1', 'valor2');
-- Actualizar valores
UPDATE MiTabla SET col1 = 56 WHERE col2 = 'algo';
-- Eliminar filas
DELETE FROM MiTabla WHERE col1 = 'algo';
-- Seleccionar columnas
SELECT col1, col2 FROM MiTabla;
Gestión de Migraciones de Base de Datos con Flyway en Spring Boot"
- Mauricio ECR
- Persistencia
- 14 Apr, 2025
Introducción El desarrollo de aplicaciones modernas no solo implica escribir código de negocio, sino también gestionar la evolución de la base de datos. A medida que un proyecto crece, mantener
Gestión de Migraciones de Base de Datos con Flyway en Spring Boot"
- Mauricio ECR
- Persistencia
- 14 Apr, 2025
Introducción
El desarrollo de aplicaciones modernas no solo implica escribir código de negocio, sino también gestionar la evolución de la base de datos. A medida que un proyecto crece, mantener la coherencia del esquema entre desarrolladores, ramas y entornos puede volverse complejo.
Aquí es donde Flyway entra en juego: una herramienta de migración de base de datos ligera y poderosa que permite controlar versiones de esquemas de forma segura, repetible y automatizada.
¿Qué es Flyway?
Flyway es una herramienta de migración de base de datos que permite aplicar scripts de manera controlada y automática. Utiliza una convención de nombres para identificar versiones y aplica cambios incrementales cada vez que la aplicación se inicia.
Problemas que resuelve:
- Desincronización entre esquemas de desarrollo, prueba y producción.
- Cambios accidentales o no versionados.
- Dificultad para aplicar migraciones en equipo o CI/CD.
- Fragilidad de los esquemas generados automáticamente por JPA.
Comparación breve con alternativas:
| Herramienta | Lenguaje | Comunidad | SQL puro | Migraciones Java |
|---|---|---|---|---|
| Flyway | Java | Muy activa | ✅ Sí | ✅ Opcional |
| Liquibase | Java | Activa | ✅ Sí | ✅ Más flexible |
¿Cuándo usar Flyway?
Escenarios ideales:
- Proyectos con evolución frecuente del esquema.
- Equipos distribuidos o con múltiples entornos (dev, test, prod).
- Necesidad de auditoría o trazabilidad de cambios en el esquema.
Ventajas sobre auto-DDL de JPA (spring.jpa.hibernate.ddl-auto):
- Evita sobrescritura accidental de datos.
- Versionado explícito de cambios.
- Mayor control y trazabilidad de la evolución del esquema.
Implementación en Spring Boot
Requisitos previos:
Agrega las siguientes dependencias en tu archivo pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
</dependencies>
Para Gradle:
implementation 'org.flywaydb:flyway-core'
Configuración básica (application.properties):
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=none
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration
Ejemplo Práctico: Proyecto de Gestión de Usuarios
Supongamos una aplicación con una tabla users. Vamos a construir el esquema paso a paso usando Flyway.
Estructura del proyecto:
src/
└── main/
└── resources/
└── db/
└── migration/
├── V1__Create_user_table.sql
└── V2__Add_user_role_column.sql
Primera Iteración – Crear tabla users
Archivo: V1__Create_user_table.sql
CREATE TABLE users (
id BIGINT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE
);
✅ Al iniciar la aplicación, Flyway detecta este archivo y lo ejecuta. Marca la versión como aplicada en su propia tabla de control (flyway_schema_history).
Segunda Iteración – Agregar columna role
Archivo: V2__Add_user_role_column.sql
ALTER TABLE users ADD COLUMN role VARCHAR(20) DEFAULT 'USER';
✅ Flyway identifica que esta versión aún no ha sido aplicada, la ejecuta y actualiza su historial. No vuelve a aplicar la versión 1.
Visualización del Estado de la Base de Datos Tras las Migraciones
Después de ejecutar las dos migraciones (V1 y V2), Flyway deja una huella en la base de datos que te permite auditar el estado de los cambios.
Tablas creadas tras las migraciones:
1. Tabla de usuarios (users):
SELECT * FROM users;
Estructura:
| Columna | Tipo | Restricciones |
|---|---|---|
| id | BIGINT | PRIMARY KEY |
| username | VARCHAR(50) | NOT NULL |
| VARCHAR(100) | UNIQUE | |
| role | VARCHAR(20) | DEFAULT 'USER' |
2. Tabla de control de Flyway (flyway_schema_history):
SELECT * FROM flyway_schema_history;
Ejemplo de contenido:
| installed_rank | version | description | type | script | success |
|---|---|---|---|---|---|
| 1 | 1 | Create user table | SQL | V1__Create_user_table.sql | true |
| 2 | 2 | Add user role column | SQL | V2__Add_user_role_column.sql | true |
Migraciones Java-based
Aunque Flyway trabaja perfectamente con scripts SQL, en algunos casos puede ser útil definir migraciones programáticamente en Java. Esto es útil cuando:
- Necesitas lógica condicional o control de flujo.
- Quieres reutilizar servicios de Spring.
- Trabajas con bases de datos no relacionales o lógicas avanzadas.
Cómo crear una migración Java:
- Implementa la clase extendiendo
BaseJavaMigration. - Ubícala en el paquete
db.migrationo configura la ubicación. - Nómbrala con el patrón
V{n}__Descripción.
Ejemplo: Crear tabla de auditoría
package db.migration;
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import java.sql.Statement;
public class V3__Create_audit_table extends BaseJavaMigration {
@Override
public void migrate(Context context) throws Exception {
try (Statement stmt = context.getConnection().createStatement()) {
stmt.execute("""
CREATE TABLE audit (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
action VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
""");
}
}
}
Configuración adicional si se cambia la ubicación:
spring.flyway.locations=classpath:db/migration
spring.flyway.java-migrations-location=com.ejemplo.migraciones
Tabla Resumen
| Concepto | Descripción |
|---|---|
| Migraciones | Archivos SQL con prefijo V{n}__ que modifican el esquema. |
| Integración con Spring | Ejecuta automáticamente migraciones al iniciar la app. |
| Ubicación por defecto | classpath:db/migration |
| Uso recomendado | Proyectos con cambios frecuentes en el esquema y colaboración en equipo. |
Conclusión
Flyway no solo gestiona migraciones de manera declarativa (con SQL), sino que también ofrece una vía programática potente para casos avanzados. Su integración con Spring Boot hace que los cambios de esquema sean seguros, trazables y consistentes.
Recomendaciones Finales:
- Nunca modifiques un script ya aplicado.
- Usa migraciones Java cuando lo SQL no sea suficiente.
- Verifica la tabla
flyway_schema_historypara diagnosticar errores o validar versiones.
Referencias
Descubre el Poder del SemVer: Optimiza el Versionado de tu Software y Mantén un CHANGELOG Excepcional
- Mauricio ECR
- Convenciones
- 08 Apr, 2025
El Versionado Semántico (SemVer) es una herramienta fundamental para comunicar de forma precisa los cambios en el software, facilitando el mantenimiento y la colaboración. Complementarlo con un **
Descubre el Poder del SemVer: Optimiza el Versionado de tu Software y Mantén un CHANGELOG Excepcional
- Mauricio ECR
- Convenciones
- 08 Apr, 2025
El Versionado Semántico (SemVer) es una herramienta fundamental para comunicar de forma precisa los cambios en el software, facilitando el mantenimiento y la colaboración. Complementarlo con un CHANGELOG bien estructurado potencia la transparencia y la trazabilidad, ofreciendo una visión detallada de la evolución de cada versión. En este artículo, exploraremos en profundidad cómo adoptar SemVer y cómo mantener un CHANGELOG que vaya de la mano para optimizar tu proceso de desarrollo.
Introducción
El control de versiones en el desarrollo de software se convierte en una ventaja competitiva cuando se implementa de forma clara y estructurada. SemVer aporta un sistema numérico que indica el alcance de los cambios (cambios mayores, menores o correcciones), mientras que el CHANGELOG documenta y narra el proceso evolutivo de tu proyecto. Esta sinergia no solo facilita la colaboración interna, sino que también mejora la comunicación con los usuarios y clientes, ayudando a identificar qué, cuándo y por qué se han realizado determinados cambios.
1. Estructura Básica de SemVer
El formato principal de SemVer se compone de tres segmentos:
MAJOR.MINOR.PATCH
- MAJOR: Se incrementa cuando se realizan cambios incompatibles con versiones anteriores (por ejemplo, la eliminación o modificación de una API pública).
- MINOR: Se aumenta cuando se agregan nuevas funcionalidades de manera compatible.
- PATCH: Se incrementa al corregir errores sin afectar las funcionalidades existentes.
Etiquetas Adicionales
- Pre-release: Indica versiones inestables, por ejemplo,
1.0.0-beta.1. - Build Metadata: Añade información adicional de compilación, como en
1.0.0+20230901.
2. Reglas para Incrementar Versiones
Al actualizar una versión, es fundamental distinguir entre los diferentes tipos de cambios:
Versión MAJOR (X.y.z → X+1.0.0):
Se utiliza cuando se introducen cambios que rompen la compatibilidad con versiones anteriores.
Ejemplo: Modificar o eliminar una API de forma incompatible.Versión MINOR (x.Y.z → x.Y+1.0):
Se incrementa al introducir nuevas funcionalidades sin afectar la compatibilidad.
Ejemplo: Agregar un método opcional a una clase.Versión PATCH (x.y.Z → x.y.Z+1):
Se utiliza para corregir errores, preservando la compatibilidad con versiones anteriores.
Ejemplo: Arreglar un error en una función de cálculo.
3. Ejemplos Prácticos
Versión Inicial:
0.1.0
Indica la fase de desarrollo inicial donde el software puede sufrir cambios drásticos.Primera Versión Estable:
1.0.0
Marca el lanzamiento oficial cuando la API es considerada estable y está documentada.Actualización con Nueva Funcionalidad:
De1.0.0a1.1.0para incorporar mejoras sin romper compatibilidad.Corrección Crítica:
De1.1.0a1.1.1para solucionar errores puntuales.Cambio Incompatible:
De1.1.1a2.0.0cuando se realizan modificaciones que requieren cambios en el código del consumidor.
4. La Importancia de Mantener un CHANGELOG
Un CHANGELOG es un registro sistemático y estructurado que documenta de manera cronológica cada cambio, mejora y corrección en el software. Su incorporación al proceso de SemVer proporciona un contexto narrativo, detallando el "porqué" y el "cómo" detrás de cada versión.
Beneficios Clave del CHANGELOG
Claridad en la Comunicación:
Complementa los números de versión de SemVer con descripciones detalladas de los cambios implementados.Trazabilidad y Historial:
Permite rastrear la evolución del software a lo largo del tiempo, facilitando la depuración y la revisión histórica.Transparencia Interna y Externa:
Informa tanto a los desarrolladores como a los usuarios finales sobre las mejoras y cambios realizados, fortaleciendo la confianza en el proceso de actualización.Soporte a la Automatización:
Herramientas integradas pueden actualizar el CHANGELOG de forma automática al seguir convenciones de commits, como Conventional Commits.
Mejores Prácticas para un CHANGELOG Efectivo
Estructuración Clara:
Organiza el CHANGELOG por versiones, empezando por la más reciente. Dentro de cada versión, clasifica los cambios en secciones como "Añadido", "Modificado", "Corregido" y "Notas de Deprecación".Actualización Continua:
Registra los cambios a medida que se van implementando para capturar detalles precisos y evitar omisiones.Integración con el Proceso de Versionado:
Vincula cada entrada del CHANGELOG con commits específicos y números de versión, facilitando la sincronización entre lo que se comunica y lo que se versiona.Comunicación Externa:
Publica el CHANGELOG en el repositorio del proyecto y en las notas de lanzamiento para que usuarios y colaboradores comprendan la evolución del software.
Ejemplo Básico de CHANGELOG
# CHANGELOG
## [2.0.0] - 2025-04-01
### Añadido
- Nueva función para exportación de datos.
### Modificado
- Actualización del sistema de autenticación (rompe compatibilidad con versiones anteriores).
### Corregido
- Error en el módulo de notificaciones.
## [1.2.1] - 2025-03-15
### Corregido
- Solucionado el error en la actualización automática del perfil del usuario.
5. Herramientas para Generar CHANGELOG Automáticamente
Usar herramientas automatizadas facilita enormemente el mantenimiento de un CHANGELOG claro y actualizado. A continuación, se presentan algunas opciones destacadas:
Conventional Changelog
- Base de muchas herramientas automatizadas.
- Genera el
CHANGELOG.mda partir de commits con formato estándar. - Ejemplo:
npx conventional-changelog -p angular -i CHANGELOG.md -s
standard-version
- Automatiza el versionado y el changelog sin publicar a npm.
- Ideal para control manual con automatización parcial.
npm install --save-dev standard-version
npx standard-version
semantic-release
- Automatiza TODO: changelog, versionado, publicación en npm o GitHub.
- Requiere entorno CI/CD (ej: GitHub Actions).
npm install --save-dev semantic-release
release-it
- Personalizable, ideal para flujos mixtos.
npm install --save-dev release-it
npx release-it
6. Convenciones de Commit (Conventional Commits)
Estas convenciones estructuran los mensajes de commit para que puedan ser procesados automáticamente:
<tipo>(opcional: alcance): descripción
[opcional] cuerpo del mensaje
[opcional] notas de ruptura (BREAKING CHANGE)
Ejemplos:
feat(auth): agregar login con Google
fix(api): corregir error de serialización
docs(readme): actualizar instrucciones de uso
BREAKING CHANGE: se eliminó el endpoint /v1/user
Tipos más comunes:
| Tipo | Uso |
|---|---|
feat |
Nueva funcionalidad |
fix |
Corrección de errores |
docs |
Cambios en la documentación |
style |
Cambios de formato (espacios, comas, etc.) |
refactor |
Refactorización del código sin cambios de funcionalidad |
test |
Cambios relacionados a pruebas |
chore |
Tareas menores de mantenimiento |
7. Gestión de Dependencias
El versionado semántico también afecta cómo se definen y gestionan las dependencias en los proyectos:
Caret ( ^ ):
Permite actualizaciones de versiones MINOR y PATCH. Por ejemplo,^1.2.3abarca versiones de la serie1.x.x.Tilde ( ~ ):
Restringe las actualizaciones a parches solamente. Por ejemplo,~1.2.3asegura que se mantenga la versión1.2.x.
8. Herramientas Recomendadas
- semver: Librería para comparar y validar versiones.
- Conventional Commits: Estándar de mensajes para facilitar changelogs automáticos.
- GitHub Actions/GitLab CI: Automatiza versiones y publicaciones.
- semantic-release / standard-version / release-it: Para generar changelogs y manejar versiones automáticamente.
Tabla Resumen
| Aspecto | Descripción | Ejemplo |
|---|---|---|
| Formato Básico de SemVer | MAJOR.MINOR.PATCH | 2.4.1 |
| Versión MAJOR | Cambios incompatibles que rompen versiones anteriores | 1.1.1 → 2.0.0 |
| Versión MINOR | Nuevas funcionalidades sin romper compatibilidad | 1.0.0 → 1.1.0 |
| Versión PATCH | Correcciones de errores sin afectar la funcionalidad | 1.1.0 → 1.1.1 |
| Pre-release | Versión inestable para pruebas | 1.0.0-beta.1 |
| Build Metadata | Información adicional de compilación | 1.0.0+20230901 |
| Gestión de Dependencias | Uso de caret (^) para permitir MINOR y PATCH; tilde (~) para solo PATCH | ^1.2.3 y ~1.2.3 |
| CHANGELOG | Registro detallado de cambios, organizado por versiones y secciones claras | Ver ejemplo en el artículo |
| Convenciones de Commit | Estilo estructurado que facilita la generación automática de changelog | feat, fix, chore, etc. |
| Herramientas de Automatización | Facilitan la gestión de versiones y changelog | semantic-release, standard-version, release-it |
Conclusión
Integrar el Versionado Semántico con un CHANGELOG completo y bien documentado garantiza un proceso de actualización y mantenimiento de software más transparente y predecible. Adoptar ambas prácticas no solo mejora la organización interna y el flujo de trabajo, sino que también fortalece la comunicación con los usuarios al explicar de forma detallada cada cambio realizado. Utiliza esta guía para transformar tu estrategia de versionado y construir una base sólida para el crecimiento y la evolución de tu proyecto de software.