Quick Guide to Seamlessly Connect gRPC with Dubbo-go

Image for post
Image for post

By Deng Ming

gRPC

First, let’s consider a brief overview of gRPC. It’s a Remote Procedure Call (RPC) framework launched by Google. It is implemented by using Interface Definition Language (IDL) to compile a client in different languages. gRPC is a very standard implementation of RPC theory. Therefore, gRPC inherently supports a variety of languages. Over the past few years, it has become a standard way to implement cross-language RPC frameworks. Many outstanding RPC frameworks, such as Spring Cloud and Alibaba’s Dubbo, support gRPC.

The following snippet shows the server usage in Go.

Image for post
Image for post

This mainly consists of two steps. s := grpc.NewServer() and pb.RegisterGreeterServer(s, &server{}).

The first step is easy, but the second step, RegisterGreeterServer, is more difficult because pb.RegisterGreeterServer(s, &server{}) is compiled by the user-defined protobuf.

Fortunately, the compiled method is essentially as follows:

Image for post
Image for post

Therefore, obtain _ Greeter_serviceDesc in Dubbo-go, to register the server. Hence, in Dubbo-go, the most important issue is how to obtain serviceDesc.

Client

The following snippet displays the client usage.

Image for post
Image for post

The procedure is relatively complicated:

1) Create a Connection: conn, err := grpc.Dial(address).
2) Create a Client: c := pb.NewGreeterClient(conn).
3) Call Mthod: r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name}).

It is easy to solve the first problem by reading the address from the user’s configuration. The difficulty lies in the second problem. Just like RegisterGreeterServer, NewGreeterClient is compiled. It seems that the third problem might be solved by reflection.

However, while opening SayHello, the following screen appears.

Image for post
Image for post

Given the definition of greetClient, it is easy to find that the most important part is err := c.cc.Invoke ( ctx, "/helloworld.Greeter/SayHello", in, out, opts... ). In other words, just create a connection and obtain a method and parameters to impersonate c.SayHello through similar calls. Based on this simple analysis of gRPC, it's easy to figure out a solution. However, there is still a need to link the solution with Dubbo-go.

Design

First, review the overall design of Dubbo-go and identify the level on which to adapt gRPC.

Image for post
Image for post

From the preceding features of gRPC, note that gRPC has solved the problems at the codec and transport levels. However, gRPC does not involve the cluster and upper levels. Therefore, this figure shows that the protocol level is most suitable for implementing the adaptation. This implies that it’s possible to develop a gRPC protocol like a Dubbo protocol.

The gRPC protocol is basically an adapter that links the underlying gRPC implementation with a specific Dubbo-go.

Image for post
Image for post

Implementation

In Dubbo-go, gRPC is mainly related to the following:

Image for post
Image for post

Let’s see how the key points mentioned in the gRPC section are implemented.

Server

Image for post
Image for post

The preceding figure presents the elements clearly. Like other Dubbo-go protocols, obtain the service and then the serviceDesc to register the service.

Note the string indicated by a red arrow in the preceding figure, ds, ok := service.(" DubboGrpcService).

This string seems strange. In theory, the service registered here is the one that is compiled by protobuf on the gRPC server. Obviously, compiling a protobuf interface alone does not implement the DubboGrpcService API.

Image for post
Image for post

So, thie critical issue at this point is how to ensure that the execution of ds, ok := service.( DubboGrpcService) is successful? This is explained in the article later..

Client

Dubbo-go designs its own client to impersonate and encapsulate the client in gRPC.

Image for post
Image for post

The definition of this client is similar to that of the preceding greetClient. Check the following NewClient method, which creates a connection and then a client instance through this connection.

Here, the maintained invoker is actually a stub.

The following snippet shows what happens when a call is actually initiated.

Image for post
Image for post

The red box indicates the key steps. Use reflection to obtain the called method from the invoker, which is stub. Then, call the method through reflection.

As mentioned earlier, the problem for ds, ok := service.( DubboGrpcService) is how to have the code compiled by protobuf implement the DubboGrpcService interface.

Some of the preceding code shows how to obtain method instances through reflection based on names, such as method := reflect.ValueOf(impl).MethodByName("GetDubboStub") in the NewClient method. Here, impl indicates the implementation of the service, which is compiled in protobuf. But how can the code compiled in protobuf contain this GetDubboStub method?

The answer is to modify the code generation logic compiled by protobuf.

Fortunately, protobuf allows us to develop our own code generation logic in the form of plug-ins.

Therefore, we simply need to register our own plug-in as shown below.

Image for post
Image for post

Then, this plug-in embeds the required code. For example, it may embed the GetDubboStub method as hown below.

Image for post
Image for post

It may also embed the DubboGrpcService API as shown below.

Image for post
Image for post

This may seem tricky, but it is not! It is only difficult if you don’t know how to modify the generated code in the form of a plug-in. However, once you know this, the process becomes much easier.

Original Source:

Written by

Follow me to keep abreast with the latest technology news, industry insights, and developer trends.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store