CQRS en Django: un read model desnormalizado sin Event Sourcing

CQRS en Django: un read model desnormalizado sin Event Sourcing

Los cuatro artículos anteriores de la serie pusieron las bases para que un sistema distribuido se mantenga coherente: Saga para orquestar workflows, Outbox para publicar eventos fiables, Inbox para consumirlos sin duplicados, Idempotency Keys para proteger la API. Queda una pregunta: ¿qué se hace con los eventos una vez publicados? La respuesta más frecuente es: se usan para construir vistas de lectura. Es exactamente lo que propone el patrón CQRS (Command Query Responsibility Segregation): separar el modelo de escritura del modelo de lectura, cuando ambos divergen lo suficiente como para que forzarlos en una misma estructura cueste más que duplicarlos. ...

5 de junio de 2026 · 8 min · Anthony
Idempotency Keys: evitar que un cliente pague dos veces

Idempotency Keys: evitar que un cliente pague dos veces

Los dos artículos anteriores trataron la idempotencia del lado evento: el patrón Outbox garantiza que un mensaje se publica al menos una vez, y el patrón Inbox garantiza que solo se consume una vez. Queda un tercer lugar donde aparece el mismo problema, más arriba: la propia API HTTP. Cuando un cliente lanza un POST /api/payments y su conexión se cae antes de recibir la respuesta, no sabe si el pago se creó o no. Si reintenta, arriesga pagar dos veces. Si no reintenta, arriesga no pagar nada. El patrón Idempotency Key, popularizado por Stripe y adoptado desde por la mayoría de APIs de pago, resuelve ese dilema poniendo el control del retry en manos del cliente. ...

4 de junio de 2026 · 8 min · Anthony
Patrón Inbox: consumir eventos sin reproducirlos dos veces

Patrón Inbox: consumir eventos sin reproducirlos dos veces

El artículo anterior sobre el Transactional Outbox estableció una garantía clara: todo evento escrito en base acabará siendo publicado. Esa garantía es intencionadamente at-least-once. Un consumidor puede recibir el mismo evento dos veces, tres veces, o más si la red se porta mal. El patrón Outbox nunca promete unicidad. La consecuencia es inmediata: si el consumidor aplica dos veces el efecto del mensaje, factura dos veces, envía dos correos, descuenta stock dos veces. La coherencia garantizada del lado productor se rompe del lado lector. ...

3 de junio de 2026 · 7 min · Anthony
Transactional Outbox: publicar eventos sin perder la coherencia

Transactional Outbox: publicar eventos sin perder la coherencia

Cuando un servicio modifica su base y quiere avisar al resto del sistema enviando un evento a Kafka, RabbitMQ o SQS, el código ingenuo se parece a esto: escribir en base y luego publicar. Si la publicación falla tras el commit, el evento se pierde. Si la publicación tiene éxito pero el commit falla, el evento habla de un estado que no existe. Ambos casos son la definición del dual-write problem. ...

2 de junio de 2026 · 8 min · Anthony
Patrón Saga: gestionar transacciones distribuidas sin rollback

Patrón Saga: gestionar transacciones distribuidas sin rollback

Una operación de negocio que toca varios servicios plantea una pregunta que SQL resuelve desde hace cincuenta años dentro de una sola base de datos: ¿qué pasa cuando un paso tiene éxito y el siguiente falla? Mientras todo vive en la misma base, BEGIN ... ROLLBACK basta. En cuanto llamas a un servicio externo, una API de terceros u otra base de datos, esa red de seguridad desaparece. El patrón Saga responde a esa pregunta. En lugar de intentar una transacción ACID imposible, divide la operación en pasos locales, cada uno acompañado de una transacción compensatoria que sabe deshacer su efecto. Si el paso 4 falla, las compensaciones de los pasos 1, 2 y 3 se reproducen en orden inverso. ...

1 de junio de 2026 · 8 min · Anthony
Permisos declarativos en DRF con rest_access_policy

Permisos declarativos en DRF con rest_access_policy

Los permisos en Django REST Framework funcionan, pero muestran sus límites en cuanto las reglas de acceso se vuelven moderadamente complejas. Varios roles, objetos pertenecientes a un usuario específico, acciones custom en un ViewSet: acabas con clases has_permission y has_object_permission que mezclan verificaciones heterogéneas, difíciles de leer y aún más difíciles de testear. rest_access_policy (paquete djangorestframework-access-policy) propone un enfoque diferente: declarar las reglas de acceso como statements, similar a las políticas IAM de AWS. El resultado es legible de un vistazo, testeable independientemente del ViewSet, y extensible sin reescribir toda la clase. ...

26 de mayo de 2026 · 7 min · Anthony
Hash, HMAC y cifrado: cómo proteger un token en Django

Hash, HMAC y cifrado: cómo proteger un token en Django

Una comparación == sobre un hash no es suficiente para elegir el mecanismo correcto. sha256, HMAC, hash salado, cifrado: cada enfoque ofrece garantías distintas. Entender cuáles son cambia concretamente la forma de almacenar y verificar un token en Django. Hash simple import hashlib token_hash = hashlib.sha256(token.encode()).hexdigest() Un hash simple es determinista: la misma entrada siempre produce la misma salida. No interviene ningún secreto del servidor. Es imposible recuperar el token original a partir del hash (sha256 es una función unidireccional). Pero si alguien conoce o adivina el token, puede recalcular el hash y comparar. ...

25 de mayo de 2026 · 4 min · Anthony
Django: save() no llama a full_clean() — ciclo de validación

Django: save() no llama a full_clean() — ciclo de validación

Llamar a obj.save() después de definir validators y un método clean() en el modelo da la impresión de que la validación está garantizada. No lo está. Django no llama a full_clean() durante un save(), y este comportamiento es deliberado. Entender por qué cambia la forma de diseñar la validación en un proyecto. Lo que save() hace realmente El ciclo de vida de un save() es más corto de lo que parece: ...

18 de mayo de 2026 · 5 min · Anthony
Timing attacks en Django con constant_time_compare

Timing attacks en Django con constant_time_compare

Una comparación == sobre un token parece inofensiva. En la práctica, filtra información medible: el tiempo de ejecución varía según cuántos caracteres coinciden. Ese es el principio de una timing attack, y basta para que un atacante reconstruya el token carácter a carácter. El problema: la comparación que se detiene demasiado pronto Python compara cadenas carácter a carácter y se detiene en cuanto encuentra una diferencia. def verify_token(request): user_token = request.GET.get('token') valid_token = "secret_abc123" if user_token == valid_token: return JsonResponse({'access': 'granted'}) return JsonResponse({'access': 'denied'}) En la práctica: ...

14 de mayo de 2026 · 3 min · Anthony
Vistas materializadas vs cache Django para queries lentas

Vistas materializadas vs cache Django para queries lentas

El instinto ante un endpoint de reporting lento suele ser el cache. Un @cache_page, un cache.set(), y el problema parece desaparecer hasta la próxima expiración. Este enfoque tiene una limitación estructural que las vistas materializadas PostgreSQL resuelven desde la raíz. El problema del cache en los endpoints analíticos El cache Django almacena el resultado de una vista Python. La consulta SQL costosa se ejecuta igualmente en cada expiración del cache. Para un informe construido sobre múltiples JOINs y agregaciones, eso significa que el primer usuario tras cada cache miss espera varios segundos. ...

13 de mayo de 2026 · 4 min · Anthony

Newsletter

Recibe los nuevos artículos directamente en tu bandeja de entrada.

Sin spam. Baja en un clic.