Dubbo-js uses RPC to get through node and java. If we can automatically generate Dubbo service interface definition, parameter conversion, and have automatic completion ability in ide, our development experience will be better.
"Translator" came into being for this purpose!
We will like it.
Duty
- Generate typescript code corresponding to Dubbo service interface;
- Parametric conversion, converting JavaScript object to a format that hession. JS recognizes;
- Synchronization of interface annotation information;
- MVN packaged plug-in;
We divide the whole process into three steps.
-
Generate jar packages and install dependencies in Java API projects;
-
Extract ast information from generated jar bytecode, and then generate typescript based on it.
-
Calling services;
mvn package
mvn install dependency:copy-dependencies
npm install interpret-dubbo2js -g
interpret -c dubbo.json
dubbo.json:
{
"output": "./src",
"entry": "com.qianmi",
"entryJarPath": "${jarPath}",
"libDirPath": "${denpendJarDir}"
}
note: Reference examples;
parameter | affect |
---|---|
output | the dir to save output eg: config example |
entry | package path filter eg: config example |
entryJarPath | jar package for dubbo api eg: config example |
libDirPath | the dubbo api dependencies eg: config example |
import { ShowCaseProvider } from '@qianmi/***-api/lib/com/qianmi/ShowCaseProvider'
const dubbo = new Dubbo({
application: { name: 'd2p-visitor-bff' },
dubboInvokeTimeout: 10,
//zookeeper address
register: app.config.zookeeper,
dubboVersion: '2.4.13',
logger: app.logger as ILogger,
interfaces: [
'com.qianmi.cloudshop.api.marketing.d2p.D2pMarketingQueryProvider'
]
})
let showCaseProvider = ShowCaseProvider(dubbo)
showCaseProvider.show()
Tip npm install interpret-util dubbo-js
;
Note: The code snippet in the following explanation comes from hello-egg-node
There are two meanings.
First, Dubbo provides services interfaces, such as
package com.alibaba.dubbo.demo;
public interface DemoProvider {
String sayHello(String name);
String echo() ;
void test();
UserResponse getUserInfo(UserRequest request);
}
Second, the client for node.js, corresponding to the Dubbo service, such as
import { UserRequest } from './UserRequest'
import { UserResponse } from './UserResponse'
import { argumentMap, JavaString } from 'interpret-util'
import { TDubboCallResult, Dubbo } from 'dubbo2.js'
export interface IDemoProvider {
sayHello(name: JavaString): TDubboCallResult<string>
test(): TDubboCallResult<void>
echo(): TDubboCallResult<string>
getUserInfo(request: UserRequest): TDubboCallResult<UserResponse>
}
export const DemoProviderWrapper = {
sayHello: argumentMap,
test: argumentMap,
echo: argumentMap,
getUserInfo: argumentMap
}
export function DemoProvider(dubbo: Dubbo): IDemoProvider {
return dubbo.proxyService<IDemoProvider>({
dubboInterface: 'com.alibaba.dubbo.demo.DemoProvider',
methods: DemoProviderWrapper
})
}
//generate by interpret-cli dubbo-js
The main responsibility of a translator is to seamlessly connect with nodejs dubbo.
The main responsibility of the converter is to convert JavaScript code into JS objects in hession.JS format and then communicate with Dubbo service.
Let's see how it works:
//dubbo-demo/dubbo-demo-api/src/main/java/com/alibaba/dubbo/demo/DemoProvider.java
public interface DemoProvider {
UserResponse getUserInfo(UserRequest request);
}
//dubbo-demo/dubbo-demo-api/src/main/java/com/alibaba/dubbo/demo/UserRequest.java
public class UserRequest implements Serializable {
private Integer id;
private String name;
private String email;
}
Corresponding TS code
//
import java from 'js-to-java'
//
export interface IUserRequest {
name?: string
id?: number
email?: string
}
export class UserRequest {
constructor(params: IUserRequest) {
this.name = params.name
this.id = params.id
this.email = params.email
}
name?: string
id?: number
email?: string
__fields2java() {
return {
$class: 'com.alibaba.dubbo.demo.UserRequest',
$: {
name: java.String(this.name),
id: java.Integer(this.id),
email: java.String(this.email)
}
}
}
}
//generate by interpret-cli dubbo-js
Extract ast from Java source code and use it to convert to typescritp code
Let's look at the ast information for DemoProvider and UserRequest in the example above.
{
"classes": {
"com.alibaba.dubbo.demo.DemoProvider": {
"fields": {},
"isAbstract": true,
"isEnum": false,
"isInterface": true,
"methods": {
"sayHello": {
"formalParams": ["name"],
"isOverride": false,
"params": [
{
"name": "java.lang.String",
"typeArgs": []
}
],
"ret": {
"name": "java.lang.String",
"typeArgs": []
},
"typeParams": []
},
"test": {
"formalParams": [],
"isOverride": false,
"params": [],
"ret": {
"name": "java.lang.Void"
},
"typeParams": []
},
"echo": {
"formalParams": [],
"isOverride": false,
"params": [],
"ret": {
"name": "java.lang.String",
"typeArgs": []
},
"typeParams": []
},
"getUserInfo": {
"formalParams": ["request"],
"isOverride": false,
"params": [
{
"name": "com.alibaba.dubbo.demo.UserRequest",
"typeArgs": []
}
],
"ret": {
"name": "com.alibaba.dubbo.demo.UserResponse",
"typeArgs": []
},
"typeParams": []
}
},
"name": "com.alibaba.dubbo.demo.DemoProvider",
"privateFields": [],
"typeParams": [],
"values": []
},
"com.alibaba.dubbo.demo.UserRequest": {
"fields": {
"name": {
"name": "java.lang.String",
"typeArgs": []
},
"id": {
"name": "java.lang.Integer",
"typeArgs": []
},
"email": {
"name": "java.lang.String",
"typeArgs": []
}
},
"isAbstract": false,
"isEnum": false,
"isInterface": false,
"methods": {
"setName": {
"formalParams": ["name"],
"isOverride": false,
"params": [
{
"name": "java.lang.String",
"typeArgs": []
}
],
"ret": {
"name": "java.lang.Void"
},
"typeParams": []
},
"getName": {
"formalParams": [],
"isOverride": false,
"params": [],
"ret": {
"name": "java.lang.String",
"typeArgs": []
},
"typeParams": []
},
"setEmail": {
"formalParams": ["email"],
"isOverride": false,
"params": [
{
"name": "java.lang.String",
"typeArgs": []
}
],
"ret": {
"name": "java.lang.Void"
},
"typeParams": []
},
"setId": {
"formalParams": ["id"],
"isOverride": false,
"params": [
{
"name": "java.lang.Integer",
"typeArgs": []
}
],
"ret": {
"name": "java.lang.Void"
},
"typeParams": []
},
"getEmail": {
"formalParams": [],
"isOverride": false,
"params": [],
"ret": {
"name": "java.lang.String",
"typeArgs": []
},
"typeParams": []
},
"getId": {
"formalParams": [],
"isOverride": false,
"params": [],
"ret": {
"name": "java.lang.Integer",
"typeArgs": []
},
"typeParams": []
},
"toString": {
"formalParams": [],
"isOverride": false,
"params": [],
"ret": {
"name": "java.lang.String",
"typeArgs": []
},
"typeParams": []
}
},
"name": "com.alibaba.dubbo.demo.UserRequest",
"privateFields": ["id", "name", "email"],
"typeParams": [],
"values": []
},
"com.alibaba.dubbo.demo.UserResponse": {
"fields": {
"status": {
"name": "java.lang.String",
"typeArgs": []
},
"info": {
"name": "java.util.Map",
"typeArgs": [
{
"isWildcard": false,
"type": {
"name": "java.lang.String",
"typeArgs": []
}
},
{
"isWildcard": false,
"type": {
"name": "java.lang.String",
"typeArgs": []
}
}
]
}
},
"isAbstract": false,
"isEnum": false,
"isInterface": false,
"methods": {
"getInfo": {
"formalParams": [],
"isOverride": false,
"params": [],
"ret": {
"name": "java.util.Map",
"typeArgs": [
{
"isWildcard": false,
"type": {
"name": "java.lang.String",
"typeArgs": []
}
},
{
"isWildcard": false,
"type": {
"name": "java.lang.String",
"typeArgs": []
}
}
]
},
"typeParams": []
},
"toString": {
"formalParams": [],
"isOverride": false,
"params": [],
"ret": {
"name": "java.lang.String",
"typeArgs": []
},
"typeParams": []
},
"setInfo": {
"formalParams": ["info"],
"isOverride": false,
"params": [
{
"name": "java.util.Map",
"typeArgs": [
{
"isWildcard": false,
"type": {
"name": "java.lang.String",
"typeArgs": []
}
},
{
"isWildcard": false,
"type": {
"name": "java.lang.String",
"typeArgs": []
}
}
]
}
],
"ret": {
"name": "java.lang.Void"
},
"typeParams": []
},
"getStatus": {
"formalParams": [],
"isOverride": false,
"params": [],
"ret": {
"name": "java.lang.String",
"typeArgs": []
},
"typeParams": []
},
"setStatus": {
"formalParams": ["status"],
"isOverride": false,
"params": [
{
"name": "java.lang.String",
"typeArgs": []
}
],
"ret": {
"name": "java.lang.Void"
},
"typeParams": []
}
},
"name": "com.alibaba.dubbo.demo.UserResponse",
"privateFields": ["status", "info"],
"typeParams": [],
"values": []
}
},
"providers": ["com.alibaba.dubbo.demo.DemoProvider"]
}
ArgumentMap is a runtime assistant method whose main responsibility is to trigger data structure transformation.
Main steps:
-
Traversal parameters are called if _fields 2java is included.
-
Delete null and undefined values;
//Examples of argumentMap usage
import { UserRequest } from './UserRequest'
import { UserResponse } from './UserResponse'
import { argumentMap, JavaString } from 'interpret-util'
import { TDubboCallResult, Dubbo } from 'dubbo-js'
export interface IDemoProvider {
sayHello(name: JavaString): TDubboCallResult<string>
test(): TDubboCallResult<void>
echo(): TDubboCallResult<string>
getUserInfo(request: UserRequest): TDubboCallResult<UserResponse>
}
export const DemoProviderWrapper = {
sayHello: argumentMap,
test: argumentMap,
echo: argumentMap,
getUserInfo: argumentMap
}
export function DemoProvider(dubbo: Dubbo): IDemoProvider {
return dubbo.proxyService<IDemoProvider>({
dubboInterface: 'com.alibaba.dubbo.demo.DemoProvider',
methods: DemoProviderWrapper
})
}
//generate by interpret-cli dubbo-js
//Content of argumentMap method
export function argumentMap() {
let _arguments = Array.from(arguments)
return _arguments.map((argumentItem) =>
argumentItem.__fields2java
? paramEnhance(argumentItem.__fields2java())
: argumentItem
)
}
function paramEnhance(javaParams: Array<object> | object) {
if (javaParams instanceof Array) {
for (let i = 0, ilen = javaParams.length; i < ilen; i++) {
let itemParam = javaParams[i]
minusRedundancy(itemParam)
}
} else {
minusRedundancy(javaParams)
}
return javaParams
}
function minusRedundancy(itemParam: any) {
if (!itemParam) {
return
}
for (var _key in itemParam.$) {
if (itemParam.$[_key] === null || itemParam.$[_key] === undefined) {
delete itemParam.$[_key]
log('删除 key %s from %j ', itemParam, _key)
}
}
}
There are two ways to use it.
-
Embedding projects directly;
-
Publish NPM packages;
The first approach is very suitable for a small number of Dubbo interfaces, single projects; see hello-egg
The second approach is suitable for large-scale projects, especially multi-project shared interfaces; see Automatic Translation Service
Tip 生成的代码可以发 npm 包供其他业务线使用或直接在项目中引用