3.7 KiB
Синхронизация
Цель
Один аккаунт Balance может использоваться на нескольких устройствах:
iPhone
│
├── transaction A
├── category B
└── budget C
│
▼
BalanceServer
│
┌────┴────┐
▼ ▼
Android Mac
Cursor
Каждый клиент хранит cursor последнего успешно применённого server change.
Принцип:
cursor = 100
server:
101 A
102 B
103 C
pull(cursor=100)
→ A, B, C
→ new cursor = 103
Клиент должен сохранять cursor только после успешного применения соответствующего batch.
Sequence
Server sequence задаёт глобальный порядок изменений в sync stream.
Требования:
- монотонность;
- отсутствие уменьшения;
- однозначность порядка;
- корректность при concurrent requests.
Push
Push передаёт изменения клиента:
local mutations
↓
POST /v1/sync
↓
validation
↓
ownership check
↓
persist
↓
append to change stream
Retry не должен создавать две разные логические записи из одного client change.
Pull
Pull получает изменения после cursor.
Сервер не должен возвращать изменения другого пользователя.
При pagination клиент должен иметь возможность продолжить с нового cursor.
Conflict resolution
Клиенты Balance используют timestamp-based versioning.
Типовая проверка:
local.updatedAt < remote.updatedAt
Следовательно, server должен сохранять updatedAt достаточно точно и не подменять его произвольным временем каждого чтения.
При конкурентных writes сервер должен иметь однозначное правило победителя.
Deletions
Удаление создаёт tombstone/change:
{
"entity": "transaction",
"id": "...",
"deleted": true,
"updatedAt": "..."
}
Tombstone должен попасть в change stream так же, как обычное изменение.
Idempotency
Push может повториться из-за:
- timeout;
- network failure;
- app restart;
- retry after 401/token refresh.
Поэтому сервер должен обрабатывать повторную отправку безопасно.
Batch
Apple-клиент отправляет batches до 100 изменений и может делить крупные payloads.
Android должен использовать совместимый protocol.
Server не должен предполагать, что все изменения приходят одним запросом.
Reset/full sync
Если client потерял cursor или требует reset, сервер должен поддерживать предусмотренный текущим API механизм полного pull либо новый cursor baseline.
Нельзя молча трактовать неизвестный/просроченный cursor как актуальный.
Security
Sync endpoint всегда ограничен текущим authenticated user.
deviceId — идентификатор устройства, а не механизм авторизации.