tree: 85fb81ed54c367c9559404b9fb06c2602104e037 [path history] [tgz]
  1. .github/
  2. docs/
  3. examples/
  4. java/
  5. packages/
  6. perf/
  7. resources/
  8. website/
  9. .gitignore
  10. .gitignore-example-from-docusaurus
  11. .yarnrc
  12. _config.yml
  13. CODE_OF_CONDUCT.md
  14. CONTRIBUTING.md
  15. dubbo.json
  16. lerna.json
  17. LICENSE
  18. Makefile
  19. package.json
  20. README.md
  21. tsconfig.json
  22. yarn.lock
README.md

dubbo2.js


love dubbo

NPM

dubbo2.js = Nodejs connected Java Dubbo service by dubbo protocol (dubbo head + hessian body)

After all these years, dubbo 2 comes finally~ We love dubbo!👏

With core module js-to-java, and hessian.js, dubbo2 is finished. And many thanks to fengmk2dead-horse.

Features


  1. Keep it Simple (build tools for humans).

  2. Support zookeeper as register center

  3. TCP Dubbo Native protocol (Dubbo Header + Hessian Body)

  4. Socket Container (ServerAgent -> SocketWorker)

  5. Support Directly Dubbo (const Dubbo = DirectlyDubbo({..}))

  6. Middleware the same as Koa middleware, Easy to extend.

  7. Tracing (runtime info, call stack)

  8. Supported Dubbox

  9. Typescript type definition

  10. Convert java dubbo interface to typescript module by interpret tools

  11. SocketWorker was disconnected auto retry

Getting Started


yarn add dubbo2.js

How to Usage?


dubbo2.js@2.0.4+

//=====================service.ts==================
//generated by interpret tools
import {BasicTypeProvider} from './providers/com/alibaba/dubbo/demo/BasicTypeProvider';
import {DemoProvider} from './providers/com/alibaba/dubbo/demo/DemoProvider';
import {ErrorProvider} from './providers/com/alibaba/dubbo/demo/ErrorProvider';

export default {
  BasicTypeProvider,
  DemoProvider,
  ErrorProvider,
};

//===============dubbo.ts========================
import {Dubbo} from 'dubbo2.js';
import service from './service';

//Create dubbo object
const dubbo = new Dubbo<typeof service>({
  application: {name: 'node-dubbo'},
  //zookeeper address
  register: 'localhost:2181',
  service,
});

//main method
(async () => {
  let {res, err} = await dubbo.service.DemoProvider.sayHello('node');
  //print {err: null, res:'hello node from dubbo service'}
  ({res, err} = await dubbo.service.DemoProvider.echo());
  //print {err: null, res: 'pang'}
  ({res, err} = await dubbo.service.DemoProvider.getUserInfo());
  //status: 'ok', info: { id: '1', name: 'test' }
})();

If the typescript code is not automatically generated from java, how to inject it into dubbo object?


//Create the service to be injected
import {Dubbo} from 'dubbo2.js';
const demoProvider = dubbo =>
  dubbo.proxyService({
    dubboInterface: 'com.alibaba.dubbo.demo.DemoProvider',
    version: '1.0.0',
    methods: {
      sayHello(name) {
        return [java.String(name)];
      },

      echo() {},

      test() {},

      getUserInfo() {
        return [
          java.combine('com.alibaba.dubbo.demo.UserRequest', {
            id: 1,
            name: 'nodejs',
            email: 'node@qianmi.com',
          }),
        ];
      },
    },
  });

//Integrate the service in demoProvider and dubbo object constructor
const service = {
  demoProvider,
};

const dubbo = new Dubbo<typeof service>({
  //  ....other parameters
  service,
});

dubbo2.js@1.xxx

import {Dubbo, java, TDubboCallResult} from 'dubbo2.js';
//generated by interpret tools
import {BasicTypeProvider} from './providers/com/alibaba/dubbo/demo/BasicTypeProvider';
import {DemoProvider} from './providers/com/alibaba/dubbo/demo/DemoProvider';
import {ErrorProvider} from './providers/com/alibaba/dubbo/demo/ErrorProvider';

//Create dubbo object
const dubbo = new Dubbo({
  application: {name: 'node-dubbo'},
  //zookeeper address
  register: 'localhost:2181',
  interfaces: [
    'com.alibaba.dubbo.demo.DemoProvider',
    'com.alibaba.dubbo.demo.BasicTypeProvider',
    'com.alibaba.dubbo.demo.ErrorProvider',
  ],
});

const basicTypeProvider = BasicTypeProvider(dubbo);
const demoProvider = DemoProvider(dubbo);
const errorProvider = ErrorProvider(dubbo);

//main method
(async () => {
  let {res, err} = await demoProvider.sayHello('node');
  //print {err: null, res:'hello node from dubbo service'}
  ({res, err} = await demoProvider.echo());
  //print {err: null, res: 'pang'}
  ({res, err} = await demoProvider.getUserInfo());
  //status: 'ok', info: { id: '1', name: 'test' }
})();

as developer


brew install zookeeper
brew services start zookeeper

#Run test example in java/dubbo-demo-provider
yarn run test

#Full link log tracking
DEBUG=dubbo\* yarn run test

dubbo flow graph

dubbo-flow

API


create dubbo object

const dubbo = new Dubbo({
  isSupportedDubbox //Support dubbox or not: selectable, type:Boolean, false as default
  application //Name of the application: selectable, write the consumer type when zookeeper is called: {name: string}
  dubboInvokeTimeout //Dubbo timeout: selectable, 10s as default, type: number
  dubboSocketPool //Set the size of socket pool: selectable, 4 as default, type: number
  register //Set zookeeper registration center address: required, type: string
  zkRoot //Default root path of zk: ‘ /dubbo’ as default, type: string
  interfaces //Set the interface identifier for zk montoring: required, type: Array<string>, no longer exist in version dubbo2.js@2.0.4+
  service //Dubbo service injected in dubbo container: type: Object, used in version dubbo2.js@2.0.4+
});

// Or( Same as above)
const dubbo = Dubbo.from({
  isSupportedDubbox //Support dubbox or not: selectable, type:Boolean, false as default
  application //Name of the application: selectable, write the consumer type when zookeeper is called: {name: string}
  dubboInvokeTimeout //Dubbo timeout: selectable, 10s as default, type: number
  dubboSocketPool //Set the size of socket pool: selectable, 4 as default, type: number
  register //Set zookeeper registration center address: required, type: string
  zkRoot //Default root path of zk: ‘ /dubbo’ as default, type: string
  interfaces //Set the interface identifier for zk montoring: required, type: Array<string>, no longer exist in version dubbo2.js@2.0.4+
  service //Dubbo service injected in dubbo container: type: Object, used in version dubbo2.js@2.0.4+
})

//Agent service in dubbo
const demoSerivce = dubbo.proxService({
  //Agent service interface: string, required
  dubboInterface: 'com.alibaba.dubbo.demo.DemoService',
  //Version of the service interface: string, selectable
  version: '1.0.0',
  //Timeout: number, selectable
  timeout: 10
  //Group: string, selectable
  group: 'qianmi',
  //Methods within the interface: Array<Function>, required
  methods: {
  //method name
  sayHello(name) {
  //Only for parameter conversion(hessian)
  return [java.String(name)];
  },
  //method name
  getUserInfo() {
    //Only for parameter conversion(hessian)
    return [java.combine('com.alibaba.dubbo.demo.UserRequest', {
      id: 1,
      name: 'nodejs',
      email: 'node@qianmi.com',
      })];
    },
  },
})

connect dubbo directly


import {DirectlyDubbo, java} from 'dubbo2.js';
import {
  DemoProvider,
  DemoProviderWrapper,
  IDemoProvider,
} from './providers/com/alibaba/dubbo/demo/DemoProvider';
import {UserRequest} from './providers/com/alibaba/dubbo/demo/UserRequest';

const dubbo = DirectlyDubbo.from({
  dubboAddress: 'localhost:20880',
  dubboVersion: '2.0.0',
  dubboInvokeTimeout: 10,
});

const demoService = dubbo.proxyService<IDemoProvider>({
  dubboInterface: 'com.alibaba.dubbo.demo.DemoProvider',
  methods: DemoProviderWrapper,
  version: '1.0.0',
});

When dubbo was ready?


const dubbo = Dubbo.from(/*...*/);

(async () => {
  await dubbo.ready();
  //TODO dubbo was ready
})();

//for example, in egg.js
app.beforeStart(async () => {
  await dubbo.ready();
  app.logger.info('dubbo was ready...');
});

How to trace dubbo2.js runtime system info?


const dubbo = Dubbo.from(/*...*/);

//Trace by ‘subscribe’
dubbo.subcribe({
  onTrace(msg: ITrace) {
    //logger msg
  },
});

You will get all runtim system info just like this.

{ type: 'INFO', msg: 'dubbo:bootstrap version => 2.1.5' }
    { type: 'INFO', msg: 'connected to zkserver localhost:2181' }
    { type: 'INFO',
      msg: 'ServerAgent create socket-pool: 172.19.6.203:20880' }
    { type: 'INFO',
      msg: 'socket-pool: 172.19.6.203:20880 poolSize: 1' }
    { type: 'INFO',
      msg: 'new SocketWorker#1 |> 172.19.6.203:20880' }
    { type: 'INFO',
      msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' }
    { type: 'INFO',
      msg: 'SocketWorker#1 <=connected=> 172.19.6.203:20880' }
    { type: 'INFO', msg: 'scheduler is ready' }
    { type: 'INFO',
      msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.DemoProvider/providers, type: NODE_CHILDREN_CHANGED' }
    { type: 'INFO',
      msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.ErrorProvider/providers, type: NODE_CHILDREN_CHANGED' }
    { type: 'INFO',
      msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.BasicTypeProvider/providers, type: NODE_CHILDREN_CHANGED' }
    { type: 'ERR',
      msg: Error: Can not be found any agents
    at Object.Scheduler._handleZkClientOnData [as onData] (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/scheduler.js:68:29)
    at EventEmitter.<anonymous> (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/zookeeper.js:275:30)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:118:7) }
    { type: 'ERR',
      msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 6
    at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29)
    at Socket.emit (events.js:180:13)
    at TCP._handle.close [as _onclose] (net.js:541:12) }
    { type: 'INFO',
      msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' }
    { type: 'ERR',
      msg:
       { Error: connect ECONNREFUSED 172.19.6.203:20880
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1173:14)
     errno: 'ECONNREFUSED',
     code: 'ECONNREFUSED',
     syscall: 'connect',
     address: '172.19.6.203',
     port: 20880 } }
    { type: 'ERR',
      msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 5
    at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29)
    at Socket.emit (events.js:180:13)
    at TCP._handle.close [as _onclose] (net.js:541:12) }
    { type: 'INFO',
      msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.DemoProvider/providers, type: NODE_CHILDREN_CHANGED' }
    { type: 'INFO',
      msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.BasicTypeProvider/providers, type: NODE_CHILDREN_CHANGED' }
    { type: 'INFO',
      msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.ErrorProvider/providers, type: NODE_CHILDREN_CHANGED' }
    { type: 'INFO',
      msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.ErrorProvider/providers, type: NODE_CHILDREN_CHANGED' }
    { type: 'INFO',
      msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.DemoProvider/providers, type: NODE_CHILDREN_CHANGED' }
    { type: 'INFO',
      msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.BasicTypeProvider/providers, type: NODE_CHILDREN_CHANGED' }
    { type: 'ERR',
      msg: Error: Can not be found any agents
    at Object.Scheduler._handleZkClientOnData [as onData] (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/scheduler.js:68:29)
    at EventEmitter.<anonymous> (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/zookeeper.js:275:30)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:118:7) }
    { type: 'INFO',
      msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' }
    { type: 'ERR',
      msg:
       { Error: connect ECONNREFUSED 172.19.6.203:20880
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1173:14)
     errno: 'ECONNREFUSED',
     code: 'ECONNREFUSED',
     syscall: 'connect',
     address: '172.19.6.203',
     port: 20880 } }
    { type: 'ERR',
      msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 4
    at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29)
    at Socket.emit (events.js:180:13)
    at TCP._handle.close [as _onclose] (net.js:541:12) }
    { type: 'INFO',
      msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' }
    { type: 'ERR',
      msg:
       { Error: connect ECONNREFUSED 172.19.6.203:20880
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1173:14)
     errno: 'ECONNREFUSED',
     code: 'ECONNREFUSED',
     syscall: 'connect',
     address: '172.19.6.203',
     port: 20880 } }
    { type: 'ERR',
      msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 3
    at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29)
    at Socket.emit (events.js:180:13)
    at TCP._handle.close [as _onclose] (net.js:541:12) }
    { type: 'INFO',
      msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' }
    { type: 'ERR',
      msg:
       { Error: connect ECONNREFUSED 172.19.6.203:20880
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1173:14)
     errno: 'ECONNREFUSED',
     code: 'ECONNREFUSED',
     syscall: 'connect',
     address: '172.19.6.203',
     port: 20880 } }
    { type: 'ERR',
      msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 2
    at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29)
    at Socket.emit (events.js:180:13)
    at TCP._handle.close [as _onclose] (net.js:541:12) }
    { type: 'INFO',
      msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' }
    { type: 'ERR',
      msg:
       { Error: connect ECONNREFUSED 172.19.6.203:20880
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1173:14)
     errno: 'ECONNREFUSED',
     code: 'ECONNREFUSED',
     syscall: 'connect',
     address: '172.19.6.203',
     port: 20880 } }
    { type: 'ERR',
      msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 1
    at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29)
    at Socket.emit (events.js:180:13)
    at TCP._handle.close [as _onclose] (net.js:541:12) }
    { type: 'INFO',
      msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' }
    { type: 'ERR',
      msg:
       { Error: connect ECONNREFUSED 172.19.6.203:20880
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1173:14)
     errno: 'ECONNREFUSED',
     code: 'ECONNREFUSED',
     syscall: 'connect',
     address: '172.19.6.203',
     port: 20880 } }
    { type: 'ERR',
      msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 0
    at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29)
    at Socket.emit (events.js:180:13)
    at TCP._handle.close [as _onclose] (net.js:541:12) }
    { type: 'ERR',
      msg: Error: 172.19.6.203:20880's pool socket-worker had all closed. delete 172.19.6.203:20880
    at ServerAgent._clearClosedPool (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/server-agent.js:66:33)
    at Object.onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/server-agent.js:51:34)
    at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:97:34)
    at Socket.emit (events.js:180:13)
    at TCP._handle.close [as _onclose] (net.js:541:12) }
    { type: 'INFO',
      msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.DemoProvider/providers, type: NODE_CHILDREN_CHANGED' }
    { type: 'INFO',
      msg: 'ServerAgent create socket-pool: 172.19.6.203:20880' }
    { type: 'INFO',
      msg: 'socket-pool: 172.19.6.203:20880 poolSize: 1' }
    { type: 'INFO',
      msg: 'new SocketWorker#2 |> 172.19.6.203:20880' }
    { type: 'INFO',
      msg: 'SocketWorker#2 =connecting=> 172.19.6.203:20880' }
    { type: 'INFO',
      msg: 'SocketWorker#2 <=connected=> 172.19.6.203:20880' }
    { type: 'INFO', msg: 'scheduler is ready' }
    { type: 'INFO',
      msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.BasicTypeProvider/providers, type: NODE_CHILDREN_CHANGED' }
    { type: 'INFO',
      msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.ErrorProvider/providers, type: NODE_CHILDREN_CHANGED' }

middleware


Use calling link abstractly and the same middleware mechanism as koa. It’s convenient to customize interceptors, such as loggers.

//cost-time middleware
dubbo.use(async (ctx, next) => {
  const startTime = Date.now();
  await next();
  const endTime = Date.now();
  console.log('invoke cost time->', endTime - startTime);
});

dubbo-invoker


Set dynamic parameters in the interface interchange of dubbo, such as version, group, timeout, retry, etc.

Assign these parameters precisely when calling consumer.

Abstract a dubbo-invoker as the middleware for seting parameters in order to set various runtime parameters dynamically convenientely.

import {dubboInvoker, matcher} from 'dubbo-invoker';

//init
const dubbo = Dubbo.from(/*....*/);

const matchRuler = matcher
  //match interface precisely
  .match('com.alibaba.demo.UserProvider', {
    version: '1.0.0',
    group: 'user',
  })
  //match thunk
  .match(ctx => {
    if (ctx.dubboInterface === 'com.alibaba.demo.ProductProvider') {
      ctx.version = '2.0.0';
      ctx.group = 'product-center';
      //inform dubboInvoker of successful match
      return true;
    }
  })
  //regular matching
  .match(/^com.alibaba.dubbo/, {
    version: '2.0.0',
    group: '',
  });

dubbo.use(dubboInvoke(matchRuler));

Translator => Cool.

Believing the experience of development is as important as that of users, some innovations and cool practices are made.

Translator is designed and implemented to make the call between node and dubbo is the same simple and transparent as java calling dubbo.

Analyze bytecode in java jar package and extract interface information in dubbo. Typescript type definition file and callable code is generated automatically.

Duty

  1. translate Interface code for node-side calling
  2. convert the parameters to recognizable objects for hessian.js automatically
  3. interface method and parameter type promption

a detailed description of translator

Performance

 loadtest -t 20 -c 200 http://localhost:3000/dubbo -k
[Wed Jun 20 2018 15:10:16 GMT+0800 (CST)] INFO Requests: 0, requests per second: 0, mean latency: 0 ms
[Wed Jun 20 2018 15:10:21 GMT+0800 (CST)] INFO Requests: 37305, requests per second: 7484, mean latency: 26.9 ms
[Wed Jun 20 2018 15:10:26 GMT+0800 (CST)] INFO Requests: 79187, requests per second: 8371, mean latency: 23.9 ms
[Wed Jun 20 2018 15:10:31 GMT+0800 (CST)] INFO Requests: 120374, requests per second: 8247, mean latency: 24.2 ms
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO Target URL: http://localhost:3000/dubbo
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO Max time (s):20
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO Concurrency level: 200
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO Agent: keepalive
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO Completed requests: 161828
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO Total errors:0
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO Total time: 20.000902374000002 s
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO Requests per second: 8091
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO Mean latency:24.7 ms
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO Percentage of the requests served within a certain time
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO 50% 22 ms
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO 90% 30 ms
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO 95% 34 ms
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO 99% 49 ms
[Wed Jun 20 2018 15:10:36 GMT+0800 (CST)] INFO 100% 134 ms (longest request)

FAQ


import {Dubbo} from 'dubbo2.js';

The default compiling language: es2017 (support node7.10+)

For lower version of node:

import {Dubbo} from 'dubbo2.js/es6';

How to participate in development?


  1. welcome pr

  2. welcome issue

Sharing


Click here!