Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mvp 版本讨论 #322

Open
creasy2010 opened this issue Nov 28, 2022 · 9 comments
Open

Mvp 版本讨论 #322

creasy2010 opened this issue Nov 28, 2022 · 9 comments
Assignees

Comments

@creasy2010
Copy link
Contributor

creasy2010 commented Nov 28, 2022

以此为Mvp目标,大家看看是否有要补充的。

# filename: helloworld.proto

syntax = "proto3";
package grpc.health.v1;

option go_package = "dubbojs/mvp";

message HealthCheckRequest {
  string service = 1;
}

message HealthCheckResponse {
  enum ServingStatus {
    UNKNOWN = 0;
    SERVING = 1;
    NOT_SERVING = 2;
    SERVICE_UNKNOWN = 3;  // Used only by the Watch method.
  }
  ServingStatus status = 1;
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}


service Mvp {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
  rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}
@fengwei5280
Copy link
Contributor

辛苦东哥,protobuf这边没有问题; invoker这边比较关心最终生成代码的调用方式,期待 @creasy2010 更新

@hufeng
Copy link
Member

hufeng commented Nov 28, 2022

ok,可以以这个例子作为开始

@hufeng
Copy link
Member

hufeng commented Nov 28, 2022

@creasy2010 目前生成的代码是什么样子

@hufeng
Copy link
Member

hufeng commented Nov 29, 2022

因为protocolbuff 强依赖proto或者proto生成的valid type文件定义,所以IDL生成的代码需要和序列化模块耦合起来
接下来讨论IDL和serialization的协作机制以及代码生成部分。

序列化接口设计

interface Serialization {
  // load 所有的proto文件,获取request类型
  loadProto(protoPath: string): void;
  
 // encode 请求数据,后置type参数且是可选,为了将来切换其他协议如不需要type类型,可以不传,我们接口可以不改 
  encode<T>(data: T, type?: string): Buffer;

// decode 请求数据,后置type参数且是可选,为了将来切换其他协议如不需要type类型,可以不传,我们接口可以不改 
  decode<T>(data: Buffer, type?: string): T;
}

序列化模块的设计

  • 模块名 dubbo-serialization
  • 实现上述接口方法

IDL 代码生成

针对Mvp的proto定义,对client stub service代码样例:

// define service interface
export interface Mvp {
  SayHello(req: HelloRequest): Promise<HelloReply>;
  Check(req: HealthCheckRequest): Promise<HealthCheckResponse>;
}

// define enum
enum ServingStatus {
  UNKNOWN = 0,
  SERVING = 1,
  NOT_SERVING = 2,
  SERVICE_UNKNOWN = 3, // Used only by the Watch method.
}

// define request && response, 实际代码可以根据不同的namespace生成到不同的目录
export interface HealthCheckResponse {
  status: ServingStatus;
}

export interface HealthCheckRequest {
  service: string;
}

export interface HelloRequest {
  name: string;
}

export interface HelloReply {
  message: string;
}


// define service metadata

import ds from 'dubbo-serialization'

// TODO 或者对于ecode和decode的过程,IDL生成代码只返回 {path, data} 由invoke来负责底层的调用
export const Mvp = {
  SayHello: { 
     path: "/helloworld.Mvp/SayHello", 
     encode(data: HelloRequest) {
       return ds.encode(data, ` hellorequest在对象path路径 `)
    },
    decode(data: Buffer) {
       return  ds.decode(data, `helloreplay 的path路径 `)
    }
  },
  Check: { 
    path: "/helloworld.Mvp/Check"
    encode(data: HealthCheckRequest) {
       return ds.encode(data, ` HealthCheckRequest在对象path路径 `)
    },
   decode(data: Buffer) {
       return  ds.decode(data, `helloreplay 的path路径 `)
    }
 },
};


// server 端


// 生成抽象类
export abstract class AbstractMvp {
  path = "/helloworld.Greeter";

  methods = {
    SayHello: this.SayHello.bind(this),
    Check: this.Check.bind(this),
  };

  abstract SayHello(req: HelloRequest): Promise<HelloReply>;
  abstract Check(req: HealthCheckRequest): Promise<HealthCheckResponse>;
}

// 生成实现类
export class MvpService extends AbstractMvp {
  SayHello(req: HelloRequest): Promise<HelloReply> {
    throw new Error("Method not implemented.");
  }
  Check(req: HealthCheckRequest): Promise<HealthCheckResponse> {
    throw new Error("Method not implemented.");
  }
}


@fengwei5280
Copy link
Contributor

基于Mvp,实际invoker的调用方式如下:

// define DubboClientsTstubService
export interface DubboClientsTstubService {
  mvp: IMvp;
}

// index.ts
import DubboClient from './../dubbo'
import stubService from './stubServices'
import {DubboClientsTstubService} from './mvpService'

const dubbo = new DubboClient<DubboClientsTstubService>(stubService)

@wawIsready
Copy link

因为protocolbuff 强依赖proto或者proto生成的valid type文件定义,所以IDL生成的代码需要和序列化模块耦合起来 接下来讨论IDL和serialization的协作机制以及代码生成部分。

序列化接口设计

interface Serialization {
  // load 所有的proto文件,获取request类型
  loadProto(protoPath: string): void;
  
 // encode 请求数据,后置type参数且是可选,为了将来切换其他协议如不需要type类型,可以不传,我们接口可以不改 
  encode<T>(data: T, type?: string): Buffer;

// decode 请求数据,后置type参数且是可选,为了将来切换其他协议如不需要type类型,可以不传,我们接口可以不改 
  decode<T>(data: Buffer, type?: string): T;
}

序列化模块的设计

  • 模块名 dubbo-serialization
  • 实现上述接口方法

IDL 代码生成

针对Mvp的proto定义,对client stub service代码样例:

// define service interface
export interface Mvp {
  SayHello(req: HelloRequest): Promise<HelloReply>;
  Check(req: HealthCheckRequest): Promise<HealthCheckResponse>;
}

// define enum
enum ServingStatus {
  UNKNOWN = 0,
  SERVING = 1,
  NOT_SERVING = 2,
  SERVICE_UNKNOWN = 3, // Used only by the Watch method.
}

// define request && response, 实际代码可以根据不同的namespace生成到不同的目录
export interface HealthCheckResponse {
  status: ServingStatus;
}

export interface HealthCheckRequest {
  service: string;
}

export interface HelloRequest {
  name: string;
}

export interface HelloReply {
  message: string;
}


// define service metadata

import ds from 'dubbo-serialization'

// TODO 或者对于ecode和decode的过程,IDL生成代码只返回 {path, data} 由invoke来负责底层的调用
export const Mvp = {
  SayHello: { 
     path: "/helloworld.Mvp/SayHello", 
     encode(data: HelloRequest) {
       return ds.encode(data, ` hellorequest在对象path路径 `)
    },
    decode(data: Buffer) {
       return  ds.decode(data, `helloreplay 的path路径 `)
    }
  },
  Check: { 
    path: "/helloworld.Mvp/Check"
    encode(data: HealthCheckRequest) {
       return ds.encode(data, ` HealthCheckRequest在对象path路径 `)
    },
   decode(data: Buffer) {
       return  ds.decode(data, `helloreplay 的path路径 `)
    }
 },
};


// server 端


// 生成抽象类
export abstract class AbstractMvp {
  path = "/helloworld.Greeter";

  methods = {
    SayHello: this.SayHello.bind(this),
    Check: this.Check.bind(this),
  };

  abstract SayHello(req: HelloRequest): Promise<HelloReply>;
  abstract Check(req: HealthCheckRequest): Promise<HealthCheckResponse>;
}

// 生成实现类
export class MvpService extends AbstractMvp {
  SayHello(req: HelloRequest): Promise<HelloReply> {
    throw new Error("Method not implemented.");
  }
  Check(req: HealthCheckRequest): Promise<HealthCheckResponse> {
    throw new Error("Method not implemented.");
  }
}

client 里的 path 和 methods 的一一对应关系是不是要和 server 这边的逻辑一致?

@wawIsready
Copy link

经讨论确认,dubbo-server 的抽象类生成形式如下所示:

export abstract class AbstractMvp {
 
metaData:{
    '/helloworld.Greeter/SayHello': this.SayHello.bind(this),
    '/helloworld.Greeter/Check': this.Check.bind(this)
  };

  abstract SayHello(req: HelloRequest): Promise<HelloReply>;
  abstract Check(req: HealthCheckRequest): Promise<HealthCheckResponse>;
}

dubbo-client 模块依此做对应调整。 @hufeng @fengwei5280

@godkun
Copy link
Contributor

godkun commented Dec 6, 2022

调整后的 transport mvp

pr入口:#325

入口

import { DubboClientTransport } from './client'
import { DubboServerTransport } from './server'

export { DubboClientTransport, DubboServerTransport }

client transport 代码

import { debug } from 'debug'
import http2 from 'node:http2'
import { IDubboClientTransport, DubboContext } from './transport'

// init log
const log = debug('dubbo3:transport:client')

export class DubboClientTransport implements IDubboClientTransport {
  // transport 实例
  private transport: any
  private ctx: DubboContext

  constructor(opts: DubboContext) {
    this.ctx = opts
    this.connect()
  }

  get url() {
    return this.ctx.url
  }

  /**
   * 建立连接
   */
  connect() {
    this.transport = http2.connect(this.url)

    this.transport.once('connect', () => {
      log('has connected')
    })
  }

  /**
   * 发送消息
   * @param msg
   */
  async send(msg: DubboContext): Promise<void> {
    this.transport.request(msg)
  }
}

server transport 代码:

import debug from 'debug'
import EventEmitter from 'node:events'
import http2 from 'node:http2'
import { IDubboServerTransport, DubboContext } from './transport'

// init log
const log = debug('dubbo3:transport:client')

export class DubboServerTransport
  extends EventEmitter
  implements IDubboServerTransport
{
  private ctx: DubboContext
  transport: any

  constructor(opts: DubboContext) {
    super()
    this.ctx = opts
    this.transport = this.start()
  }

  get url() {
    return this.ctx.url
  }

  get port() {
    return this.ctx.port
  }

  /**
   * 启动服务端 transport
   * @returns
   */
  start() {
    const server = http2.createServer()
    server.on('stream', (stream, headers) => {
      log(stream)
      stream.on('data', (data) => {
        log(data)
        // TODO:
      })
      stream.on('end', () => {
        log('end...')
        // TODO: 通知 client
      })
      stream.on('error', (error) => {
        log(error)
      })
    })
    server.listen(this.port)
    return server
  }
}

tansport.ts 接口定义:

export interface DubboContext {
  url: string
  body?: Object | null
  port: number
}

export interface IDubboClientTransport {
  send(msg: DubboContext): Promise<any>
}

export interface IDubboServerTransport {
  // start(msg: DubboContext): Promise<any>
}

依赖:

  • dubbo context 数据和定义

@fengwei5280

@fengwei5280
Copy link
Contributor

fengwei5280 commented Dec 6, 2022

context的数据和定义 @godkun

export default class Context {
    path: string
    method: string
    args: Array<any>
    resolve: Fuction
    reject: Fuction
      // final result
    body: any
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants