Architektonická rozhodnutí

10 min čtení

Architektonická designová rozhodnutí

Tato stránka vysvětluje důvody klíčových architektonických a technologických voleb v projektu PgArachne. Tato rozhodnutí byla učiněna s ohledem na výkon, bezpečnost a produktivitu vývojářů, přičemž byl kladen důraz na vysokou kompatibilitu s moderními AI agenty a jazykovými modely (LLM).

1. Proč PostgreSQL

PgArachne je záměrně postaven výhradně nad PostgreSQL a nesnaží se být databázově agnostický. Většina toho, co brána nabízí, není vlastní logika v Go, ale přímé využití vlastností PostgreSQL.

Proč PostgreSQL:

  • Vestavěný model oprávnění: Role, GRANT/REVOKE a oprávnění EXECUTE na funkcích jsou součástí databáze. PgArachne proto nepotřebuje vlastní autorizační vrstvu — pouze přepne roli pomocí SET LOCAL ROLE a o zbytek se postará PostgreSQL. Pravidla přístupu tak platí stejně pro API, psql i jakýkoli jiný nástroj.
  • Row-Level Security: Politiky na úrovni řádků se vyhodnocují podle role, za kterou volání probíhá. Izolace dat mezi uživateli nebo tenanty tak žije v databázi, ne v kódu brány.
  • Nativní JSON (jsonb): Kontrakt funkce(jsonb) → json přesně odpovídá tělu JSON-RPC požadavku a odpovědi. Parametry není třeba mapovat na typy ani generovat obálky — JSON prochází bránou beze změny.
  • Transakční záruky: Každé volání běží v jedné transakci s plnými ACID garancemi. Funkce může atomicky zapsat do více tabulek a při chybě se vše vrátí zpět, aniž by o tom brána musela cokoli vědět.
  • Výkonný programovací model: PL/pgSQL, SQL funkce a další procedurální jazyky umožňují zapsat byznysovou logiku tam, kde jsou data, bez přenosu mezivýsledků po síti.
  • LISTEN/NOTIFY: Real-time notifikace (rozhodnutí 5) vycházejí přímo z mechanismu PostgreSQL. Není potřeba externí message broker.
  • Introspekce systémového katalogu: Seznam volatelných metod, jejich popisy i schémata parametrů se čtou z pg_proc a komentářů funkcí. Z jediného zdroje pravdy tak vznikají capabilities, MCP tools/list i specifikace OpenAPI a nemohou se rozejít s realitou.
  • Ekosystém rozšíření: PostGIS, pgvector, TimescaleDB, pg_trgm a další rozšíření jsou okamžitě dostupná jako běžné SQL funkce, a tedy i jako API metody a nástroje pro AI agenty — bez jediného řádku Go kódu.
  • Otevřenost a vyspělost: Svobodná licence bez vendor lock-inu, desítky let ověřený provoz, aktivní vývoj a dostupnost u všech významných cloudových poskytovatelů i jako samostatně provozovaná instance.

Proč ne jiná databáze nebo databázově agnostická vrstva:

  • Nejmenší společný jmenovatel: Podpora více databází by znamenala vzdát se právě těch vlastností, na kterých PgArachne stojí — bezpečnostního modelu rolí, jsonb, LISTEN/NOTIFY a introspekce funkcí. Zbyla by tenká a méně bezpečná vrstva nad generickým SQL.
  • MySQL/MariaDB: Nemají nativní Row-Level Security ani obdobu LISTEN/NOTIFY a jejich podpora JSON a procedurální logiky je omezenější.
  • SQL Server a Oracle: Proprietární licencování a provozní náklady jsou v rozporu s cílem snadného, bezplatného nasazení jedním binárním souborem.
  • NoSQL databáze (dokumentové, key-value, sloupcové): Škálování a flexibilita schématu, kvůli kterým se NoSQL volí, pro PgArachne nejsou hlavním přínosem. Potřebné dokumentové úložiště pokrývá jsonb včetně indexace a dotazování, a to vedle relačních dat a ve stejné transakci. Naopak chybí to, na čem PgArachne stojí: serverové funkce volatelné s granulárním oprávněním EXECUTE pro konkrétní roli, Row-Level Security, introspekce funkcí z katalogu a jednotný dotazovací jazyk. Bez nich by musela brána autorizaci, validaci i popis API řešit sama — a přestala by být tenkou vrstvou.
  • SQLite: Jde o embedded databázi bez serverového modelu rolí a oprávnění, na kterém je celé zabezpečení PgArachne postaveno.

Důsledkem je, že PgArachne zůstává malý: bezpečnost, transakce, notifikace i objevování API deleguje na PostgreSQL a sám se stará pouze o překlad protokolů a autentizaci.

2. PostgreSQL funkce jako API rozhraní

PgArachne záměrně vystavuje databázové funkce namísto přímého přístupu k tabulkám.

Proč funkce:

  • Zapouzdření: Byznysová logika žije společně s daty v databázi – na jednom místě pro audit, verzování i zabezpečení.
  • Explicitní bezpečnost: Přes API jsou dostupné pouze funkce, kterým bylo explicitně uděleno oprávnění EXECUTE pro konkrétní roli.
  • Abstrakce: Validace vstupů, vypočtená pole a složité operace nad více tabulkami jsou před klientem skryty, což poskytuje čisté rozhraní.

Proč ne CRUD na úrovni tabulek:

  • Těsná vazba: Přímé vystavení tabulek váže vaše API na interní schéma databáze, což ztěžuje refaktorování databáze bez rozbití klientských aplikací.
  • Roztříštěnost byznysových pravidel: Logika pak končí rozdělená mezi databázová omezení a jakýkoli middleware použitý k filtrování HTTP požadavků.

3. Go vs. alternativy

PgArachne je napsán v jazyce Go, aby poskytoval nejlepší rovnováhu mezi výkonem a jednoduchostí nasazení.

Proč Go:

  • Statické binární soubory: Kompiluje se do jediného souboru bez externích závislostí. Nasazení je tak prosté jako zkopírování souboru na server.
  • Konkurence (concurrency): Gorutiny činí obsluhu tisíců souběžných SSE a databázových připojení jednoduchou a přímočarou.
  • Robustní standardní knihovna: Vestavěné knihovny pro HTTP, TLS a JSON jsou na produkční úrovni a nevyžadují žádné „node_modules" ani externí runtime prostředí.
  • Křížová kompilace: Snadno cílí na Linux, macOS i Windows (amd64 i arm64) z jakéhokoli vývojářského stroje.

Proč ne Node.js, PHP nebo Ruby:

  • Runtime prostředí: Tyto jazyky vyžadují instalaci specifického prostředí na každém cílovém stroji.
  • Efektivita: Single-threaded loop v Node.js nebo model process-per-request v PHP jsou méně efektivní pro udržování tisíců neaktivních SSE připojení.
  • Paměťová stopa: Go využívá výrazně méně paměti na jedno připojení než skriptovací jazyky.

Proč ne Rust:

  • Rychlost vývoje: Zatímco Rust nabízí extrémní výkon, jeho složitost (borrow checker) zpomaluje iterace u nástroje zaměřeného na I/O, kde je výkon Go již více než dostatečný.

Proč ne C/C++:

  • Bezpečnost: Manuální správa paměti přináší značná bezpečnostní rizika (přetečení bufferu) bez reálného přínosu k výkonu v aplikaci typu gateway.

4. JSON-RPC 2.0 vs. REST

PgArachne používá JSON-RPC 2.0 jako primární komunikační protokol místo tradičního RESTu.

Proč JSON-RPC 2.0:

  • Jeden koncový bod (endpoint): Veškerá komunikace probíhá přes POST /{prefix}/{databaze}/jsonrpc. Není třeba navrhovat složité struktury URL ani debatovat o sémantice HTTP metod.
  • Samostatné požadavky: Každý požadavek je kompletní JSON objekt (metoda + parametry + id). Tento formát je pro LLM a AI agenty snadno generovatelný a parsovatelný s vysokou spolehlivostí.
  • Standardizované zpracování chyb: Chybové kódy a zprávy jsou součástí specifikace, což eliminuje potřebu „vymýšlet" konvence HTTP stavových kódů pro byznysové chyby.
  • Dávkové požadavky (batching): Protokol nativně podporuje dávkové požadavky, což umožňuje provést více operací během jediného HTTP dotazu bez dodatečné práce.
  • Objevování (discovery): Endpoint capabilities poskytuje úplný popis API ve formátu, který AI agenti dokáží konzumovat a porozumět dostupným nástrojům bez halucinací.

Proč ne REST:

  • Složitost pro AI: Sémantika RESTu (GET/POST/PATCH/DELETE + parametry v URL + tělo požadavku) je rozprostřena na více místech, což AI agentům ztěžuje spolehlivé sestavení volání.
  • Únik schématu: CRUD nad tabulkami (styl PostgREST) často přímo odhaluje interní strukturu databáze. PgArachne záměrně vystavuje funkce, čímž udržuje byznysovou logiku zapouzdřenou v SQL.
  • Chybějící standardy: REST nenabízí žádný univerzální standard pro dávkové operace, křížové chybové obálky ani automatizované objevování API.

5. SSE (Server-Sent Events) vs. WebSockets

Pro real-time notifikace implementuje PgArachne Server-Sent Events (SSE).

Proč SSE:

  • Čisté HTTP: SSE je běžný HTTP protokol. Funguje přes proxy servery, load balancery a CDN bez speciální konfigurace „protocol upgrade".
  • Nativní podpora v prohlížeči: Rozhraní EventSource je vestavěné ve všech moderních prohlížečích a automaticky řeší znovupřipojení bez nutnosti externích knihoven.
  • Odpovídá sémantice NOTIFY: PostgreSQL příkaz NOTIFY je jednosměrný (server → klient), což přesně odpovídá charakteru SSE.
  • Multiplexování: Přes HTTP/2 mohou stovky SSE proudů sdílet jediné TCP spojení, což je extrémně efektivní.
  • Provozní jednoduchost: SSE spojení se v logách a monitorovacích nástrojích jeví jako běžné HTTP požadavky, což usnadňuje ladění a omezování provozu (rate limiting).

Proč ne WebSockets:

  • Zbytečná obousměrnost: Protože klient nikdy nepotřebuje posílat data zpět přes notifikační kanál, složitost WebSockets nepřináší žádný užitek.
  • Problémy s konektivitou: WebSockets jsou často blokovány nebo předčasně ukončovány korporátními firewally a některými cloudovými load balancery.
  • Vyšší režie: Přidává protokolární složitost (handshaky, ping/pong rámce), která není pro jednoduché streamování událostí nutná.

6. Struktura URL: /{prefix}/{databaze}/{endpoint}

PgArachne směruje všechny endpointy pod konfigurovatelný prefixový segment: /db/{databaze}/jsonrpc, /db/{databaze}/file, /db/{databaze}/sse, /db/{databaze}/mcp. Výchozí prefix je db a lze ho změnit přes proměnnou API_PREFIX.

Proč tato struktura:

  • Směrování přes reverse proxy: Jedna instance PgArachne může obsluhovat více databází. Reverse proxy (Nginx, Caddy, Traefik) může směrovat podle prefixu nebo názvu databáze bez nutnosti inspekce těla požadavku, což je klíčové pro load balancing a path-based pravidla.
  • Horizontální škálovatelnost: S názvem databáze v URL cestě lze provozovat více instancí PgArachne a směrovat provoz do konkrétních instancí podle databáze pomocí standardních proxy pravidel — bez sticky sessions nebo inspekce těla.
  • Multiplexování protokolů na databázi: Seskupení /jsonrpc, /file, /sse a /mcp pod stejný jmenný prostor /{prefix}/{databaze}/ umožňuje přirozeně aplikovat per-databázové ověřování, rate limiting a řízení přístupu na úrovni proxy.
  • Konfigurovatelný prefix: Nasazení, která již používají /api/ jako prefix ve své infrastruktuře, mohou nastavit API_PREFIX=api.
  • Pozorovatelnost: Agregátory logů a metrické systémy mohou seskupovat a filtrovat provoz podle názvu databáze přímo z URL bez parsování JSON těl.

Proč ne plochá struktura jako /api/{databaze}:

  • Nejednoznačnost protokolu: Jeden plochý endpoint nedokáže rozlišit provoz JSON-RPC, SSE a MCP na úrovni směrování — toto rozhodnutí by padlo do aplikační logiky nebo inspekce hlaviček.
  • Obtížnější rozšíření: Přidání nových protokolů (např. GraphQL, gRPC-gateway) by stejně vyžadovalo zavedení nových cest nejvyšší úrovně, takže strukturovaný jmenný prostor design dopředu připravuje.

7. MCP jako překladová vrstva, nikoli databázový protokol

PgArachne implementuje Model Context Protocol (MCP) jako tenkou překladovou vrstvu v Go serveru. PostgreSQL funkce o MCP nikdy nevědí — zůstávají jednoduchými funkcemi jsonb → json.

Proč překládat MCP na serveru:

  • Žádné změny existujících funkcí: Jakákoli funkce již vystavená přes JSON-RPC je okamžitě dostupná jako MCP nástroj. Žádné SQL změny, žádné přenasazení databázových objektů.
  • MCP je víc než jen nástroje: Protokol zahrnuje discovery metodu (server/discover), metadata verze protokolu a schopností posílaná s každým požadavkem, notifikace a rozšíření (resources, prompts) nad rámec prostých volání nástrojů. To jsou obavy na úrovni protokolu, které patří do Go, ne do SQL funkcí.
  • Bezpečnost zůstává na jednom místě: Autentizace, přepínání rolí a validace vstupů jsou již implementovány v Go. MCP endpoint tuto logiku používá beze změny.
  • Více protokolů, jeden backend: Stejnou PostgreSQL funkci lze volat přes JSON-RPC (z běžného klienta), MCP (z Claude Desktop nebo Cursor) nebo sledovat přes SSE. Databáze je protokolově neutrální.
  • Jednodušší SQL: Zpracování MCP obálek (server/discover, tools/list, obsluha notifikací) uvnitř PostgreSQL funkcí by vyžadovalo parsování složitých JSON struktur v PL/pgSQL, čímž by funkce byly obtížněji psatelné, testovatelné a udržovatelné.

Proč netlačit MCP do databáze:

  • Validace MCP transportu nepotřebuje databázi: server/discover a kontroly verze protokolu/hlaviček u každého požadavku jsou čisté protokolové zprávy. Otevírání databázového připojení pro ně plýtvá zdroji a přidává latenci.
  • SQL není správný nástroj pro protokolovou logiku: Chybové kódy JSON-RPC 2.0, směrování notifikací a správa idempotency klíčů jsou middleware záležitosti, nikoli datové záležitosti.

8. Export OpenAPI: virtuální cesty, nikoli nový protokol

GET /{prefix}/{databaze}/openapi.json (nebo .yaml) generuje dokument OpenAPI 3.1 popisující každou metodu, kterou smí autentizovaný volající vykonat. Skutečné volání stále probíhá výhradně přes jediný endpoint POST /{prefix}/{databaze}/jsonrpc — specifikace navíc uvádí každou metodu pod vlastní cestou, například /{prefix}/{databaze}/rpc/api.hello_world, ale tato cesta je pouze dokumentační a neexistuje jako HTTP route, kterou lze volat přímo.

Proč virtuální cesty pro jednotlivé metody:

  • Kompatibilita s nástroji: Swagger UI, Postman, Insomnia a většina generátorů kódu OpenAPI očekává jednu operaci na položku paths. Jediná cesta JSON-RPC jinak nedokáže reprezentovat N různých signatur metod způsobem, kterému tyto nástroje rozumí.
  • Žádné změny backendu nejsou nutné: Přidání skutečné route pro každou metodu by znamenalo druhý způsob volání každé funkce, s vlastní autentizací, zpracováním chyb a verzováním, které by bylo nutné udržovat v souladu s JSON-RPC. Generování cest čistě z capabilities() zachovává povrch protokolu přesně tak, jak je popsán v rozhodnutí 4, a přitom uspokojuje nástroje vyžadující schémata pro jednotlivé operace.
  • Samodokumentující se: Popis každé virtuální operace uvádí přesný požadavek JSON-RPC (metodu + parametry) potřebný k jejímu skutečnému zavolání, takže absencí skutečné route se nic neztrácí.

Proč je specifikace autentizovaná a filtrovaná podle role:

  • Konzistence se zbytkem API: Každý další endpoint (JSON-RPC, MCP tools/list) vždy odhaluje pouze metody, které smí role volajícího vykonat. Neautentizovaný, nefiltrovaný dokument OpenAPI by prozradil existenci a tvar parametrů funkcí, které daný volající ve skutečnosti nemůže zavolat.
  • Stejný mechanismus jako všude jinde: Go handler autentizuje pomocí stejné logiky Basic/JWT/API-token jako /jsonrpc a poté před generováním specifikace provede SET LOCAL ROLE — pgarachne.generate_openapi_spec() je SECURITY INVOKER právě proto, aby její vnitřní volání capabilities() tuto roli vidělo, stejně jako to již funguje u MCP tools/list.