Перейти к содержанию

Данные и схемы

Одна база PostgreSQL 17, три изолированные схемы: public, pm, blog. Каждая схема принадлежит одному сервису и мигрируется только его инструментом. Чтение чужих данных — исключительно через views в public (v_*). Архитектурное решение: adr/0003-schema-separation.md.

На какие вопросы отвечает

  • Где лежат данные пользователя, кошелька, блога — в какой схеме?
  • Кто имеет право писать в pm.* / public.* / blog.*?
  • Как Auth Center читает баланс и историю, если не пишет в pm.*?
  • Каким инструментом создавать миграцию для конкретной таблицы?
  • Какие views существуют и что через них видно?
  • Сколько таблиц в pm.* и за что каждая отвечает?

Три схемы: владелец, миграции, содержимое

Схема Владелец Инструмент миграций Что лежит
public.* Auth Center (Serverpod 3.4.8) serverpod generate + dart bin/main.dart --apply-migrations Пользователи, профили, KYC, PII (user_profile.encryptedPii), мини-апп каталог, NFC-метки, уведомления
pm.* Payment Manager (Node 22 + Drizzle 0.45.2) drizzle-kit migrate Платёжные интенты, маршруты, комиссии, лимиты, маппинг TB-аккаунтов, история, outbox
blog.* one_blog (SvelteKit + Drizzle) drizzle-kit migrate (в проекте one_blog) CMS «What's on»: locations, categories, post_groups, posts

Правило изоляции: никто не пишет в чужую схему. Serverpod не пишет в pm.* и blog.*; PM не пишет в public.* и blog.*; one_blog пишет только в blog.*. PM подключается с search_path=pm,public, но public.* использует только на чтение. Это даёт независимые релизы, раздельные права доступа и отсутствие миграционных конфликтов между сервисами.

Cross-schema только через views

Любое чтение данных другой схемы идёт через view в public, объявленный в projects/deploy/seeds/create-views.sql. Прямые запросы к чужой схеме (SELECT ... FROM pm.intent) из Auth Center запрещены.

View Источник Назначение
v_user_tb_accounts pm.tb_account_map TigerBeetle-аккаунты пользователя (Auth Center → баланс)
v_tx_history pm.tx_history История транзакций для приложения
v_blog_category blog.categories Категории блога для Serverpod-эндпоинта
v_blog_location blog.locations Локации блога
v_blog_post blog.post_groups (+ posts) Опубликованные посты «What's on»

Серверные обёртки сгенерированы как Serverpod-модели (v_blog_post доступен через protocol.dart и blog_endpoint.dart).

11 таблиц pm.*

Из projects/payment-manager/src/shared/schema.ts (pgSchema('pm')):

Таблица Назначение
service_key HMAC service-id + secret (см. adr/0002-single-hmac-auth.md)
tb_account_map Имя аккаунта ↔ детерминированный TB account id (uuidv5)
intent Платёжный интент + state machine (CREATED…SETTLED)
payment_route Маршрутизация интента по каналу/PSP
fee_rule Правила расчёта комиссий
intent_event Журнал переходов состояния интента
tx_history Денормализованная история транзакций
outbox_event Transactional outbox (события наружу)
psp_tx_map Маппинг внешних PSP-транзакций (Phase 1, IppsDriver)
limit_rule Правила лимитов
auth_policies Step-up политики (NONE/PIN/OTP/BIOMETRIC/KYC_UPLIFT)

Направления владения и чтения

graph LR
  AC[Auth Center<br/>Serverpod]
  PM[Payment Manager<br/>Drizzle]
  BL[one_blog<br/>Drizzle]

  AC -->|write| PUB[(public.*)]
  PM -->|write| PMS[(pm.*)]
  BL -->|write| BLG[(blog.*)]

  PMS -->|view| VPM[public.v_user_tb_accounts<br/>public.v_tx_history]
  BLG -->|view| VBL[public.v_blog_category<br/>public.v_blog_location<br/>public.v_blog_post]

  VPM -->|read| AC
  VBL -->|read| AC

Пример

Auth Center показывает баланс кошелька в приложении. Он не читает pm.tb_account_map напрямую, а делает Serverpod-запрос к view v_user_tb_accounts (получить TB account id), а сам баланс берёт через nginx→PM прокси GET /api/pm/accounts/{name}/balance. Запись денежных операций при этом всегда идёт через POST /intents в PM (write в TigerBeetle — только PM).

См. также