일괄 요청 전송

이 문서에서는 API 호출을 일괄 처리하여 클라이언트가 수행해야 하는 연결 수를 줄이는 방법을 보여줍니다.

이 문서에서는 특히 자바 클라이언트 라이브러리를 사용하여 일괄 요청을 작성하는 방법을 다룹니다. .NET용 Google API 클라이언트 라이브러리에서도 기본적인 예를 확인할 수 있습니다. Google Play EMM API용 일괄 처리 시스템은 OData 일괄 처리 시스템과 동일한 HTTP 구문을 사용합니다.

개요

클라이언트가 Google Play EMM API를 통해 요청할 때마다 일정량의 오버헤드가 발생합니다. Google Play EMM API는 일괄 처리를 지원하므로 클라이언트에서 단일 API 요청에 여러 개의 API 호출을 넣을 수 있습니다.

일괄 처리를 사용해야 하는 상황의 예는 다음과 같습니다.

  • 방금 도메인을 등록했고 업로드할 데이터가 많아졌습니다.
  • 애플리케이션이 오프라인 상태일 때 사용자가 데이터를 변경했으므로 애플리케이션에서 로컬 데이터를 많이 동기화해야 합니다.

이 경우 각 호출을 개별적으로 보내는 대신 단일 요청으로 그룹화할 수 있습니다. 여러 사용자 또는 여러 Google API의 요청을 그룹화할 수도 있습니다.

하지만 단일 일괄 요청의 호출 수는 1,000개로 제한됩니다. 이보다 많은 호출을 해야 하는 경우 일괄 요청을 여러 개 사용합니다.

일괄 처리 세부정보

일괄 요청은 하나의 JSON-RPC 요청으로 결합된 여러 API 호출로 구성됩니다. 이 섹션에서는 일괄 요청 구문을 자세히 설명하며 다음 섹션의 예시를 참조하세요.

참고: n 요청 집합은 사용량 한도가 1개가 아닌 n 요청으로 계산됩니다. 일괄 요청은 처리 전에 일련의 요청으로 분리됩니다.

일괄 요청의 형식

자바 클라이언트 라이브러리에는 각 Google Play EMM API 호출에 대한 요청을 생성하는 호출이 포함되어 있습니다. 예를 들어 기기에 설치된 모든 앱을 나열하려면 다음을 사용합니다.

AndroidEnterprise enterprise = ...;
InstallsListResponse response = enterprise.installs().list(enterpriseId, userId, deviceId)
  .execute();

다음과 같이 여러 요청을 큐에 추가할 수 있는 추가 batch() 호출이 있습니다.

AndroidEnterprise enterprise = ...;
BatchRequest batchRequest = enterprise.batch();
enterprise.installs().list(enterpriseId, userId, deviceId1).queue(batchRequest, callback1);
enterprise.installs().list(enterpriseId, userId, deviceId2).queue(batchRequest, callback2);
enterprise.installs().list(enterpriseId, userId, deviceId3).queue(batchRequest, callback3);
batchRequest.execute();
batchRequest.execute()가 호출되면 큐에 추가된 모든 요청이 JSON 배열로 서버에 한 번에 전송됩니다. 서버는 외부 요청의 쿼리 매개변수와 헤더를 각 부분에 적절하게 적용한 다음 각 부분을 별도의 JSON 요청처럼 취급합니다.

일괄 요청 응답

서버는 각각의 개별 요청을 실행하고 결과를 단일 배열로 이루어진 단일 응답으로 그룹화합니다. 클라이언트 라이브러리는 이 응답을 개별 응답으로 분할하고 각 응답은 queue()에 전달되는 콜백 함수로 전송됩니다. 콜백은 실패를 위한 메서드와 성공하는 메서드를 정의하는 인터페이스입니다. 예를 들어 callback1는 다음 인스턴스로 구현됩니다.

private class InstallsCallback implements JsonBatchCallback<InstallsListResponse> {

  @Override
  public void onSuccess(InstallsListResponse response, HttpHeaders responseHeaders) {
    ...
  }

  @Override
  public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) {
    ...
  }
}

참고: 서버는 호출을 임의의 순서로 수행할 수 있으므로 요청에 지정된 순서대로 결과를 수신하는 데 의존하지 마세요. 두 호출이 특정 순서로 실행되도록 하려면 단일 요청으로 두 번 전송할 수 없습니다. 대신 첫 번째 요청을 단독으로 전송하고 두 번째 응답을 보내기 전에 응답을 기다립니다.

일괄 요청 예

다음 예는 지정된 모든 사용자 기기에 설치된 모든 앱을 나열하는 방법을 보여줍니다. 첫 번째 호출은 기업 및 사용자의 ID를 가져오는 데 사용되며, 순차적으로 실행되어야 합니다. enterprise.devices().list()를 사용하여 모든 기기 ID를 가져온 후에는 일괄 요청을 수행하여 모든 사용자의 기기에 있는 모든 애플리케이션을 한 번에 검색할 수 있습니다.

package com.google.playenterprise.example;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.batch.BatchRequest;
import com.google.api.client.googleapis.batch.json.JsonBatchCallback;
import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.androidenterprise.AndroidEnterprise;
import com.google.api.services.androidenterprise.AndroidEnterprise.Installs;
import com.google.api.services.androidenterprise.AndroidEnterpriseScopes;
import com.google.api.services.androidenterprise.model.Device;
import com.google.api.services.androidenterprise.model.DevicesListResponse;
import com.google.api.services.androidenterprise.model.Enterprise;
import com.google.api.services.androidenterprise.model.Install;
import com.google.api.services.androidenterprise.model.InstallsListResponse;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * Lists all the apps installed on all devices of a given user.
 */
public class ListAllInstalls {
  private AndroidEnterprise enterprise;
  private final List<String> installList = new ArrayList<>();

  public static void main(String[] argv) throws Exception {
    if (argv.length != 2) {
      throw new IllegalArgumentException("Usage: ListAllInstalls email jsonFilename");
    } else if (!argv[0].contains("@")) {
      throw new IllegalArgumentException("First parameter should be a valid email.");
    }
    new ListAllInstalls().run(argv[0], argv[1]);
  }

  private void run(String userEmail, String jsonKeyPath) throws IOException {
    enterprise = createAndroidEnterprise(jsonKeyPath);

    // Get the enterprise id, user id, and user devices.
    String domain = userEmail.split("@")[1];
    List<Enterprise> results = enterprise.enterprises().list(domain).execute().getEnterprise();
    if (results.isEmpty()) {
      throw new RuntimeException("No enterprise found.");
    }
    String enterpriseId = results.get(0).getId();
    String userId = enterprise
        .users()
        .list(enterpriseId, userEmail)
        .execute()
        .getUser()
        .get(0)
        .getId();
    List<Device> devices = getAllDevices(enterpriseId, userId);

    // Batch all calls to get installs on all user devices.
    gatherAllInstalls(enterpriseId, userId, devices);

    for (String entry : installList) {
      // Do something.
      System.out.println(entry);
    }
  }

  private List<Device> getAllDevices(String enterpriseId, String userId) throws IOException {
    DevicesListResponse devices = enterprise.devices().list(enterpriseId, userId).execute();
    return devices.getDevice();
  }

  private void gatherAllInstalls(String enterpriseId, String userId, List<Device> devices)
      throws IOException {
    BatchRequest batchRequest = enterprise.batch();
    for (Device device : devices) {
      Installs.List list = enterprise
          .installs().list(enterpriseId, userId, device.getAndroidId());
      // Each callback can take the specifics of the associated request in its constructor.
      list.queue(batchRequest, new InstallsCallback(device.getAndroidId()));
    }
    // Executes all the queued requests and their callbacks, single-threaded.
    batchRequest.execute();
  }

  private class InstallsCallback extends JsonBatchCallback<InstallsListResponse> {
    private final String androidId;

    InstallsCallback(String androidId) {
      this.androidId = androidId;
    }

    @Override
    public void onSuccess(InstallsListResponse response, HttpHeaders responseHeaders) {
      for (Install install : response.getInstall()) {
        installList.add(androidId + "," + install.getProductId());
      }
    }

    @Override
    public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) {
      throw new RuntimeException("Error fetching a device");
    }
  }

  private AndroidEnterprise createAndroidEnterprise(String jsonKeyPath) throws IOException {
    HttpTransport httpTransport = new NetHttpTransport();
    JsonFactory jsonFactory = new JacksonFactory();

    InputStream is = new BufferedInputStream(new FileInputStream(jsonKeyPath));
    final Credential credential = GoogleCredential.fromStream(is, httpTransport, jsonFactory)
        .createScoped(AndroidEnterpriseScopes.all());

    HttpRequestInitializer httpRequestInitializer = new HttpRequestInitializer() {
      @Override
      public void initialize(HttpRequest request) throws IOException {
        credential.initialize(request);
      }
    };
    return new AndroidEnterprise.Builder(httpTransport, jsonFactory, httpRequestInitializer)
        .build();
  }
}