Node.js 在广发证券 – koa2 和 微服务实战新一代API Server – 我们是谁?



Node.js 在广发证券 – koa2 和 微服务实战新一代API Server – 我们是谁?

16 41


node-party-gf-security-practice

Node Party 分享 广发互联网金融 的应用:koa2 和 微服务实战新一代API Server

On Github gaohailang / node-party-gf-security-practice

Node.js 在广发证券

koa2 和 微服务实战新一代API Server

分享者: SivaGao / @gaohailang

## 个人介绍 - 豌豆荚 - WebApp - 广发证券 - Hybrid App && Node.js 开发 - [我的博客](https://github.com/gaohailang/blog) (一些技术上的思考和分享) ![](./images/14598836453050.jpg)
## 分享大纲 * 我们是谁? * 我们对 Node.js 的定位 * API Server 一览 * Koa2 和 Node.js * 日常开发 与 Node.js * 微服务 和 Node.js

我们是谁?

## 广发 `FinTech` 态度 - 金融创新 - 技术前沿

向国际顶级投行看起

Bleeding Edge,技术追求!

## 这样选型的原因 - 吸引爱玩技术的你们 - 解决问题弯道超车 - 形成学习型组织 ![](./images/14598837513672.jpg)

Node.js 的定位

API Server 和 Node.js

## 不同时代的Server - Backend 渲染页面 + 部分 Ajax - 前后分离 RESTful(或 db access) - RESTful extend - API Server 新方向 - graphql & falcor - meteor
## RESTful Extend - Optional-Field - Filter & Sort - Pagination - Bulk Inserts - Embedded & Sub Resource ```js /people?where={"lastname": "Doe"} /people?sort=city,-lastname /people?sort=[("lastname", -1)] // mongodb style /people?projection={"lastname": 1, "born": 1} /comment/?embedded={"author": 0} curl -d '[{"firstname": "barack", "lastname": "obama"}, {"firstname": "mitt", "lastname": "romney"}]' -H 'Content-Type: application/json' http://eve-demo.herokuapp.com/people ```
## API Server 新方向 - GraphQL & Relay @ Facebook - [GraphQL:数据定义和逻辑前移]() - [Relay:客户端状态和缓存]() - [graphql-server 开源实现](https://github.com/RisingStack/graphql-server) - Falcor @ Netflix - 类似效果,容易实现 - Meteor - send data over the wire
## 另类的Meteor - LiveQuery, DDP, pub/sub, remote method ![](./images/14599935528704.jpg)

Koa2 与 Node.js

## 关于 Koa2 - [它是什么](https://github.com/koajs/koa/issues/533) - [也来八卦:Express 的奇葩发展史](https://github.com/gaohailang/blog/issues/7) - 何时发布 - [ecmascript async-await stage 3](https://tc39.github.io/ecmascript-asyncawait/) - 等V8引擎(Chromium)开发完2a - 等2a被集成到 Node.js - 不要害怕,很多人通过babel用在线上 - [Microsoft ChakraCore 已经实现](https://github.com/Microsoft/ChakraCore/tree/master/test/es7),Cheers! - 哪些改动? - 中间件 & generator, yield
## 为什么选择 Koa2 - 1 语义合适的异步 - 2 干干净净的错误处理 - 3 优雅回形针的中间件 - response-time demo
## 语义合适的异步 ```js const fs = require('fs-promise'); async function readDirContent(doc) { let paths = await fs.readdir('docs'); let files = await paths.map(function(path){ return fs.readFile('docs/' + path, 'utf8'); }); this.type = 'markdown'; this.body = files.join(''); }; /*async function fetchSepcialShops (ctx, next){ let spNumTypeMap = ['manager', 'developer', 'userFav']; ctx.checkQuery('tradeId', 'Invalid trade id').notEmpty(); ctx.checkQuery('shopId', 'Invalid shop id').notEmpty(); if(!isReqRight(ctx)) return; let {tradeId, shopId} = ctx.request.query , userId = ctx.request.get('userId'), reses; let [r1, r2, r3] = await Promise.all([ rp({url: storeSpShopList, qs: {type: 1, userId: tradeId}}), rp({url: storeSpShopList, qs: {type: 2, userId: tradeId}}), userId ? rp({url: userFavShopList, headers: {userId}, qs: { shopId, from: 1, size: 5 }}) : {} ]); ctx.body = _.zipObject(spNumTypeMap, [r1.data, r2.data, _.map(r3.data, 'shop')]); }*/ ```
## 干干净净的错误处理 ```js module.exports = async function(ctx, next) { try { await next(); } catch (err) { if (err == null) { err = new Error('Null or undefined error'); } ctx.status = err.status || 500; ctx.body = { success: false, message: err.stack }; ctx.app.emit('error', err, this); } }; // 用于关闭前的一些处理如保存数据,记录错误,发送邮件等等 process.on('uncaught', ()={}); ```
## 优雅回形针的中间件 ```js function responseTime() { return async(ctx, next) => { let start = new Date; await next(); var delta = new Date - start; ctx.set('X-Response-Time', delta + 'ms'); } } ```
## 我们的koa中间件 - koa-compose - 常见的middleware - koa-adapter - 文件即路由 - koa-validator & joi

中间件回形针的运行

## 回形针的实现 koa compose ```js function compose (middleware) { return function (context, next) { // last called middleware # let index = -1 return dispatch(0) function dispatch (i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i const fn = middleware[i] || next if (!fn) return Promise.resolve() try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } } ```
## 常见的中间件 ![](./images/14598798901987.jpg)
## 兼容中间件 - [koa adapter](https://github.com/th507/koa-adapter) - 1.x中间件的引入 - 把 es6 的 generator/yield - 变成 es7 的 async/await 写法 ```js // Koa 1.0 middleware app.use(function*(next) { const start = Date.now(); yield next; const ms = Date.now() - start; console.log(`${this.method} ${this.url} - ${ms}ms`); }); // koa-res-time@1 only support koa@1 const adapt = require("koa-adapter"); const resTime = require("koa-res-time"); // use legacy middlewares with adapt(...) app.use(adapt(resTime)); ```
## 接口验证和数据验证 - [koa-validate: validate/format req params](https://github.com/RocksonZeta/koa-validate) - [joi: object schema validation](https://github.com/hapijs/joi) ```js ctx.checkQuery('query', 'Invalid query').notEmpty(); ctx.checkQuery('type', 'Invalid type') .isIn(baseSearchTypes.concat(['all'])); let joiUserSchema = Joi.object({ name: Joi.object({ first: Joi.string().required(), last: Joi.string().required() }), email: Joi.string().email().required(), bestFriend: Joi.string().meta({ type: 'ObjectId', ref: 'User' }), metaInfo: Joi.any() }); ```

日常开发与 Node.js

## 日常注意点 - ES6 化 - 构建过程 - 在线上运行 - 2016年新注意点 - Web 上下游
## ES6,马上开始 - 迁移 legacy 代码 ```js // before var page = req.query.page, , size = req.query.size; // after let {pgae, size} = req.query; rp({ uri, params: {page, size} }); ``` - Babel 集成 ```json { "presets": ["es2015-node5"], "plugins": [ "transform-async-to-generator", "syntax-async-functions" ] } ```
## npm scripts 构建过程 更详细参见我们的[脚手架文章](https://github.com/gf-rd/blog/issues/20) - 及时更新依赖 - 文件变化监控 - 入库前检查 - 代码质量检查 - 代码坏味道
## 及时更新依赖 - [npm-check](https://github.com/dylang/npm-check) ![](./images/14598767264510.jpg)
## 文件变化监控 - nodemon ```json { "main": "index.js", "scripts": { "dev": "nodemon --exec babel-node -- $npm_package_main" } } ``` ![](./images/14598770272183.jpg)
## 入库前检查 [husky](https://npmjs.org/package/husky) 可以修改你的git命令,提供hook点在commit/push/merge 前执行检查 - npm run precommit : npm test - npm run prepush : npm-run-all lint test test:deps - npm run inspect : jsinspect ![](./images/14599285872337.jpg)
## 哪些部分要测试覆盖 npm run coverage : lab -r lcov - 那些经常要被修改的 change a lot - 那些有很高复杂度的 risky - 那些重要功能/常被查看的 more traffic

代码质量检查

  • plato
    • JavaScript 源代码可视化, 静态分析和复杂度管理
  • jsinspect
    • 找出粘贴复制和代码结构类似的代码
![](./images/94419d84gw1f2jqooa7i9j213y10an94.jpg)
## [针针见血:怎么消除JavaScript中的代码坏味道](https://github.com/gaohailang/blog/issues/5) > 如临时定时器,双向数据绑定的坑,复杂的分支语句,重复赋值等,对它们进行分析如现场还原,糟糕代码回顾,问题诊断和识别(通过ESlint或其他工具),代码重构方案,给出了怎么写好代码的一手经验~
![](./images/54aeed78b5e8033c6b14.png)
## 在线上运行 - 性能调优 - 上线准备 - 安全施工
## StrongLoop 性能优化系列 - [heap profiling](https://strongloop.com/strongblog/node-js-performance-heap-profiling-tip/) - [memory leak diagnosis](https://strongloop.com/strongblog/node-js-performance-tip-of-the-week-memory-leak-diagnosis/) - [CPU profiling](https://strongloop.com/strongblog/node-js-performance-tip-cpu-profiler/) - [scaling proxies clusters](https://strongloop.com/strongblog/node-js-performance-scaling-proxies-clusters/) - [event loop monitoring](https://strongloop.com/strongblog/node-js-performance-event-loop-monitoring/) - [garbage collection](https://strongloop.com/strongblog/node-js-performance-garbage-collection/)
![](./images/14598782052144.jpg)
![](./images/14598781886376.jpg)
## 上线前准备 - Gzip压缩中间件/Nginx开启 - 静态文件中间件/Nginx加入 - 避免尽量少的复杂同步代码(Event Loop 监控) - 打好全量的log信息(通过环境变量和进程信号调整) - 合理的处理异常
## 安全施工 - 不要使用过期老旧的版本框架 - 快点用上 https - Helmet 中间件 - 安全使用Cookie & Session - 确保其他组件依赖是安全的 - 其他一些基础的:[security-checklist](https://blog.risingstack.com/node-js-security-checklist)
## [2016年新项目哪些注意点?](https://github.com/gf-rd/blog/issues/29) - 异步函数支持回调惯例和Promise新写法 - 智能的 .npmrc 和正确的版本管理做法 - Web 应用开发的十二条军规 - 始终用 npm init 开始新项目 - 使用标准的 JavaScript 代码风格 - 使用 eslint 工具,同时配合 eslint-plugin-standard 插件
## 应用服务器上下游 - 关于openresty & haproxy & varnish - 关于 mongodb, redis - 关于log,监控和指标计算 - 关于 kafka 等 - 关于扩容,Docker,kubernetes

微服务和Node.js

## 微服务 [Node 接入层 微服务架构](https://github.com/gf-rd/blog/issues/10) - 特点 - 架构 - 要素
## 微服务集成开发 - docker 部署 - 无状态(随时被动态扩缩容掉 - request监控(链路追踪 - 配置加载管理(环境变量

Docker 部署

容器,Dokcer,虚拟化 - 写给开发者的入门指南
## Docker 部署 More ```json { "rsync:gftest": "rsync --cvs-exclude -cauvz -e \"ssh -A gf@ ssh\" .es5 ubuntu@:/opt/gf/gfwealth-composite/", "docker:prebuild": "rsync -avz --exclude .es5 --exclude .idea . ./.es5 && babel . --out-dir ./.es5 --ignore ./.es5,./node_modules", "docker:build": "cd .. && docker build -t /gfwealth-composite:koa-2016-03-17 .", "docker:run": "sudo docker run -d --name gfwealth-composite -p 8000:9321 -v /opt/gf/gfwealth-composite/logs:/opt/gf/gfwealth-composite/logs -v /etc/localtime:/etc/localtime:ro --env-file=\"config/env/product\" docker.gf.com.cn/gfwealth-composite:koa-2016-03-17", "docker:restart": "npm run docker:run && npm run docker:rm" "docker:rm": "sudo docker rm -f gfwealth-composite", "docker:push": "docker login docker.gf.com.cn && docker push /gfwealth-composite:koa-2016-03-17" } ```
## 请求的监控和debug ```js require('request-debug')(rp, function(type, data, r) { // put your request or response handling logic here // Todo: request-post data lost? response json.stringify broken? if(type === 'request') { var {debugId, uri, method, body} = data; var userId = data.headers.userId; var more = body ? '' : ('-'+body) + userId ? '' : ('userId:'+userId); debug(`${debugId}-${method}-${uri}` + more); } if(type === 'response') { var {debugId, statusCode, body} = data; debug(`${debugId}-response-${statusCode}-`+JSON.stringify(body).slice(0, 155)); } }); ```

请求的链路监控

## 配置加载和环境变量 关于配置我们集成了confit和shorthandle(来自paypal) - 各种 handler - 基于不同环境实现差异化配置 - 根据env变量自动加载 ```js // envfile GFWC_storeExShopList=http://shopdev.gf.com.cn/api/store/shop/excellentshop/{id}/{page}/{size} GFWC_storeSpShopList=http://shopdev.gf.com.cn/api/store/shop/special GFWC_userFavShopList=http://shopdev.gf.com.cn/api/store/auth/favorites/shop/inorg // 配置: { "env": "development", "api": { "articleSearch": "env:GFWC_articleSearch", "shopSearch": "env:GFWC_shopSearch", "stockSearch": "env:GFWC_stockSearch", "portfolioSearch": "env:GFWC_portfolioSearch" } } ```

我们和开源

开源的参与和贡献

  • ES6
  • Angular2
  • 更多见Blog,我们的技术分享
QCon分享 翻译 技术博客

回顾一下

    • 我们是谁?
    • 我们和开源
    • 对 Node.js 的定位
    • API Server 一览
    • Koa2 和 Node.js
    • 日常开发 与 Node.js
    • 微服务 和 Node.js

谢谢大家!

微信:ghlndsl,邮箱:gaohailang@gf.com.cn

## 引用和链接 - [广发IT招聘](http://it.gf.com.cn) - [广发RD Github Organization](https://github.com/gf-rd) - [广发技术博客](https://github.com/gf-rd/blog) - [16年,新 Node 项目注意点](https://github.com/gf-rd/blog/issues/29) - [Node 构建推荐做法](https://github.com/gf-rd/blog/issues/20) - [Node 接入层 微服务架构](https://github.com/gf-rd/blog/issues/10) - [Node.js 在广发证券:koa2 和 微服务实战新一代API Server](https://github.com/gaohailang/node-party-gf-security-practice)