Using IDL + Protobuf to define services across languages

Service is the core concept in Dubbo. A service represents a set of RPC methods. Service is the basic unit of user-oriented programming and service discovery mechanism. The basic process of Dubbo development is: user-defined RPC service, through agreed configuration Declare RPC as a Dubbo service, and then program based on the service API. For the service provider, it provides a specific implementation of the RPC service, while for the service consumer, it uses specific data to initiate a service call.

The following describes how to quickly develop Dubbo services from three aspects: defining services, compiling services, configuring and loading services.

For specific use cases, please refer to: [dubbo-samples-triple/stub](https://github.com/apache/dubbo-samples/tree/master/3-extensions/protocol/dubbo-samples-triple/src/main/java /org/apache/dubbo/sample/tri/stub);

Define the service

Dubbo3 recommends using IDL to define cross-language services. If you are more accustomed to using language-specific service definition methods, please move to Multi-language SDK to view.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "org.apache.dubbo.demo";
option java_outer_classname = "DemoServiceProto";
option objc_class_prefix = "DEMOSRV";

package demoservice;

// The demo service definition.
service DemoService {
   rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
   string name = 1;
}

// The response message containing the greetings
message HelloReply {
   string message = 1;
}

The above is a simple example of using IDL to define a service. We can name it DemoService.proto. The RPC service name DemoService and method signature are defined in the proto file SayHello (HelloRequest) returns (HelloReply) {}, and also defines the method input parameter structure, output parameter structure HelloRequest and HelloReply. The service in IDL format relies on the Protobuf compiler to generate client and server programming APIs that can be called by users. Dubbo provides unique plug-ins for multiple languages based on the native Protobuf Compiler to adapt to the Dubbo framework Proprietary API and programming model.

The service defined using Dubbo3 IDL only allows one input and output parameter. This form of service signature has two advantages. One is that it is more friendly to multi-language implementation, and the other is that it can guarantee the backward compatibility of the service, relying on the Protobuf sequence With optimized compatibility, we can easily adjust the transmitted data structure, such as adding and deleting fields, without worrying about interface compatibility at all.

Compile service

According to the language currently used, configure the corresponding Protobuf plug-in, and after compilation, the language-related service definition stub will be produced.

Java

Java compiler configuration reference:

<plugin>
     <groupId>org.xolstice.maven.plugins</groupId>
     <artifactId>protobuf-maven-plugin</artifactId>
     <version>0.6.1</version>
     <configuration>
         <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}
         </protocArtifact>
         <pluginId>grpc-java</pluginId>
         <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
         </pluginArtifact>
         <protocPlugins>
             <protocPlugin>
                 <id>dubbo</id>
                 <groupId>org.apache.dubbo</groupId>
                 <artifactId>dubbo-compiler</artifactId>
                 <version>3.0.10</version>
                 <mainClass>org.apache.dubbo.gen.tri.Dubbo3TripleGenerator</mainClass>
             </protocPlugin>
         </protocPlugins>
     </configuration>
     <executions>
         <execution>
             <goals>
                 <goal>compile</goal>
                 <goal>test-compile</goal>
                 <goal>compile-custom</goal>
                 <goal>test-compile-custom</goal>
             </goals>
         </execution>
     </executions>
</plugin>

The stub generated by the Java language is as follows, the core is an interface definition

@javax.annotation.Generated(
value = "by Dubbo generator",
comments = "Source: DemoService.proto")
public interface DemoService {
     static final String JAVA_SERVICE_NAME = "org.apache.dubbo.demo.DemoService";
     static final String SERVICE_NAME = "demoservice. DemoService";

     org.apache.dubbo.demo.HelloReply sayHello(org.apache.dubbo.demo.HelloRequest request);

     CompletableFuture<org.apache.dubbo.demo.HelloReply> sayHelloAsync(org.apache.dubbo.demo.HelloRequest request);
}

Golang

The stub generated by the Go language is as follows. This stub stores user-defined interfaces and data types.

func _DUBBO_Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HelloRequest)
if err := dec(in); err != nil {
return nil, err
}
base := srv.(dgrpc.Dubbo3GrpcService)
args := []interface{}{}
args = append(args, in)
invo := invocation. NewRPCInvocation("SayHello", args, nil)
if interceptor == nil {
result := base.GetProxyImpl().Invoke(ctx, invo)
return result.Result(), result.Error()
}
info := &grpc. UnaryServerInfo{
Server: srv,
FullMethod: "/main. Greeter/SayHello",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
result := base.GetProxyImpl().Invoke(context.Background(), invo)
return result.Result(), result.Error()
}
return interceptor(ctx, in, info, handler)
}

Configure and load the service

The provider is responsible for providing specific Dubbo service implementations, that is, following the format constrained by the RPC signature to implement specific business logic codes. After implementing the service, register the service implementation as a standard Dubbo service, Afterwards, the Dubbo framework can forward the received request to the service implementation, execute the method, and return the result.

The configuration on the consumer side will be simpler. You only need to declare that the service defined by IDL is a standard Dubbo service, and the framework can help developers generate corresponding proxies. Developers will be completely oriented to proxy programming. Basically, the implementation of all languages in Dubbo ensures that the proxy exposes standardized interfaces according to the IDL service definition.

Java

Provider, implement service

public class DemoServiceImpl implements DemoService {
     private static final Logger logger = LoggerFactory. getLogger(DemoServiceImpl. class);

     @Override
     public HelloReply sayHello(HelloRequest request) {
         logger.info("Hello " + request.getName() + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
         return HelloReply. newBuilder()
     .setMessage("Hello " + request.getName() + ", response from provider: "
            + RpcContext.getContext().getLocalAddress())
    .build();
    }

    @Override
    public CompletableFuture<HelloReply> sayHelloAsync(HelloRequest request) {
        return CompletableFuture.completedFuture(sayHello(request));
    }
}

Provider, registration service (take Spring XML as an example)

<bean id="demoServiceImpl" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>
<dubbo:service serialization="protobuf" interface="org.apache.dubbo.demo.DemoService" ref="demoServiceImpl"/>

Consumer side, reference service

<dubbo:reference scope="remote" id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/>

On the consumer side, use the service proxy

public void callService() throws Exception {
     ...
     DemoService demoService = context. getBean("demoService", DemoService. class);
     HelloRequest request = HelloRequest.newBuilder().setName("Hello").build();
     HelloReply reply = demoService.sayHello(request);
     System.out.println("result: " + reply.getMessage());
}

Golang

Provider, implement service

type User struct {
ID string
name string
Age int32
Time time. Time
}

type UserProvider struct {
}

func (u *UserProvider) GetUser(ctx context.Context, req []interface{}) (*User, error) {
gxlog.CInfo("req:%#v", req)
rsp := User{"A001", "Alex Stocks", 18, time. Now()}
gxlog.CInfo("rsp:%#v", rsp)
return &rsp, nil
}

func (u *UserProvider) Reference() string {
return "UserProvider"
}

func (u User) JavaClassName() string {
return "org.apache.dubbo.User"
}

func main() {
     hessian.RegisterPOJO(&User{})
config. SetProviderService(new(UserProvider))
}

On the consumer side, use the service proxy

func main() {
config. Load()
user := &pkg. User{}
err := userProvider.GetUser(context.TODO(), []interface{}{"A001"}, user)
if err != nil {
os. Exit(1)
return
}
gxlog.CInfo("response result: %v\n", user)
}

Last modified January 2, 2023: Enhance en docs (#1798) (95a9f4f6c1c)