Decisões de Arquitetura
Decisões de Design Arquitetônico
Esta página explica o raciocínio por trás das principais escolhas tecnológicas e arquiteturais do PgArachne. Estas decisões foram tomadas para priorizar performance, segurança e produtividade do desenvolvedor, garantindo ao mesmo tempo alta compatibilidade com agentes de IA e modelos LLM modernos.
1. Por que PostgreSQL
O PgArachne é construído deliberadamente apenas sobre o PostgreSQL e não tenta ser agnóstico em relação ao banco de dados. A maior parte do que o gateway oferece não é lógica própria em Go, mas o uso direto de recursos do PostgreSQL.
Por que PostgreSQL:
- Modelo de permissões integrado: Papéis,
GRANT/REVOKEe permissõesEXECUTEem funções fazem parte do banco de dados. Por isso, o PgArachne não precisa de uma camada de autorização própria — ele apenas troca o papel comSET LOCAL ROLEe o PostgreSQL cuida do resto. As regras de acesso valem, assim, da mesma forma para a API, opsqle qualquer outra ferramenta. - Row-Level Security: As políticas em nível de linha são avaliadas de acordo com o papel em nome do qual a chamada é executada. O isolamento de dados entre usuários ou tenants vive no banco de dados, e não no código do gateway.
- JSON nativo (
jsonb): O contratofunção(jsonb) → jsoncorresponde exatamente ao corpo da requisição e da resposta JSON-RPC. Não é preciso mapear parâmetros para tipos nem gerar envelopes — o JSON atravessa o gateway sem alterações. - Garantias transacionais: Cada chamada é executada em uma única transação com garantias ACID completas. Uma função pode gravar atomicamente em várias tabelas e, em caso de erro, tudo é revertido, sem que o gateway precise saber de nada.
- Modelo de programação poderoso: PL/pgSQL, funções SQL e outras linguagens procedurais permitem escrever a lógica de negócio onde estão os dados, sem transferir resultados intermediários pela rede.
LISTEN/NOTIFY: As notificações em tempo real (decisão 5) vêm diretamente do mecanismo do PostgreSQL. Não é necessário um message broker externo.- Introspecção do catálogo do sistema: A lista de métodos chamáveis, suas descrições e os esquemas de parâmetros são lidos de
pg_proce dos comentários das funções. De uma única fonte da verdade surgemcapabilities, otools/listdo MCP e a especificação OpenAPI, que não podem divergir da realidade. - Ecossistema de extensões: PostGIS, pgvector, TimescaleDB,
pg_trgme outras extensões ficam imediatamente disponíveis como funções SQL comuns e, portanto, também como métodos da API e ferramentas para agentes de IA — sem uma única linha de código Go. - Abertura e maturidade: Licença livre sem vendor lock-in, décadas de operação comprovada, desenvolvimento ativo e disponibilidade em todos os principais provedores de nuvem, e também como instância autogerenciada.
Por que não outro banco de dados ou uma camada agnóstica:
- Menor denominador comum: Suportar vários bancos de dados significaria abrir mão justamente dos recursos sobre os quais o PgArachne se apoia — o modelo de segurança por papéis, o
jsonb, oLISTEN/NOTIFYe a introspecção de funções. Sobraria uma camada fina e menos segura sobre SQL genérico. - MySQL/MariaDB: Não têm Row-Level Security nativo nem equivalente ao
LISTEN/NOTIFY, e o suporte a JSON e a lógica procedural são mais limitados. - SQL Server e Oracle: O licenciamento proprietário e os custos operacionais contradizem o objetivo de uma implantação simples e gratuita com um único binário.
- Bancos NoSQL (documentos, chave-valor, colunares): A escalabilidade e a flexibilidade de esquema pelas quais se escolhe NoSQL não são o principal benefício para o PgArachne. O armazenamento de documentos necessário é coberto pelo
jsonb, incluindo indexação e consultas, ao lado dos dados relacionais e na mesma transação. Em contrapartida, falta justamente aquilo em que o PgArachne se apoia: funções no servidor chamáveis com permissão granularEXECUTEpara um papel específico, Row-Level Security, introspecção de funções a partir do catálogo e uma linguagem de consulta unificada. Sem isso, o gateway teria de resolver sozinho a autorização, a validação e a descrição da API — e deixaria de ser uma camada fina. - SQLite: É um banco de dados embarcado sem modelo de servidor com papéis e permissões, no qual toda a segurança do PgArachne é construída.
Como consequência, o PgArachne continua pequeno: delega ao PostgreSQL a segurança, as transações, as notificações e a descoberta da API e cuida apenas da tradução de protocolos e da autenticação.
2. Funções PostgreSQL como Superfície da API
O PgArachne expõe deliberadamente funções do banco de dados em vez de tabelas diretamente.
Por que funções:
- Encapsulamento: A lógica de negócio reside com os dados no banco de dados — um único lugar para auditoria, versionamento e segurança.
- Segurança explícita: Apenas as funções com permissões
EXECUTEpara um papel específico são acessíveis. - Abstração: Validação, campos calculados e operações complexas ficam ocultos para o cliente.
Por que não CRUD em nível de tabela:
- Acoplamento forte: Expor tabelas vincula a API ao esquema interno, dificultando refatorações.
- Fragmentação de regras de negócio: A lógica se divide entre restrições do banco de dados e middleware.
3. Go vs. Alternativas
O PgArachne é escrito em Go para oferecer o melhor equilíbrio entre performance e simplicidade de implantação.
Por que Go:
- Binários estáticos: Um único arquivo executável sem dependências externas.
- Concorrência: As goroutines gerenciam milhares de conexões simultâneas de forma eficiente.
- Biblioteca padrão robusta: HTTP, TLS e JSON integrados, de nível de produção.
- Compilação cruzada: Linux, macOS e Windows (amd64 e arm64) a partir de qualquer máquina.
Por que não Node.js, PHP ou Ruby:
- Runtimes: Requerem a instalação de um ambiente específico em cada máquina de destino.
- Eficiência: Menos eficientes para manter milhares de conexões SSE inativas.
- Consumo de memória: O Go utiliza significativamente menos memória por conexão.
Por que não Rust:
- Velocidade de desenvolvimento: A sua complexidade retarda a iteração para uma ferramenta de I/O onde o Go já é suficiente.
Por que não C/C++:
- Segurança: O gerenciamento manual de memória adiciona riscos sem ganhos relevantes em uma aplicação de gateway.
4. JSON-RPC 2.0 vs. REST
O PgArachne utiliza JSON-RPC 2.0 como seu protocolo de comunicação primário em vez do tradicional REST.
Por que JSON-RPC 2.0:
- Endpoint único: Toda a comunicação ocorre via
POST /{prefixo}/{banco_de_dados}/jsonrpc. Não há necessidade de projetar estruturas de URL complexas. - Chamadas autocontidas: Cada requisição é um objeto JSON completo (método + parâmetros + id), fácil de gerar e processar para LLMs.
- Tratamento de erros padronizado: Códigos e mensagens de erro fazem parte da especificação.
- Loteamento (Batching): O protocolo suporta nativamente requisições em lote em uma única viagem de ida e volta HTTP.
- Descoberta (Discovery): O endpoint de capacidades fornece uma descrição completa da API para agentes de IA sem alucinações.
Por que não REST:
- Complexidade para IA: A semântica REST está dispersa em vários lugares, dificultando a construção de chamadas confiáveis para agentes de IA.
- Exposição do esquema: O CRUD sobre tabelas muitas vezes expõe a estrutura interna. O PgArachne expõe funções deliberadamente.
- Ausência de padrões: O REST não oferece padrão universal para lotes, envelopes de erro ou descoberta automatizada.
5. SSE (Server-Sent Events) vs. WebSockets
Para notificações em tempo real, o PgArachne implementa Server-Sent Events (SSE).
Por que SSE:
- HTTP puro: O SSE é HTTP padrão, funcionando através de proxies e CDNs sem configurações especiais.
- Suporte nativo do navegador: A API
EventSourcegerencia a reconexão automática sem bibliotecas externas. - Corresponde à semântica do NOTIFY: O
NOTIFYdo PostgreSQL é unidirecional, adequando-se perfeitamente ao SSE. - Multiplexação: Sobre HTTP/2, centenas de fluxos SSE compartilham uma única conexão TCP.
- Simplicidade operacional: As conexões SSE aparecem como requisições HTTP normais nos logs.
Por que não WebSockets:
- Bidirecionalidade desnecessária: O cliente nunca precisa enviar dados pelo canal de notificação.
- Problemas de conectividade: Frequentemente bloqueados por firewalls corporativos e alguns balanceadores de carga em nuvem.
- Maior sobrecarga: Handshakes e frames de ping/pong desnecessários para o simples streaming de eventos.
6. Estrutura de URL: /{prefixo}/{banco_de_dados}/{endpoint}
O PgArachne roteia todos os endpoints sob um segmento de prefixo configurável:
/db/{banco_de_dados}/jsonrpc, /db/{banco_de_dados}/file, /db/{banco_de_dados}/sse, /db/{banco_de_dados}/mcp.
O prefixo padrão é db e pode ser alterado via API_PREFIX.
Por que esta estrutura:
- Roteamento por proxy reverso: Uma única instância do PgArachne pode servir múltiplos bancos de dados. Um proxy reverso pode rotear por prefixo ou nome do banco de dados sem inspecionar o corpo da requisição, o que é essencial para balanceamento de carga.
- Escalabilidade horizontal: Com o nome do banco de dados no caminho da URL, é possível executar múltiplas instâncias do PgArachne e direcionar o tráfego por banco de dados usando regras de proxy padrão, sem sessões persistentes.
- Multiplexação de protocolos por banco de dados: Agrupar
/jsonrpc,/file,/ssee/mcpsob o mesmo namespace/{prefixo}/{banco_de_dados}/permite aplicar autenticação, rate limiting e controle de acesso por banco de dados no nível do proxy. - Prefixo configurável: Deployments que já usam
/api/podem configurarAPI_PREFIX=api. - Observabilidade: Sistemas de log e métricas podem agrupar o tráfego por nome de banco de dados diretamente da URL sem analisar corpos JSON.
Por que não uma estrutura plana como /api/{banco_de_dados}:
- Ambiguidade de protocolo: Um único endpoint plano não consegue distinguir o tráfego JSON-RPC, SSE e MCP no nível de roteamento.
- Mais difícil de estender: Adicionar novos protocolos exigiria de qualquer forma novas rotas; portanto, o namespace estruturado prepara o design para o futuro.
7. MCP como Camada de Tradução, não como Protocolo de Banco de Dados
O PgArachne implementa o Model Context Protocol (MCP)
como uma fina camada de tradução no servidor Go. As funções PostgreSQL nunca têm conhecimento do MCP —
permanecem simples funções jsonb → json.
Por que traduzir MCP no servidor:
- Sem alterações nas funções existentes: Qualquer função já exposta via JSON-RPC fica instantaneamente disponível como tool MCP. Sem alterações SQL.
- MCP é mais do que apenas tools: O protocolo inclui um método de descoberta (
server/discover), metadados de versão do protocolo e de capacidades em cada requisição, notificações e extensões (resources, prompts) além de simples chamadas de tools — preocupações de nível de protocolo que pertencem ao Go. - A segurança permanece em um único lugar: Autenticação, troca de papel e validação já estão implementados em Go. O endpoint MCP reutiliza essa lógica sem alterações.
- Múltiplos protocolos, um backend: A mesma função PostgreSQL pode ser chamada via JSON-RPC, MCP ou SSE. O banco de dados é agnóstico ao protocolo.
- SQL mais simples: Processar envelopes MCP (
server/discover,tools/list, tratamento de notificações) em PostgreSQL exigiria analisar estruturas JSON complexas em PL/pgSQL, tornando as funções mais difíceis de escrever, testar e manter.
Por que não levar MCP ao banco de dados:
- A validação de transporte MCP não requer o banco de dados:
server/discovere as verificações de versão de protocolo/cabeçalhos em cada requisição são mensagens de protocolo puras. Abrir uma conexão para elas desperdiça recursos. - SQL é a ferramenta inadequada para lógica de protocolo: Códigos de erro JSON-RPC, roteamento de notificações e gerenciamento de chaves de idempotência são preocupações de middleware, não de dados.
8. Exportação de OpenAPI: Caminhos Virtuais, Não um Novo Protocolo
GET /{prefix}/{database}/openapi.json (ou .yaml) gera um documento OpenAPI 3.1
descrevendo todos os métodos que o chamador autenticado pode executar. A invocação real ainda acontece
exclusivamente através do endpoint único POST /{prefix}/{database}/jsonrpc — a especificação
lista adicionalmente cada método sob seu próprio caminho, como /{prefix}/{database}/rpc/api.hello_world,
mas esse caminho é apenas documentação e não existe como uma rota HTTP que você possa chamar diretamente.
Por que caminhos virtuais por método:
- Compatibilidade com ferramentas: Swagger UI, Postman, Insomnia e a maioria dos geradores de código OpenAPI esperam uma operação por entrada em
paths. Um único caminho JSON-RPC não consegue, de outra forma, representar N assinaturas de métodos diferentes de uma forma que essas ferramentas entendam. - Nenhuma alteração de backend necessária: Adicionar uma rota real por método significaria uma segunda forma de invocar cada função, com sua própria superfície de autenticação, tratamento de erros e versionamento a ser mantida em sincronia com o JSON-RPC. Gerar os caminhos puramente a partir de
capabilities()mantém a superfície do protocolo exatamente como descrita na decisão 4, ao mesmo tempo que satisfaz as ferramentas que precisam de esquemas por operação. - Autodocumentado: A descrição de cada operação virtual detalha exatamente a requisição JSON-RPC (método + parâmetros) necessária para realmente chamá-la, portanto nada se perde por não haver uma rota real.
Por que a especificação é autenticada e filtrada por papel:
- Consistência com o restante da API: Todos os demais endpoints (JSON-RPC,
tools/listdo MCP) só revelam os métodos que o papel do chamador pode executar. Um documento OpenAPI não autenticado e não filtrado vazaria a existência e o formato dos parâmetros de funções que um determinado chamador não pode realmente invocar. - Mesmo mecanismo utilizado em todo o resto: O handler Go autentica com a mesma lógica Basic/JWT/token de API usada em
/jsonrpc, e então executaSET LOCAL ROLEantes de gerar a especificação —pgarachne.generate_openapi_spec()éSECURITY INVOKERespecificamente para que sua chamada interna acapabilities()veja esse papel, da mesma forma que otools/listdo MCP já funciona.