Architektonische Design-Entscheidungen
Diese Seite erläutert die Beweggründe hinter den wichtigsten Architektur- und Technologieentscheidungen in PgArachne. Diese Entscheidungen wurden getroffen, um Leistung, Sicherheit und Entwicklerproduktivität zu priorisieren und gleichzeitig eine hohe Kompatibilität mit modernen AI- und LLM-Agenten sicherzustellen.
1. JSON-RPC 2.0 vs. REST
PgArachne verwendet JSON-RPC 2.0 als primäres Kommunikationsprotokoll anstelle von traditionellem REST.
Warum JSON-RPC 2.0:
- Einzelner Endpunkt: Die gesamte Kommunikation erfolgt über
POST /{prefix}/{datenbank}/jsonrpc. Es müssen keine komplexen URL-Strukturen entworfen oder HTTP-Verb-Semantiken debattiert werden. - Eigenständige Aufrufe: Jede Anfrage ist ein vollständiges JSON-Objekt (Methode + Parameter + ID). Dieses Format ist für LLMs und AI-Agenten trivial zu generieren und mit hoher Zuverlässigkeit zu parsen.
- Standardisierte Fehlerbehandlung: Fehlercodes und -meldungen sind Teil der Spezifikation, was die Notwendigkeit eliminiert, HTTP-Statuscode-Konventionen für Geschäftsfehler selbst zu „erfinden".
- Stapelverarbeitung (Batching): Das Protokoll unterstützt nativ Batch-Anfragen, was mehrere Operationen in einem einzigen HTTP-Roundtrip ohne zusätzliche Arbeit ermöglicht.
- Discovery: Der Capabilities-Endpunkt bietet eine vollständige Beschreibung der API in einem Format, das AI-Agenten konsumieren können, um verfügbare Werkzeuge ohne Halluzinationen zu verstehen.
Warum nicht REST:
- Komplexität für AI: REST-Semantiken (GET/POST/PATCH/DELETE + URL-Parameter + Body) sind über mehrere Stellen verteilt, was es AI-Agenten erschwert, Aufrufe zuverlässig zu konstruieren.
- Schema-Leckage: CRUD über Tabellen (wie bei PostgREST) gibt oft die interne Datenbankstruktur direkt über die API preis. PgArachne exponiert bewusst Funktionen und behält die Geschäftslogik in SQL gekapselt.
- Fehlende Standards: REST bietet keinen universellen Standard für Batch-Operationen, plattformübergreifende Fehlerformate oder automatisierte API-Erkennung.
2. SSE (Server-Sent Events) vs. WebSockets
Für Echtzeit-Benachrichtigungen implementiert PgArachne Server-Sent Events (SSE).
Warum SSE:
- Reines HTTP: SSE ist Standard-HTTP. Es funktioniert über Proxies, Load Balancer und CDNs ohne spezielle Konfiguration für „Protokoll-Upgrades".
- Native Browser-Unterstützung: Die
EventSource-API ist in allen modernen Browsern integriert und übernimmt die automatische Wiederverbindung ohne zusätzliche Client-Bibliotheken. - Entspricht NOTIFY-Semantik: Der PostgreSQL-Befehl
NOTIFYist unidirektional (Server zu Client), was perfekt auf SSE passt. - Multiplexing: Über HTTP/2 können hunderte von SSE-Streams eine einzige TCP-Verbindung teilen, was extrem effizient ist.
- Betriebliche Einfachheit: SSE-Verbindungen erscheinen als normale HTTP-Anfragen in Logs und Monitoring-Tools, was das Debugging und Rate Limiting vereinfacht.
Warum nicht WebSockets:
- Unnötige Bidirektionalität: Da der Client nie Daten über den Benachrichtigungskanal zurücksenden muss, bringt die Komplexität von WebSockets keinen Vorteil.
- Konnektivitätsprobleme: WebSockets werden oft von Unternehmens-Firewalls und einigen Cloud-Load-Balancern blockiert oder vorzeitig beendet.
- Höherer Overhead: Fügt Protokollkomplexität (Handshakes, Ping/Pong-Frames) hinzu, die für einfaches Event-Streaming nicht erforderlich ist.
3. Go vs. Alternativen
PgArachne ist in Go geschrieben, um die beste Balance zwischen Leistung und Bereitstellungs-Einfachheit zu bieten.
Warum Go:
- Statische Binärdateien: Kompiliert in eine einzige ausführbare Datei ohne externe Abhängigkeiten. Die Bereitstellung ist so einfach wie das Kopieren der Datei auf den Server.
- Nebenläufigkeit: Goroutinen in Go machen die Handhabung tausender gleichzeitiger SSE- und Datenbankverbindungen leichtgewichtig und unkompliziert.
- Robuste Standardbibliothek: Die integrierten Bibliotheken für HTTP, TLS und JSON sind für den Produktionseinsatz geeignet und benötigen keine „node_modules" oder externen Laufzeitumgebungen.
- Cross-Kompilierung: Erstellt problemlos Binärdateien für Linux, macOS und Windows (amd64 und arm64) von jedem Entwicklungsrechner aus.
Warum nicht Node.js, PHP oder Ruby:
- Laufzeitumgebungen: Diese Sprachen erfordern die Installation einer spezifischen Laufzeitumgebung auf jedem Zielrechner.
- Effizienz: Die Single-Threaded-Loop von Node.js oder das Modell „Prozess-pro-Anfrage" von PHP sind weniger effizient für die Aufrechterhaltung tausender inaktiver SSE-Verbindungen.
- Speicherbedarf: Go verbraucht deutlich weniger Speicher pro Verbindung als Skriptsprachen.
Warum nicht Rust:
- Entwicklungsgeschwindigkeit: Während Rust extreme Leistung bietet, verlangsamt seine Komplexität (Borrow Checker) die Iteration für ein I/O-orientiertes Werkzeug, bei dem die Leistung von Go bereits mehr als ausreichend ist.
Warum nicht C/C++:
- Sicherheit: Manuelle Speicherverwaltung birgt erhebliche Sicherheitsrisiken (Pufferüberläufe) ohne nennenswerten Leistungsgewinn in einer Gateway-Anwendung.
4. PostgreSQL-Funktionen als API-Oberfläche
PgArachne exponiert bewusst Datenbankfunktionen anstelle von Rohtabellen.
Warum Funktionen:
- Kapselung: Die Geschäftslogik befindet sich zusammen mit den Daten in der Datenbank – ein einziger Ort für Audit, Versionierung und Absicherung.
- Explizite Sicherheit: Über die API sind nur Funktionen zugänglich, denen explizit
EXECUTE-Berechtigungen für eine bestimmte Rolle erteilt wurden. - Abstraktion: Eingabevalidierung, berechnete Felder und komplexe Operationen über mehrere Tabellen sind vor dem Client verborgen, was eine saubere Schnittstelle ermöglicht.
Warum kein CRUD auf Tabellenebene:
- Enge Kopplung: Das direkte Exponieren von Tabellen bindet Ihre API an Ihr internes Datenbanklayout, was das Refactoring der Datenbank erschwert, ohne Client-Anwendungen zu beschädigen.
- Fragmentierung der Geschäftsregeln: Die Logik wird am Ende zwischen Datenbank-Constraints und jeglicher Middleware aufgeteilt, die zur Filterung von HTTP-Anfragen verwendet wird.
5. URL-Struktur: /{prefix}/{datenbank}/{endpunkt}
PgArachne leitet alle Endpunkte unter einem konfigurierbaren Präfix-Segment weiter:
/db/{datenbank}/jsonrpc, /db/{datenbank}/sse, /db/{datenbank}/mcp.
Der Präfix ist standardmäßig db und kann über API_PREFIX geändert werden.
Warum diese Struktur:
- Reverse-Proxy-Routing: Eine einzelne PgArachne-Instanz kann mehrere Datenbanken bedienen. Ein Reverse-Proxy (Nginx, Caddy, Traefik) kann nach Präfix oder Datenbankname routen, ohne den Request-Body zu inspizieren — entscheidend für Load Balancing und pfadbasierte Regeln.
- Horizontale Skalierbarkeit: Mit dem Datenbanknamen im URL-Pfad können mehrere PgArachne-Instanzen betrieben werden, wobei der Datenverkehr über Standard-Proxy-Regeln zu datenbankspezifischen Instanzen geleitet wird — ohne Sticky Sessions oder Body-Inspektion.
- Protokoll-Multiplexing pro Datenbank: Die Gruppierung von
/jsonrpc,/sseund/mcpunter demselben/{prefix}/{datenbank}/-Namensraum ermöglicht es, datenbankspezifische Authentifizierung, Rate Limiting und Zugriffskontrolle auf Proxy-Ebene anzuwenden. - Konfigurierbarer Präfix: Deployments, die bereits
/api/als Präfix in ihrer Infrastruktur verwenden, könnenAPI_PREFIX=apisetzen. Legacy-Clients werden über307 Temporary Redirectvon den alten Pfaden unterstützt. - Beobachtbarkeit: Log-Aggregatoren und Metriksysteme können den Datenverkehr direkt anhand des Datenbanknamens aus der URL gruppieren und filtern, ohne JSON-Bodies zu parsen.
Warum keine flache Struktur wie /api/{datenbank}:
- Protokoll-Mehrdeutigkeit: Ein einzelner flacher Endpunkt kann auf Routing-Ebene nicht zwischen JSON-RPC-, SSE- und MCP-Datenverkehr unterscheiden — diese Entscheidung fiele in die Anwendungslogik oder Header-Inspektion.
- Schwieriger zu erweitern: Das Hinzufügen neuer Protokolle (z. B. GraphQL, gRPC-Gateway) würde ohnehin neue Pfade erfordern, sodass der strukturierte Namensraum das Design zukunftssicher macht.
6. MCP als Übersetzungsschicht, nicht als Datenbankprotokoll
PgArachne implementiert das Model Context Protocol (MCP)
als dünne Übersetzungsschicht im Go-Server. PostgreSQL-Funktionen wissen nie von MCP — sie bleiben
einfache jsonb → json-Funktionen.
Warum MCP auf dem Server übersetzen:
- Keine Änderungen an bestehenden Funktionen: Jede bereits über JSON-RPC exponierte Funktion ist sofort als MCP-Tool verfügbar. Keine SQL-Änderungen, keine Neubereitstellung von Datenbankobjekten.
- MCP ist mehr als nur Tools: Das Protokoll umfasst einen Initialisierungs-Handshake, Ping, Benachrichtigungen und potenzielle zukünftige Erweiterungen. Das sind Protokoll-Belange, die in Go gehören, nicht in SQL-Funktionen.
- Sicherheit bleibt an einem Ort: Authentifizierung, Rollenwechsel und Eingabevalidierung sind bereits in Go implementiert. Der MCP-Endpunkt nutzt diese Logik unverändert.
- Mehrere Protokolle, ein Backend: Dieselbe PostgreSQL-Funktion kann über JSON-RPC (von einem regulären Client), MCP (von Claude Desktop oder Cursor) oder SSE aufgerufen werden. Die Datenbank ist protokoll-agnostisch.
- Einfacheres SQL: Die Verarbeitung von MCP-Envelopes in PostgreSQL-Funktionen würde das Parsen komplexer JSON-Strukturen in PL/pgSQL erfordern, was Funktionen schwerer schreibbar, testbar und wartbar machen würde.
Warum MCP nicht in die Datenbank verlagern:
- MCP-Handshake benötigt keine Datenbank:
initializeundpingsind reine Protokollnachrichten. Das Öffnen einer Datenbankverbindung dafür verschwendet Ressourcen und erhöht die Latenz. - SQL ist das falsche Werkzeug für Protokolllogik: JSON-RPC-2.0-Fehlercodes, Benachrichtigungs-Routing und Idempotency-Key-Verwaltung sind Middleware-Belange, keine Daten-Belange.