使用 IDL + Protobuf 定义跨语言服务

服务是 Dubbo 中的核心概念。服务代表一组 RPC 方法。服务是面向用户的编程和服务发现机制的基本单位。Dubbo 开发的基本流程是:用户定义 RPC 服务,通过约定配置将 RPC 声明为 Dubbo 服务,然后基于服务 API 进行编程。对于服务提供者,它提供 RPC 服务的具体实现,而对于服务消费者,它使用特定数据发起服务调用。

以下从三个方面描述如何快速开发 Dubbo 服务:定义服务、编译服务、配置和加载服务。

对于特定用例,请参考:[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);

定义服务

Dubbo3 建议使用 IDL 定义跨语言服务。如果您更习惯使用特定语言的服务定义方法,请移至 多语言 SDK 查看。

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;
}

以上是使用 IDL 定义服务的简单示例。我们可以将其命名为 DemoService.proto。RPC 服务名称 DemoService 和方法签名在 proto 文件 SayHello (HelloRequest) returns (HelloReply) {} 中定义,还定义了方法输入参数结构、输出参数结构 HelloRequestHelloReply。IDL 格式的服务依赖于 Protobuf 编译器生成用户可调用的客户端和服务器编程 API。Dubbo 基于原生 Protobuf 编译器为多种语言提供独特的插件,以适应 Dubbo 框架专有 API 和编程模型。

使用 Dubbo3 IDL 定义的服务只允许一个输入和输出参数。这种形式的服务签名有两个优点。一是更利于多语言实现,二是能够保证服务的向后兼容性,依靠 Protobuf 序列化的优化兼容性,我们可以轻松调整传输的数据结构,比如添加和删除字段,完全不用担心接口兼容性问题。

编译服务

根据当前使用的语言,配置相应的 Protobuf 插件,编译后会生成语言相关的服务定义桩。

Java

Java 编译器配置参考

<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>

Java 语言生成的桩如下,核心是接口定义

@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

Go 语言生成的桩如下。该桩存储用户定义的接口和数据类型。

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)
}

配置和加载服务

提供者负责提供具体的 Dubbo 服务实现,即按照 RPC 签名约束的格式实现具体的业务逻辑代码。实现服务后,将服务实现注册为标准 Dubbo 服务,之后 Dubbo 框架可以将接收到的请求转发到服务实现,执行方法并返回结果。

消费者端的配置会更简单。您只需要声明 IDL 定义的服务是标准 Dubbo 服务,框架就可以帮助开发者生成相应的代理。开发者将完全面向代理编程。基本上,Dubbo 中所有语言的实现都确保代理根据 IDL 服务定义公开标准化接口。

Java

提供者,实现服务

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));
    }
}

提供者,注册服务(以 Spring XML 为例)

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

消费者端,引用服务

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

在消费者端,使用服务代理

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

提供者,实现服务

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))
}

在消费者端,使用服务代理

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)
}

上次修改时间:2023 年 1 月 2 日:增强 en 文档 (#1798) (95a9f4f6c1c)