ลองสร้าง gRPC ด้วย Java แบบง่ายๆ
gRPC เป็น open source RPC ประสิทธิภาพสูงที่เริ่มพัฒนาโดย Google ที่ช่วยลด boilerplate code และช่วยเชื่อมโยง polyglot service และข้าม data center โดยใช้ protocal buffers เป็น Interface Definition Language (IDL) และรูปแบบ message interchange
gRPC client สามารถ call โดยตรงไปที่ server ในเครื่องที่แตกต่างกัน หรือถ้าเป็นเครื่องเดียวกัน ซึ่งเป็นการง่ายที่เราจะสร้าง distributed application และ service
ในบทความนี้เราจะมาสร้าง gRPC ด้วย Java แบบง่ายๆ (Hello, gRPC)
เริ่มสร้างกันเลย
ในบนความนี้จะใช้ Maven package management ดังนั้น dependency ที่ใช้จะเป็น grpc-netty, grpc-protobuf และ grpc-stub ดังนี้
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>1.16.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.16.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.16.1</version>
</dependency>
กำหนด Service
ก่อนอื่นเราต้องกำหนด Service ด้วยการกำหนด method ที่สามารถ call remotely ได้ด้วย parameter และ return type ที่กำหนด ซึ่งสามารถกำหนดได้ในไฟล์ .proto ซึ่งใช้ protocal buffers ในการกำหนดโครงสร้างของ payload message
ในบทความนี้สร้างไฟล์ HelloService.proto สำหรับตัวอย่าง HelloService
Configuration พื้นฐาน
เริ่มต้นไฟล์ด้วนการกำหนด configuration พื้นฐานของ protocal buffers ด้วยการระบุว่าจะใช้ syntax เวอร์ชั่น proto3 และเพิ่ม java option ให้ compile แยกไฟล์ (ปรกติ java compiler จะสร้างไฟล์เดียว) และสุดท้ายก็กำหนด package ที่จะสร้าง java classes
syntax = "proto3";
option java_multiple_files = true;
package com.iphayao.grpc;
กำหนด Message structure
โดยกำหนด request payload ของ message โดยกำหนดด้วย type HelloRequest และมี attribute เป็น firstName และ lastName และกำหนด unique number ให้กับแต่ละ attribute ซึ่งจะใช้กับ protocal buffers แทนที attribute แทนที่จะใช้ชื่อ
message HelloRequest {
string fistName = 1;
string lastName = 2;
}
กำหนด response payload ของ message
message HelloResponse {
string greeting = 1;
}
กำหนด Service contract
ในไฟล์ HelloService.proto กำหนด service contract ในกับ HelloService ซึ่งเราจะใช้ hello() operation โดยจะรับ request และส่งกลับ response
service HelloService {
rpc hello(HelloRequest) returns (HelloResponse);
}
Generate Code
หลังจากที่สร้างไฟล์ proto แล้วเราก็ใช้ protocal buffers compiler ในการ generate code จากไฟล์ HelloService.proto ในบทความนี้จะใช้ maven plugin ของ protocal buffers โดยเพิ่มส่วนของ build ใน pom.xml เพื่อใช้ protobuf.maven.plugins
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>
com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier}
</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>
io.grpc:protoc-gen-grpc-java:1.4.0:exe:${os.detected.classifier}
</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
ซึ่งจะต้องใช้คำสั่ง mvn build
เพื่อ generate boilerplate code สำหรับ gRPC จะมีไฟล์ที่สำคัญดังนี้
- HelloRequest — เป็นไฟล์ที่กำหนด HelloRequest type
- HelloResponse — เป็นไฟล์ที่กำหนด HelloRespose type
- HelloServiceGrpc — เป็นไฟล์ที่มี abstract class HelloServiceImplBase สำหรับจัดการกับ
hello()
operation
สร้าง Server
implementation เริ่มต้นของ HelloServiceImplBase class จะ throw runtime exception ดังนั้นเราจะต้อง implement method hello() ของเราเองเพื่อจัดการกับ hello operation ซึ่งจะ override method hello เพื่อตอบกลับด้วย HelloResponse
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
@Override
public void hello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
String greeting = new StringBuffer()
.append("Hello, ")
.append(request.getFistName())
.append(" ")
.append(request.getLastName())
.toString();
HelloResponse response = HelloResponse.newBuilder()
.setGreeting(greeting)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
ต่อไปสร้ง gRPC Server เพื่อรอรับ request ด้วยการสร้าง Server ระบุ port ที่จะใช้ call start()
method และใช้ awaitTermination()
สำหรับ idle server จนกว่าจะมีการปิด server
public class GrpcServer {
public static void main(String[] args) throws IOException, InterruptedException {
int port = 8080;
Server server = ServerBuilder.forPort(port)
.addService(new HelloServiceImpl()).build();
server.start();
System.out.println("Server started listening on " + port);
server.awaitTermination();
}
}
สร้าง Client
เมื่อสร้าง server แล้วก็มาสร้าง gRPC Client เพื่อส่ง request ไปหา Server ซึ่ง gRPC จะมี channel construct ซึ่งจะมี abstract implementation เช่น connection, connection pooling, load balancing เป็นต้น
public class GrpcClient {
public static void main(String[] args) {
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
.usePlaintext()
.build();
HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel);
HelloResponse helloResponse = stub.hello(HelloRequest.newBuilder()
.setFistName("Demo")
.setLastName("gRPC")
.build());
System.out.println(helloResponse.getGreeting()); channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
}
สร้าง channel ของ gRPC ด้วย ManagedChannelBuilder
โดยกำหนด host name และ port (localhost, 8080) ใช้ pain text ที่ไม่ได้เข้ารหัสใดๆ (เน้นง่ายไว้ก่อน)
ต่อไปสร้าง stub ที่จะใช้ในการ remote call ไปยัง server ซึ่ง stub เป็นวิธีหลักสำหรับ client ในการติดต่อกับ server ใช้ auto generated stub ครอบ chennel อีกที
เมื่อเราใช้ blocking/synchronous stub ซึ่ง RPC call จะรอ server response และจะไม่ return response ก็ throw exception แต่ก็สามารถใช้แบบ non-blocking/ asynchronous ได้เช่นเดียวกัน
สุดท้ายก็สร้าง call hello operation ด้วย stub ด้วยการสร้าง HelloRequest ส่งไปที่ server และรอการ response หลังจากได้ response แล้วก็แสดงผลลัพย์ทางหน้าจอ
ทดลอง Run
เมื่อเราสร้าง Serer และ Client ทั้งหมดแล้วก็มาลอง Run ดูว่าทำงานได้หรือไม่ โดยใช้คำสั่ง mvn compile
เพื่อ compile project และ mvn exec:java
เพื่อรัน server และ client ของ gRPC
Run Server
$ mvn exec:java -Dexec.mainClass=com.iphayao.grpc.server.GrpcServer
$ Server started listening on 8080
Run Client
$ mvn exec:java -Dexec.mainClass=com.iphayao.grpc.client.GrpcClient
$ Hello, Demo gRPC
สรุป
บทความนี้ทดลองสร้าง gRPC project อย่างง่ายๆ ด้วย Java และ maven จะเห็นได้ว่าเราสามารถสร้างได้ไม่ยากโดยใช้ dependency ที่จำเป็นของ gRPC เพื่อจัดการทั้ง server และ clinet แต่ gRPC สามารถรองรับได้หลายภาษาซึ่งเหมาะสมมากกับแนวคิด microservice ที่แต่ละ service เป็นอิสระทาง framework ต่อกันแต่ก็ใช้ gRPC ในการสื่อสารกันได้