Архітектурні рішення
Архітектурні проєктні рішення
Ця сторінка пояснює обґрунтування ключових архітектурних та технологічних рішень у PgArachne. Ці рішення були прийняті, щоб надати пріоритет продуктивності, безпеці та продуктивності розробників, водночас забезпечуючи, що система залишається високо сумісною з сучасними AI та LLM-агентами.
1. JSON-RPC 2.0 проти REST
PgArachne використовує JSON-RPC 2.0 як основний протокол комунікації замість традиційного REST.
Чому JSON-RPC 2.0:
- Єдина точка входу: Уся комунікація відбувається через
POST /{prefix}/{database}/jsonrpc. Немає потреби проєктувати складні структури URL або сперечатися про семантику HTTP-методів. - Самодостатні виклики: Кожен запит — це повний JSON-об’єкт (метод + параметри + id). Цей формат тривіально генерується та парситься LLM та AI-агентами з високою надійністю.
- Стандартизована обробка помилок: Коди та повідомлення про помилки є частиною специфікації, що усуває потребу «вигадувати» конвенції HTTP-кодів статусу для бізнес-помилок.
- Пакетна обробка (batching): Протокол нативно підтримує пакетні запити, дозволяючи виконати кілька операцій (наприклад, декілька викликів функцій) за один HTTP-раундтрип без додаткової роботи.
- Виявлення (discovery): Ендпоінт можливостей (capabilities) надає повний опис API у форматі, який AI-агенти можуть споживати, щоб зрозуміти доступні інструменти без галюцинацій.
Чому не REST:
- Складність для AI: Семантика REST (GET/POST/PATCH/DELETE + параметри URL + тіло запиту) розкидана по кількох місцях, що ускладнює AI-агентам надійне формування викликів.
- Витік схеми: CRUD над таблицями (як у PostgREST) часто напряму розкриває внутрішню структуру бази даних в API. PgArachne свідомо надає доступ до функцій, зберігаючи бізнес-логіку інкапсульованою в SQL.
- Відсутність стандартів: REST не пропонує універсального стандарту для пакетних операцій, уніфікованих обгорток помилок або автоматизованого виявлення API.
2. SSE (Server-Sent Events) проти WebSocket
Для сповіщень у реальному часі PgArachne реалізує Server-Sent Events (SSE).
Чому SSE:
- Звичайний HTTP: SSE — це стандартний HTTP. Він працює через проксі, балансувальники навантаження та CDN без спеціальної конфігурації «оновлення протоколу».
- Нативна підтримка в браузерах: API
EventSourceвбудований в усі сучасні браузери та обробляє автоматичне повторне підключення без будь-яких клієнтських бібліотек. - Відповідає семантиці NOTIFY:
NOTIFYу PostgreSQL є односпрямованим (сервер до клієнта), що ідеально відображається на SSE. - Мультиплексування: Через HTTP/2 сотні потоків SSE можуть спільно використовувати одне TCP-з’єднання, що робить це надзвичайно ефективним.
- Операційна простота: З’єднання SSE виглядають як звичайні HTTP-запити в логах та інструментах моніторингу, що полегшує їх діагностику та обмеження частоти запитів (rate limiting).
Чому не WebSocket:
- Непотрібна двоспрямованість: Оскільки клієнту ніколи не потрібно надсилати дані назад через канал сповіщень, складність WebSocket не дає жодної переваги.
- Проблеми з підключенням: WebSocket часто блокується або передчасно закривається корпоративними фаєрволами та деякими хмарними балансувальниками навантаження.
- Вищі накладні витрати: Додає складність протоколу (handshake, ping/pong фрейми), яка не потрібна для простого потокового передавання подій.
3. Go проти альтернатив
PgArachne написаний на Go, щоб забезпечити найкращий баланс між продуктивністю та простотою розгортання.
Чому Go:
- Статичні бінарні файли: Компілюється в один бінарний файл без зовнішніх залежностей. Розгортання настільки просте, як копіювання файлу на сервер.
- Конкурентність (concurrency): Горутини Go роблять обробку тисяч одночасних з’єднань SSE та бази даних легкою та прямолінійною.
- Надійна стандартна бібліотека: Вбудовані бібліотеки для HTTP, TLS та JSON мають промисловий рівень якості і не вимагають жодних «node_modules» чи зовнішніх середовищ виконання.
- Крос-компіляція: Легко націлюється на Linux, macOS та Windows (amd64 та arm64) з будь-якої машини розробника.
Чому не Node.js, PHP чи Ruby:
- Середовища виконання: Вони вимагають встановлення специфічного середовища виконання на кожній цільовій машині.
- Ефективність: Однопотоковий цикл Node.js або модель «процес на запит» PHP менш ефективні для підтримки тисяч неактивних з’єднань SSE.
- Обсяг пам’яті: Go використовує значно менше пам’яті на з’єднання, ніж скриптові мови.
Чому не Rust:
- Швидкість розробки: Хоча Rust пропонує надзвичайну продуктивність, його складність (borrow checker) сповільнює ітерації для інструменту, орієнтованого на I/O, де продуктивності Go вже більш ніж достатньо.
Чому не C/C++:
- Безпека: Ручне керування пам’яттю додає суттєві ризики безпеки (переповнення буфера) без відчутного приросту продуктивності в застосунку типу шлюзу.
4. Функції PostgreSQL як поверхня API
PgArachne свідомо надає доступ до функцій бази даних, а не до необроблених таблиць.
Чому функції:
- Інкапсуляція: Бізнес-логіка розташована разом з даними в базі даних — одне місце для аудиту, версіювання та захисту.
- Явна безпека: Через API доступні лише функції, яким явно надано права
EXECUTEдля конкретної ролі. - Абстракція: Валідація вхідних даних, обчислювані поля та складні операції з кількома таблицями приховані від клієнта, що забезпечує чистий інтерфейс.
Чому не CRUD на рівні таблиць:
- Тісне зв’язування: Прямий доступ до таблиць прив’язує ваш API до внутрішньої схеми бази даних, що ускладнює рефакторинг бази даних без порушення роботи клієнтів.
- Фрагментація бізнес-правил: Бізнес-логіка в кінцевому підсумку розділяється між обмеженнями бази даних та будь-яким middleware, що використовується для фільтрації HTTP-запитів.
5. Структура URL: /{prefix}/{database}/{endpoint}
PgArachne маршрутизує всі ендпоінти під конфігурованим сегментом префікса:
/db/{database}/jsonrpc, /db/{database}/sse, /db/{database}/mcp.
За умовчанням префікс — db, і його можна змінити через API_PREFIX.
Чому саме така структура:
- Маршрутизація через зворотний проксі: Один екземпляр PgArachne може обслуговувати кілька баз даних. Зворотний проксі (Nginx, Caddy, Traefik) може маршрутизувати за префіксом або назвою бази даних без перевірки тіла запиту, що є критично важливим для балансування навантаження та правил маршрутизації на основі шляхів.
- Горизонтальна масштабованість: З назвою бази даних у шляху URL можна запускати декілька екземплярів PgArachne та спрямовувати трафік до конкретних екземплярів по базі даних, використовуючи стандартні правила проксі — без sticky sessions чи перевірки тіла запиту.
- Мультиплексування протоколів на базу даних: Групування
/jsonrpc,/sseта/mcpпід одним простором імен/{prefix}/{database}/робить природним застосування автентифікації, обмеження частоти запитів та контролю доступу для кожної бази даних на рівні проксі. - Налаштовуваний префікс: Розгортання, що вже використовують
/api/як префікс у своїй інфраструктурі, можуть встановитиAPI_PREFIX=api, щоб відповідати своїм конвенціям. - Спостережуваність: Агрегатори логів та системи метрик можуть групувати та фільтрувати трафік за назвою бази даних безпосередньо з URL без парсингу тіл JSON.
Чому не плоска структура, як /api/{database}:
- Неоднозначність протоколу: Один плоский ендпоінт не може розрізнити трафік JSON-RPC, SSE та MCP на рівні маршрутизації — це рішення потрапляє в логіку застосунку або перевірку заголовків.
- Складніше розширювати: Додавання нових протоколів (наприклад, GraphQL, gRPC-gateway) все одно вимагатиме введення нових шляхів верхнього рівня, тож структурований простір імен убезпечує дизайн на майбутнє.
6. MCP як рівень трансляції, а не протокол бази даних
PgArachne реалізує Model Context Protocol (MCP)
як тонкий рівень трансляції в сервері на Go. Функції PostgreSQL ніколи не знають про MCP — вони
залишаються простими функціями jsonb → json.
Чому транслювати MCP на сервері:
- Нуль змін до існуючих функцій: Будь-яка функція, вже надана через JSON-RPC, миттєво стає доступною як інструмент MCP. Жодних змін SQL, жодного повторного розгортання об’єктів бази даних.
- MCP — це більше, ніж просто інструменти: Протокол включає квитування ініціалізації (handshake), ping, сповіщення та потенційні майбутні розширення (ресурси, промпти, sampling). Це проблеми на рівні протоколу, які належать до Go, а не до SQL-функцій.
- Безпека залишається в одному місці: Автентифікація, перемикання ролей та валідація вхідних даних уже реалізовані в Go. Ендпоінт MCP повторно використовує цю логіку без змін.
- Кілька протоколів, один backend: Ту саму функцію PostgreSQL можна викликати через JSON-RPC (зі звичайного клієнта), MCP (з Claude Desktop або Cursor) або SSE (для підписок на події). База даних не залежить від протоколу.
- Простіший SQL: Обробка конвертів MCP (
initialize,tools/list, обробка сповіщень) всередині функцій PostgreSQL вимагала б парсингу складних структур JSON у PL/pgSQL, що ускладнило б написання, тестування та підтримку функцій.
Чому не переносити MCP в базу даних:
- Квитування MCP не потребує бази даних:
initializeтаping— це чисті протокольні повідомлення. Відкриття з’єднання з базою даних для них марнує ресурси та додає латентність. - SQL — неправильний інструмент для протокольної логіки: Коди помилок JSON-RPC 2.0, маршрутизація сповіщень та керування ключами ідемпотентності є проблемами middleware, а не проблемами даних.