META

Шина событий

Мотивация

Нужно стремится делать компоненты системы масимально независимыми от бизнес-логики.

Идеально, если при сохранении чего-то смотреть изменившиеся поля и понимать что именно надо выполнить по бизнес-процессу, если, например изменился только статус объекта, однако пока что это невозможно. Однако мы хотели сделать возможность подписываться на изменения объектов, чтобы не добавлять логику оповещения внутренних подсистем предприятия прямо в код страниц META.

Пример: на карточке клиента при сохранении списка главных пользователей клиента надо записывать изменения в сторонюю CRM. В простейшем случае все тривиально - в код страницы вставляется вызов апи CRM, например даже через какое-то наше внутрее API. Однако список таких систем может расти и вам просто сложно будет за этим всем следить, а так же параллелить и перезапускать при недостуности внешних API, код будет ухудшаться и не будет соблюдаться принцип единой ответственности (SRP).

Кроме того, если вы или с вами будут делиться модулями меты, вы врядли захотите менять эти страницы, чтобы уведомить какие-то части своей подсистемы.

Как работает

На основе Starter в качестве backend для распределенного запуска скриптов. Он же обеспечивает перезапуск при падениях, ограничение очереди выполнения, логи и пр. вещи нужные для обработки очередей

META Страницы

В коде страниц редактирования карточек объектов в принципе вседа есть похожий код, который занимается журналированием изменений:

<script type="meta/js" elem="hidden" states="save">
  function main(pvm, vm, env, ObjectLogService) {
    // тут обычное сохранение вашего объекта или списка объектов

    // Это стандартная запись в журнал изменений по объекту
    ObjectLogService.logValue(env.entityId, objectId, isNew ? 'ADD' : 'SET', env.sp.obj);

    // или с пятым параметром, который укажет на некую "форму", которая менала объект
    //  например тут придумано название main_user_list, которое именно у нас в приложении будет означать форму со списоком главных пользователей 
    ObjectLogService.logValue(env.entityId, objectId, isNew ? 'ADD' : 'SET', env.sp.obj, 'main_user_list');
  }
</script> 

Dispatchers

Содержится в параметре dispatcher

Будет использоваться для шины событий как клиент, который продуцирует полезную работу: запускает фоновые процессы, записыват логи, генерирует события в шину

Примеры:

  • meta.{META_APP_ALIAS} - мета-приложение
  • appscript.{SERVICE_ID} - фоновый скрипт (включает в сещя обработчики шины событий)
  • apiservice.{SERVICE_ID} - http апи сервисы

Python worker (Listener)

from metasdk import MetaApp
from metasdk.event_bus import Event

META = MetaApp()
log = META.log

# в декораторе вы долэны указать на какие типу уведомлений подписываетесь и начать обработку события
@META.event_bus.listener(entity_id="2830", code=["ADD", "SET"], form_pattern=["*"], dispatcher_pattern=["meta.garpun_feeds"])
def on_upsert_ex_access(event: Event):
    print(u"on_upsert_ex_access = %s" % str(event))
    print(u"event.value = %s" % str(event.value)) # для получения value нужено делать receive_value=True, но не делайте это всегда, часто это не нужно, но создает большую доп. нагрузку
    print(u"event.object_id = %s" % str(event.object_id))
    import time
    print("sleep...")
    time.sleep(60)


# Это пример для тестового локального запуска, прямо как в типичных фоновых скриптах
META.worker.debug_tasks = [{
    "data": {
        "event": {
            "userId": 10191,
            "entityId": 2830,
            "objectId": "42",
            "code": "SET",
            "form": "test",
            "value": {
                "a": 1
            }
        }
    }
}]

# Бойлерплейт, который запускает обработку
@META.worker.single_task
def main(task):
    META.event_bus.accept(task)

Python Client

Если вам надо спровоцировать запись в журнал с записью в Шину Событий. Вы должны вызвать журналирование, а в app-content вы должны иметь настроенные обработчики (listeners)

from metasdk import MetaApp

META = MetaApp()

META.ObjectLogService.log({
    "userId": 10191,
    "entityId": 2671,
    "objectId": 53,
    "code": "SET",
    "form": "my_form", # optional
    "value": {  # optional
        "foo": "bar",
        "subFoo": {
            "foo2": [1, 2, 3]
        }
    }
})

Ограничения

  • Звездочка в паттерне формы “*” может стоять или в начале или в конце, но НЕ в середине выражения
  • Если нет подписчиков события, событие будет записано в журнал, но не уйдет в Starter
  • По-умолчанию value лога не прокидывается в таски для шины. Для того, чтобы получать его делайте receive_value=True

Отказоустойчивость

Мы стараемся делать все надежно, однако все ломается (например падение сети или выключение электричества) и событие может не уйти и вы должны иметь механизмы перепроверки. Желательно подумать о них примо при проектировании своей подсистемы. Работать они могут сильно реже - напирмер раз в час перепроеряют, что все сработало как надо и если что-то не так - делают нужные задачи. Мы будем улучшать надежность Шины, однако все же надо помнить, что только вы знаете как правильно ореагировать на серьезные ошибки в вашем продукте.

ROADMAP

  • попытаться очень быстро обрабатывать события с минимальными накладными расходами на запуск питона, например
  • убрать оповещение юзеров и какие-то уведомления туда же
  • автоматические уведомления popup от ObjectLogService через PubSub
  • понятные ограничения обработки очередей event-роутеров