Skip to content

Terry Architecture

High-Level View

Terry описывает платформу, в которой ingestion и delivery разделены на независимые capability blocks:

  • adapters для чтения из source;
  • orchestrator для запуска ingestion job;
  • validation layer для rule execution;
  • transformation layer для приведения данных к internal shape;
  • persistence layer для записи в sink;
  • notification layer для рендеринга шаблонов и отправки в delivery channel.

Logical Components

flowchart LR
  S["Source adapters"] --> J["Ingestion job runner"]
  J --> V["Validation layer"]
  V --> T["Transformation layer"]
  T --> P["Persistence / sink writers"]
  P --> N["Notification engine"]
  V --> E["Error envelope / dead-letter"]
  T --> E
  P --> E
  N --> D["Delivery channels"]

Responsibilities

  • Source adapters инкапсулируют transport-specific extraction: HTTP, polling, scraping, files, queues.
  • Ingestion job runner поднимает execution context, применяет job config и управляет retries/timeouts.
  • Validation layer отделяет hard-fail ошибки от soft-fail сигналов и возвращает структурированный результат.
  • Transformation layer переводит source payload в normalized record и добавляет derived fields.
  • Persistence layer обеспечивает идемпотентную запись и трассируемость источника данных.
  • Notification engine реагирует на результат pipeline и рендерит сообщения по шаблонам.

Architectural Boundaries

Terry не должен документироваться как набор отдельных workflow/activity entrypoints. Этот стиль уже подходит текущему Temporal runtime, но для Terry важнее platform narrative:

  • какие capabilities есть;
  • как они связаны;
  • где проходят contracts;
  • где лежат operational guarantees.

Когда появится код, implementation-specific страницы можно будет линковать из Terry, но не заменять ими этот раздел.

Execution Model

Базовая модель исполнения:

  • job может запускаться по API, расписанию или внешнему событию;
  • обработка может быть synchronous на ingress, но основной pipeline считается asynchronous;
  • каждая стадия должна иметь correlation identifiers и execution metadata;
  • ошибки и повторные попытки описываются отдельным envelope, а не теряются в логах.

Storage and Delivery Concerns

  • sink может быть разным, но normalized record должен иметь стабильный internal shape;
  • persistence должна отделять raw payload, normalized result и delivery events;
  • notification pipeline не должен читать source напрямую, только итог pipeline execution и associated payload.

Current Runtime Baseline

На текущем этапе Terry уже имеет минимальный runtime/config backbone в коде:

  • config catalog хранится в PostgreSQL schema terry в таблицах workspaces, domains, data_sources, loader_configs;
  • data_sources хранит source identity/policy и pointer на текущую активную config version;
  • loader_configs хранит versioned runtime config и version-level statistics;
  • universal loader вызывается по data_source_id, читает конфигурацию из catalog-а и строит execution input внутри LoaderActivity;
  • source selection по domain + page_type, write-side config flows, execution history и notification orchestration пока не реализованы.

Reference ERD

Ниже canonical ERD для первого прохода Terry. Это target-state schema narrative: часть сущностей уже реализована, часть остаётся архитектурным направлением.

erDiagram
    %% --- ДОМЕН: ИСТОЧНИКИ И НАСТРОЙКИ (PostgreSQL) ---
    WORKSPACE ||--o{ DOMAIN : "владеет"
    DOMAIN ||--o{ DATA_SOURCE : "включает"
    DATA_SOURCE ||--o{ LOADER_CONFIG : "версии конфигурации"
    DATA_SOURCE ||--|| TRANSFORMER_CONFIG : "имеет"
    TRANSFORMER_CONFIG ||--o{ DATA_SOURCE_FIELD : "определяет поля"

    %% --- ДОМЕН: ПРОКСИ-СЕРВЕРЫ (PostgreSQL / Redis) ---
    PROXY_GROUP ||--o{ PROXY_SERVER : "содержит"
    PROXY_SERVER ||--o{ EXCLUDED_PROXY : "временно блокируется в"
    DATA_SOURCE ||--o{ EXCLUDED_PROXY : "имеет блокировки"

    %% --- ДОМЕН: ВЫПОЛНЕНИЕ И ОРКЕСТРАЦИЯ (Temporal / PostgreSQL) ---
    DATA_SOURCE ||--o{ JOB_INSTANCE : "порождает"
    JOB_INSTANCE ||--o{ RAW_DATA_FILE : "скачивает"
    JOB_INSTANCE ||--o{ STRUCTURED_DATA : "извлекает"
    JOB_INSTANCE ||--o{ NOTIFICATION : "генерирует"

    %% --- ДОМЕН: ПРАВИЛА И УВЕДОМЛЕНИЯ (PostgreSQL) ---
    DATA_SOURCE ||--o{ RULE : "триггерит"
    RULE ||--o{ SUBSCRIBER : "уведомляет"
    SUBSCRIBER ||--o{ NOTIFICATION : "адресовано"

    %% --- ОПИСАНИЕ СУЩНОСТЕЙ ---

    WORKSPACE {
        uuid id PK
        string code_name "Например: terry"
        string name
        string description
        boolean is_active
    }

    DOMAIN {
        uuid id PK
        uuid workspace_id FK
        string code_name "Например: cbr.ru"
        string name "Центральный банк РФ"
        string description
        int rate_limit "Разрешенная частота запросов"
    }

    DATA_SOURCE {
        uuid id PK
        uuid domain_id FK
        string code_name "Например: cbrf_onDate"
        string name
        string page_type "Логическая страница или endpoint family"
        string source_type "http, playwright, file, database, sdk, custom"
        boolean is_historical
        int expiration_days "Срок хранения сырых данных"
        int priority
        boolean is_active
        uuid current_loader_config_id FK
    }

    LOADER_CONFIG {
        uuid id PK
        uuid data_source_id FK
        int version
        string status "draft, active, archived"
        jsonb loader_settings
        jsonb auth_settings
        jsonb validator_settings
        jsonb freshness_settings
        jsonb processor_settings
        jsonb fingerprint
        jsonb incremental
        jsonb schedule_settings
        bigint total_runs
        bigint successful_runs
        bigint failed_runs
        timestamp last_run_at
        timestamp last_success_at
        timestamp last_failure_at
        timestamp last_failure_start_date
    }

    TRANSFORMER_CONFIG {
        uuid id PK
        uuid data_source_id FK
        string transformer_type "Например: transformer_xslt"
        text xsl_template "Сам шаблон трансформации"
    }

    DATA_SOURCE_FIELD {
        uuid id PK
        uuid transformer_config_id FK
        string expression_field_name "Поле для формул"
        string structured_field_name "Имя поля в БД (Name, Curs)"
        string structured_field_type "Тип (Date, Float, Text)"
        string description
    }

    PROXY_GROUP {
        uuid id PK
        string name "Название группы прокси"
    }

    PROXY_SERVER {
        uuid id PK
        uuid proxy_group_id FK
        string ip_address
        int port
        boolean is_active "Доступен ли физически"
    }

    EXCLUDED_PROXY {
        uuid proxy_id PK, FK
        uuid data_source_id PK, FK
        string exclusion_reason "Причина (auto, manual)"
        timestamp excluded_at "Время добавления в пул исключений"
    }

    JOB_INSTANCE {
        uuid id PK
        uuid data_source_id FK
        string status "Created, Requesting_loading, Success, Failed"
        string result_code
        text error_message
        int notification_messages_count "Счетчик писем"
        timestamp start_time
        timestamp end_time
    }

    RAW_DATA_FILE {
        uuid id PK
        uuid job_instance_id FK
        string file_path "Путь к объекту в MinIO"
        string hash "SHA-256 скачанного файла (для дедупликации)"
        timestamp request_time
        timestamp expiration_time "Когда удалить файл"
        boolean is_new "Флаг дубликата"
    }

    STRUCTURED_DATA {
        timestamp time PK "Primary Key (TimescaleDB Time-Series)"
        uuid data_source_id FK
        uuid job_instance_id FK
        jsonb data_payload "Распарсенные данные (Курс, Валюта и т.д.)"
    }

    RULE {
        uuid id PK
        uuid data_source_id FK
        string name "Например: USD exchange rate change"
        text expression "Формула: Abs(Round_down(...) - ...) > 0"
        boolean is_active
    }

    SUBSCRIBER {
        uuid id PK
        uuid rule_id FK
        string delivery_type "Email, Http_post"
        string endpoint "URL вебхука или Email адрес"
    }

    NOTIFICATION {
        uuid id PK
        uuid job_instance_id FK
        uuid subscriber_id FK
        string status "Pending, Sent, Failed"
        string error_message
        timestamp created_at
    }

ERD Notes

  • WORKSPACE, DOMAIN и DATA_SOURCE задают ownership hierarchy и rate-limit boundary для внешних интеграций.
  • DATA_SOURCE.expiration_days это config-level retention policy для raw payload artifacts и поэтому должен жить в source catalog.
  • LOADER_CONFIG это versioned runtime config entity; active version определяется через DATA_SOURCE.current_loader_config_id.
  • История и stats живут на уровне LOADER_CONFIG, чтобы Terry мог связывать regressions и rollout effects с конкретной config version.
  • Метрики вроде total_req_count, failed_req_count и last_failure_start_date лучше трактовать как execution telemetry, а не как статическую конфигурацию источника. Их owner должен быть у JOB_INSTANCE/observability слоя, а не у catalog row.
  • TRANSFORMER_CONFIG и DATA_SOURCE_FIELD пока не реализованы, но остаются частью planned declarative transform stack.
  • JOB_INSTANCE, RAW_DATA_FILE и STRUCTURED_DATA покрывают execution trace от raw artifacts до normalized payload.
  • RULE, SUBSCRIBER и NOTIFICATION отделяют detection logic от delivery logic.
  • PROXY_GROUP, PROXY_SERVER и EXCLUDED_PROXY фиксируют отдельный operational subdomain, который не должен смешиваться с core contracts pipeline.