Какова наилучшая практика написания API для действия, которое влияет на несколько таблиц?
Рассмотрим пример использования, приведенный ниже.
Вам необходимо пригласить компанию в качестве своего подключения. В этой ситуации необходимо выполнить следующие действия.
- Необходимо создать компанию, добавив запись в таблицу Компания.
- Необходимо создать учетную запись пользователя для входа сотрудника в систему, создав запись в таблице User.
- Создается объект Staff для обеспечения доступа Пользователя к компании путем создания записи в таблице Staff.
- Приглашенная компания связана с приглашенной компанией, поэтому создается отношение, подобное дружескому, для связи двух компаний путем создания записи в таблице Connection.
- Создается объект Invitation для хранения информации о том, кто кого пригласил в систему, с другой информацией, такой как время приглашения, сообщение приглашения и т.д. Для этого создается запись в таблице Invitation.
- Пользователю должно быть отправлено электронное письмо, чтобы он принял приглашение и присоединился, установив пароль.
Как видите, записи должны быть сделаны в 5 таблицах. Является ли хорошей практикой делать все это в одном вызове API? Если нет, то каковы другие варианты. Как мне сохранить целостность данных, если их нужно разделить на несколько API?
Если действия должны быть атомарными, то, безусловно, лучше сделать это в одном вызове API. В противном случае вы рискуете тем, что кто-то не выполнит все необходимые задачи и оставит ресурсы в потенциально конфликтном состоянии.
Кроме того, вы обновляете не один ресурс, поэтому это не очень подходит для одного RESTful вызова создания ресурса (например, POST /companyInvitations
) - поскольку все эти другие вещи, создаваемые и сшиваемые вместе, могут привести к некоторой путанице.
Если действие, которое вы выполняете, - это "приглашение компании", то один из вариантов - использовать синтаксис "пользовательского метода" Google (POST /resources/1234:action
), как определено в AIP-136. В этом случае вы можете сделать POST /companies/1234:invite
, который говорит: "Я хочу пригласить компанию №1234 стать моим соединением".
Под капотом, это может атомарно апсертить (создавать, если ресурсы еще не существуют) все нужные вещи, которые вы перечислили.
При подходе к вызову API, где при вызове происходит несколько действий, следует учитывать, сколько времени занимают эти последующие действия. Оставлять вызов API заблокированным - не самая лучшая идея в мире, пока все обрабатывается в фоновом режиме.
Вы можете рассмотреть возможность (в зависимости от вашего случая) принимать api запрос, немедленно отвечать на него статусом 200 и передавать запрос во внутреннюю очередь для обработки. Когда ваш фоновый сервис получает запрос, он может обновить все, что нужно обновить, и соответствующим образом управлять транзакциями и т.д. Это также подходит для сценариев горизонтального масштабирования, где множество "рабочих" служб могут быть развернуты для обработки запросов
В рамках этого можно рассмотреть возможность добавления еще одной конечной точки "status", к которой можно делать запросы, чтобы узнать, как идут дела. Чтобы избежать множества запросов статуса, можно также принимать детали обратного вызова как часть исходного api вызова, который затем вызывается, когда фоновая обработка завершена. Или вы можете сделать и то, и другое!