プロトコルバッファを含むSpring REST API

プロトコルバッファーを使用したSpring REST API

1. 概要

Protocol Buffersは、構造化データのシリアル化と逆シリアル化のための言語とプラットフォームに依存しないメカニズムであり、その作成者であるGoogleによって、XMLやJSONなどの他のタイプのペイロードよりもはるかに高速で小さくシンプルであると宣言されています。

このチュートリアルでは、このバイナリベースのメッセージ構造を活用するためのREST APIのセットアップについて説明します。

2. プロトコルバッファ

このセクションでは、プロトコルバッファに関する基本的な情報と、それらがJavaエコシステムにどのように適用されるかについて説明します。

2.1. プロトコルバッファの概要

プロトコルバッファを利用するには、.protoファイルでメッセージ構造を定義する必要があります。 各ファイルは、あるノードから別のノードに転送されるか、データソースに保存されるデータの説明です。 これは、example.protoという名前で、src/main/resourcesディレクトリにある.protoファイルの例です。 このファイルは、後でこのチュートリアルで使用されます。

syntax = "proto3";
package example;
option java_package = "com.example.protobuf";
option java_outer_classname = "exampleTraining";

message Course {
    int32 id = 1;
    string course_name = 2;
    repeated Student student = 3;
}
message Student {
    int32 id = 1;
    string first_name = 2;
    string last_name = 3;
    string email = 4;
    repeated PhoneNumber phone = 5;
    message PhoneNumber {
        string number = 1;
        PhoneType type = 2;
    }
    enum PhoneType {
        MOBILE = 0;
        LANDLINE = 1;
    }
}

このチュートリアルでは、we use version 3 of both protocol buffer compiler and protocol buffer language、したがって.protoファイルはsyntax = “proto3”宣言で始まる必要があります。 コンパイラバージョン2が使用されている場合、この宣言は省略されます。 次に、package宣言があります。これは、他のプロジェクトとの名前の競合を回避するための、このメッセージ構造の名前空間です。

次の2つの宣言はJavaでのみ使用されます。java_packageオプションは、生成されたクラスが存在するパッケージを指定し、java_outer_classnameオプションは、この.protoで定義されたすべての型を囲むクラスの名前を示します。 sファイル。

以下のサブセクション2.3では、残りの要素と、それらがJavaコードにコンパイルされる方法について説明します。

2.2. Javaを使用したプロトコルバッファ

メッセージ構造が定義された後、この言語に依存しないコンテンツをJavaコードに変換するコンパイラが必要です。 適切なコンパイラバージョンを取得するために、Protocol Buffers repositoryの指示に従うことができます。 または、com.google.protobuf:protocアーティファクトを検索し、プラットフォームに適したバージョンを選択して、Maven中央リポジトリからビルド済みのバイナリコンパイラをダウンロードすることもできます。

次に、コンパイラをプロジェクトのsrc/mainディレクトリにコピーし、コマンドラインで次のコマンドを実行します。

protoc --java_out=java resources/example.proto

これにより、example.protoファイルのoption宣言で指定されているように、com.example.protobufパッケージ内のexampleTrainingクラスのソースファイルが生成されます。

コンパイラに加えて、プロトコルバッファランタイムが必要です。 これは、Maven POMファイルに次の依存関係を追加することで実現できます。


    com.google.protobuf
    protobuf-java
    3.0.0-beta-3

コンパイラのバージョンと同じであれば、別のバージョンのランタイムを使用できます。 最新のものについては、this linkをチェックしてください。

2.3. メッセージの説明のコンパイル

コンパイラを使用することにより、.protoファイル内のメッセージは静的にネストされたJavaクラスにコンパイルされます。 上記の例では、CourseおよびStudentメッセージはそれぞれCourseおよびStudentJavaクラスに変換されます。 同時に、メッセージのフィールドは、生成された型内でJavaBeansスタイルのゲッターとセッターにコンパイルされます。 各フィールド宣言の最後にある等号と数字で構成されるマーカーは、関連するフィールドをバイナリ形式でエンコードするために使用される一意のタグです。

メッセージのタイプされたフィールドをウォークスルーして、それらがどのようにアクセサメソッドに変換されるかを確認します。

Courseメッセージから始めましょう。 idcourse_nameを含む2つの単純なフィールドがあります。 それらのプロトコルバッファタイプ、int32およびstringは、JavaintおよびStringタイプに変換されます。 以下に、コンパイル後の関連するゲッターを示します(簡潔にするために実装は省略されています)。

public int getId();
public java.lang.String getCourseName();

他の言語との連携を維持するために、入力したフィールドの名前はスネークケースにする必要があります(個々の単語はアンダースコア文字で区切る)ことに注意してください。 コンパイラは、Javaの規則に従ってこれらの名前をキャメルケースに変換します。

Courseメッセージの最後のフィールドであるstudentは、Student複合型であり、これについては以下で説明します。 このフィールドの前にはrepeatedキーワードが付いています。これは、このフィールドを何度でも繰り返すことができることを意味します。 コンパイラーは、studentフィールドに関連付けられたいくつかのメソッドを次のように生成します(実装なし)。

public java.util.List getStudentList();
public int getStudentCount();
public com.example.protobuf.exampleTraining.Student getStudent(int index);

次に、Courseメッセージのstudentフィールドの複合型として使用されるStudentメッセージに移ります。 idfirst_namelast_name、およびemailを含むその単純なフィールドは、Javaアクセサーメソッドを作成するために使用されます。

public int getId();
public java.lang.String getFirstName();
public java.lang.String getLastName();
public java.lang.String.getEmail();

最後のフィールドphoneは、PhoneNumber複合型です。 Courseメッセージのstudentフィールドと同様に、このフィールドは反復的であり、いくつかの関連するメソッドがあります。

public java.util.List getPhoneList();
public int getPhoneCount();
public com.example.protobuf.exampleTraining.Student.PhoneNumber getPhone(int index);

PhoneNumberメッセージは、exampleTraining.Student.PhoneNumberネスト型にコンパイルされ、メッセージのフィールドに対応する2つのゲッターがあります。

public java.lang.String getNumber();
public com.example.protobuf.exampleTraining.Student.PhoneType getType();

PhoneNumberメッセージのtypeフィールドの複合型であるPhoneTypeは列挙型であり、exampleTraining.Student内にネストされたJavaenum型に変換されます。 )sクラス:

public enum PhoneType implements com.google.protobuf.ProtocolMessageEnum {
    MOBILE(0),
    LANDLINE(1),
    UNRECOGNIZED(-1),
    ;
    // Other declarations
}

3. Spring RESTAPIのProtobuf

このセクションでは、Spring Bootを使用してRESTサービスを設定する方法を説明します。

3.1. Bean宣言

メインの@SpringBootApplicationの定義から始めましょう:

@SpringBootApplication
public class Application {
    @Bean
    ProtobufHttpMessageConverter protobufHttpMessageConverter() {
        return new ProtobufHttpMessageConverter();
    }

    @Bean
    public CourseRepository createTestCourses() {
        Map courses = new HashMap<>();
        Course course1 = Course.newBuilder()
          .setId(1)
          .setCourseName("REST with Spring")
          .addAllStudent(createTestStudents())
          .build();
        Course course2 = Course.newBuilder()
          .setId(2)
          .setCourseName("Learn Spring Security")
          .addAllStudent(new ArrayList())
          .build();
        courses.put(course1.getId(), course1);
        courses.put(course2.getId(), course2);
        return new CourseRepository(courses);
    }

    // Other declarations
}

ProtobufHttpMessageConverter Beanは、@RequestMappingアノテーション付きメソッドによって返された応答をプロトコルバッファメッセージに変換するために使用されます。

もう1つのBeanCourseRepositoryには、APIのテストデータが含まれています。

ここで重要なのは、Protocol Buffer specific data – not with standard POJOsで操作していることです。

CourseRepositoryの簡単な実装は次のとおりです。

public class CourseRepository {
    Map courses;

    public CourseRepository (Map courses) {
        this.courses = courses;
    }

    public Course getCourse(int id) {
        return courses.get(id);
    }
}

3.2. コントローラー構成

テストURLの@Controllerクラスは次のように定義できます。

@RestController
public class CourseController {
    @Autowired
    CourseRepository courseRepo;

    @RequestMapping("/courses/{id}")
    Course customer(@PathVariable Integer id) {
        return courseRepo.getCourse(id);
    }
}

繰り返しになりますが、ここで重要なのは、コントローラーレイヤーから返されるコースDTOが標準のPOJOではないということです。 これが、クライアントに転送される前にプロトコルバッファメッセージに変換されるトリガーになります。

4. RESTクライアントとテスト

これで、2つの方法を使用して単純なAPI実装を確認しました(deserialization of protocol buffer messages on the client sideを説明しましょう)。

1つ目は、事前構成されたProtobufHttpMessageConverter Beanを使用してRestTemplate APIを利用して、メッセージを自動的に変換します。

2つ目は、protobuf-java-formatを使用して、プロトコルバッファ応答をJSONドキュメントに手動で変換することです。

まず、統合テストのコンテキストを設定し、次のようにテストクラスを宣言して、Applicationクラスの構成情報を検索するようにSpringBootに指示する必要があります。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebIntegrationTest
public class ApplicationTest {
    // Other declarations
}

このセクションのすべてのコードスニペットは、ApplicationTestクラスに配置されます。

4.1. 期待される応答

RESTサービスにアクセスする最初のステップは、リクエストURLを決定することです。

private static final String COURSE1_URL = "http://localhost:8080/courses/1";

このCOURSE1_URLは、前に作成したRESTサービスから最初のテストダブルコースを取得するために使用されます。 GETリクエストが上記のURLに送信された後、対応する応答は次のアサーションを使用して検証されます。

private void assertResponse(String response) {
    assertThat(response, containsString("id"));
    assertThat(response, containsString("course_name"));
    assertThat(response, containsString("REST with Spring"));
    assertThat(response, containsString("student"));
    assertThat(response, containsString("first_name"));
    assertThat(response, containsString("last_name"));
    assertThat(response, containsString("email"));
    assertThat(response, containsString("[email protected]"));
    assertThat(response, containsString("[email protected]"));
    assertThat(response, containsString("[email protected]"));
    assertThat(response, containsString("phone"));
    assertThat(response, containsString("number"));
    assertThat(response, containsString("type"));
}

後続のサブセクションで説明する両方のテストケースで、このヘルパーメソッドを使用します。

4.2. RestTemplateでのテスト

クライアントを作成し、指定された宛先にGET要求を送信し、プロトコルバッファメッセージの形式で応答を受信し、RestTemplateAPIを使用してそれを確認する方法を次に示します。

@Autowired
private RestTemplate restTemplate;

@Test
public void whenUsingRestTemplate_thenSucceed() {
    ResponseEntity course = restTemplate.getForEntity(COURSE1_URL, Course.class);
    assertResponse(course.toString());
}

このテストケースを機能させるには、RestTemplateタイプのBeanを構成クラスに登録する必要があります。

@Bean
RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) {
    return new RestTemplate(Arrays.asList(hmc));
}

受信したプロトコルバッファメッセージを自動的に変換するには、ProtobufHttpMessageConverterタイプの別のBeanも必要です。 このBeanは、サブセクション3.1で定義されているものと同じです。 このチュートリアルでは、クライアントとサーバーが同じアプリケーションコンテキストを共有しているため、ApplicationクラスでRestTemplate Beanを宣言し、ProtobufHttpMessageConverterBeanを再利用できます。

4.3. HttpClientでのテスト

HttpClient APIを使用し、プロトコルバッファメッセージを手動で変換する最初のステップは、次の2つの依存関係をMavenPOMファイルに追加することです。


    com.googlecode.protobuf-java-format
    protobuf-java-format
    1.4


    org.apache.httpcomponents
    httpclient
    4.5.2

これらの依存関係の最新バージョンについては、Maven中央リポジトリーのprotobuf-java-formatおよびhttpclientアーティファクトを確認してください。

次に、クライアントを作成し、GETリクエストを実行して、指定されたURLを使用して関連するレスポンスをInputStreamインスタンスに変換しましょう。

private InputStream executeHttpRequest(String url) throws IOException {
    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpGet request = new HttpGet(url);
    HttpResponse httpResponse = httpClient.execute(request);
    return httpResponse.getEntity().getContent();
}

ここで、InputStreamオブジェクトの形式のプロトコルバッファメッセージをJSONドキュメントに変換します。

private String convertProtobufMessageStreamToJsonString(InputStream protobufStream) throws IOException {
    JsonFormat jsonFormat = new JsonFormat();
    Course course = Course.parseFrom(protobufStream);
    return jsonFormat.printToString(course);
}

そして、テストケースが上記で宣言されたプライベートヘルパーメソッドを使用して、応答を検証する方法を次に示します。

@Test
public void whenUsingHttpClient_thenSucceed() throws IOException {
    InputStream responseStream = executeHttpRequest(COURSE1_URL);
    String jsonOutput = convertProtobufMessageStreamToJsonString(responseStream);
    assertResponse(jsonOutput);
}

4.4. JSONでの応答

明確にするために、前のサブセクションで説明したテストで受け取った応答のJSON形式をここに含めます。

id: 1
course_name: "REST with Spring"
student {
    id: 1
    first_name: "John"
    last_name: "Doe"
    email: "[email protected]"
    phone {
        number: "123456"
    }
}
student {
    id: 2
    first_name: "Richard"
    last_name: "Roe"
    email: "[email protected]"
    phone {
        number: "234567"
        type: LANDLINE
    }
}
student {
    id: 3
    first_name: "Jane"
    last_name: "Doe"
    email: "[email protected]"
    phone {
        number: "345678"
    }
    phone {
        number: "456789"
        type: LANDLINE
    }
}

5. 結論

このチュートリアルでは、プロトコルバッファを簡単に紹介し、Springの形式を使用したREST APIのセットアップを説明しました。 その後、クライアントサポートとシリアル化-逆シリアル化メカニズムに移行しました。

すべての例とコードスニペットの実装は、a GitHub projectにあります。