Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Protocol Buffers for implementing RPC in ZeroMQ

I have a simple setup of one client and one server. The client wants to execute a method in the server using ZeroMQ for communications. I am going to use the REQ and REP sockets because they are suitable for this use case. Nevertheless I have a doubt about the protobuf definitions. I think these two options can be used for achieving the goal:

message ControlService{
    string control = 1;
    int32 serverId = 2;
    bool block = 3;
    double temperature = 4;
}

Where "control" contains the name of the method to be executed remotely. The other alternative can be:

message InputParameters{
    int32 serverId = 1;
    bool block = 2;
    double temperature = 3;
}
message Empty{

}
service ControlService{
    rpc control (InputParameters) returns (Empty);
}

What would be the best approach? Or at least what are the trade-offs of using one approach instead of the other?

like image 770
Renato Sanhueza Avatar asked Dec 30 '17 22:12

Renato Sanhueza


People also ask

Is ZeroMQ an RPC?

RPCZ is built on top of ZeroMQ for handling the low-level I/O details in a lock-free manner. The Python module is a Cython wrapper around the C++ API.

What is RPC Protobuf?

Protocol Buffer, a.k.a. Protobuf Protobuf is the most commonly used IDL (Interface Definition Language) for gRPC. It's where you basically store your data and function contracts in the form of a proto file.

What is gRPC used for?

gRPC uses a binary payload that is efficient to create and to parse, and it exploits HTTP/2 for efficient management of connections. Of course, you can also use binary payloads and HTTP/2 directly without using gRPC, but this requires you and your clients to master more technology.


1 Answers

Don't do it that way. Have a message:

message InputParameters{
    req oneof
    {
        InputParametersA a = 1;
        InputParametersB b = 2;
    }
}
message InputParametersA
{
    bool block = 1;
    float temperature = 2;
}
message InputParametersB
{
    <more fields>
}

That way you send only the InputParameters message. The method to call is dictated by whether InputParameters.req contains an InputParametersA (implies that method A should be called), or InputParmetersB (for method B).

This avoids parsing a string to determine a method name (highly error prone), and instead gives you an enumeration to switch on (the possible content of the req field). Depending on the implementation of GPB you're using (C++, etc) you may even get a compile time warning if your switch statement doesn't adequately cover all values of that enumeration.

It also means that there's no problems in determining which of the fields should be passed to a method; you're passing either InputParameters.req.a into method A or .b into method B. There's no need to break them out into separate parameters for a method, simple pass the whole thing in as a single parameter.

You can define different return types in the same way, passing them all back through a single oneof.

Alternatives

Now if you were using ASN.1 (which is conceptually the same kind of thing as GPB), you could set constraints on the values and / or sizes of message fields (see here, Chapter 13 in this PDF. That way you'd have parameter validation being performed automatically, defined solely in the ASN.1 schema. The lack of value / size constraints in GPB is a glaring omission.

Take a look here (overview), here (free schema compiler for C/C++ that looks OK), and here (PDF, reference manual).

ASN.1 has stronger typing in its wire format (if you use BER encoding). It's possible to interrogate a wire bit stream to find out what type of message it contains. Thus there is no need to resort to wrapping all your possible messages up into a single oneof like you do with GPB.

like image 171
bazza Avatar answered Sep 16 '22 15:09

bazza