Кэш чатов
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
# Архитектура Offline-first для мессенджера Knot
|
||||
|
||||
## Обзор
|
||||
|
||||
Система кэширования истории чатов реализует паттерн **Offline-first** с использованием:
|
||||
- **Room** - локальная база данных
|
||||
- **Paging 3** - пагинация с RemoteMediator
|
||||
- **WorkManager** - фоновая синхронизация
|
||||
- **SignalR** - real-time обновления
|
||||
|
||||
## Компоненты
|
||||
|
||||
### 1. Data Layer
|
||||
|
||||
#### MessageEntity
|
||||
```kotlin
|
||||
@Entity(tableName = "messages")
|
||||
data class MessageEntity(
|
||||
@PrimaryKey val id: String,
|
||||
val chatId: String,
|
||||
val senderId: String,
|
||||
val content: String?,
|
||||
val sequenceId: Int,
|
||||
val createdAt: String,
|
||||
|
||||
// Поля синхронизации
|
||||
val syncStatus: SyncStatus, // SYNCED, SYNCING, FAILED
|
||||
val isDeletedLocally: Boolean, // Помечено на удаление
|
||||
val isEditedLocally: Boolean, // Помечено на редактирование
|
||||
val editedContent: String?, // Новое содержимое
|
||||
val lastUpdated: Long // Время последнего изменения
|
||||
)
|
||||
```
|
||||
|
||||
#### MessageDao
|
||||
Основные методы:
|
||||
- `getMessagesPagingSource()` - PagingSource для Paging 3
|
||||
- `upsertMessage()` - Вставка/обновление с разрешением конфликтов
|
||||
- `markAsDeletedLocally()` - Пометка на удаление
|
||||
- `markAsEditedLocally()` - Пометка на редактирование
|
||||
- `getPendingSyncMessages()` - Получение сообщений для синхронизации
|
||||
|
||||
### 2. Pagination (Paging 3)
|
||||
|
||||
#### MessageRemoteMediator
|
||||
Управляет загрузкой данных:
|
||||
- **REFRESH** - первая загрузка последних сообщений
|
||||
- **APPEND** - загрузка более старых сообщений (прокрутка вниз)
|
||||
- **PREPEND** - загрузка более новых сообщений (прокрутка вверх)
|
||||
|
||||
Логика:
|
||||
1. Проверяет наличие данных в Room
|
||||
2. При необходимости загружает из API
|
||||
3. Сохраняет в Room
|
||||
4. Paging читает из локальной базы
|
||||
|
||||
### 3. Background Sync (WorkManager)
|
||||
|
||||
#### MessageSyncWorker
|
||||
Обрабатывает отложенную синхронизацию:
|
||||
- Отправка новых сообщений (SYNCING)
|
||||
- Обновление отредактированных (isEditedLocally = true)
|
||||
- Удаление помеченных (isDeletedLocally = true)
|
||||
- Повтор при ошибках (FAILED)
|
||||
|
||||
Политика повторных попыток:
|
||||
- Экспоненциальная задержка
|
||||
- Максимум 3 попытки
|
||||
- Требуется подключение к сети
|
||||
|
||||
### 4. Real-time Updates (SignalR)
|
||||
|
||||
#### MessageSignalRHandler
|
||||
Обрабатывает события:
|
||||
- `new_message` - новое сообщение
|
||||
- `message_edited` - редактирование
|
||||
- `message_deleted` - удаление
|
||||
- `messages_read` - прочтение
|
||||
- `reaction_added/removed` - реакции
|
||||
|
||||
Все изменения сразу записываются в Room → UI обновляется через Flow
|
||||
|
||||
### 5. Repository
|
||||
|
||||
#### ChatRepositoryImpl
|
||||
Единая точка входа для ViewModel:
|
||||
- `getMessagesPaging()` - Paging 3 поток
|
||||
- `getMessagesFlow()` - простой Flow списка
|
||||
- `sendMessage()` - отправка с локальным сохранением
|
||||
- `deleteLocalMessage()` - локальное удаление
|
||||
- `editLocalMessage()` - локальное редактирование
|
||||
|
||||
## Conflict Resolution
|
||||
|
||||
Приоритет данных:
|
||||
1. **Сообщения в процессе отправки (SYNCING)** - локальные данные имеют приоритет
|
||||
2. **Сообщения в процессе редактирования** - локальные данные имеют приоритет
|
||||
3. **Все остальные случаи** - серверные данные имеют приоритет
|
||||
|
||||
## Схема работы
|
||||
|
||||
### Отправка сообщения
|
||||
```
|
||||
User → sendMessage() → Сохранение в Room (SYNCING) → UI показывает сообщение
|
||||
→ WorkManager планирует синхронизацию
|
||||
→ Отправка на сервер
|
||||
→ Обновление статуса (SYNCED)
|
||||
```
|
||||
|
||||
### Получение сообщений
|
||||
```
|
||||
UI ← getMessagesPaging() ← Room ← RemoteMediator ← API
|
||||
↑
|
||||
└─── SignalR обновления
|
||||
```
|
||||
|
||||
### Удаление сообщения
|
||||
```
|
||||
User → deleteLocalMessage() → Пометка (isDeletedLocally = true)
|
||||
→ WorkManager удаляет на сервере
|
||||
→ Удаление из Room
|
||||
```
|
||||
|
||||
## Использование
|
||||
|
||||
### Paging 3 в ViewModel
|
||||
```kotlin
|
||||
@HiltViewModel
|
||||
class ChatViewModel @Inject constructor(
|
||||
private val repository: ChatRepository
|
||||
) : ViewModel() {
|
||||
|
||||
val messages: Flow<PagingData<Message>> =
|
||||
repository.getMessagesPaging(chatId)
|
||||
.cachedIn(viewModelScope)
|
||||
}
|
||||
```
|
||||
|
||||
### Офлайн отправка
|
||||
```kotlin
|
||||
// Сообщение сразу появится в UI
|
||||
val message = repository.sendMessage(
|
||||
chatId = chatId,
|
||||
content = "Hello"
|
||||
)
|
||||
|
||||
// Синхронизация произойдёт в фоне
|
||||
```
|
||||
|
||||
## Миграции
|
||||
|
||||
При обновлении схемы БД используется миграция `MIGRATION_1_2`:
|
||||
- Добавляет поля синхронизации
|
||||
- Сохраняет существующие данные
|
||||
- Устанавливает значения по умолчанию
|
||||
|
||||
## Тестирование
|
||||
|
||||
### Юнит-тесты
|
||||
- MessageDao тесты
|
||||
- MessageRemoteMediator тесты
|
||||
- ChatRepositoryImpl тесты
|
||||
|
||||
### Интеграционные тесты
|
||||
- Синхронизация с сервером
|
||||
- Обработка конфликтов
|
||||
- WorkManager сценарии
|
||||
Reference in New Issue
Block a user