Android CI / Build and Test (push) Failing after 13m25s
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s
89 lines
6.1 KiB
Markdown
89 lines
6.1 KiB
Markdown
# Единый протокол синхронизации
|
|
|
|
Все клиенты (iOS, macOS, watchOS, Android, web) синхронизируются с
|
|
`BalanceServer` через один и тот же HTTP(S) API и один и тот же набор
|
|
инвариантов. Это сводка контракта; детальные описания на стороне
|
|
сервера — [`BalanceServer/docs/API.md`](../BalanceServer/docs/API.md) и
|
|
[`BalanceServer/docs/SYNC.md`](../BalanceServer/docs/SYNC.md).
|
|
Клиентские реализации — [`Balance/docs/SYNC.md`](../Balance/docs/SYNC.md)
|
|
и [`BalanceAndroid/docs/SYNC.md`](../BalanceAndroid/docs/SYNC.md).
|
|
|
|
## Эндпоинты
|
|
|
|
| Метод | Путь | Назначение |
|
|
| --- | --- | --- |
|
|
| `GET` | `/health` | Health check |
|
|
| `POST` | `/v1/auth/register` | Создание аккаунта |
|
|
| `POST` | `/v1/auth/login` | Вход, выдача access/refresh токенов |
|
|
| `POST` | `/v1/auth/refresh` | Ротация токенов |
|
|
| `POST` | `/v1/auth/logout` | Отзыв refresh-токена |
|
|
| `GET` | `/v1/me` | Текущий аккаунт |
|
|
| `POST` | `/v1/sync` | Push и pull изменений |
|
|
|
|
Нативные клиенты используют `Authorization: Bearer <access-token>`.
|
|
Web-клиент использует `HttpOnly`/`SameSite=Strict` cookie-сессию —
|
|
это единственное отличие транспорта, сама sync-семантика идентична.
|
|
|
|
## Основные инварианты
|
|
|
|
1. **Cursor** — каждый клиент хранит курсор последнего успешно
|
|
применённого изменения. Курсор сохраняется только после успешного
|
|
применения соответствующего batch, не раньше.
|
|
2. **Sequence** — сервер поддерживает глобальный монотонный порядок
|
|
изменений в change stream; порядок не должен уменьшаться и должен
|
|
быть однозначным при конкурентных запросах.
|
|
3. **Push** — клиент отправляет свои изменения (`POST /v1/sync`);
|
|
сервер валидирует, проверяет владение записью (ownership) и
|
|
добавляет изменение в change stream. Повторный push (retry) не
|
|
должен создавать вторую логическую запись из одного и того же
|
|
client-change (идемпотентность).
|
|
4. **Pull** — клиент запрашивает изменения после своего курсора;
|
|
сервер никогда не возвращает чужие данные (изоляция по аккаунту) и
|
|
поддерживает постраничный pull с продолжением от нового курсора.
|
|
5. **Conflict resolution** — last-write-wins по `updatedAt`
|
|
(`syncUpdatedAt` на Apple, `updatedAt` на Android/сервере). Сервер —
|
|
единственный источник правды в этом сравнении, поэтому не должен
|
|
произвольно перезаписывать `updatedAt` при каждом чтении.
|
|
6. **Удаления (tombstones)** — удаление сущности не является простым
|
|
`DELETE`; оно создаёт запись изменения вида
|
|
`{ "entity": ..., "id": ..., "deleted": true, "updatedAt": ... }`,
|
|
которая проходит через тот же change stream, что и обычные
|
|
изменения. На Apple это `SyncTombstone`/`modelContext.deleteForSync`,
|
|
на Android — `deletion_journal` через DAO-методы `deleteTransaction` /
|
|
`deleteCategory` / `deleteBudget`.
|
|
7. **Batch** — Apple-клиент отправляет батчи до 100 изменений и может
|
|
делить крупные payload'ы; сервер не должен предполагать, что все
|
|
изменения аккаунта приходят одним запросом. Android должен
|
|
оставаться совместимым с этим же батчингом.
|
|
8. **Reset/full sync** — если клиент теряет курсор, сервер должен
|
|
поддерживать предусмотренный API механизм полного pull или новый
|
|
baseline-курсор. Неизвестный/просроченный курсор нельзя молча
|
|
трактовать как актуальный.
|
|
|
|
## Синхронизируемые сущности
|
|
|
|
- **Транзакция** (доход/расход, сумма, дата, заметка, snapshot
|
|
категории);
|
|
- **Пользовательская категория** (имя, иконка/emoji, цвет);
|
|
- **Месячный бюджет** (категория, месяц, лимит);
|
|
- **Tombstone / deletion journal** запись для удалений.
|
|
|
|
Подробное пополевое сопоставление — [`docs/DATA_MODEL.md`](DATA_MODEL.md).
|
|
|
|
## Что нельзя менять без координации
|
|
|
|
Любое из следующих изменений — breaking change для всех клиентов
|
|
одновременно, даже если каждый подпроект по отдельности компилируется
|
|
и проходит собственные тесты:
|
|
|
|
- имена/типы JSON-полей в payload'ах `/v1/sync` и auth-эндпоинтов;
|
|
- HTTP-методы или пути эндпоинтов;
|
|
- семантика курсора/sequence;
|
|
- правило разрешения конфликтов (last-write-wins по `updatedAt`);
|
|
- формат tombstone-записи.
|
|
|
|
При таком изменении обнови одновременно: `BalanceServer` (API +
|
|
хендлеры + `docs/API.md`/`docs/SYNC.md`), `Balance/Shared/ServerSync.swift`
|
|
(+ `Balance/docs/SYNC.md`), `BalanceAndroid/.../sync/*`
|
|
(+ `BalanceAndroid/docs/SYNC.md`) и этот файл.
|