使用 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` 和方法簽名 `SayHello (HelloRequest) returns (HelloReply) {}` 定義在 proto 檔案中,同時也定義了方法輸入參數結構、輸出參數結構 `HelloRequest` 和 `HelloReply`。IDL 格式的服務依賴 Protobuf 編譯器產生使用者可呼叫的客戶端和伺服器端程式設計 API。Dubbo 基於原生 Protobuf 編譯器,為多種語言提供了獨特的插件,以適應 Dubbo 框架的專屬 API 和程式設計模型。
使用 Dubbo3 IDL 定義的服務只允許一個輸入和輸出參數。這種服務簽名的形式有兩個優點。一是對多語言實現更友善,二是能保障服務的向下相容性,依賴 Protobuf 序列化的相容性優化,我們可以輕鬆調整傳輸的資料結構,例如新增和刪除欄位,完全不用擔心介面相容性的問題。
編譯服務
根據目前使用的語言,配置對應的 Protobuf 插件,編譯後會產生該語言相關的服務定義樁件(Stub)。
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)
}