Java gRPC dari Awal

Diterbitkan: 2021-07-16

Mari kita jelajahi cara mengimplementasikan gRPC di Java.

gRPC (Panggilan Prosedur Jarak Jauh Google): gRPC adalah arsitektur RPC sumber terbuka yang dikembangkan oleh Google untuk memungkinkan komunikasi berkecepatan tinggi antar layanan mikro. gRPC memungkinkan pengembang untuk mengintegrasikan layanan yang ditulis dalam bahasa yang berbeda. gRPC menggunakan format pesan Protobuf (Protocol Buffers), format pesan yang sangat efisien dan sangat padat untuk membuat serialisasi data terstruktur.

Untuk beberapa kasus penggunaan, API gRPC mungkin lebih efisien daripada REST API.

Mari kita coba menulis server di gRPC. Pertama, kita perlu menulis beberapa file .proto yang menjelaskan layanan dan model (DTO). Untuk server sederhana, kami akan menggunakan ProfileService dan ProfileDescriptor.

ProfileService terlihat seperti ini:

 syntax = "proto3"; package com.deft.grpc; import "google/protobuf/empty.proto"; import "profile_descriptor.proto"; service ProfileService { rpc GetCurrentProfile (google.protobuf.Empty) returns (ProfileDescriptor) {} rpc clientStream (stream ProfileDescriptor) returns (google.protobuf.Empty) {} rpc serverStream (google.protobuf.Empty) returns (stream ProfileDescriptor) {} rpc biDirectionalStream (stream ProfileDescriptor) returns (stream ProfileDescriptor) {} }

gRPC mendukung berbagai opsi komunikasi client-server. Kami akan memecah semuanya:

  • Panggilan server normal – permintaan/tanggapan.
  • Streaming dari klien ke server.
  • Streaming dari server ke klien.
  • Dan, tentu saja, aliran dua arah.

Layanan ProfileService menggunakan ProfileDescriptor, yang ditentukan di bagian impor:

 syntax = "proto3"; package com.deft.grpc; message ProfileDescriptor { int64 profile_id = 1; string name = 2; }
  • int64 Panjang untuk Java. Biarkan id profil menjadi milik.
  • String – seperti di Java, ini adalah variabel string.

Anda dapat menggunakan Gradle atau pakar untuk membangun proyek. Lebih nyaman bagi saya untuk menggunakan maven. Dan selanjutnya akan menjadi kode menggunakan maven. Ini cukup penting untuk dikatakan karena untuk Gradle, generasi mendatang dari .proto akan sedikit berbeda, dan file build perlu dikonfigurasi secara berbeda. Untuk menulis server gRPC sederhana, kita hanya membutuhkan satu dependensi:

 <dependency> <groupId>io.github.lognet</groupId> <artifactId>grpc-spring-boot-starter</artifactId> <version>4.5.4</version> </dependency>

Ini luar biasa. Pemula ini melakukan banyak pekerjaan bagi kami.

Proyek yang akan kita buat akan terlihat seperti ini:

Kita membutuhkan GrpcServerApplication untuk memulai aplikasi Spring Boot. Dan GrpcProfileService, yang akan mengimplementasikan metode dari layanan .proto . Untuk menggunakan protoc dan menghasilkan kelas dari file .proto tertulis, tambahkan protobuf-maven-plugin ke pom.xml. Bagian build akan terlihat seperti ini:

 <build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.6.2</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.6.1</version> <configuration> <protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot> <outputDirectory>${basedir}/target/generated-sources/grpc-java</outputDirectory> <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.38.0:exe:${os.detected.classifier}</pluginArtifact> <clearOutputDirectory>false</clearOutputDirectory> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
  • protoSourceRoot – menentukan direktori tempat file .proto berada.
  • outputDirectory – pilih direktori tempat file akan dibuat.
  • clearOutputDirectory – tanda yang menunjukkan untuk tidak menghapus file yang dihasilkan.

Pada tahap ini, Anda dapat membangun sebuah proyek. Selanjutnya, Anda harus pergi ke folder yang kami tentukan di direktori output. File yang dihasilkan akan ada di sana. Sekarang Anda dapat mengimplementasikan GrpcProfileService secara bertahap.

Deklarasi kelas akan terlihat seperti ini:

 @GRpcService public class GrpcProfileService extends ProfileServiceGrpc.ProfileServiceImplBase

Anotasi GRPcService – Menandai kelas sebagai kacang layanan grpc.

Karena kami mewarisi layanan kami dari ProfileServiceGrpc , ProfileServiceImplBase , kami dapat mengganti metode kelas induk. Metode pertama yang akan kita timpa adalah getCurrentProfile :

 @Override public void getCurrentProfile(Empty request, StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) { System.out.println("getCurrentProfile"); responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor .newBuilder() .setProfileId(1) .setName("test") .build()); responseObserver.onCompleted(); }

Untuk menanggapi klien, Anda perlu memanggil metode onNext pada StreamObserver yang diteruskan. Setelah mengirim respons, kirim sinyal ke klien bahwa server telah selesai bekerja padaCompleted . Saat mengirim permintaan ke server getCurrentProfile, responsnya adalah:

 { "profile_id": "1", "name": "test" }

Selanjutnya, mari kita lihat aliran server. Dengan pendekatan pesan ini, klien mengirimkan permintaan ke server, server merespons klien dengan aliran pesan. Misalnya, ia mengirimkan lima permintaan dalam satu lingkaran. Saat pengiriman selesai, server mengirim pesan ke klien tentang penyelesaian streaming yang berhasil.

Metode aliran server yang diganti akan terlihat seperti ini:

 @Override public void serverStream(Empty request, StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) { for (int i = 0; i < 5; i++) { responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor .newBuilder() .setProfileId(i) .build()); } responseObserver.onCompleted(); }

Dengan demikian, klien akan menerima lima pesan dengan ProfileId, sama dengan nomor respons.

 { "profile_id": "0", "name": "" } { "profile_id": "1", "name": "" } … { "profile_id": "4", "name": "" }

Aliran klien sangat mirip dengan aliran server. Hanya sekarang klien mengirimkan aliran pesan, dan server memprosesnya. Server dapat memproses pesan segera atau menunggu semua permintaan dari klien dan kemudian memprosesnya.

 @Override public StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> clientStream(StreamObserver<Empty> responseObserver) { return new StreamObserver<>() { @Override public void onNext(ProfileDescriptorOuterClass.ProfileDescriptor profileDescriptor) { log.info("ProfileDescriptor from client. Profile id: {}", profileDescriptor.getProfileId()); } @Override public void onError(Throwable throwable) { } @Override public void onCompleted() { responseObserver.onCompleted(); } }; }

Di aliran Klien, Anda harus mengembalikan StreamObserver ke klien, yang akan menerima pesan server. Metode onError akan dipanggil jika terjadi kesalahan dalam aliran. Misalnya, diakhiri dengan tidak benar.

Untuk mengimplementasikan aliran dua arah, perlu untuk menggabungkan pembuatan aliran dari server dan klien.

 @Override public StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> biDirectionalStream( StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) { return new StreamObserver<>() { int pointCount = 0; @Override public void onNext(ProfileDescriptorOuterClass.ProfileDescriptor profileDescriptor) { log.info("biDirectionalStream, pointCount {}", pointCount); responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor .newBuilder() .setProfileId(pointCount++) .build()); } @Override public void onError(Throwable throwable) { } @Override public void onCompleted() { responseObserver.onCompleted(); } }; }

Dalam contoh ini, sebagai tanggapan atas pesan klien, server akan mengembalikan profil dengan peningkatan pointCount .

Kesimpulan

Kami telah membahas opsi dasar untuk pengiriman pesan antara klien dan server menggunakan gRPC : aliran server yang diimplementasikan, aliran klien, aliran dua arah.

Artikel ini ditulis oleh Sergey Golitsyn