Decisiones de Arquitectura
Decisiones de Diseño Arquitectónico
Esta página explica el razonamiento detrás de las principales elecciones tecnológicas y de arquitectura en PgArachne. Estas decisiones se tomaron para priorizar el rendimiento, la seguridad y la productividad del desarrollador, asegurando al mismo tiempo una alta compatibilidad con agentes de IA y modelos LLM modernos.
1. Por qué PostgreSQL
PgArachne está construido deliberadamente solo sobre PostgreSQL y no intenta ser agnóstico respecto a la base de datos. La mayor parte de lo que ofrece la pasarela no es lógica propia en Go, sino el aprovechamiento directo de las capacidades de PostgreSQL.
Por qué PostgreSQL:
- Modelo de permisos integrado: Los roles,
GRANT/REVOKEy los permisosEXECUTEsobre funciones forman parte de la base de datos. Por eso PgArachne no necesita su propia capa de autorización: solo cambia de rol conSET LOCAL ROLEy PostgreSQL se encarga del resto. Las reglas de acceso se aplican igual a la API, apsqly a cualquier otra herramienta. - Row-Level Security: Las políticas a nivel de fila se evalúan según el rol con el que se realiza la llamada. El aislamiento de datos entre usuarios o tenants vive así en la base de datos, no en el código de la pasarela.
- JSON nativo (
jsonb): El contratofunción(jsonb) → jsoncoincide exactamente con el cuerpo de la petición y de la respuesta JSON-RPC. No hace falta mapear parámetros a tipos ni generar envoltorios: el JSON atraviesa la pasarela sin cambios. - Garantías transaccionales: Cada llamada se ejecuta en una única transacción con plenas garantías ACID. Una función puede escribir de forma atómica en varias tablas y, si hay un error, todo se revierte sin que la pasarela tenga que saber nada al respecto.
- Modelo de programación potente: PL/pgSQL, las funciones SQL y otros lenguajes procedimentales permiten escribir la lógica de negocio donde están los datos, sin transferir resultados intermedios por la red.
LISTEN/NOTIFY: Las notificaciones en tiempo real (decisión 5) se basan directamente en el mecanismo de PostgreSQL. No se necesita un message broker externo.- Introspección del catálogo del sistema: La lista de métodos invocables, sus descripciones y los esquemas de parámetros se leen de
pg_procy de los comentarios de las funciones. De esta única fuente de verdad surgencapabilities, eltools/listde MCP y la especificación OpenAPI, de modo que no pueden divergir de la realidad. - Ecosistema de extensiones: PostGIS, pgvector, TimescaleDB,
pg_trgmy otras extensiones están disponibles de inmediato como funciones SQL normales y, por tanto, también como métodos de API y herramientas para agentes de IA, sin una sola línea de código Go. - Apertura y madurez: Licencia libre sin vendor lock-in, décadas de uso probado en producción, desarrollo activo y disponibilidad en todos los grandes proveedores cloud, además de poder operarse como instancia propia.
Por qué no otra base de datos ni una capa agnóstica:
- Mínimo común denominador: Soportar varias bases de datos supondría renunciar precisamente a las características sobre las que se sostiene PgArachne: el modelo de seguridad de roles,
jsonb,LISTEN/NOTIFYy la introspección de funciones. Quedaría una capa delgada y menos segura sobre SQL genérico. - MySQL/MariaDB: No tienen Row-Level Security nativa ni un equivalente de
LISTEN/NOTIFY, y su soporte de JSON y de lógica procedimental es más limitado. - SQL Server y Oracle: El licenciamiento propietario y los costes operativos contradicen el objetivo de un despliegue sencillo y gratuito con un único binario.
- Bases de datos NoSQL (documentales, clave-valor, columnares): La escalabilidad y la flexibilidad de esquema por las que se elige NoSQL no son el principal beneficio para PgArachne. El almacenamiento documental necesario lo cubre
jsonb, con indexación y consultas, junto a los datos relacionales y en la misma transacción. En cambio, falta aquello sobre lo que se sostiene PgArachne: funciones del lado del servidor invocables con permisosEXECUTEgranulares por rol, Row-Level Security, introspección de funciones desde el catálogo y un lenguaje de consulta unificado. Sin ello, la pasarela tendría que resolver por sí misma la autorización, la validación y la descripción de la API, y dejaría de ser una capa delgada. - SQLite: Es una base de datos embebida sin un modelo de roles y permisos de servidor, sobre el que se construye toda la seguridad de PgArachne.
La consecuencia es que PgArachne se mantiene pequeño: delega en PostgreSQL la seguridad, las transacciones, las notificaciones y el descubrimiento de la API, y se ocupa únicamente de traducir protocolos y de la autenticación.
2. Funciones de PostgreSQL como Superficie de API
PgArachne expone deliberadamente funciones de base de datos en lugar de tablas puras.
Por qué funciones:
- Encapsulamiento: La lógica de negocio reside junto con los datos en la base de datos: un único lugar para auditar, versionar y asegurar.
- Seguridad Explícita: Solo las funciones a las que se les ha otorgado explícitamente permisos
EXECUTEpara un rol específico son accesibles a través de la API. - Abstracción: La validación de entradas, los campos calculados y las operaciones complejas están ocultos para el cliente, proporcionando una interfaz limpia.
Por qué no CRUD a nivel de tabla:
- Acoplamiento Estrecho: Exponer las tablas directamente vincula tu API a tu esquema interno de base de datos, dificultando refactorizarla sin romper a los clientes.
- Fragmentación de Reglas de Negocio: La lógica termina dividida entre las restricciones de la base de datos y cualquier middleware.
3. Go frente a Alternativas
PgArachne está escrito en Go para ofrecer el mejor equilibrio entre rendimiento y simplicidad de despliegue.
Por qué Go:
- Binarios Estáticos: Se compila en un único archivo ejecutable sin dependencias externas. El despliegue consiste simplemente en copiar el archivo al servidor.
- Concurrencia: Las goroutines de Go hacen que el manejo de miles de conexiones SSE y de base de datos simultáneas sea ligero y directo.
- Biblioteca Estándar Robusta: Las librerías integradas para HTTP, TLS y JSON son de grado de producción y no requieren «node_modules» ni entornos de ejecución externos.
- Compilación Cruzada: Genera fácilmente binarios para Linux, macOS y Windows (amd64 y arm64) desde cualquier máquina de desarrollo.
Por qué no Node.js, PHP o Ruby:
- Entornos de Ejecución: Requieren instalar un entorno específico en cada máquina de destino.
- Eficiencia: El bucle monohilo de Node o el modelo de proceso por petición de PHP son menos eficientes para mantener miles de conexiones SSE inactivas.
- Huella de Memoria: Go utiliza significativamente menos memoria por conexión.
Por qué no Rust:
- Velocidad de Desarrollo: Su complejidad (borrow checker) ralentiza la iteración para una herramienta orientada a I/O donde el rendimiento de Go ya es más que suficiente.
Por qué no C/C++:
- Seguridad: El manejo manual de la memoria añade riesgos significativos sin una ganancia de rendimiento relevante en una aplicación de pasarela.
4. JSON-RPC 2.0 frente a REST
PgArachne utiliza JSON-RPC 2.0 como su protocolo de comunicación principal en lugar del tradicional REST.
Por qué JSON-RPC 2.0:
- Endpoint Único: Toda la comunicación ocurre a través de
POST /{prefijo}/{base_de_datos}/jsonrpc. No es necesario diseñar estructuras de URL complejas ni debatir sobre la semántica de los verbos HTTP. - Llamadas Autocontenidas: Cada petición es un objeto JSON completo (método + parámetros + id). Este formato es trivial de generar y parsear para LLMs y agentes de IA con alta fiabilidad.
- Gestión de Errores Estandarizada: Los códigos y mensajes de error forman parte de la especificación, eliminando la necesidad de «inventar» convenciones de códigos de estado HTTP para errores de negocio.
- Loteado (Batching): El protocolo admite nativamente peticiones por lotes, lo que permite múltiples operaciones en un solo viaje de ida y vuelta HTTP sin trabajo extra.
- Descubrimiento (Discovery): El endpoint de capacidades proporciona una descripción completa de la API en un formato que los agentes de IA pueden consumir para entender las herramientas disponibles sin alucinaciones.
Por qué no REST:
- Complejidad para la IA: La semántica de REST (GET/POST/PATCH/DELETE + parámetros de URL + cuerpo) está dispersa en varios lugares, lo que dificulta que los agentes de IA construyan llamadas de forma fiable.
- Fuga de Esquema: El CRUD sobre tablas a menudo filtra la estructura interna de la base de datos directamente a través de la API. PgArachne expone funciones deliberadamente, manteniendo la lógica de negocio encapsulada en SQL.
- Falta de Estándares: REST no ofrece un estándar universal para operaciones por lotes, envoltorios de errores multiplataforma o descubrimiento automático de API.
5. SSE (Server-Sent Events) frente a WebSockets
Para las notificaciones en tiempo real, PgArachne implementa Server-Sent Events (SSE).
Por qué SSE:
- HTTP Puro: SSE es HTTP estándar. Funciona a través de proxies, balanceadores de carga y CDNs sin configuraciones especiales.
- Soporte Nativo del Navegador: La API
EventSourceestá integrada en todos los navegadores modernos y gestiona la reconexión automática sin librerías externas. - Coincide con la Semántica de NOTIFY: El comando
NOTIFYde PostgreSQL es unidireccional (servidor a cliente), lo que encaja perfectamente con SSE. - Multiplexación: Sobre HTTP/2, cientos de flujos SSE pueden compartir una única conexión TCP, lo que lo hace extremadamente eficiente.
- Simplicidad Operativa: Las conexiones SSE aparecen como peticiones HTTP normales en los logs y herramientas de monitorización, lo que facilita su depuración y la aplicación de límites de tasa.
Por qué no WebSockets:
- Bidireccionalidad Innecesaria: El cliente nunca necesita enviar datos de vuelta por el canal de notificaciones, por lo que la complejidad de WebSockets no aporta ningún beneficio.
- Problemas de Conectividad: Los WebSockets a menudo son bloqueados por firewalls corporativos y algunos balanceadores de carga en la nube.
- Mayor Sobrecarga: Añade complejidad al protocolo que no es necesaria para el simple streaming de eventos.
6. Estructura de URL: /{prefijo}/{base_de_datos}/{endpoint}
PgArachne enruta todos los endpoints bajo un segmento de prefijo configurable:
/db/{base_de_datos}/jsonrpc, /db/{base_de_datos}/file, /db/{base_de_datos}/sse, /db/{base_de_datos}/mcp.
El prefijo por defecto es db y puede cambiarse mediante API_PREFIX.
Por qué esta estructura:
- Enrutamiento por reverse proxy: Un único servidor PgArachne puede servir múltiples bases de datos. Un reverse proxy puede enrutar por prefijo o nombre de base de datos sin inspeccionar el cuerpo de la petición, lo que es clave para el balanceo de carga.
- Escalabilidad horizontal: Con el nombre de la base de datos en la ruta URL, se pueden ejecutar múltiples instancias de PgArachne y dirigir el tráfico a instancias específicas usando reglas de proxy estándar, sin sesiones persistentes.
- Multiplexado de protocolos por base de datos: Agrupar
/jsonrpc,/file,/ssey/mcpbajo el mismo espacio de nombres permite aplicar autenticación, rate limiting y control de acceso por base de datos a nivel de proxy. - Prefijo configurable: Los despliegues que ya usan
/api/pueden configurarAPI_PREFIX=api. - Observabilidad: Los sistemas de logs y métricas pueden agrupar y filtrar el tráfico por nombre de base de datos directamente desde la URL sin parsear cuerpos JSON.
Por qué no una estructura plana como /api/{base_de_datos}:
- Ambigüedad de protocolo: Un único endpoint plano no puede distinguir entre tráfico JSON-RPC, SSE y MCP a nivel de enrutamiento.
- Más difícil de extender: Añadir nuevos protocolos requeriría de todas formas nuevas rutas, por lo que el espacio de nombres estructurado prepara el diseño para el futuro.
7. MCP como Capa de Traducción, no como Protocolo de Base de Datos
PgArachne implementa el Model Context Protocol (MCP)
como una capa de traducción delgada en el servidor Go. Las funciones de PostgreSQL nunca saben de MCP —
siguen siendo simples funciones jsonb → json.
Por qué traducir MCP en el servidor:
- Sin cambios en funciones existentes: Cualquier función ya expuesta vía JSON-RPC está disponible instantáneamente como herramienta MCP. Sin cambios SQL ni redistribución de objetos de base de datos.
- MCP es más que solo herramientas: El protocolo incluye un método de descubrimiento (
server/discover), metadatos de versión de protocolo y capacidades en cada solicitud, notificaciones y extensiones (resources, prompts) más allá de simples llamadas a herramientas. Estas son preocupaciones de nivel de protocolo que pertenecen a Go, no a funciones SQL. - La seguridad permanece en un lugar: La autenticación, el cambio de rol y la validación de entradas ya están implementados en Go. El endpoint MCP reutiliza esta lógica sin cambios.
- Múltiples protocolos, un backend: La misma función PostgreSQL puede llamarse vía JSON-RPC (desde un cliente normal), MCP (desde Claude Desktop o Cursor) o SSE (para suscripciones a eventos). La base de datos es agnóstica al protocolo.
- SQL más simple: Procesar envoltorios MCP (
server/discover,tools/list, gestión de notificaciones) dentro de funciones PostgreSQL requeriría parsear estructuras JSON complejas en PL/pgSQL, haciendo las funciones más difíciles de escribir, probar y mantener.
Por qué no llevar MCP a la base de datos:
- La validación de transporte MCP no necesita base de datos:
server/discovery las comprobaciones de versión de protocolo/cabeceras en cada solicitud son mensajes de protocolo puros. Abrir una conexión de base de datos para ellos desperdicia recursos y aumenta la latencia. - SQL es la herramienta equivocada para la lógica de protocolo: Los códigos de error JSON-RPC 2.0, el enrutamiento de notificaciones y la gestión de claves de idempotencia son preocupaciones de middleware, no de datos.
8. Exportación de OpenAPI: Rutas Virtuales, no un Protocolo Nuevo
GET /{prefijo}/{base_de_datos}/openapi.json (o .yaml) genera un documento OpenAPI 3.1 que describe cada método que el llamante autenticado puede ejecutar. La invocación real sigue ocurriendo
exclusivamente a través del único endpoint POST /{prefijo}/{base_de_datos}/jsonrpc — la
especificación además enumera cada método bajo su propia ruta, como
/{prefijo}/{base_de_datos}/rpc/api.hello_world, pero esa ruta es solo documentación y no
existe como una ruta HTTP que se pueda llamar directamente.
Por qué rutas virtuales por método:
- Compatibilidad con herramientas: Swagger UI, Postman, Insomnia y la mayoría de los generadores de código OpenAPI esperan una operación por cada entrada de
paths. Una única ruta JSON-RPC no puede, de otro modo, representar N firmas de método distintas de una forma que estas herramientas entiendan. - Sin cambios en el backend: Añadir una ruta real por método significaría una segunda forma de invocar cada función, con su propia superficie de autenticación, gestión de errores y versionado que mantener en sincronía con JSON-RPC. Generar las rutas exclusivamente a partir de
capabilities()mantiene la superficie del protocolo exactamente como se describe en la decisión 4, a la vez que satisface a las herramientas que necesitan esquemas por operación. - Autodocumentado: La descripción de cada operación virtual detalla la petición JSON-RPC exacta (método + parámetros) necesaria para llamarla realmente, así que no se pierde nada por no tener una ruta real.
Por qué la especificación está autenticada y filtrada por rol:
- Coherencia con el resto de la API: Cualquier otro endpoint (JSON-RPC, MCP
tools/list) nunca revela más que los métodos que el rol del llamante puede ejecutar. Un documento OpenAPI sin autenticar ni filtrar filtraría la existencia y la forma de los parámetros de funciones que un llamante determinado no puede invocar realmente. - El mismo mecanismo que en el resto: El manejador en Go se autentica con la misma lógica Basic/JWT/token de API que
/jsonrpc, y luego ejecutaSET LOCAL ROLEantes de generar la especificación —pgarachne.generate_openapi_spec()esSECURITY INVOKERprecisamente para que su llamada interna acapabilities()vea ese rol, del mismo modo en que ya funcionatools/listde MCP.