Open rbair23 opened 1 month ago
Here's an example implementation of ConsensusService
. I had to do a little threading in open
to support streaming.
public interface ConsensusService extends ServiceInterface {
enum ConsensusMethod implements Method {
createTopic,
updateTopic,
deleteTopic,
submitMessage,
getTopicInfo;
}
TransactionResponse createTopic(Transaction tx);
TransactionResponse updateTopic(Transaction tx);
TransactionResponse deleteTopic(Transaction tx);
TransactionResponse submitMessage(Transaction tx);
Response getTopicInfo(Query q);
default String serviceName() {
return "ConsensusService";
}
default String fullName() {
return "proto.ConsensusService";
}
default List<Method> methods() {
return List.of(
ConsensusMethod.createTopic,
ConsensusMethod.updateTopic,
ConsensusMethod.deleteTopic,
ConsensusMethod.submitMessage,
ConsensusMethod.getTopicInfo);
}
@Override
default void open(
final @NonNull Method method,
final @NonNull BlockingQueue<Bytes> messages,
final @NonNull ResponseCallback callback) {
final var m = (ConsensusMethod) method;
Thread.ofVirtual().start(() -> {
try {
switch (m) {
case ConsensusMethod.createTopic -> {
// Unary method
final var message = messages.take();
callback.start();
final var messageBytes = Transaction.PROTOBUF.parse(message);
final var response = createTopic(messageBytes);
final var responseBytes = TransactionResponse.PROTOBUF.toBytes(response);
callback.send(responseBytes);
callback.close();
}
case ConsensusMethod.updateTopic -> {
// Unary method
final var message = messages.take();
callback.start();
final var messageBytes = Transaction.PROTOBUF.parse(message);
final var response = updateTopic(messageBytes);
final var responseBytes = TransactionResponse.PROTOBUF.toBytes(response);
callback.send(responseBytes);
callback.close();
}
case ConsensusMethod.deleteTopic -> {
// Unary method
final var message = messages.take();
callback.start();
final var messageBytes = Transaction.PROTOBUF.parse(message);
final var response = deleteTopic(messageBytes);
final var responseBytes = TransactionResponse.PROTOBUF.toBytes(response);
callback.send(responseBytes);
callback.close();
}
case ConsensusMethod.submitMessage -> {
// Unary method
final var message = messages.take();
callback.start();
final var messageBytes = Transaction.PROTOBUF.parse(message);
final var response = submitMessage(messageBytes);
final var responseBytes = TransactionResponse.PROTOBUF.toBytes(response);
callback.send(responseBytes);
callback.close();
}
case ConsensusMethod.getTopicInfo -> {
// Unary method
final var message = messages.take();
callback.start();
final var messageBytes = Query.PROTOBUF.parse(message);
final var response = getTopicInfo(messageBytes);
final var responseBytes = Response.PROTOBUF.toBytes(response);
callback.send(responseBytes);
callback.close();
}
}
} catch (Exception e) {
e.printStackTrace();
callback.close();
}
});
}
}
Problem
PBJ generates Java objects for each
message
it encounters in the protobuf schema definitions, but it does not generate anything forservice
definitions. Right now the only way to use PBJ as a library in a web server is to use a very low-level API provided by the gRPC libraries that basically deliver you a byte array from which you can parse. An example of how this can be done can be found in the Hashgraph consensus node repo.Solution
Create two new files:
ServiceInterface
: this represents theservice
defined in the protobuf schema files. In addition to some helper methods, it contains the actual interface definition for the different methods defined on theservice
, such that a user can implement this interface in their code. It also contains default methods for various aspects of the actual handling of the call.ServiceMethod
: a simple definition of the method on theservice
-- what kind of method it is (unary, server streaming, client streaming, bidi streaming), and what it is named.Alternatives
We could generate the same types of stubs used by the normal gRPC libraries. The stubs we create would be different from those created by protoc, but they should interoperate with the normal gRPC libraries. This would increase interoperability, because it would just work with any existing servers with gRPC support. And maybe this is something we should consider doing as well.
However, generating our own solution has benefits: