Getting Started with gRPC

gRPC service နှင့် client application တစ်ခု ရေး ကြည့်ရအောင်။

ProductInfo Service

၁) Service Definition သတ်မှတ်ခြင်း

လိုအပ်တဲ့ services တွေနဲ့ client နှင့် server အကြား communicate လုပ်မယ့် message type တွေကို Protocol buffer ကို အသုံးပြုပြီး သတ်မှတ်နိုင်ပါတယ်။

service တစ်ခုမှာ တစ်ခုနဲ့ တစ်ခုထက်ပိုတဲ့ methods တွေပါ၀င်ပါမယ်၊ method တစ်ခုချင်းစီမှာ သူ့ရဲ့ type၊ သူလက်ခံမယ့် input parameter နှင့် သူပြန်လည် ထုတ်ပေးမယ့် output parameter တွေပါရပါမယ်။

service ProductInfo {
   rpc addProduct(Product) returns (ProductID);
   rpc getProduct(ProductID) returns (Product);
}

message တစ်ခုမှာတော့ တစ်ခု သို့မဟုတ် တစ်ခုထက်ပိုတဲ့ fields တွေပါ၀င်ပါမယ်၊ field တစ်ခုချင်းစီမှာ သူ့ရဲ့ အမျိုးအစား (type) နှင့် unique index value ပါ၀င်ရပါမယ်။

message Product {
   string id = 1;
   string name = 2;
   string description = 3;
}

ProductInfo.proto

//ProductInfo.proto
syntax = "proto3";
package ecommerce;

service ProductInfo {
   rpc addProduct(Product) returns (ProductID);
   rpc getProduct(ProductID) returns (Product);
}

message Product {
   string id = 1;
   string name = 2;
   string description = 3;
}

message ProductID {
   string value = 1;
}

၂) Implementation

ပထမဦးစွာ အပေါ်မှာ create လုပ်ခဲ့တဲ့ service definition (ProductInfo.proto) ကနေ၊ ရွေးချယ်ထားတဲ့ language (Java) ရဲ့ source code အဖြစ် generate လုပ်ရပါမယ်

protocol buffer compiler ကို သုံးပြီး source code generate လုပ်လို့ရသလို၊ အခြား automation tools တွေဖြစ်တဲ့ Bazel, Maven ဒါမှမဟုတ် Gradle တို့ကို သုံးပြီးတော့လဲ generate လုပ်နိုင်ပါတယ်။

Eclipse IDE ကို သုံးပြီး Gradle Project တစ်ခုဆောက်ပါမယ်

  • File > New > Project
  • Gradle > Gradle Project
  • Project Name အဖြစ် product-info-server လို့ ပေးပါ

အောက်မှာ ဖော်ပြထားသလိုမျိုး Project Structure လို ရပါလိမ့်မည်

├── build.gradle
...
└── src
    ├── main
    │   ├── java
    │   └── resources
    └── test
        ├── java
        └── resources

src/main directory အောက်မှာ folder တစ်ခုဆောက်ပါ၊ အမည်ကို proto လို့ပေးပါ။ ပြီးရင် အပေါ်မှာ create လုပ်ခဲ့တဲ့ service definition file (ProjectInfo.proto) ကို src/main/proto ထဲသို့ ကူးယူပါ။

build.gradle file ကို အောက်ပါအတိုင်း ပြင်ပါ

apply plugin: 'java'
apply plugin: 'com.google.protobuf'

repositories {
    jcenter()
    mavenCentral()
}

def grpcVersion = '1.24.1'

dependencies {
   compile "io.grpc:grpc-netty:${grpcVersion}"
   compile "io.grpc:grpc-protobuf:${grpcVersion}"
   compile "io.grpc:grpc-stub:${grpcVersion}"
   compile 'com.google.protobuf:protobuf-java:3.9.2'
}

buildscript {
   repositories {
       mavenCentral()
   }
   dependencies {
       classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
   }
}

protobuf {
   protoc {
       artifact = 'com.google.protobuf:protoc:3.9.2'
   }
   plugins {
       grpc {
           artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
       }
   }
   generateProtoTasks {
       all()*.plugins {
           grpc {}
       }
   }
}

sourceSets {
   main {
       java {
           srcDirs 'build/generated/source/proto/main/grpc'
           srcDirs 'build/generated/source/proto/main/java'
       }
   }
}
   
jar {
   manifest {
       attributes "Main-Class": "ecommerce.ProductInfoServer"
   }
   from {
       configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
   }
}

apply plugin: 'application'
startScripts.enabled = false

Service Generation file ကနေ Java Code ကို generate လုပ်ပါ

command prompt ကနေ၊ build.gradle ရှိတဲ့ directory ကိုသွားပြီး၊ gradle build ကို execute လုပ်။ ဒါမှမဟုတ်ရင် Eclipse IDE ရဲ့ Gradle Task View ထဲက build task ကို double click လုပ်ပါ။

build.gradle မှာ ကြေငြာထားသလို၊ အောက်ပါ java class file နှစ်ခုကို generate လုပ်ပေးပါလိမ့်မယ်။

├── build
│   ├── generated
│   │   ├── source
│   │   │   └── proto
│   │   │       ├── main
│   │   │       │   ├── grpc
│   │   │       │   │   └── ecommerce
│   │   │       │   │       └── ProductInfoGrpc.java
│   │   │       │   └── java
│   │   │       │       └── ecommerce
│   │   │       │           └── ProductInfoOuterClass.java

အခုဆိုရင် ဒီ Java project က အားလုံး Ready လို့ပြောနိုင်တဲ့ အဆင့်ကို ရောက်ပါပြီ၊ protoc-gen က generate လုပ်ပေးလိုက်တဲ့ interface ကို implement လုပ်ပြီး၊ လိုချင်တဲ့ business ကို implement လုပ်ဖို့ပဲလိုပါတော့တယ်။

business logic ကို implement လုပ်ပါမယ်

  • src/main/java အောက်မှာ ecommerce ဆိုပြီး Java Package တစ်ခုဆောက်ပါ
  • အဲ့ဒီ package ထဲမှာ၊ ProductInfoImpl.java ဆိုပြီး java class တစ်ခု create လုပ်ပါ

ProductInfoImpl.java

package ecommerce;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import ecommerce.ProductInfoOuterClass.Product;
import ecommerce.ProductInfoOuterClass.ProductID;
import io.grpc.Status;
import io.grpc.StatusException;
import io.grpc.stub.StreamObserver;

public class ProductInfoImpl extends ProductInfoGrpc.ProductInfoImplBase {
   private Map<String, Product> productMap = new HashMap<>(); 
   
   @Override public void addProduct(Product request, StreamObserver<ProductID> responseObserver) {
   UUID uuid = UUID.randomUUID(); 
   String randomUUIDString = uuid.toString();      
   productMap.put(randomUUIDString, request); 
   ProductID id = ProductID.newBuilder()
         .setValue(randomUUIDString)
         .build(); 
   System.out.println("addProduct: " + randomUUIDString);
   responseObserver.onNext(id); 
   responseObserver.onCompleted(); 
} 

   @Override public void getProduct(ProductID request, StreamObserver<Product> responseObserver) {
      System.out.println("getProduct: " + request.getValue()); 
      String id = request.getValue(); 
      if (productMap.containsKey(id)) {   
         responseObserver.onNext(productMap.get(id)); 
         responseObserver.onCompleted(); 
      } else { 
         responseObserver.onError(new StatusException(Status.NOT_FOUND)); 
      } 
   }
}

Java Server တစ်ခုဆောက်မယ်

ecommerce package ထဲမှာ၊ ProductInfoServer.java ဆိုပြီး java class တစ်ခု create လုပ်ပါ

ProductInfoServer.java

package ecommerce;

import java.io.IOException;
import io.grpc.Server;
import io.grpc.ServerBuilder;

public class ProductInfoServer {

   public static void main(String ...args) throws IOException,    InterruptedException {
      int port = 50051;
      Server server = ServerBuilder.forPort(port)
            .addService(new ProductInfoImpl())
            .build()
            .start();

   System.out.println("Server started, listening on " + port);

   Runtime.getRuntime().addShutdownHook(new Thread(() -> {
          System.err.println("Shutting down gRPC server since JVM is " +
                "shutting down");
          if (server != null) {
              server.shutdown();
          }
          System.err.println("Server shut down");
      }));
      server.awaitTermination();
   }
}
└── src
    ├── main
    │   ├── java
    │   │   └── ecommerce
    │   │       ├── ProductInfoImpl.java
    │   │       └── ProductInfoServer.java

gRPC Client ဆောက်မယ်

  • အပေါ်က အဆင့်တွေအတိုင်း gradle project တစ်ခုဆောက်မယ်၊ prject name ကို product-info-client လို့ပေးပါ
  • build.gradle file ကိုလဲ အပေါ်ကအတိုင်းပြင်ပါ၊
  • အပေါ်က အတိုင်းပဲ ProjectInfo.proto file ကို copy ကူးပါ။
  • Build လုပ်ပါ

ecommerce package အောက်မှာ ProductInfoClient.java file ကို create လုပ်ပါ

ProductInfoClient.java

package ecommerce;

import ecommerce.ProductInfoOuterClass.Product;
import ecommerce.ProductInfoOuterClass.ProductID;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

public class ProductInfoClient {

	public static void main(String ...args) throws InterruptedException {
		
		ManagedChannel channel = ManagedChannelBuilder
				.forAddress("localhost", 50051)
				.usePlaintext()
				.build();
		
		ProductInfoGrpc.ProductInfoBlockingStub stub = ProductInfoGrpc.newBlockingStub(channel);
		ProductID productID = stub.addProduct(
				Product.newBuilder()
				.setName("Google Pixel 4")
				.setDescription("this is the best phone")
				.build()
				);
		
		System.out.println(productID.getValue());
		
		Product product = stub.getProduct(productID);
		System.out.println(product.toString());
		
		channel.shutdown();
	}
}

build.gradle file ထဲမှာ Main-Class attributes ကိုတော့ “ecommerce.ProductInfoClient ကို အောက်ပါအတိုင်း reference လုပ်ပါ

...
jar {
   manifest {
       attributes "Main-Class": "ecommerce.ProductInfoClient"
   }
...
└── src
    ├── main
    │   ├── java
    │   │   ├── ecommerce
    │   │   │   └── ProductInfoClient.java

၃) Building and Running

အခုဆိုရင် အားလုံး အဆင်သင့် ဖြစ်ပါပြီ၊ ဒီ project နှစ်ခုကို buid လုပ်ပြီး၊ Run ကြည့်ဖိုပဲလိုပါတော့တယ်။

Building server application and client application

ဒီ Java Application တွေကို Gradle Project အဖြစ်နဲ့ တည်ဆောက်ထားတဲ့ အတွက်၊ Build process က အလွန်လွယ်ကူပါတယ်၊

product-info-server project folder စီသွားပြီး၊ အောက်က command ကို run ပါ

$ gradle build

build process ပြီးသွားရင်၊ build/libs အောက်မှာ executable jar (product-info-server.jar) ကို generate လုပ်ပေးပါလိမ့်မည်။

├── build
│   ├── libs
│   │   └── product-info-server.jar

product-info-server project folder စီသွားပြီး၊ အောက်က command ကို run ပါ

$ gradle build

build process ပြီးသွားရင်၊ build/libs အောက်မှာ executable jar (product-info-client.jar) ကို generate လုပ်ပေးပါလိမ့်မည်။

├── build
│   ├── libs
│   │   └── product-info-client.jar

Running Server Application and Client Application

$ java -jar build/libs/product-info-server.jar 
Server started, listening on 50051
$ java -jar build/libs/product-info-client.jar 
83ba6dd8-4b66-46ce-b694-a903a2271547
name: "Google Pixel 4"
description: "this is the best phone"

စပ်ဆက်အကြောင်းအရာများ