API-интерфейсы RESTful довольно распространены в настоящее время как четкий и хорошо структурированный стандарт, ориентированный на ресурсы. Но нам часто приходится сталкиваться с прецедентом, требующим вызова нескольких API для сбора всех необходимых данных. Это быстро становится проблемой, если ваш клиент является мобильным устройством с ограниченной сетью и интересуется только подмножеством данных всех ваших ответов API.
Популярным решением этой проблемы является GraphQL , язык запросов от Facebook. Хотя GraphQL является мощным инструментом, он по-прежнему требует, чтобы вы определили все схемы и функции, прежде чем клиент сможет его использовать. В этом посте я покажу вам другой подход, создав API пакетных запросов, который может быстро интегрироваться в вашу службу REST без необходимости определять какую-либо схему.
Код
Для API пакетных запросов нам понадобится механизм, способный последовательно создавать и выполнять запросы, а затем собирать все ответы. Для всей сложной части я буду использовать эту библиотеку Json Patch , которая предоставляет множество готовых функций, таких как: создание и извлечение объекта JSON, условные и циклические запросы, агрегатные функции ,… Вы можете протестировать его функции здесь
Ниже приведен код для настройки механизма пакетной обработки:
@Bean public BatchEngine getBatchEngine(ObjectMapper objectMapper, RequestDispatcherService dispatcherService) { Configuration conf = Configuration.builder() .options(Option.SUPPRESS_EXCEPTIONS) .jsonProvider(new JacksonJsonProvider(objectMapper)) .mappingProvider(new JacksonMappingProvider(objectMapper)) .build(); JsonBuilder jsonBuilder = new JsonBuilder(Functions.basic()); return new BatchEngine(conf, jsonBuilder, dispatcherService); }
А дальше идет Контроллер. Логика довольно проста, нам нужно только создать исходный запрос и передать его в механизм пакетной обработки вместе с шаблоном пакетной обработки. После ответа на возврат пакетного движка мы просто искажаем его с помощью ResponseEntity и возвращаем.
@RequestMapping(value = "/batch", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity batchRequests(@RequestHeader MultiValueMapheaders, @RequestBody BatchRequest batchRequest) { Request request = buildOriginalRequest(headers, batchRequest.getData()); Response response = batchEngine.execute(request, batchRequest.getTemplate()); return new ResponseEntity(response.getBody(), HttpStatus.resolve(response.getStatus())); return responseEntity; } private Request buildOriginalRequest(MultiValueMap headers, Object body) { Request request = new Request(); request.setBody(body); HashMap > headerMap = new HashMap<>(); headers.forEach(headerMap::put); request.setHeaders(headerMap); return request; }
Пакетный запрос – это простой объект Json:
{ "data": { input data needed to build requests } "template": { batch template to instruct how to build & execute requests } }
Нам также необходимо реализовать простой RequestDispatcher:
@Service public class RequestDispatcherService implements RequestDispatcher { @Autowired private RestTemplate restTemplate; @Override public Response dispatch(Request request, JsonProvider jsonProvider, DispatchOptions options) { HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.putAll(request.getHeaders()); HttpEntity
И это сделано. Теперь давайте проверим это.
Реальный сценарий
В настоящее время в нашей системе (продукт Fintech) у нас есть такой сценарий: после входа пользователя в нашу систему мы получим всю информацию о пользователе, которая включает:
- Профиль пользователя
- Профиль компании пользователя
- Информация о кошельке пользователя
Обычно нам приходится вызывать 4 API для сбора всех ответов, но нам нужно только подмножество данных. С помощью пакетного API нам нужно сделать только один вызов с таким телом запроса:
{ "data": { "token": "user's token" }, "template": { "requests": [ { "http_method": "GET", "url": "/oauth/payload", "headers": { "Authorization": "Bearer @{$.original.body.token}@" }, "body": null, "requests": [ { "http_method": "POST", "url": "/report/users", "headers": { "Authorization": "Bearer @{$.original.body.token}@" }, "body": { "id": "$.responses[0].body.data.user_id" }, "requests": [ { "http_method": "POST", "url": "/report/wallets", "headers": { "Authorization": "Bearer @{$.original.body.token}@" }, "body": { "user_id": "$.responses[0].body.data.user_id" }, "requests": [ { "http_method": "POST", "url": "/report/companies", "headers": { "Authorization": "Bearer @{$.original.body.token}@" }, "body": { "id": "$.responses[1].body.data.users[0].company_id" } } ] } ] } ] } ], "responses":[ { "body": { "id": "$.responses[1].body.data.users[0].id", "full_name": "@{$.responses[1].body.data.users[0].first_name}@ @{$.responses[1].body.data.users[0].last_name}@", "email": "$.responses[1].body.data.users[0].email", "company": { "id": "$.responses[3].body.data.companies[0].id", "name": "$.responses[3].body.data.companies[0].name" }, "wallets": [ { "id": "$.id", "balance": "$.balance", "currency": "$.currency", "__array_schema": "$.responses[2].body.data.wallets" } ] } } ] } }
Я провел тест производительности в обоих направлениях. Ниже приведен результат теста при выполнении нескольких запросов:
А далее следует результат теста при выполнении пакетного запроса:
Как вы можете видеть, способ пакетного запроса занимал в среднем всего ~141 мс в то время как путь с несколькими запросами занял ~369 мс .
Далее давайте посмотрим на размер ответа. Ниже приведен размер ответа при выполнении нескольких запросов:
А вот размер ответа при выполнении пакетного запроса:
Удалив все ненужные данные из ответа, мы смогли уменьшить размер ответа более чем в 30 раз (9730 > 272)
Вывод
Как вы можете видеть, с помощью пакетного API мы можем архивировать почти тот же результат, что и GraphQL, без ограничения заранее определенной схемы
Первоначально опубликовано на Первоначально опубликовано на
Оригинал: “https://dev.to/rey5137/power-up-your-rest-service-with-batch-api-3773”