docs
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
# `db/` — доступ к данным
|
||||
|
||||
- **`models.rs`** — табличные структуры (`#[derive(FromRow)]`: `User`, `Subscription`,
|
||||
`VpnNode`, `PromoCode`) и производные DTO, которые уходят напрямую в JSON API
|
||||
(`ProfileData`, `SubscriptionData`, `ReferralData`).
|
||||
- **`repository.rs`** — трейт [`AppRepository`] (единая граница между `modules::*::service`
|
||||
и Postgres) и его единственная реализация `PgRepository`. Сервисы модулей зависят
|
||||
только от трейта (`Arc<dyn AppRepository>`), не от конкретной СУБД.
|
||||
|
||||
## Почему трейт, а не просто `PgPool` напрямую в сервисах
|
||||
|
||||
`AppRepository` — это явная граница: каждый метод — одна осмысленная доменная операция
|
||||
(`consume_ticket_and_reward`, `apply_promo_code`, `buy_subscription`), а не сырой SQL,
|
||||
размазанный по сервисам. Это также даёт возможность подменить реализацию в тестах без
|
||||
БД, хотя на практике сейчас (см. `tests/db_integration_test.rs`) тесты гоняются через
|
||||
`#[sqlx::test]` на реальном (эфемерном) Postgres, а не через мок трейта.
|
||||
|
||||
## Транзакционные инварианты
|
||||
|
||||
Там, где над данными возможна гонка при параллельных запросах, используется
|
||||
`pool.begin()` + явная блокировка строки:
|
||||
|
||||
| Операция | Защита |
|
||||
|---|---|
|
||||
| `consume_ticket_and_reward` (билет Cyberhack) | `SELECT ... FOR UPDATE` по юзеру |
|
||||
| `apply_promo_code` | `SELECT ... FOR UPDATE` по промокоду + `UNIQUE(promo_id, user_id)` |
|
||||
| `reward_referrer` | `UPDATE ... RETURNING` атомарно проверяет и переводит статус реферала |
|
||||
| Инвойсы | `invoices.external_id` — `UNIQUE`, поэтому один tx-хеш нельзя засчитать дважды |
|
||||
|
||||
## Модели, для которых важно помнить о NULL
|
||||
|
||||
`User.tg_id` и `User.seed_hash` — взаимоисключающе опциональны: обычный юзер имеет
|
||||
`tg_id`, анонимный Ghost-юзер — `seed_hash`. Код, который трактует отсутствие `tg_id`
|
||||
как «анонимный юзер» (например, запрет триала), находится в
|
||||
[`billing::service::activate_trial`](../modules/billing/README.md).
|
||||
Reference in New Issue
Block a user