Files
home_automatization/services/rule-engine-service
cacto abc83faf05 Реализация rule-engine-service: RabbitMQ → правила из PostgreSQL → gRPC
Слушает telemetry.new_reading (ручной ack/nack, реквизишн только при
транспортных ошибках gRPC), кэширует активные automation_rules в памяти
с периодическим обновлением из PostgreSQL (JOIN с devices — резолвит
внутренние ID в external_id, которым оперируют MQTT/Redis/gRPC). Условия
правил (>,<,>=,<=,=,!=) оцениваются обобщённо, без привязки к конкретным
типам устройств. При срабатывании вызывает device-control-service по gRPC
и публикует automation.rule_triggered в RabbitMQ.

Проверено сквозным тестом через docker compose на полном пайплайне:
mosquitto_pub → ingest-service (ClickHouse + telemetry.new_reading) →
rule-engine-service (совпадение правила) → device-control-service (gRPC →
Redis desired_state + MQTT-команда) → automation.rule_triggered. Показание
ниже порога проверено отдельно — правило корректно не срабатывает.
2026-07-26 21:18:59 +05:00
..

rule-engine-service (Go)

Статус: реализован (MVP).

Зона ответственности:

  • Потребляет события «новое показание» из RabbitMQ (очередь telemetry.new_reading, публикуемая ingest-service) — асинхронно, с ручным ack/nack: надёжность доставки здесь важнее задержки.
  • Загружает активные automation_rules из PostgreSQL в кэш в памяти (обновляется по тикеру), чтобы не ходить в БД на каждое событие.
  • Оценивает условия правил обобщённо: sensor_type X operator value против показания — никаких зашитых названий датчиков/устройств, всё берётся из строки automation_rules.
  • При срабатывании: вызывает device-control-service по gRPC (нужен быстрый результат успех/неудача) и публикует событие automation.rule_triggered в RabbitMQ для будущего notification-service (не критично к задержке).

Не входит в зону ответственности: прямое взаимодействие с MQTT или Redis — изменения состояния устройства всегда идут через gRPC API device-control-service.

Архитектурные решения

  • automation_rules хранит внутренние ID Postgres, а не внешние device_id. condition_source_device_id/target_device_id — это FK на devices.id (bigserial), а MQTT/Redis/gRPC везде оперируют devices.external_id (строка). Поэтому кэш правил грузится JOIN'ом automation_rules с devices (дважды — для source и target), резолвя оба ID в external_id один раз при загрузке, а не на каждое событие.
  • Retry — только на транспортных ошибках. Если gRPC-вызов device-control-service не удался физически (сеть, сервис недоступен) — событие из RabbitMQ nack'ается с requeue=true, всё правило переигрывается позже. Если устройство само отклонило команду (CommandResult.success=false, например неизвестный device_id) — это финальный исход, ack, повторов не будет. Осознанное упрощение: если из нескольких правил на одно показание одно не удалось из-за сети, при повторной доставке переиграются ВСЕ правила читающие это показание, включая уже успешно сработавшие — дедупликация не реализована, для MVP это приемлемо (команды идемпотентны: turn_on дважды — не проблема).
  • Кэш правил — только для чтения активных правил, никакой записи назад в Postgres. CRUD правил — это зона ответственности Laravel (этап 2).

Запуск

cd services/rule-engine-service
go test ./...
go build ./cmd/rule-engine-service

Конфигурация — через переменные окружения (секция rule-engine-service в корневом .env.example). Как и device-control-service, использует proto/device_control через replace на ../../proto в go.mod.