23 How to Perform Rpc Calls Without an Interface

23 How to perform RPC calls without an interface #

Hello, I’m He Xiaofeng. In the previous lecture, we learned how RPC achieves second-level scaling in and out through dynamic grouping, with the key points being “dynamic” and “isolation.” Today, let’s talk about how to make RPC calls without interfaces.

What are the application scenarios? #

During the operation of RPC, the requirement for the calling party to initiate an RPC call without an interface API is not only mentioned by one business party, but here I will list two very typical scenario examples.

Scenario One: We want to build a unified testing platform that allows various business parties to test their published RPC services online by entering the interface, group name, method name, and parameter values. We have a problem to solve here. The unified testing platform is actually used as the calling party for various RPC services. In the use of the RPC framework, the calling party needs to rely on the interface API provided by the service provider. However, the unified testing platform cannot rely on the interface API provided by all service providers. We cannot modify the code of the platform and redeploy it every time a new service is published. At this time, we need to enable the calling party to initiate an RPC call normally even without the interface provided by the service provider.

Scenario Two: We want to build a lightweight service gateway that allows various business parties to use HTTP to call other services through the service gateway. There is the same problem as in scenario one. The service gateway needs to act as the calling party for all RPC services and cannot rely on the interface API provided by all service providers. The calling party still needs to be able to initiate an RPC call normally even without the interface provided by the service provider.

These two scenarios are encountered frequently, and the ability for the calling party to initiate an RPC call without the interface API provided by the service provider is also very valuable in the RPC framework.

How to do it? #

To implement this feature in the RPC framework, we can use generic invocation. But what exactly does generic invocation mean? Let’s learn how to make an RPC call without an interface.

Let’s recall what I mentioned in the basics section. Through our previous study, we learned that in the process of an RPC call, the client sends a request to the server. As I mentioned in Lesson 05, dynamic proxy can help shield the RPC processing logic, making a remote call look like a local call.

In an RPC call, since the client initiates the remote call to the server through dynamic proxy, the client program must rely on the interface API provided by the service provider, as the dynamic proxy is generated based on this interface API. But what if there is no interface API? How can the client still make an RPC call?

In an RPC call, the client sends a request message to the server, and the server receives and processes it. Then the server sends a response message back to the client. After the client processes the response message, the RPC call is completed. So, as long as the client can send the correct request message to the server without relying on the interface provided by the service provider, then the problem can be solved, right?

Yes, as long as the client encapsulates all the necessary information that the server needs to know, such as the interface name, business group name, method name, and parameter information into a request message sent to the server, the server can parse and process this request message, solving the problem. The process is as shown in the following diagram:

Now that we understand the key to solving the problem, the client needs to send the request message to the server using dynamic proxy as an entry point. Now we need to figure out how the client can send the request message I just mentioned.

We can define a unified interface (GenericService), and when the client creates a GenericService proxy, it specifies the interface name and group name of the interface it needs to call. The parameters of the $invoke method of the GenericService interface are the method name and parameter information.

By passing all the necessary information to the server, including the interface name, business group name, method name, and parameter information, through the $invoke method of the GenericService proxy, we can achieve RPC calls in a situation where there is no interface. The specific interface definition is as follows:

class GenericService {

  Object $invoke(String methodName, String[] paramTypes, Object[] params);
  
}

The dynamic proxy generated based on the GenericService interface class allows us to make RPC calls without an interface. We call this feature generic invocation.

With the generic invocation feature, we can make RPC calls even without the interface API provided by the service provider, but is this feature perfect?

Let’s review what I mentioned in Lesson 17. An RPC framework can improve throughput by using asynchronous calls. The key point of implementing a fully asynchronous RPC framework lies in the support for CompletableFuture. So, can our generic invocation also support asynchronous calls?

Of course, it can. We can add another asynchronous method $asyncInvoke to the GenericService interface. The return value of this method is CompletableFuture. The specific definition of the GenericService interface is as follows:

class GenericService {

  Object $invoke(String methodName, String[] paramTypes, Object[] params);

  CompletableFuture<Object> $asyncInvoke(String methodName, String[] paramTypes, Object[] params);

}

By now, I believe you have a basic understanding of the generic invocation feature. Have you ever thought of this question? When there is no interface API provided by the service provider, we can make RPC calls using the generic invocation method. But without the interface API, we cannot obtain the Class type of the input parameters and return values, which means we cannot serialize the input objects properly. In this case, we will face two problems:

Problem 1: Since the client cannot serialize the input objects properly, how should the client and server serialize and deserialize the input objects after receiving a request message?

Let’s recall what I mentioned in Lesson 07. In that lesson, I explained how to design an extensible RPC framework by using a plugin system. The overall architecture of the RPC framework includes a serialization plugin, and we can provide a dedicated serialization plugin for generic invocation to solve the serialization and deserialization problems.

Problem 2: What should be the types of input objects (params) and return values in the client?

In the interface API provided by the service provider, the input parameter type of the method being called is an object. Therefore, the client using the generic invocation feature can use a Map object as the input parameter type, and then serialize this Map object using the serialization method specific to the generic invocation. After receiving the message, the server can deserialize it back to an object using the deserialization method specific to the generic invocation.

Summary #

Today we mainly covered how to make RPC calls without interfaces by using the feature of generic invocation.

The implementation principle of this feature is that the RPC framework provides a unified generic invocation interface (GenericService). In creating a GenericService proxy, the calling end specifies the interface name and group name of the actual interface to be called. By calling the $invoke method of the GenericService proxy, all the information required by the server, including the interface name, business group name, method name, and parameter information, is encapsulated into a request message and sent to the server to achieve the function of RPC calling without interfaces.

When making calls through the generic invocation approach, as the calling end does not have access to the interface API provided by the serving end, normal serialization and deserialization cannot be achieved. To address this issue, we can provide a dedicated serialization plugin for generic invocation to solve practical problems.

After-class Reflection #

During the explanation of generic invocation, I mentioned that when the server receives a request sent by the client through generic invocation, it will use a serialization plugin exclusive to generic invocation to deserialize it. So, how does the server determine that the request message is sent through generic invocation?

Feel free to leave a comment to share your answer with me. You are also welcome to share this article with your friends and invite them to join the study. See you in the next class!