План перехода на новые-кленовые события от Директории

Преамбула

Сейчас формат событий от директории чересчур запутан, местами избыточен и неинтуитивен. Когда мы их проектировали, то перемудрили и теперь вынуждены признать, что это место требует переделки. Вот лишь некоторые из проблем:

  • Формат разных событий, хотя и похож, но тем не менее иногда сильно отличается и обманывает ожидания.

  • Наличие опции expand_content усложняет понимание того, каких данных ожидать в payload.

  • В коде Директории генерация одного и того же типа событий может происходить в разных местах, но из-за того, что почти всегда это делается через обобщённую функцию, иногда возникает ситуация, что данные для события складываются в базу и payload в чуть разном состоянии. Из-за этого, payload одного и того же события может отличаться.

Далее изложен план по переделке формата событий и переходу со старого формата на новый.

Что нужно сделать

Основные вещи которые нужно сделать:

  • поменять формат событий на более интуитивный и понятный;

  • сделать код для генерации событий более защищённым от ошибок;

  • позволить клиентам API постепенно мигрировать со старого формата на новый.

Изменение формата

Текущий формат обусловлен тем, что мы пытались сделать унификацию разных типов событий. Отсюда и получилось что данные выглядят примерно так:

{
    "content": {
        "diff": {
            "contacts": [
                [
                    {
                        "type": "skype",
                        "value": "the-bob"
                    },
                    {
                        "type": "email",
                        "value": "bob@home.com"
                    }
                ],
                [
                    {
                        "type": "skype",
                        "value": "the-bob"
                    }
                ]
            ],
            "position": [
                {
                    "ru": "Тестировщик"
                },
                {
                    "ru": "Разработчик"
                }
            ]
        },
        "directly": true
    },
    "event": "user_property_changed",
    "obj": {
        "about": null,
        "aliases": [],
        "birthday": null,
        "contacts": [
            {
                "type": "skype",
                "value": "the-bob"
            }
        ],
        "department_id": 2,
        "email": "bob@art-dev.yaconnect.com",
        "external_id": null,
        "gender": "male",
        "groups": [],
        "id": 100500,
        "is_admin": false,
        "is_dismissed": false,
        "login": "bob",
        "name": {
            "first_name": {
                "ru": "Bob"
            },
            "last_name": {
                "ru": "Hopkins"
            }
        },
        "nickname": "bob",
        "position": {
            "ru": "Разработчик"
        }
    },
    "org_id": 12,
    "revision": 1444
}

Это пример события user_property_changed

Тут мы видим два поля с данными - obj и content. Почему obj не находится в content, но в там есть diff - не понятно.

Вот другой пример, событие department_user_deleted:

{
    "content": {
        "diff": {
            "members": {
                "add": {},
                "remove": {
                    "users": [
                        100500
                    ]
                }
            }
        },
        "directly": true,
        "subject": {
            "about": null,
            "aliases": [],
            "birthday": null,
            "contacts": [
                {
                    "type": "skype",
                    "value": "the-bob"
                }
            ],
            "department_id": 2,
            "email": "bob@example.com",
            "external_id": null,
            "gender": "male",
            "groups": [],
            "id": 100500,
            "is_dismissed": false,
            "login": null,
            "name": {
                "first_name": {
                    "ru": "Bob"
                },
                "last_name": {
                    "ru": "Hopkins"
                }
            },
            "nickname": "bob",
            "org_id": 12,
            "position": {
                "ru": "Разработчик"
            }
        }
    },
    "event": "department_user_deleted",
    "obj": {
        "description": {
            "ru": ""
        },
        "email": null,
        "external_id": null,
        "id": 1,
        "label": null,
        "members_count": 3,
        "name": {
            "en": "All employees",
            "ru": "Все сотрудники"
        },
        "parent_id": null,
        "removed": false
    },
    "org_id": 12,
    "revision": 1444
}

Тут в словаре content появляются ещё два поля: subject и direct. При этом в subject полностью описание сотрудника, которого удалили из отдела, а в direct - признак того, что пользователь входил непосредственно в тот отдел, который указан в поле obj.

Как видно, всё очень сложно. Поэтому предлагается формат событий упростить, сделать более лаконичным и привязанным к конкретному типу событий.

К примеру, вышеупомянутый user_property_changed будет выглядеть как-то так:

{
    "org_id": 12,
    "revision": 1444
    "event": "user_property_changed",
    "user_id": 100500,
    "diff": {
        "contacts": {
            "removed": [
                {
                    "type": "email",
                    "value": "bob@home.com"
                }
            ],
        },
        "position": {
            "before": "Тестировщик",
            "after": "Разработчик"
        ]
    }
}

При этом поля org_id, revision и event - обязательные, а остальные опциональные.

А вот так будет выглядеть событие department_user_deleted:

{
    "event": "department_user_deleted",
    "org_id": 12,
    "revision": 1444
    "user_id": 100500,
    "departement_id": 1,
    "directly": true,
}

Ну разве не прелесть!?

٩(◕‿◕)۶

Так как же добиться этой прелести и начать её использовать?

В коде Директории придётся какое-то время поддерживать оба формата. Для этого, нужно ввести версионирование, такое же, как и для остальной части API. Подписки на события, события в базе и ручки для получения событий, должны быть версионированы.

Код, генерирующий события, придётся дублировать, создавая одновременно события старого, и нового образца.

Предлагается в коде генерировать события с помощью одной функции generate_event, которая будет принимать название события, и kwargs, затем строить словарь, соответствующий payload события и проверять его на соответствие заданной json схеме. Сами JSON схемы каждого из событий, будут частью API и доступны, как часть документации.

Переход

Переходить на новый формат событий можно будет постепенно. Для перехода надо будет сделать несколько вещей:

  1. На время поддержать обработку и старого и нового формата payload, опираясь на версию API, передаваемую в заголовке X-API-Version.

  2. Одномоментно подписаться на новую версию событий и отписаться от старой.

  3. Если есть код, использующий ручку /events/, то перейти там на использование новой версии API.

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