Советы по оптимизации

В этой статье описывается, как можно повысить производительность приложения. В некоторых случаях используются примеры из общих и других API, однако те же принципы применимы и к Google Pay API for Passes.

Сжатие с помощью GZIP

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

Чтобы получить ответ в архиве GZIP, необходимо, во-первых, задать заголовок Accept-Encoding, а во-вторых – добавить в агент пользователя строку gzip. Вот пример правильного оформления заголовков HTTP, которое обеспечивает сжатие GZIP:

Accept-Encoding: gzip
User-Agent: my program (gzip)

Работа с частичными ресурсами

Ещё один способ повысить эффективность вызовов API – запрашивать только интересующую вас часть данных. Приложение не будет передавать, анализировать и сохранять ненужные поля, что позволит максимально эффективно использовать все имеющиеся ресурсы, включая сеть, процессор и память.

Частичные запросы бывают двух типов:

  • Запрос частичного ответа – при этом вы указываете, какие поля необходимо включить в ответ (используя параметр запроса fields).
  • PATCH-запрос – это запрос на обновление, в котором вы передаете только те поля, которые требуется изменить (используя HTTP-метод PATCH).

Далее подробно описывается, как выполнять частичные запросы обоих типов.

Частичный ответ

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

Чтобы запросить частичный ответ, укажите необходимые поля, используя параметр запроса fields. Его можно применять во всех запросах, возвращающих данные.

Обратите внимание, что параметр fields влияет только на те данные, которые возвращаются в ответе, а не отправляются в запросе. Чтобы сократить объем данных, пересылаемых при изменении ресурсов, используйте PATCH-запрос.

Пример

Следующий пример иллюстрирует использование параметра fields в гипотетическом ресурсе Demo.

Простой запрос. В этом HTTP-запросе GET не используется параметр fields, и это значит, что по нему возвращается весь ресурс.

https://www.googleapis.com/demo/v1

Ответ со всем ресурсом. Полные данные по ресурсу включают не только перечисленные поля, но и многие другие, которые мы опускаем для краткости.

{
  "kind": "demo",
  ...
  "items": [
  {
    "title": "First title",
    "comment": "First comment.",
    "characteristics": {
      "length": "short",
      "accuracy": "high",
      "followers": ["Jo", "Will"],
    },
    "status": "active",
    ...
  },
  {
    "title": "Second title",
    "comment": "Second comment.",
    "characteristics": {
      "length": "long",
      "accuracy": "medium"
      "followers": [ ],
    },
    "status": "pending",
    ...
  },
  ...
  ]
}

Запрос частичного ответа. В этом запросе для того же ресурса используется параметр fields, что позволяет существенно сократить объем возвращаемых данных.

https://www.googleapis.com/demo/v1?fields=kind,items(title,characteristics/length)

Частичный ответ. В ответ на указанный выше запрос сервер отправляет информацию о поле kind и массив парных данных items, включающий только заголовки HTML и сведения о длине.

200 OK
{
  "kind": "demo",
  "items": [{
    "title": "First title",
    "characteristics": {
      "length": "short"
    }
  }, {
    "title": "Second title",
    "characteristics": {
      "length": "long"
    }
  },
  ...
  ]
}

Обратите внимание на то, что ответ представляет собой объект JSON, включающий только выбранные поля и их родительские объекты.

Далее рассказывается о форматировании параметра fields, а также более подробно рассматривается содержание ответа.

Краткая информация о синтаксисе параметра fields

Формат значения параметра запроса fields основан на синтаксисе XPath. Его основные принципы перечислены ниже. В следующем разделе также представлены дополнительные примеры.

  • Чтобы выбрать несколько полей, перечислите их через запятую.
  • Формат a/b позволяет выбрать поле b, вложенное в поле a, а формат a/b/c – поле c, вложенное в поле b.

    Исключение. Для ответов API, в которых используются оболочки data (то есть ответ вложен в объект data вида data: { ... }), не включайте объект data в спецификацию fields (например, data/a/b), так как это приведет к ошибке. Вместо этого используйте спецификацию fields вида a/b.

  • Используйте подселектор, чтобы запросить набор определенных подполей массивов или объектов. Для этого поместите выражения в скобки: ( ).

    Пример: fields=items(id,author/email) возвращает только адреса электронной почты авторов и идентификаторы всех элементов в массиве. Вы также можете указать одно подполе, где fields=items(id) эквивалентно fields=items/id.

  • При выборе полей можно использовать подстановочные знаки.

    Пример: fields=items/pagemap/* выбирает все объекты, содержащиеся в данных PageMap.

Другие примеры использования параметра fields

Из следующих примеров видно, как значение параметра fields влияет на ответ.

Примечание. Как и все остальные значения параметров запроса, значение fields должно быть закодировано. В целях наглядности в приведенных здесь примерах кодировка не используется.

Укажите поля, которые вы хотите получить, или используйте селекторы
Значение параметра fields представляет собой список разделенных запятыми полей, каждое их которых определено относительно корневой части запроса. Таким образом, при выполнении операции list ответ будет представлять собой коллекцию, обычно включающую в себя массив ресурсов. Если вы выполняете операцию, возвращающую один ресурс, поля необходимо определить относительно него. Если выбранное поле представляет собой массив или входит в него, сервер возвращает выбранную часть всех элементов массива.

Несколько примеров на уровне коллекций.
Примеры Результат
items Возвращает все элементы из массива items, в том числе все поля для каждого элемента, но не другие поля.
etag,items Возвращает поле etag и все элементы массива.
items/title Возвращает только поле title для всех элементов массива.

Если возвращается вложенное поле, в ответ также включаются его родительские объекты. Если для родительского объекта явным образом не выбраны другие дочерние поля, они не возвращаются.
context/facets/label Возвращает только поле label для всех элементов массива facets, вложенного в объект context.
items/pagemap/*/title Для всех элементов массива items возвращает только поле title дочерних объектов pagemap (при его наличии).

Несколько примеров на уровне ресурсов.
Примеры Результат
title Возвращает поле title запрошенного ресурса.
author/uri Возвращает подполе uri объекта author для запрошенного ресурса.
links/*/href
Возвращает поле href всех дочерних объектов links.
Чтобы запрашивать лишь часть данных из определенных полей, используйте подселекторы.
Если в запросе указаны определенные поля, сервер по умолчанию возвращает все соответствующие объекты или элементы массива. Вы можете указать, что ответ должен включать только определенные подполя. Для этого используйте подселектор ( ).
Пример Результат
items(title,author/uri) Возвращает только значения title и uri автора для всех элементов массива items.

Работа с частичными ответами

После обработки действительного запроса, включающего параметр fields, сервер возвращает код статуса HTTP 200 OK вместе с запрошенными данными. Если в параметре запроса fields есть ошибка или он недействителен, сервер возвращает код статуса HTTP 400 Bad Request, а также сообщение об ошибке, указывающее, что пользователь неправильно выбрал поля (например, "Invalid field selection a/b").

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

https://www.googleapis.com/demo/v1?fields=kind,items(title,characteristics/length)

Частичный ответ выглядит так:

200 OK
{
  "kind": "demo",
  "items": [{
    "title": "First title",
    "characteristics": {
      "length": "short"
    }
  }, {
    "title": "Second title",
    "characteristics": {
      "length": "long"
    }
  },
  ...
  ]
}

Примечание. В API, которые поддерживают параметры запроса для разбиения данных на страницы (например, maxResults и nextPageToken), можно использовать такие параметры для сокращения числа результатов по каждому запросу. В противном случае основные преимущества частичных ответов теряют свою актуальность.

Метод PATCH (частичное обновление)

При изменении ресурсов вы также можете избежать отправки излишних данных. Чтобы передать новые данные только для определенных полей, используйте HTTP-метод PATCH. Описанная в этом документе семантика PATCH отличается от той, которая была реализована ранее в протоколе GData (к тому же новая версия проще в применении).

Приведенный ниже пример показывает, как использование метода PATCH позволяет сократить объем передаваемых данных.

Пример

Допустим, нам нужно обновить только заголовок гипотетического ресурса Demo. Кроме заголовка у ресурса также имеется комментарий, набор характеристик, статус и ещё много других полей, однако в PATCH-запросе указывается только то поле, которое требуется изменить, а именно title.

PATCH https://www.googleapis.com/demo/v1/324
Authorization: Bearer your_auth_token
Content-Type: application/json

{
  "title": "New title"
}

Ответ:

200 OK
{
  "title": "New title",
  "comment": "First comment.",
  "characteristics": {
    "length": "short",
    "accuracy": "high",
    "followers": ["Jo", "Will"],
  },
  "status": "active",
  ...
}

Сервер возвращает код статуса 200 OK вместе с частичным представлением обновленного ресурса. Поскольку в PATCH-запрос было включено одно только поле title, изменилось лишь его значение.

Примечание. Используя как метод PATCH, так и параметр для запроса частичного ответа fields, вы добьетесь ещё большей эффективности: PATCH сократит размер запроса, а частичный ответ – размер ответа. Таким образом, при параллельном использовании PATCH-запроса и параметра fields вы минимизируете объем данных, передаваемых в обоих направлениях.

Семантика PATCH-запроса

В тело PATCH-запроса включаются только те поля, которые нужно изменить в ресурсе. Вместе с полем необходимо указать любые включающие его родительские объекты (аналогично тому, как при частичном ответе возвращаются все включающие родительские объекты). Новые данные, которые вы отправляете, войдут в данные для родительского объекта, если таковой имеется.

  • Добавление. Чтобы добавить новое поле, укажите его название и задайте значение.
  • Изменение. Чтобы изменить значение существующего поля, укажите его название и задайте новое значение.
  • Удаление. Чтобы удалить поле, укажите его название и задайте значение null. Пример: "comment": null. Вы также можете удалить весь объект (если возможно его изменение), установив для него значение null. Если вы используете клиентскую библиотеку Java API, используйте Data.NULL_STRING. Узнать больше о функции удаления можно в разделе JSON null.

Примечание относительно массивов. Массив из PATCH-запроса полностью замещает существующий массив. Изменять, добавлять и удалять элементы массива по отдельности нельзя.

Использование PATCH-запроса в цикле "Чтение – изменение – запись"

Прежде всего имеет смысл получить частичный ответ с данными, которые вы хотите изменить. Это особенно важно, если ресурс использует идентификаторы ETag, так как для его успешного обновления вам нужно будет предоставить текущее значение ETag в HTTP-заголовке If-Match. Получив данные, вы сможете изменить требуемые значения и отправить новое частичное представление ресурса в PATCH-запросе. Приведем пример. Допустим, ресурс Demo использует идентификаторы ETag:

GET https://www.googleapis.com/demo/v1/324?fields=etag,title,comment,characteristics
Authorization: Bearer your_auth_token

Частичный ответ:

200 OK
{
  "etag": "ETagString"
  "title": "New title"
  "comment": "First comment.",
  "characteristics": {
    "length": "short",
    "level": "5",
    "followers": ["Jo", "Will"],
  }
}

Показанный ниже PATCH-запрос основан на данных этого ответа. В нем также используется параметр fields, ограничивающий объем данных, которые будут возвращены в ответе на PATCH-запрос:

PATCH https://www.googleapis.com/demo/v1/324?fields=etag,title,comment,characteristics
Authorization: Bearer your_auth_token
Content-Type: application/json
If-Match: "ETagString"
{
  "etag": "ETagString"
  "title": "",                  /* Clear the value of the title by setting it to the empty string. */
  "comment": null,              /* Delete the comment by replacing its value with null. */
  "characteristics": {
    "length": "short",
    "level": "10",              /* Modify the level value. */
    "followers": ["Jo", "Liz"], /* Replace the followers array to delete Will and add Liz. */
    "accuracy": "high"          /* Add a new characteristic. */
  },
}

Сервер возвращает код статуса 200 ОК вместе с частичным представлением обновленного ресурса:

200 OK
{
  "etag": "newETagString"
  "title": "",                 /* Title is cleared; deleted comment field is missing. */
  "characteristics": {
    "length": "short",
    "level": "10",             /* Value is updated.*/
    "followers": ["Jo" "Liz"], /* New follower Liz is present; deleted Will is missing. */
    "accuracy": "high"         /* New characteristic is present. */
  }
}

PATCH-запрос без предварительного ответа

Для выполнения определенных PATCH-запросов вам понадобятся данные, полученные ранее. Например, если вы хотите добавить в массив новый элемент, не потеряв при этом ни один из старых, сначала нужно будет получить существующие данные. Точно так же, чтобы обновить ресурс с идентификаторами ETag, в запросе необходимо будет указать предыдущее значение ETag.

Примечание. При использовании идентификаторов ETag можно задать HTTP-заголовок "If-Match: *", чтобы обеспечить реализацию метода PATCH.  В этом случае запись можно выполнять без предварительного чтения.

Однако в остальных случаях для PATCH-запроса не требуется получать имеющиеся данные. Так, вы можете обновить существующее поле или добавить новое. Пример:

PATCH https://www.googleapis.com/demo/v1/324?fields=comment,characteristics
Authorization: Bearer your_auth_token
Content-Type: application/json

{
  "comment": "A new comment",
  "characteristics": {
    "volume": "loud",
    "accuracy": null
  }
}

Если в поле комментария уже есть значение, оно будет перезаписано, а если нет – будет задано новое значение. Точно так же и с полем volume: если в нем имеется значение, оно будет заменено на новое, а если нет – создано. Значение поля accuracy будет удалено.

Ответ на PATCH-запрос

Обработав PATCH-запрос, API возвращает код HTTP 200 OK200 OK и полное представление измененного ресурса. При использовании идентификаторов ETag сервер обновляет значения ETag после успешной обработки PATCH-запроса (точно так же, как и в случае с методом PUT).

В ответ на PATCH-запрос вы получите полное представление ресурса, если только не был задан параметр fields, ограничивающий объем возвращаемых данных.

Если выполнение PATCH-запроса приведет к тому, что ресурс станет синтаксически или семантически недействительным, сервер возвращает код статуса HTTP 400 Bad Request или 422 Unprocessable Entity и оставляет ресурс без изменений. Например, если вы попытаетесь удалить значение обязательного поля, сервер выдаст ошибку.

Если PATCH не поддерживается

Если ваш брандмауэр блокирует HTTP-запросы PATCH, выполните запрос POST, задав в заголовке override значение PATCH, как показано ниже:

POST https://www.googleapis.com/...
X-HTTP-Method-Override: PATCH
...

Разница между методами PATCH и PUT

При отправке запроса на обновление с использованием HTTP-метода PUT вам нужно задать только обязательные и необязательные поля. Если вы укажете значения для полей, устанавливаемых сервером, они будут проигнорированы. Хотя теоретически PUT вполне может применяться для пересылки частичных обновлений, на практике у этого метода есть несколько недостатков. Если вы не предоставите обязательные параметры, PUT-запрос не будет выполнен вовсе, а если вы не укажете необязательные параметры, то предыдущие данные будут стерты.

Поэтому для частичного обновления гораздо безопаснее использовать метод PATCH: поля, для которых вы не задали новые значения, просто останутся без изменений. Единственное исключение из этого правила – повторяющиеся элементы или массивы. Если вы не включите в запрос ни один из элементов, все они останутся без изменений. Если же вы обновите какой-либо из них, будет заменен весь набор элементов.

Рекомендуемое ограничение на частоту запросов

Частота вызовов API может быть ограничена. Рекомендуем выполнять не более 20 запросов в секунду.

Пакетные запросы Google Pay for Passes

В Google Pay for Passes API поддерживаются пакетные вызовы. Благодаря этому со стороны клиента может выполняться меньше подключений. Подробнее о пакетных запросах написано здесь.

Ниже приведен пример кода для пакетных запросов с использованием библиотек Google Pay for Passes.

Java

//Set up client batch
Walletobjects client = new Walletobjects.Builder(httpTransport, jsonFactory, credentials)
        .setApplicationName(config.getApplicationName())
        .build();
BatchRequest batch = client.batch();

//Pre insert class
Services.VerticalType verticalType = Services.VerticalType.OFFER;
String classUid = String.format("%s_CLASS_%s", verticalType.toString(), UUID.randomUUID().toString());
String classId = String.format("%s.%s" , config.getIssuerId(), classUid);
OfferClass theClass = rsc.makeOfferClassResource(classId);
client.offerclass().insert(theClass).execute();

// Batch 10 object inserts
for (int i = 0; i < 10; ++i){

    // your objectUid should be a hash based off of pass metadata, for the demo we will use pass-type_object_uniqueid
    String objectUid = String.format("%s_OBJECT_%s", verticalType.toString(), UUID.randomUUID().toString());

    // check Reference API for format of "id", for example offer:(https://developers.google.com/pay/passes/reference/v1/offerobject/insert).
    // Must be alphanumeric characters, ".", "_", or "-".
    String objectId = String.format("%s.%s", config.getIssuerId(), objectUid);
    OfferObject anObject = rsc.makeOfferObjectResource((classId), objectId);
    client.offerobject().insert(anObject).queue(batch, new JsonBatchCallback(){

        @Override
        public void onSuccess(OfferObject t, HttpHeaders responseHeaders) throws IOException {
            System.out.println("Success!");

        }

        @Override
        public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
            System.out.println("Error Message:" + e.getMessage());

        }
    });
}
//Execute batch
batch.execute();

Python

# Generating Credentials for auth request
credentials = service_account.Credentials.from_service_account_file(
    config.SERVICE_ACCOUNT_FILE,
        scopes = config.SCOPES)
authed_session = AuthorizedSession(credentials)
# Pre insert class
classUid = 'OFFER' + '_CLASS_'+ str(uuid.uuid4())
config.offer_class['id'] = '%s.%s' % (config.ISSUER_ID,classUid)
response = authed_session.post('https://www.googleapis.com/walletobjects/v1/offerClass?strict=true', json=config.offer_class)
# Build a batch of 10 object insert requests
data = ''
for i in range(9):
  objectUid = 'OFFER' + '_OBJECT_'+ str(uuid.uuid4())
  objectId = '%s.%s' % (config.ISSUER_ID,objectUid)
  data += '--batch_mybatch\n'
  data += 'Content-Type: application/json\n\n'
  data += 'POST /walletobjects/v1/offerObject/\n\n'
  config.offer_object['id'] = objectId
  data += json.dumps(config.offer_object) + '\n\n'
data += '--batch_mybatch--'
# execute batch
response = authed_session.post('https://walletobjects.googleapis.com/batch', data=data, headers={'Content-Type': 'multipart/mixed; boundary=batch_mybatch'})
print('Response:')
print(response.content.decode('UTF-8'))

PHP

// Create an Google_Client which facilitates REST call
$client = new Google_Client();
// do OAuth2.0 via service account file.
// See https://developers.google.com/api-client-library/php/auth/service-accounts#authorizingrequests
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . SERVICE_ACCOUNT_FILE); // for Google_Client() initialization for server-to-server
$client->useApplicationDefaultCredentials();
// Set application name.
$client->setApplicationName(APPLICATION_NAME);
// Set Api scopes.
$client->setScopes(array(SCOPES));
// Pre insert class
$vertical= "OFFER";
$service = new Google_Service_Walletobjects($client);
$classUid = $vertical."_CLASS_".uniqid('', true);
$classId = sprintf("%s.%s" , ISSUER_ID, $classUid);
$offerClass = ResourceDefinitions::makeOfferClassResource($classId);
$response = $service->offerclass->insert($offerClass);
//Initialize service
$service = new Google_Service_Walletobjects($client);
$service->getClient()->setUseBatch(true);
//Get batch
$batch = $service->createBatch();
// Batch 10 object inserts
for($i=0; $i<=10; $i++){
   $vertical= "OFFER";
   $objectUid= $vertical."_OBJECT_".uniqid('', true)
   $objectId = sprintf("%s.%s", ISSUER_ID, $objectUid);
   $offerObject = ResourceDefinitions::makeOfferObjectResource($classId, $objectId);
   $batch->add($service->offerobject->insert($offerObject));
}
// Execute batch
$results = $batch->execute();
var_export($results);