Batch запити
Вступ
Уявімо що ми розробляємо бекенд API для інтернет-магазину і в нас вже є метод ProductService.getInfo для сутності Product. Задача на фронті зробити логіку додавання товарів в кошик і актуалізація інформації про товари по запиту (наприклад, при відкритті кошика нам потрібно оновити інформацію про товари, які користувач додав: наявність, ціни, описи, тощо).
Сценарій, яким йдуть 100% розробників
Ставиться задача на бекенд для створення окремого методу API, який має повертати колекцію об’єктів за масивом ідентифікаторів. Це вимагає робочого часу бекенд-розробника, тестувальника і фронтенд-розробника, оскільки новий метод має бути покритий юніт-тестами, а також додатковими сценаріями для регресійного тестування. Усе це потрібно зробити, незважаючи на те, що у нас вже є метод, який може повертати один товар за його id.
Сценарій, яким ніхто не йде
Надсилати окремий запит до API для кожного товару, щоб отримати актуальну інформацію. Якщо в кошику 10 товарів, це означає 10 HTTP-запитів. Це створює значне навантаження на мережу і збільшує час очікування для користувача, що є неприпустимим варіантом.
Альтернатива з використанням batch запитів
Якщо ми маємо можливість відправляти і обробляти batch запити, то можемо значно спростити реалізацію бізнес-логіки. Ми можемо використовувати batch запит, щоб об'єднати всі ці запити в один. Ми надсилаємо один запит до API, який містить всі ідентифікатори товарів, і сервер повертає інформацію про всі товари в одній відповіді. Таким чином, ми отримуємо:
- Універсальність: Використання batch запитів дозволяє уникнути створення спеціалізованих методів для кожного випадку. Це спрощує API і зменшує кількість необхідного коду.
- Зручність для клієнта: Клієнт може самостійно визначити, які саме запити потрібно об'єднати, що робить API більш гнучким і зручним для використання.
- Зниження складності бекенду: Замість того, щоб писати та підтримувати спеціалізовані методи для різних сценаріїв, можна використовувати універсальний підхід з batch запитами, що спрощує архітектуру бекенду.
Приклад
Розглянемо запит, приклад якого я наводив вище
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"id":"example_1",
"method":"ProductService.getInfo",
"params":{
"productId": 345234
}
},
{
"id":"example_2",
"method":"ProductService.getInfo",
"params":{
"productId": 994234
}
}
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"id":"example_2",
"result": {
"id": 994234,
"name": "product 2",
"price": 11.99,
"balance": 28
}
},
{
"id":"example_1",
"result": {
"id": 345234,
"name": "product 1",
"price": 99.99,
"balance": 14
}
}
]
Переваги batch запитів:
- Спрощення бекенду: Замість того, щоб кожен раз реалізовувати специфічну логіку на боці бекенду, можна винести це на рівень побудови клієнтських запитів.
- Зменшення кількості мережевих запитів: Замість надсилання декількох HTTP-запитів, що може створювати навантаження на мережу та сервер, використовується лише один запит. Це значно знижує навантаження на мережу та серверні ресурси.
- Час відповіді: RPC Server віддасть результат всіх запитів не зважаючи на їх кількість за стільки часу, скільки необхідно на обробку найдовшого запиту з пакету, тому що обробка всіх запитів відбувається паралельно.
- Зменшення часу затримки: Виконання декількох запитів в одному з'єднанні може знизити загальний час затримки, оскільки зменшується кількість часу, витраченого на встановлення та завершення з'єднань.
- Оптимізація використання ресурсів клієнта та сервера: Оскільки менше запитів обробляється одночасно, знижується навантаження на сервер, що дозволяє йому обробляти більше запитів. Клієнт також використовує менше ресурсів, що може бути критичним для мобільних та обмежених пристроїв.
- Покращена керованість транзакцій: Коли декілька операцій необхідно виконати атомарно (усі або жодна), batch запит дозволяє це зробити легше, оскільки усі операції виконуються в межах одного запиту.
Підготовка до використання
Функціональність batch запитів доступна одразу і не вимагає додаткових налаштувань з боку розробника.
Послідовність запитів
Послідовність наповнення batch не має значення, бо на RPC сервері всі запити виконуються паралельно і ви отримаєте відповідь із швидкістю найдовшого запиту з переліку.
Якщо в якомусь запиті з переліку відбудеться помилка, це ніяк не впливає на опрацювання інших запитів з переліку.
Залежні запити
Уявімо, що нам з фронтенда потрібно отримати email адресу користувача за його ідентифікатором, а потім відправити йому повідомлення на цю адресу. В нас вже існують методи UserService.getInfo та Messenger.sendEmail. В класичному сценарії це буде або два запити або необхідно створити на бекенді окремий метод, який буде відправляти email по id користувача.
Використовуючи бібліотеку JsonRpcBundle від UFO-Tech у вас є можливість створювати batch запити, де перший один запит може ставати залежним від іншого і буде отримувати його відповідь для запуску. В контексті прикладу ми в одному запиті отримаємо інформацію про користувача, а другий запит використує отриманий емейл для відправки повідомлення.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"id":"example_1",
"method":"UserService.getInfo",
"params":{
"userId": 141
}
},
{
"id":"example_2",
"method":"Messenger.sendEmail",
"params":{
"email": "@FROM:example_1(email)",
"subject": "Welcome!",
"message": "Thank you for joining our service!"
}
}
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"id":"example_1",
"result": {
"id": 141,
"name": "John",
"email": "john.dou@example.com",
"status": 1
}
},
{
"id":"example_2",
"result": "Email for 'john.dou@example.com' sent"
}
]
У цьому прикладі:
- Перший запит отримує інформацію про користувача з id 141, включаючи його емейл.
- Другий запит відправляє лист на отриманий емейл, використовуючи дані з першого запиту.
Переваги використання залежних запитів:
- Зручність і ефективність: Залежні запити дозволяють виконувати складні операції з мінімальними зусиллями, забезпечуючи правильну послідовність виконання запитів.
- Зменшення кількості мережевих запитів: Усі запити об'єднуються в один, що знижує навантаження на мережу.
- Гнучкість: Можливість створювати складні сценарії запитів без необхідності додаткових налаштувань чи змін на бекенді.
Як це працює
Механізм обробки batch запитів працює асинхронно.
Отримуючи масив запитів RPC Server створює чергу з Symfony Process, тобто запускає CLI команди, які обробляються асинхронно. В циклі while стан процесів перевіряється, і якщо отримано результат, він додається до масиву відповідей. Якщо ж відповіді немає до закінчення таймауту, повертається помилка про те, що запит не оброблено.
Алгоритм
- Batch запит розбивається на окремі запити, кожен з яких додається до черги.
- В циклі перевіряється наявність об’єктів у черзі.
- Для кожного об'єкту черги перевіряється, чи завершився процес.
- Якщо процес завершився, результат додається до масиву відповідей, а процес видаляється з черги.
- Якщо процес не завершився і таймаут ще не вийшов, цикл продовжується.
- Якщо таймаут вийшов до отримання результату, для конкретного запиту повертається помилка про те, що запит не оброблено.
Щоб збільшити таймаут у batch запиті, в параметрах конкретного запиту можна вказати додатковий службовий параметр $rpc.timeout - максимальна кількість секунд очікування відповіді від процеса. За замовчуванням значення таймауту становить 10 секунд.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"id":"example_1",
"method":"ExampleApi.fastMethod",
"params":{
"someParam": "someValue"
}
},
{
"id":"example_2",
"method":"ExampleApi.longMethod",
"params":{
"someParam": "someValue",
"$rpc.timeout": 30
}
}
]
Це дозволяє налаштувати тривалість очікування результатів для методів, що потенційно можуть працювати довго, що може бути важливо для обробки деяких запитів.