dubbo-js icon indicating copy to clipboard operation
dubbo-js copied to clipboard

dubbo3 - stub模块

Open hufeng opened this issue 3 years ago • 10 comments
trafficstars

目标

  • 研究IDL机制,通过编译生成nodejs的rpc服务的签名 - 包含调用方client和服务提供方service
  • 实现服务的service repository的代理调用能力,client的服务调用代理能力
  • 和invoke紧密结合,实现调用对接

TODO

hufeng avatar Oct 31 '22 02:10 hufeng

同步下目前进展: 已实现通过引入protoc获取pb文件“ast”( protoc --plugin=./protoc-scene-dubbojs_proto --ts_proto_out=. ./simple.proto);然后单元测试通过ast文件进行处理; 具体任务如下:

  • [x] prototype json ast 提取逻辑
  • [x] prototype 用例测试
  • [ ] 封装ast操作,包含领域对象File、Method、Service、Message、其他
  • [ ] 定义stub的服务接口标准
  • [ ] Ast 代码生成对接
    • [ ] stubClient 代码格式对接,确定示例;
    • [ ] stubClient 底层逻辑处理对接;

接下来需讨论以下示例生成node client代码标准: ··· 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; }

···

creasy2010 avatar Nov 17 '22 01:11 creasy2010

东哥,你的repo 我直接拉么?

zhangjin-007 avatar Nov 17 '22 02:11 zhangjin-007

grpc 生成的client stub service的 代码逻辑:

// The greeting service definition.
var GreeterService = exports.GreeterService = {
  // Sends a greeting
sayHello: {
    path: '/helloworld.Greeter/SayHello',
    requestStream: false,
    responseStream: false,
    requestType: helloworld_pb.HelloRequest,
    responseType: helloworld_pb.HelloReply,
    requestSerialize: serialize_helloworld_HelloRequest,
    requestDeserialize: deserialize_helloworld_HelloRequest,
    responseSerialize: serialize_helloworld_HelloReply,
    responseDeserialize: deserialize_helloworld_HelloReply,
  },
};
exports.GreeterClient = grpc.makeGenericClientConstructor(GreeterService);

针对dubbo-js设计: 我建议这块的设计仍然可以参考dubbo-js/dubbo2分支的proxyservice的设计,对于client方invoke调用来说核心的是两点,

  • 方便获取服务调用的metadata和payload
    • path, request/response stream flag, req/res type schema
  • 开发的体验,通过typescript类型的动态推导,实现真正的本地方法调用的体验
    • 代码的提示,静态的检查等

还有序列话模块的部分,我们是不是应该设计出一个独立的encode和decode的能力,不跟着具体的生成的rpc的stub代码走

  • 交给invoke在请求之前进行调用
  • 这样的好处,生成的stub service比较干净简单 代码量也非常的少
  • stub service 简单意味着将来我们特性改进如果升级比较简单一些
  • 序列化模块根据参数重的req/res的type schema进行序列化和反序列化

其他同学 补充讨论 ~~~

hufeng avatar Nov 17 '22 03:11 hufeng

针对上面思考,我初步的想法是,

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

对应的client stub service

// generate request type
export interface HelloRequest {
  name:string
}

// generate response type
export interface HelloReply {
  message: string
}

// generate service interface
export interface IHelloService {
  sayHello(helloRequest: HelloRequest): Promise<HelloReply>
}

// generate dubbo client stub service
export const HelloService = dubbo.newClientStubService<IHelloService>({
    name: 'HelloService',
    methods: {
      sayHello(helloRequest) {
        return  {
            args: [HelloRequest],
           //... maybe other information
         }
      },
  })

dubboClient.addStubServices({helloSerivce: HelloService})

对应的server部分

// generate request type
export interface HelloRequest {
  name:string
}

// generate response type
export interface HelloReply {
  message: string
}

export default abstract class HelloService {
  path: string = 'HelloService'

  // sub class implements this abstract method
  abstract sayHello(req: HelloRequest): Promise<HelloReply>
}


export class HelloService extends AbstractHelloService {
  async sayHello(req: HelloRequest): Promise<HelloReply>: Promise<HelloReply> {
    return {message: `hello from server and receive from ${req.name}}
  }
}


dubboServer.addServices({HelloService})

hufeng avatar Nov 17 '22 03:11 hufeng

因为 protobuf 比较出名了,所以我在想是不是可以基于现有的开源库来实现,比如 ts-proto

类似 ts-proto(可能需要注意下开源协议)这样我们是否可以直接使用,或者基于它的一些工作,比如直接使用它的前端解析器部分

hsiaosiyuan0 avatar Nov 17 '22 09:11 hsiaosiyuan0

@hsiaosiyuan0 目前的生成流程也借鉴了ts-proto等库,如:pb文件解析(也是基于官方protoc)及fileDescript类型定义(google提供),其他一些基础方法在coding时可以参考;

两种形态确实要讨论下:

  1. 在ts-proto库写 generate扩展,以生成dubbojs stub;
  2. 单独封装生成命令;

creasy2010 avatar Nov 21 '22 06:11 creasy2010

只要拿到了ast,其实就解决大部分问题了,剩下的只是根据我们的诉求生成dubbo-client/dubbo-server的stub service 方法上可以借鉴,但是我们还是要梳理我们需要什么,如果我们不需要那么重的实现逻辑,也可以自己实现。

现在的重点也要思考生成代码的样子。

杨晓东 在 2022年11月21日 星期一 14:32:50 (+08:00) 写道:

@hsiaosiyuan0 目前的生成流程也借鉴了ts-proto等库,如:pb文件解析(也是基于官方protoc)及fileDescript类型定义(google提供),其他一些基础方法在coding时可以参考;

两种形态确实要讨论下:

在ts-proto库写 generate扩展,以生成dubbojs stub; 单独封装生成命令;

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.

hufeng avatar Nov 21 '22 06:11 hufeng

@creasy2010 我们要把dubbo-client/dubbo-server的stub service 接口设计出来,这样大家就好协同了,模块内部的实现不影响大家的并行协作,我和invoker模块 冯伟同学整体过了下 他这边要做的事情,我们可以根据我们上面的stub service定义 先开展起来,过程中有需要完善的再调整。

hufeng avatar Nov 21 '22 06:11 hufeng

@hufeng 目前是按照示例的内容在推进;

creasy2010 avatar Nov 21 '22 09:11 creasy2010

@creasy2010 东哥,这个地方,

// generate request type
export interface HelloRequest {
  name:string
}

// generate response type
export interface HelloReply {
  message: string
}

// generate service interface
export interface IHelloService {
  sayHello(helloRequest: HelloRequest): Promise<HelloReply>
}

// generate dubbo client stub service
export const HelloService = dubbo.newClientStubService<IHelloService>({
    name: 'HelloService',
    methods: {
      sayHello(helloRequest) {
        return  {
            args: [HelloRequest],
           //... maybe other information
         }
      },
  })

dubboClient.addStubServices({helloSerivce: HelloService})

这个地方我的想法 对newClientStubService方法设计,需要考虑点:

  • 对于invoke来说,他需要的是数据结构本身,其实这个api类似于dubbojs2中的proxyService啥也没有做就是返回这个数据结构
  • 之前设计proxyService的逻辑是类型约束,如果是类型约束也可以不需要函数来做,直接一个interface也行
  • 为了类型的推导我们覆盖了 dubbo.newClientStubService<IHelloService> 返回值类型,这样不好,导致代码内部拿不到原始类型

这个方法可以约束类型,但不应该覆盖返回类型,返回类型单独export出来

// generate request type
export interface HelloRequest {
  name:string
}

// generate response type
export interface HelloReply {
  message: string
}

// generate service interface
export interface IHelloService {
  sayHello(helloRequest: HelloRequest): Promise<HelloReply>
}

// generate dubbo client stub service
export const HelloService = dubbo.newClientStubService({
    name: 'HelloService',
    methods: {
      sayHello(helloRequest) {
        return  {
            args: [HelloRequest],
           //... maybe other information
         }
      },
  })

dubboClient.addStubServices({helloSerivce: HelloService})

hufeng avatar Nov 21 '22 09:11 hufeng