Skip to main content

ТЗ: Разработка агента синхронизации данных (Sync Agent)

ТЗ: Разработка агента синхронизации данных (Sync Agent)

Источник файл Ожерельев В.А. ТЗ: Разработка агента синхронизации.md

Дата: 03.05.2026


1. Назначение

Агент синхронизации обеспечивает загрузку и актуализацию данных из Grace CRM в:

  • PostgreSQL (операционный слой)

  • OpenSearch (поисковый слой)

Система должна поддерживать:

  • near real-time обновление (webhooks)

  • инкрементальную синхронизацию

  • полную переиндексацию


2. Состав синхронизируемых сущностей

Обязательные сущности:

Сущность Описание
Projects Сделки
Clients Клиенты
Objects Объекты клиента (ключевая связь клиент -> актив)
Activities Активности
Tasks Задачи
Comments Комментарии
Calculations Расчёты / КП
Orders Заказы
Users Пользователи

3. API интеграция с CRM

Основные endpoints

GET /api/projects
GET /api/projects/{id}
GET /api/projects?updated_since=timestamp

GET /api/activities
GET /api/activities?project_id=
GET /api/activities?updated_since=timestamp

GET /api/clients
GET /api/clients/{id}

GET /api/tasks
GET /api/tasks?project_id=

GET /api/comments
GET /api/comments?entity_type=&entity_id=

GET /api/calculations
GET /api/calculations?project_id=

GET /api/orders
GET /api/orders?project_id=

GET /api/users

3.1 Объекты (обязательное расширение CRM)

⚠️ В рамках проекта требуется наличие сущности:

Objects (объекты клиента)

Если отсутствует -- требуется добавить API:

GET /api/objects
GET /api/objects/{id}
GET /api/objects?client_id=
GET /api/objects?updated_since=timestamp

Назначение:

  • связь Client -> Object -> Project

  • понимание контекста сделки

Пример:

  • клиент: застройщик

  • объект: ЖК "Северный"

  • проекты: сделки по этому ЖК


4. Архитектура Sync Agent

Компоненты:

  • API Connector

  • Sync Scheduler

  • Webhook Listener

  • Data Transformer

  • Indexer (OpenSearch)

  • Storage Writer (PostgreSQL)


5. Mapping схемы OpenSearch (JSON)

5.1 crm-projects

{
  "mappings": {
    "properties": {
      "project_id": {"type": "keyword"},
      "client_id": {"type": "keyword"},
      "object_id": {"type": "keyword"},
      "name": {"type": "text"},
      "status": {"type": "keyword"},
      "stage": {"type": "keyword"},
      "amount": {"type": "double"},
      "manager_id": {"type": "keyword"},
      "created_at": {"type": "date"},
      "updated_at": {"type": "date"},
      "last_activity_at": {"type": "date"},
      "activities_count": {"type": "integer"}
    }
  }
}

5.2 crm-objects (новый индекс)

{
  "mappings": {
    "properties": {
      "object_id": {"type": "keyword"},
      "client_id": {"type": "keyword"},
      "name": {"type": "text"},
      "type": {"type": "keyword"},
      "status": {"type": "keyword"},
      "location": {"type": "text"},
      "created_at": {"type": "date"},
      "updated_at": {"type": "date"}
    }
  }
}

5.3 crm-clients

{
  "mappings": {
    "properties": {
      "client_id": {"type": "keyword"},
      "name": {"type": "text"},
      "industry": {"type": "keyword"},
      "segment": {"type": "keyword"},
      "created_at": {"type": "date"},
      "updated_at": {"type": "date"}
    }
  }
}

5.4 crm-activities

{
  "mappings": {
    "properties": {
      "activity_id": {"type": "keyword"},
      "project_id": {"type": "keyword"},
      "type": {"type": "keyword"},
      "description": {"type": "text"},
      "result": {"type": "text"},
      "created_at": {"type": "date"}
    }
  }
}

5.5 crm-tasks

{
  "mappings": {
    "properties": {
      "task_id": {"type": "keyword"},
      "project_id": {"type": "keyword"},
      "assignee_id": {"type": "keyword"},
      "status": {"type": "keyword"},
      "due_date": {"type": "date"}
    }
  }
}

5.6 crm-comments

{
  "mappings": {
    "properties": {
      "comment_id": {"type": "keyword"},
      "entity_type": {"type": "keyword"},
      "entity_id": {"type": "keyword"},
      "text": {"type": "text"},
      "author_id": {"type": "keyword"},
      "created_at": {"type": "date"}
    }
  }
}
{
  "mappings": {
    "properties": {
      "calculation_id": {"type": "keyword"},
      "project_id": {"type": "keyword"},
      "amount": {"type": "double"},
      "version": {"type": "integer"},
      "created_at": {"type": "date"}
    }
  }
}

5.8 crm-orders

{
  "mappings": {
    "properties": {
      "order_id": {"type": "keyword"},
      "project_id": {"type": "keyword"},
      "amount": {"type": "double"},
      "status": {"type": "keyword"},
      "created_at": {"type": "date"}
    }
  }
}

6. Распределение данных между PostgreSQL и OpenSearch

Данный раздел определяет, какие данные и в каком виде сохраняются в:

  • PostgreSQL (операционный слой)

  • OpenSearch (витрины / search layer)

Ключевой принцип:

  • PostgreSQL -> нормализованные данные и состояние системы

  • OpenSearch -> денормализованные витрины для поиска и аналитики


6.0 Диаграмма последовательности обмена данными

sequenceDiagram
    participant CRM
    participant Webhook
    participant SyncAgent
    participant PostgreSQL
    participant OpenSearch

    CRM->>Webhook: project-updated
    Webhook->>SyncAgent: событие

    SyncAgent->>CRM: GET /projects/{id}
    CRM-->>SyncAgent: project data

    SyncAgent->>SyncAgent: transform + normalize

    SyncAgent->>PostgreSQL: upsert
    SyncAgent->>OpenSearch: bulk index

    Note over SyncAgent: update last_sync_timestamp

6.1 Таблица распределения данных

Сущность PostgreSQL (что сохраняется) OpenSearch (что индексируется)
Projects (сделки) Полная структура проекта (raw JSON + нормализованные поля), статус синхронизации, технические поля (sync_state, timestamps) Денормализованная витрина: project_id, client_id, object_id, стадия, статус, сумма, менеджер, last_activity_at, activities_count
Clients (клиенты) Полная карточка клиента, связи, служебные поля Упрощённая витрина: client_id, название, отрасль, сегмент
Objects (объекты) Полная модель объекта, связь с клиентом Витрина: object_id, client_id, тип, статус, локация
Activities (активности) Полные записи активностей (raw), связь с проектом Индекс коммуникаций: тип, текст, результат, дата
Tasks (задачи) Полная структура задач, статусы, SLA Витрина: task_id, project_id, исполнитель, статус, due_date
Comments (комментарии) Полные тексты, связь с entity Индекс текстов для поиска: entity_type, entity_id, текст
Calculations (расчёты) Полные расчёты, версии, параметры Витрина: сумма, версия, привязка к проекту
Orders (заказы) Полная структура заказов Витрина: статус, сумма, дата
Users (пользователи) Полная модель пользователей и ролей Витрина: user_id, роль, команда
Sync metadata offset, updated_since, last_sync_time, retry state ❌ не индексируется
Ошибки / логи sync Полный лог операций ❌ не индексируется

6.2 Принципы формирования витрин OpenSearch

1. Денормализация

В OpenSearch данные агрегируются:

Пример:

  • project + client + object -> единый документ

  • activities -> агрегаты (count, last_activity_date)


2. Обогащение

При индексации добавляются:

  • derived fields

  • агрегаты

  • вычисленные признаки

Пример:

  • activities_count

  • days_without_activity

  • is_overdue


3. Оптимизация под сценарии поиска

OpenSearch индекс строится под:

  • фильтрацию

  • агрегации

  • полнотекстовый поиск

НЕ под:

  • транзакции

  • сложные связи


6.3 Поток записи данных

CRM → SyncAgent → PostgreSQL (raw + normalized)
                         ↓
                   Transformer
                         ↓
                  OpenSearch (витрины)

6.4 Ключевые различия слоёв

Критерий PostgreSQL OpenSearch
Тип данных нормализованные денормализованные
Назначение состояние системы поиск и аналитика
Обновление строгое (upsert) bulk indexing
Источник истины да (для AI слоя) нет
Использование Sync, Orchestrator Recommendation, Analytics

6.5 Важное архитектурное требование

  • OpenSearch не должен использоваться как источник истины

  • Все изменения идут через:

    • CRM -> SyncAgent -> PostgreSQL -> OpenSearch
  • Разделение позволяет:

  • избежать перегрузки PostgreSQL аналитикой

  • обеспечить быстрый поиск и рекомендации

  • гарантировать консистентность системы


7. Модель синхронизации: когда, что и как обновляется

Синхронизация реализуется через 3 параллельных механизма, каждый из которых решает свою задачу:

  • Webhooks -> реакция в реальном времени

  • Polling -> гарантированная консистентность

  • Full Sync -> восстановление и выравнивание


7.1 Webhooks (near real-time синхронизация)

Назначение

Мгновенная реакция на изменения в CRM.

Когда срабатывает

При событиях в CRM:

  • создание проекта

  • обновление проекта

  • создание активности

  • (опционально) изменение задачи

Поток

CRM → webhook → SyncAgent → точечный fetch → update

Какие данные синхронизируются

Сущность Что делаем
Projects загружаем 1 проект по ID
Activities загружаем активность или проект целиком
Tasks загружаем задачу
Comments при наличии webhook
Calculations при изменении проекта

Особенность

Webhook не доверяем полностью -> всегда делаем дочитывание через API


SLA

  • задержка: 1–5 секунд

  • режим: near real-time


7.2 Polling (инкрементальная синхронизация)

Назначение

Гарантия, что:

  • ничего не потерялось

  • данные актуальны

Механика

Используется:

GET /api/*?updated_since=timestamp

Частота по сущностям

Сущность Частота Причина
Projects каждые 5 мин ключевая сущность
Activities каждые 5 мин динамика общения
Tasks 5–10 мин SLA
Comments 5–10 мин контекст
Objects 10–15 мин реже меняются
Clients 15–30 мин редко меняются
Calculations 10–15 мин средняя динамика
Orders 10–15 мин финансовые события
Users 1 раз/час почти статично

Поток

Scheduler → SyncAgent → API (bulk) → batch processing → upsert → index

Особенности реализации

  • используется updated_at

  • хранится last_sync_timestamp

  • обрабатываются батчи (100–1000 записей)


SLA

  • задержка актуализации: до 5–10 минут

7.3 Full Sync (полная синхронизация)

Назначение

  • восстановление консистентности

  • устранение ошибок sync

  • перерасчёт витрин


Когда выполняется

  • 1 раз в сутки (ночью)

  • вручную (по триггеру)


Что синхронизируется

Сущность Поведение
Все полная выгрузка
Projects пересборка агрегатов
Activities полная история
Objects восстановление связей
Clients обновление справочника

Поток

Full load → overwrite / reindex → rebuild OpenSearch

SLA

  • допускается длительность: до нескольких часов

  • выполняется вне рабочего времени


7.4 Приоритет механизмов

Механизм Приоритет Роль
Webhooks высокий скорость
Polling средний надёжность
Full Sync низкий восстановление

7.5 Конфликты и консистентность

Правило:

последнее изменение по updated_at побеждает

Дополнительно:

  • idempotent операции

  • защита от дублей

  • retry (3+ попытки)


7.6 Сводная схема

            ┌──────────────┐
            │    CRM       │
            └──────┬───────┘
                   │
      ┌────────────┼────────────┐
      │            │            │
      ▼            ▼            ▼
  Webhooks     Polling     Full Sync
 (реалтайм)   (дельта)     (полный)
      │            │            │
      └──────┬─────┴─────┬──────┘
             ▼           ▼
         Sync Agent (единая логика)
                   │
        ┌──────────┴──────────┐
        ▼                     ▼
   PostgreSQL           OpenSearch

7.7 Ключевой принцип (важно для разработки)

Нельзя полагаться только на один механизм:

  • Webhooks -> могут теряться

  • Polling -> не realtime

  • Full Sync -> тяжёлый

➡️ Только вместе они дают:

  • актуальность

  • надёжность

  • консистентность


Итог

Тип данных Как обновляется
Критичные (проекты, активности) webhook + polling
Средние (задачи, расчёты) polling + webhook (если есть)
Справочники (клиенты, users) редкий polling
Связи (objects) polling + full sync

8. Требования к реализации

Обязательные:

  • idempotency (повторяемость без дубликатов)

  • поддержка bulk операций

  • retry (3 попытки минимум)

  • логирование ошибок

  • контроль updated_at

Производительность:

  • batch загрузка (100–1000 записей)

  • bulk indexing OpenSearch


9. Ключевые особенности

  • поддержка связи:

    • Client -> Object -> Project
  • денормализация данных в OpenSearch

  • готовность к аналитике и AI


10. Результат

После реализации:

  • все CRM-данные доступны в OpenSearch

  • данные связаны через Object layer

  • AI-агенты получают полноценный контекст