Open creasy2010 opened 1 year ago
辛苦东哥,protobuf这边没有问题; invoker这边比较关心最终生成代码的调用方式,期待 @creasy2010 更新
ok,可以以这个例子作为开始
@creasy2010 目前生成的代码是什么样子
因为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;
}
针对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.");
}
}
基于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)
因为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 这边的逻辑一致?
经讨论确认,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
调整后的 transport mvp
pr入口:https://github.com/apache/dubbo-js/pull/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>
}
依赖:
@fengwei5280
context的数据和定义 @godkun
export default class Context {
path: string
method: string
args: Array<any>
resolve: Fuction
reject: Fuction
// final result
body: any
}
以此为Mvp目标,大家看看是否有要补充的。