grpc-ecosystem / grpc-spring

Spring Boot starter module for gRPC framework.
https://grpc-ecosystem.github.io/grpc-spring/
Apache License 2.0
3.48k stars 815 forks source link

Use custom Marshaller and custom types in services (for using JSON for encoding) #927

Open EmhyrVarEmreis opened 1 year ago

EmhyrVarEmreis commented 1 year ago

https://grpc.io/blog/grpc-with-json/

I would like to achieve something like in tutorial above but with this framework. It requires programatically creating grpc services. Is it possible to do so?

ST-DDT commented 1 year ago

Yes, on the server side simply implement the Bindable interface yourself. And put the @GrpcService annotation on it as usual. On the client side, you have to implement a StubFactory as well as the actual AbstractStub implementation (the Bindable counterpart). You might be able to avoid the StubFactory if you mirror grpc-java's generated ___Grpc classes instead.

Please note that we cannot provide any further help for the json part of this.

Does this answer help you?

EmhyrVarEmreis commented 1 year ago

Yes, on the server side simply implement the Bindable interface yourself. And put the @GrpcService annotation on it as usual.

I see.

You might be able to avoid the StubFactory if you mirror grpc-java's generated ___Grpc classes instead.

Could you explain more wtaht do you mean by this part?

Thank you for help.

ST-DDT commented 1 year ago

The grpc-java ___Grpc files contain both the stub implementations as well as a static ___Stub new___Stub(Channel) method that will be called here:

https://github.com/yidongnan/grpc-spring-boot-starter/blob/9f67107edc6fa70d6c3a138eb18954f791ac5448/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/stubfactory/StandardJavaGrpcStubFactory.java#L39

So if you follow the grpc-java class layout you don't have to write your own StubFactory. You might want to actually let that file generate and just replace the protobuf marschalers/stuff with the JSON variant. (You don't have to implement/convert all of the class, just the parts you actually need yourself e.g. you might not need the descriptors, or can reuse the protobuf ones even though they aren't technically correct)

Example ___Grpc ````java package net.devh.boot.grpc.examples.lib; import static io.grpc.MethodDescriptor.generateFullMethodName; /** *
 * The greeting service definition.
 * 
*/ @javax.annotation.Generated( value = "by gRPC proto compiler (version 1.54.0)", comments = "Source: helloworld.proto") @io.grpc.stub.annotations.GrpcGenerated public final class SimpleGrpc { private SimpleGrpc() {} public static final String SERVICE_NAME = "Simple"; // Static method descriptors that strictly reflect the proto. private static volatile io.grpc.MethodDescriptor getSayHelloMethod; @io.grpc.stub.annotations.RpcMethod( fullMethodName = SERVICE_NAME + '/' + "SayHello", requestType = net.devh.boot.grpc.examples.lib.HelloRequest.class, responseType = net.devh.boot.grpc.examples.lib.HelloReply.class, methodType = io.grpc.MethodDescriptor.MethodType.UNARY) public static io.grpc.MethodDescriptor getSayHelloMethod() { io.grpc.MethodDescriptor getSayHelloMethod; if ((getSayHelloMethod = SimpleGrpc.getSayHelloMethod) == null) { synchronized (SimpleGrpc.class) { if ((getSayHelloMethod = SimpleGrpc.getSayHelloMethod) == null) { SimpleGrpc.getSayHelloMethod = getSayHelloMethod = io.grpc.MethodDescriptor.newBuilder() .setType(io.grpc.MethodDescriptor.MethodType.UNARY) .setFullMethodName(generateFullMethodName(SERVICE_NAME, "SayHello")) .setSampledToLocalTracing(true) .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( net.devh.boot.grpc.examples.lib.HelloRequest.getDefaultInstance())) .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( net.devh.boot.grpc.examples.lib.HelloReply.getDefaultInstance())) .setSchemaDescriptor(new SimpleMethodDescriptorSupplier("SayHello")) .build(); } } } return getSayHelloMethod; } /** * Creates a new async stub that supports all call types for the service */ public static SimpleStub newStub(io.grpc.Channel channel) { io.grpc.stub.AbstractStub.StubFactory factory = new io.grpc.stub.AbstractStub.StubFactory() { @java.lang.Override public SimpleStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { return new SimpleStub(channel, callOptions); } }; return SimpleStub.newStub(factory, channel); } /** * Creates a new blocking-style stub that supports unary and streaming output calls on the service */ public static SimpleBlockingStub newBlockingStub( io.grpc.Channel channel) { io.grpc.stub.AbstractStub.StubFactory factory = new io.grpc.stub.AbstractStub.StubFactory() { @java.lang.Override public SimpleBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { return new SimpleBlockingStub(channel, callOptions); } }; return SimpleBlockingStub.newStub(factory, channel); } /** * Creates a new ListenableFuture-style stub that supports unary calls on the service */ public static SimpleFutureStub newFutureStub( io.grpc.Channel channel) { io.grpc.stub.AbstractStub.StubFactory factory = new io.grpc.stub.AbstractStub.StubFactory() { @java.lang.Override public SimpleFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { return new SimpleFutureStub(channel, callOptions); } }; return SimpleFutureStub.newStub(factory, channel); } /** *
   * The greeting service definition.
   * 
*/ public interface AsyncService { /** *
     * Sends a greeting
     * 
*/ default void sayHello(net.devh.boot.grpc.examples.lib.HelloRequest request, io.grpc.stub.StreamObserver responseObserver) { io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getSayHelloMethod(), responseObserver); } } /** * Base class for the server implementation of the service Simple. *
   * The greeting service definition.
   * 
*/ public static abstract class SimpleImplBase implements io.grpc.BindableService, AsyncService { @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() { return SimpleGrpc.bindService(this); } } /** * A stub to allow clients to do asynchronous rpc calls to service Simple. *
   * The greeting service definition.
   * 
*/ public static final class SimpleStub extends io.grpc.stub.AbstractAsyncStub { private SimpleStub( io.grpc.Channel channel, io.grpc.CallOptions callOptions) { super(channel, callOptions); } @java.lang.Override protected SimpleStub build( io.grpc.Channel channel, io.grpc.CallOptions callOptions) { return new SimpleStub(channel, callOptions); } /** *
     * Sends a greeting
     * 
*/ public void sayHello(net.devh.boot.grpc.examples.lib.HelloRequest request, io.grpc.stub.StreamObserver responseObserver) { io.grpc.stub.ClientCalls.asyncUnaryCall( getChannel().newCall(getSayHelloMethod(), getCallOptions()), request, responseObserver); } } /** * A stub to allow clients to do synchronous rpc calls to service Simple. *
   * The greeting service definition.
   * 
*/ public static final class SimpleBlockingStub extends io.grpc.stub.AbstractBlockingStub { private SimpleBlockingStub( io.grpc.Channel channel, io.grpc.CallOptions callOptions) { super(channel, callOptions); } @java.lang.Override protected SimpleBlockingStub build( io.grpc.Channel channel, io.grpc.CallOptions callOptions) { return new SimpleBlockingStub(channel, callOptions); } /** *
     * Sends a greeting
     * 
*/ public net.devh.boot.grpc.examples.lib.HelloReply sayHello(net.devh.boot.grpc.examples.lib.HelloRequest request) { return io.grpc.stub.ClientCalls.blockingUnaryCall( getChannel(), getSayHelloMethod(), getCallOptions(), request); } } /** * A stub to allow clients to do ListenableFuture-style rpc calls to service Simple. *
   * The greeting service definition.
   * 
*/ public static final class SimpleFutureStub extends io.grpc.stub.AbstractFutureStub { private SimpleFutureStub( io.grpc.Channel channel, io.grpc.CallOptions callOptions) { super(channel, callOptions); } @java.lang.Override protected SimpleFutureStub build( io.grpc.Channel channel, io.grpc.CallOptions callOptions) { return new SimpleFutureStub(channel, callOptions); } /** *
     * Sends a greeting
     * 
*/ public com.google.common.util.concurrent.ListenableFuture sayHello( net.devh.boot.grpc.examples.lib.HelloRequest request) { return io.grpc.stub.ClientCalls.futureUnaryCall( getChannel().newCall(getSayHelloMethod(), getCallOptions()), request); } } private static final int METHODID_SAY_HELLO = 0; private static final class MethodHandlers implements io.grpc.stub.ServerCalls.UnaryMethod, io.grpc.stub.ServerCalls.ServerStreamingMethod, io.grpc.stub.ServerCalls.ClientStreamingMethod, io.grpc.stub.ServerCalls.BidiStreamingMethod { private final AsyncService serviceImpl; private final int methodId; MethodHandlers(AsyncService serviceImpl, int methodId) { this.serviceImpl = serviceImpl; this.methodId = methodId; } @java.lang.Override @java.lang.SuppressWarnings("unchecked") public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { switch (methodId) { case METHODID_SAY_HELLO: serviceImpl.sayHello((net.devh.boot.grpc.examples.lib.HelloRequest) request, (io.grpc.stub.StreamObserver) responseObserver); break; default: throw new AssertionError(); } } @java.lang.Override @java.lang.SuppressWarnings("unchecked") public io.grpc.stub.StreamObserver invoke( io.grpc.stub.StreamObserver responseObserver) { switch (methodId) { default: throw new AssertionError(); } } } public static final io.grpc.ServerServiceDefinition bindService(AsyncService service) { return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) .addMethod( getSayHelloMethod(), io.grpc.stub.ServerCalls.asyncUnaryCall( new MethodHandlers< net.devh.boot.grpc.examples.lib.HelloRequest, net.devh.boot.grpc.examples.lib.HelloReply>( service, METHODID_SAY_HELLO))) .build(); } private static abstract class SimpleBaseDescriptorSupplier implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier { SimpleBaseDescriptorSupplier() {} @java.lang.Override public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { return net.devh.boot.grpc.examples.lib.HelloWorldProto.getDescriptor(); } @java.lang.Override public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() { return getFileDescriptor().findServiceByName("Simple"); } } private static final class SimpleFileDescriptorSupplier extends SimpleBaseDescriptorSupplier { SimpleFileDescriptorSupplier() {} } private static final class SimpleMethodDescriptorSupplier extends SimpleBaseDescriptorSupplier implements io.grpc.protobuf.ProtoMethodDescriptorSupplier { private final String methodName; SimpleMethodDescriptorSupplier(String methodName) { this.methodName = methodName; } @java.lang.Override public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() { return getServiceDescriptor().findMethodByName(methodName); } } private static volatile io.grpc.ServiceDescriptor serviceDescriptor; public static io.grpc.ServiceDescriptor getServiceDescriptor() { io.grpc.ServiceDescriptor result = serviceDescriptor; if (result == null) { synchronized (SimpleGrpc.class) { result = serviceDescriptor; if (result == null) { serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME) .setSchemaDescriptor(new SimpleFileDescriptorSupplier()) .addMethod(getSayHelloMethod()) .build(); } } } return result; } } ````
ST-DDT commented 1 year ago

Please note that there is now also grpc-servlet as a library that lets you use grpc with a servlet container such as tomcat. (I think it still uses protobuf though if you don't rewrite the ___Grpc file)

EmhyrVarEmreis commented 1 year ago

Ok, thanks for clarification.

You might want to actually let that file generate and just replace the protobuf marschalers/stuff with the JSON variant. I assume it would require manual replacements each time code is generated?

ST-DDT commented 1 year ago

Well, you have to do that (adjust the code) anyway unless you automate that.