プロトコルバッファを含む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プロジェクト]にあります。