Danyloff

LRO ⏰ или длительные операции API


  • ·

· ·

Бывают такие ситуации когда выполнения некоторого метода занимает длительное время и/или использует большое количество вычислительных ресурсов. И требуется реализовывать данные методы несколько иначе чем типичные. Проблему можно просто проигнорировать и позволить потребителям Вашего API дожидаться столько времени сколько нужно для выполнения но, мне такой вариант не нравится и я хочу Вас познакомить с шаблоном LRO.

Описание

Длительные операция или LRO(long-running operations) это шаблон который помогает решить проблему методов которые выполняются дольше неких установленных временных рамок. LRO несколько похоже на Promise в JavaScript. LRO так же как и Promise формирует объект который информирует о результате выполнения метода.

Реализация

Для начала нам необходимо определить интерфейс ресурса Operation.

Operation {
    string id
    Result|OperationError|null result
    enum status: waiting|processing|done
    Metadata metadata 
}

Поле id является идентификатором ресурса Operation, чтобы потребители могли получить информацию о состоянии конкретной операции.

Поле result необязательно должен быть ресурсом, некоторые операции не предусматривают возврат структурированных ответов. Объект OperationError возвращается для исключительных ситуация, когда при выполнении операции возникает ошибка.

Поле metadata представляет собой объект с информацией о прогрессе выполнения операции. В объекте может быть информация в процентном соотношении, количество обработанных строк или тому подобное.

Поле status информирует о состоянии операции.

Пример создания LRO

Представьте что вы разрабатываете API для крупного интернет-магазина. И потребители API потребовали реализовать метод для получение статистики по заказам.

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

Так же данный метод не вписывается в стандартный список CRUD, по этому задействуем шаблон пользовательских методов.

GET /orders:stats

Данный метод будет возвращать ресурс Operation. Вот интерфейс ресурса

OrderStatistics {
    int total
    ...
}

OrderStatisticsMetadata {
    int processedOrders
    int totalOrders
}

Operation {
    string id
    OrderStatistics|OperationError|null result
    enum status: waiting|processing|done
    OrderStatisticsMetadata metadata 
}

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

Иерархия ресурсов

Рекомендуется ресурсы операций хранить на верхнем уровне.

GET /operations
GET /operations/{id}

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

Продолжительность хранения

Несмотря на то что LRO являются ресурсами, они несколько отличаются от других ресурсов и требования к хранению у них так же другое. Вы можете хранить их вечно либо реализовать автоматическое удаление, например по шаблону скользящего окна. Удаление происходит на основе временной метке их завершения. Например можно добавить поле expireTime в ресурс Operation.

Operation {
    string id
    ...
    DateTime expireTime
    ...
}

Рекомендуется информировать потребителя о сроках хранения операций потому, что результат может использоваться многократно. И чтобы для Ваших потребителей это не стало сюпризом, лучше добавить поле с датой и временем когда данный ресурс будет удален!

Итог

LRO отличный паттерн который позволяет создавать предсказуемые API для ресурсозатратных методов. Вы можете расширять LRO под Ваши требования, например добавить методы для приостановки/возобновления либо отмены операций с помощью пользовательских методов и тп. Удачи в проектировании API!


Так же интересно