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

1概要

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

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

2プロトコルバッファ

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

2.1. プロトコルバッファの紹介

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

syntax = "proto3";
package baeldung;
option java__package = "com.baeldung.protobuf";
option java__outer__classname = "BaeldungTraining";

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;
    }
}

このチュートリアルでは、プロトコルバッファコンパイラとプロトコルバッファ言語の両方のバージョン3を使用します。したがって、 .proto ファイルは syntax =“ proto3” 宣言で始める必要があります。コンパイラバージョン2が使用されている場合、この宣言は省略されます。次に、 package 宣言があります。これは、他のプロジェクトとの命名の競合を避けるための、このメッセージ構造のネームスペースです。

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

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

2.2. Java によるプロトコルバッファ

メッセージ構造を定義したら、この言語に依存しないコンテンツをJavaコードに変換するためのコンパイラが必要です。適切なコンパイラのバージョンを入手するためにhttps://github.com/google/protobuf[Protocol Buffers repository]の指示に従うことができます。代わりに、https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22com.google.protobuf%22を検索して、Maven中央リポジトリからビルド済みバイナリコンパイラをダウンロードすることもできます。 %20AND%20a%3A%22protoc%22[ com.google.protobuf:protoc ]アーティファクトしてから、プラットフォームに適したバージョンを選択してください。

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

protoc --java__out=java resources/baeldung.proto

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

コンパイラに加えて、Protocol Buffersランタイムが必要です。これは、Maven POMファイルに次の依存関係を追加することによって達成できます。

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.0.0-beta-3</version>
</dependency>

コンパイラのバージョンと同じであれば、別のバージョンのランタイムを使用することもできます。最新のものについては、https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22com.google.protobuf%22%20AND%20a%3A%22protobuf-java%22をご覧ください。[このリンク]。

2.3. メッセージ記述のコンパイル

コンパイラを使用して、 .proto ファイル内のメッセージは静的な入れ子になったJavaクラスにコンパイルされます。上記の例では、 Course および Student メッセージはそれぞれ、 Course および Student Javaクラスに変換されます。同時に、メッセージのフィールドは、それらの生成された型の中のJavaBeansスタイルのゲッターとセッターにコンパイルされます。各フィールド宣言の終わりにある、等号と数字で構成されるマーカーは、関連フィールドを2進形式でエンコードするために使用される固有のタグです。

メッセージの型付きフィールドを調べて、それらがどのようにアクセサメソッドに変換されるのかを確認します。

それでは、 コース メッセージから始めましょう。 id course name を含む2つの単純なフィールドがあります。それらのプロトコルバッファ型 int32 string は、Javaの int String__型に変換されます。これはコンパイル後の関連するゲッターです(実装は簡潔にするために省略されています)。

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

他の言語との連携を維持するために、型付きフィールドの名前は大文字と小文字を区別する必要があります(個々の単語はアンダースコア文字で区切ります)。コンパイラは、これらの名前をJavaの規則に従ってラクダ文字に変換します。

コースメッセージの最後のフィールドである学生は、学生複合タイプであり、これについては後述する。このフィールドの前には repeated キーワードが付きます。つまり、何回でも繰り返すことができます。コンパイラは student フィールドに関連するいくつかのメソッドを次のように生成します(実装なし)。

public java.util.List<com.baeldung.protobuf.BaeldungTraining.Student> getStudentList();
public int getStudentCount();
public com.baeldung.protobuf.BaeldungTraining.Student getStudent(int index);

それでは、コースメッセージの学生フィールドの複合型として使用される学生メッセージに進みます。 id first name last 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<com.baeldung.protobuf.BaeldungTraining.Student.PhoneNumber> getPhoneList();
public int getPhoneCount();
public com.baeldung.protobuf.BaeldungTraining.Student.PhoneNumber getPhone(int index);

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

public java.lang.String getNumber();
public com.baeldung.protobuf.BaeldungTraining.Student.PhoneType getType();

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

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

3 Protobuf In Spring REST API

このセクションでは、Spring Bootを使ったRESTサービスの設定について説明します。

3.1. Beanの宣言

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

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

    @Bean
    public CourseRepository createTestCourses() {
        Map<Integer, Course> 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<Student>())
          .build();
        courses.put(course1.getId(), course1);
        courses.put(course2.getId(), course2);
        return new CourseRepository(courses);
    }

   //Other declarations
}

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

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

ここで重要なのは、標準のPOJOではなく、** Protocol Buffer固有のデータを使用していることです。

これが CourseRepository の単純な実装です。

public class CourseRepository {
    Map<Integer, Course> courses;

    public CourseRepository (Map<Integer, Course> 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クライアントとテスト

これで、単純なAPI実装を見てきました。次に、2つの方法を使用して、クライアント側でのプロトコルバッファメッセージの逆シリアル化を説明しましょう。

最初のものはメッセージを自動的に変換するために事前設定された ProtobufHttpMessageConverter beanと共に RestTemplate APIを利用します。

二つ目は protobuf-java-format を使ってプロトコルバッファのレスポンスをJSONドキュメントに手動で変換することです。

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

@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リクエストを送信し、プロトコルバッファメッセージの形式でレスポンスを受け取り、 RestTemplate APIを使用してそれを確認する方法は次のとおりです。

@Autowired
private RestTemplate restTemplate;

@Test
public void whenUsingRestTemplate__thenSucceed() {
    ResponseEntity<Course> 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を宣言し、 ProtobufHttpMessageConverter Beanを再利用することができます。

4.3. HttpClient でテストする

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

<dependency>
    <groupId>com.googlecode.protobuf-java-format</groupId>
    <artifactId>protobuf-java-format</artifactId>
    <version>1.4</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.2</version>
</dependency>

これらの依存関係の最新版については、https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22com.googlecode.protobuf-java-format%22%20AND%をご覧ください。 20a%3A%22protobuf-java-format%22[protobuf-java-format]およびhttps://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22org.apache.httpcomponents%22% 20AND%20a%3A%22httpclient%22[httpclient]Maven中央リポジトリー内の成果物。

次に、クライアントを作成し、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の設定を説明しました。その後、クライアントサポートとシリアライゼーション - デシリアライゼーションメカニズムに移行しました。

すべての例とコードスニペットの実装はhttps://github.com/eugenp/tutorials/tree/master/spring-protobuf[GitHubプロジェクト]にあります。