Архітектурні рішення

6 хв читання

Архітектурні проєктні рішення

Ця сторінка пояснює обґрунтування ключових архітектурних та технологічних рішень у 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, а не проблемами даних.