Demo RDS

Demo for RDS

What is RDS?

RDS(Relational Database Service) is a fully compatible with MySQL online database.

How to USE RDS?

We will build a simple user table to illustrate how to implement the standarded CRUD operation.

  • Step 1: Start a MySQL Server by container
pwd
    mkdir -p mysql_db/db;
    cp my.cnf mysql_db;
    docker run --name mysql --restart=always -v `pwd`/mysql_db/:/etc/mysql/conf.d -v `pwd`/mysql_db/db/:/var/lib/mysql -e MYSQL_ROOT_PASSWORD='' -e MYSQL_DATABASE='' -e MYSQL_USER='' -e MYSQL_PASSWORD='' -p 3306:3306  -d mysql
  • Step 2: Create a table named "user"

notice: All field names must are lower case

CREATE TABLE `monitor_user` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID',
    `account` varchar(50) CHARACTER SET utf8mb4 NOT NULL DEFAULT '' COMMENT '帐号',
    `accountid` varchar(50) CHARACTER SET utf8mb4 NOT NULL COMMENT '帐号ID',
    `email` varchar(50) CHARACTER SET utf8mb4 NOT NULL COMMENT '邮件地址',
    `phonenumber` varchar(20) CHARACTER SET utf8mb4 NOT NULL COMMENT '电话号码',
    `salt` varchar(128) CHARACTER SET utf8mb4 NOT NULL COMMENT '密码盐',
    `passwordsha` varchar(128) CHARACTER SET utf8mb4 NOT NULL COMMENT '密码SHA',
    `createtime` bigint(20) NOT NULL COMMENT '记录创建时间',
    `updatetime` bigint(20) NOT NULL COMMENT '记录更新时间',
    UNIQUE KEY `user_id_idx` (`id`) USING BTREE,
    UNIQUE KEY `user_account_idx` (`account`) USING BTREE,
    UNIQUE KEY `user_accountid_idx` (`accountid`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=80 DEFAULT CHARSET=latin1;

  • Step 3: Generate Standard class Files

Model Template

You can generate User Model utilizing below model template. generate the file netease/monitor/backend/models/User.js

'use strict';
    var assert = require('assert');
    var logger = using('easynode.framework.Logger').forFile(__filename);
    var GenericObject = using('easynode.GenericObject');
    var Model = using('easynode.framework.mvc.Model');

    (function() {
    /**
    * Class ${CLASSNAME}
    *
    * @class ${COMPANY}.${PROJECT}.backend.models.${CLASSNAME}
    * @extends easynode.GenericObject
    * @since 0.1.0
    * @author allen.hu
    * */
    class ${CLASSNAME} extends Model {
    /**
    * 构造函数。
    *
    * @method 构造函数
    * @since 0.1.0
    * @author allen.hu
    * */
    constructor() {
    super(`${CLASSNAME}`, `SELECT * FROM ${CLASSNAME}`);
    // 调用super()后再定义子类成员。
    }

    /**
    * 定义模型字段, 通过数据字段定义数据自动生成字段定义
    *
    * @method defineFields
    * @since 0.1.0
    * @author allen.hu
    * */
    defineFields() {
    this
    .defineField('id', 'int')
    ;
    }


    getClassName() {
    return EasyNode.namespace(__filename);
    }
    }

    module.exports = ${CLASSNAME};
    })();

'use strict';
    var assert = require('assert');
    var logger = using('easynode.framework.Logger').forFile(__filename);
    var GenericObject = using('easynode.GenericObject');
    var Model = using('easynode.framework.mvc.Model');

    (function () {
    /**
    * Class User
    *
    * @class netease.monitor.backend.models.User
    * @extends easynode.GenericObject
    * @since 0.1.0
    * @author allen.hu
    * */
    class User extends Model {
    /**
    * 构造函数。
    *
    * @method 构造函数
    * @since 0.1.0
    * @author allen.hu
    * */
    constructor() {
    super(`monitor_user`, 'SELECT * FROM monitor_user');
    // 调用super()后再定义子类成员。
    }

    /**
    * 定义模型字段
    *
    * @method defineFields
    * @since 0.1.0
    * @author allen.hu
    * */
    defineFields() {
    this
    .defineField('id', 'int')
    .defineField('account', 'string')
    .defineField('accountid', 'string')
    .defineField('email', 'string')
    .defineField('phonenumber', 'string')
    .defineField('salt', 'string')
    .defineField('passwordsha', 'string')
    .defineField('createtime', 'int')
    .defineField('updatetime', 'int')
    ;
    }


    getClassName() {
    return EasyNode.namespace(__filename);
    }
    }

    module.exports = User;
    })();

Route Template

RESTful API , Action Mapping:

http method     route                       function of ctrl
    post            /${model}                   add resource
    get             /add/${model}               Open Page for adding
    put             /${model}/:id               Update resource
    get             /${model}/:id               get resource detail
    get             /${model}/:index/:pagesize  get resources of pagination
    delete          /${model}:/id               delete resource

in the backend/routes/Routes.js , add below code fragement.

// ${model}
    httpServer.addRoute('post','/${model}', Controllers.add${Model}(httpServer));
    httpServer.addRoute('get','/add/${model}', Controllers.add${Model}Page(httpServer));
    httpServer.addRoute('put','/${model}/:id',Controllers.update${Model}(httpServer));
    httpServer.addRoute('get','/${model}/:id',Controllers.get${Model}(httpServer));
    httpServer.addRoute('get','/${model}/:index/:pagesize',Controllers.get${Model}list(httpServer));
    httpServer.addRoute('delete','/${model}/:id',Controllers.del${Model}(httpServer));

route template

// User
    httpServer.addRoute('post','/user', Controllers.addUser(httpServer));
    httpServer.addRoute('get','/add/user', Controllers.addUserPage(httpServer));
    httpServer.addRoute('put','/user/:id',Controllers.updateUser(httpServer));
    httpServer.addRoute('get','/user/:id',Controllers.getUser(httpServer));
    httpServer.addRoute('get','/user/:index/:pagesize',Controllers.getUserlist(httpServer));
    httpServer.addRoute('delete','/user/:id',Controllers.delUser(httpServer));

Controller template

in the backend/controllers/Controllers.js

First import the class of processing logic.

var UserService =  using('netease.monitor.backend.services.UserService');

Controller Layer template, Template variables:

  • ${model}: All lower case
  • ${Model}: First Letter upper, other lower case
  • ${model_desc}: describe to model
 /**
    * @api {post} /${model} 添加${model_desc}
    * @apiName add${Model}
    * @apiGroup ${Model}
    * @apiPermission
    * @apiVersion 0.0.1
    * @apiDescription
    *
    * @apiSampleRequest http://127.0.0.1:8899/${model}/
    *
    * @apiParam {Object} ${model} ${model_desc}信息,模版里遍历该对像属性字段
    *
    * @apiUse APIReturn
    */
    static add${Model}(app){
    return function *(){

    var ${model}Service = new ${Model}Service(app);
    var ret = yield ${model}Service.add${Model}();

    this.type = 'json';
    this.body = ret ;
    };
    }

    /**
    * @api {get} /add/${model} 添加${model_desc}页面
    * @apiName add${Model}Page
    * @apiGroup ${Model}
    * @apiPermission
    * @apiVersion 0.0.1
    * @apiDescription
    *
    * @apiSampleRequest http://127.0.0.1:8899/add/${Model}/
    *
    * @apiSuccess 返回页面
    */
    static add${Model}Page(app){
    return function *(){
    yield this.render(`add-${model}`);
    };
    }

    /**
    * @api {put} /${model} 修改${model_desc}
    * @apiName update${Model}
    * @apiGroup ${Model}
    * @apiPermission
    * @apiVersion 0.0.1
    * @apiDescription
    *
    * @apiSampleRequest http://127.0.0.1:8899/${model}/:id
    *
    * @apiParam {Object} ${Model} ${model_desc}信息,模版里遍历该对像属性字段

    * @apiUse APIReturn
    */
    static update${Model}(app){
    return function *(id){

    var ${model}Service = new ${Model}Service(app);
    var ret = yield ${model}Service.update${Model}(id);

    this.type = 'json';
    this.body = ret;
    };
    }

    /**
    * @api {get} /${model}/:id 获取${model_desc}详情
    * @apiName get${Model}
    * @apiGroup ${Model}
    * @apiPermission
    * @apiVersion 0.0.1
    * @apiDescription
    *
    * @apiSampleRequest http://127.0.0.1:8899/${model}/:id
    *
    * @apiUse APIReturn
    */
    static get${Model}(app){
    return function *(id){

    var ${model}Service = new ${Model}Service(app);
    var ret = yield ${model}Service.get${Model}(id);

    this.type = 'json';
    this.body = ret;
    };
    }

    /**
    * @api {get} /${model}/:index/:pagesize 获取${model_desc}列表
    * @apiName get${Model}list
    * @apiGroup ${Model}
    * @apiPermission
    * @apiVersion 0.0.1
    * @apiDescription
    *
    * @apiSampleRequest http://127.0.0.1:8899/${model}/:index/:pagesize
    *
    * @apiUse APIReturn
    */
    static get${Model}list(app){
    return function *(index,pagesize){

    var ${model}Service = new ${Model}Service(app);
    var ret = yield ${model}Service.get${Model}List(index,pagesize);

    this.type = 'json';
    this.body = ret;
    };
    }

    /**
    * @api {delete} /${model}/:id 删除${model_desc}
    * @apiName del${Model}
    * @apiGroup ${Model}
    * @apiPermission
    * @apiVersion 0.0.1
    * @apiDescription
    *
    * @apiSampleRequest http://127.0.0.1:8899/${model}/:id
    *

    * @apiUse APIReturn
    */
    static del${Model}(app){
    return function *(id){

    var ${model}Service = new ${Model}Service(app);
    var ret = yield ${model}Service.del${Model}(id);

    this.type = 'json';
    this.body = ret;
    };
    }

Code fragement:

 /**
    * @api {post} /user 添加用户
    * @apiName addUser
    * @apiGroup User
    * @apiPermission
    * @apiVersion 0.0.1
    * @apiDescription
    *
    * @apiSampleRequest http://127.0.0.1:8899/user/
    *
    * @apiParam {Object} user 用户信息,模版里遍历该对像属性字段
    *
    * @apiUse APIReturn
    */
    static addUser(app){
    return function *(){

    var userService = new UserService(app);
    var ret = yield userService.addUser();

    this.type = 'json';
    this.body = ret ;
    };
    }

    /**
    * @api {get} /add/user 添加用户页面
    * @apiName addUserPage
    * @apiGroup User
    * @apiPermission
    * @apiVersion 0.0.1
    * @apiDescription
    *
    * @apiSampleRequest http://127.0.0.1:8899/add/user/
    *
    * @apiSuccess 返回页面
    */
    static addUserPage(app){
    return function *(){
    yield this.render('add-user');
    };
    }

    /**
    * @api {put} /user 修改用户
    * @apiName updateUser
    * @apiGroup User
    * @apiPermission
    * @apiVersion 0.0.1
    * @apiDescription
    *
    * @apiSampleRequest http://127.0.0.1:8899/user/:id
    *
    * @apiParam {Object} user 用户信息,模版里遍历该对像属性字段

    * @apiUse APIReturn
    */
    static updateUser(app){
    return function *(id){

    var userService = new UserService(app);
    var ret = yield userService.updateUser(id);

    this.type = 'json';
    this.body = ret;
    };
    }

    /**
    * @api {get} /user/:id 获取用户详情
    * @apiName getUser
    * @apiGroup User
    * @apiPermission
    * @apiVersion 0.0.1
    * @apiDescription
    *
    * @apiSampleRequest http://127.0.0.1:8899/user/:id
    *
    * @apiUse APIReturn
    */
    static getUser(app){
    return function *(id){

    var userService = new UserService(app);
    var ret = yield userService.getUser(id);

    this.type = 'json';
    this.body = ret;
    };
    }

    /**
    * @api {get} /user/:index/:pagesize 获取用户列表
    * @apiName getUserlist
    * @apiGroup User
    * @apiPermission
    * @apiVersion 0.0.1
    * @apiDescription
    *
    * @apiSampleRequest http://127.0.0.1:8899/user/:index/:pagesize
    *
    * @apiUse APIReturn
    */
    static getUserlist(app){
    return function *(index,pagesize){

    var userService = new UserService(app);
    var ret = yield userService.getUserList(index,pagesize);

    this.type = 'json';
    this.body = ret;
    };
    }

    /**
    * @api {delete} /user/:id 删除用户
    * @apiName delUser
    * @apiGroup User
    * @apiPermission
    * @apiVersion 0.0.1
    * @apiDescription
    *
    * @apiSampleRequest http://127.0.0.1:8899/user/:id
    *

    * @apiUse APIReturn
    */
    static delUser(app){
    return function *(id){

    var userService = new UserService(app);
    var ret = yield userService.delUser(id);

    this.type = 'json';
    this.body = ret;
    };
    }

Service Template

  • Step: 1

import model class and other class of processing Uniform format of the RESTful API.

var User = using('netease.monitor.backend.models.User');
    var APIReturn =  using('easynode.framework.util.APIReturn');

Multiple inheritance:

class UserService extends Mixin.mix(GenericObject,APIReturn)

Service Layer Template: ${Model} ${...model_field} ${model_table}

 add${Model}(){
    var me = this;
    return function *(){

    try {
    var newId = 0;
    var ret = {};
    var body = this.request.body;
    var model = new ${Model}();
    var conn = yield me.app.dataSource.getConnection();

    model.merge( Object.assign( body, {createtime:Date.now(), updatetime:Date.now()} ) );

    var record = yield conn.create(model);
    newId = record.insertId;
    ret = me.APIReturn(0,'success',{id:newId});
    } catch (e) {
    EasyNode.DEBUG && logger.error(e);
    ret = me.APIReturn(1,'failed',{});
    } finally {
    yield me.app.dataSource.releaseConnection(conn);
    return ret;
    }
    }
    }

    update${Model}(id){
    var me = this;
    return function *(){

    try {
    var ret = {};
    var body = this.request.body;
    var model = new ${Model}();
    var conn = yield me.app.dataSource.getConnection();

    model.merge( Object.assign( body, {id:id, updatetime:Date.now()} ) );

    var record = yield conn.update(model);
    ret = me.APIReturn(0,'success',{id: id});
    } catch (e) {
    EasyNode.DEBUG && logger.error(e);
    ret = me.APIReturn(1,'failed',{});
    } finally {
    yield me.app.dataSource.releaseConnection(conn);
    return ret;
    }
    }
    }

    get${Model}(id){
    var me = this;
    return function *(){

    try {
    var ret = {};
    var record = null;
    var sql = '';
    var arr = [];
    var id2 = id || 0 ;
    var conn = yield me.app.dataSource.getConnection();

    //
    sql = 'SELECT ${...model_field} FROM ${model_table} WHERE id = #id#';
    arr = yield conn.execQuery( sql, {id:id2} );
    if (arr.length <= 0) {
    return ret;
    }
    record = arr[0];
    ret = me.APIReturn(0,'success',record);
    } catch (e) {
    EasyNode.DEBUG && logger.debug(` ${e} ${e.stack}`);
    ret = me.APIReturn(1,'fail',{});
    } finally {
    yield me.app.dataSource.releaseConnection(conn);
    return ret;
    }
    }
    }

    get${Model}List(index,pagesize){
    var me = this;
    return function *(){
    try {
    var ret = {};
    var model = new ${Model}();
    var conn = yield me.app.dataSource.getConnection();

    var data = yield conn.list(model,{id: {exp:'!=',value:0} }, {page: index, rpp: pagesize}, ['updatetime ASC']);

    ret = me.APIReturn(0,'success',data);
    } catch (e) {
    EasyNode.DEBUG && logger.debug(` ${e} ${e.stack}`);
    ret = me.APIReturn(1,'fail',{});
    } finally {
    yield me.app.dataSource.releaseConnection(conn);
    return ret;
    }
    };
    }

    del${Model}(id){
    var me = this;
    return function *(){

    try {
    var ret = {};
    var model = new ${Model}();
    var conn = yield me.app.dataSource.getConnection();

    var data = yield conn.del(model,[id]);
    //{ affectedRows: 1, insertId: 0 }
    ret = me.APIReturn(0,'success',{id: id});
    } catch (e) {
    EasyNode.DEBUG && logger.debug(` ${e} ${e.stack}`);
    ret = me.APIReturn(1,'fail',{});
    } finally {
    yield me.app.dataSource.releaseConnection(conn);
    return ret;
    }
    };
    }

Code fragememt:

  addUser(){
    var me = this;
    return function *(){

    try {
    var newId = 0;
    var ret = {};
    var body = this.request.body;
    var model = new User();
    var conn = yield me.app.dataSource.getConnection();

    model.merge( Object.assign( body, {createtime:Date.now(), updatetime:Date.now()} ) );

    var record = yield conn.create(model);
    newId = record.insertId;
    ret = me.APIReturn(0,'success',{id:newId});
    } catch (e) {
    EasyNode.DEBUG && logger.error(e);
    ret = me.APIReturn(1,'failed',{});
    } finally {
    yield me.app.dataSource.releaseConnection(conn);
    return ret;
    }
    }
    }

    updateUser(id){
    var me = this;
    return function *(){

    try {
    var ret = {};
    var body = this.request.body;
    var model = new User();
    var conn = yield me.app.dataSource.getConnection();

    model.merge( Object.assign( body, {id:id, updatetime:Date.now()} ) );

    var record = yield conn.update(model);
    ret = me.APIReturn(0,'success',{id: id});
    } catch (e) {
    EasyNode.DEBUG && logger.error(e);
    ret = me.APIReturn(1,'failed',{});
    } finally {
    yield me.app.dataSource.releaseConnection(conn);
    return ret;
    }
    }
    }

    getUser(id){
    var me = this;
    return function *(){

    try {
    var ret = {};
    var record = null;
    var sql = '';
    var arr = [];
    var id2 = id || 0 ;
    var conn = yield me.app.dataSource.getConnection();

    sql = 'SELECT id,account,accountid,email, phonenumber,salt,passwordsha,createtime, updatetime FROM monitor_user WHERE id = #id#';
    arr = yield conn.execQuery( sql, {id:id2} );
    if (arr.length <= 0) {
    return ret;
    }
    record = arr[0];
    ret = me.APIReturn(0,'success',record);
    } catch (e) {
    EasyNode.DEBUG && logger.debug(` ${e} ${e.stack}`);
    ret = me.APIReturn(1,'fail',{});
    } finally {
    yield me.app.dataSource.releaseConnection(conn);
    return ret;
    }
    }
    }

    getUserList(index,pagesize){
    var me = this;
    return function *(){
    try {
    var ret = {};
    var model = new User();
    var conn = yield me.app.dataSource.getConnection();

    var data = yield conn.list(model,{id: {exp:'!=',value:0} }, {page: index, rpp: pagesize}, ['updatetime ASC']);

    ret = me.APIReturn(0,'success',data);
    } catch (e) {
    EasyNode.DEBUG && logger.debug(` ${e} ${e.stack}`);
    ret = me.APIReturn(1,'fail',{});
    } finally {
    yield me.app.dataSource.releaseConnection(conn);
    return ret;
    }
    };
    }

    delUser(id){
    var me = this;
    return function *(){

    try {
    var ret = {};
    var model = new User();
    var conn = yield me.app.dataSource.getConnection();

    var data = yield conn.del(model,[id]);
    //{ affectedRows: 1, insertId: 0 }
    ret = me.APIReturn(0,'success',{id: id});
    } catch (e) {
    EasyNode.DEBUG && logger.debug(` ${e} ${e.stack}`);
    ret = me.APIReturn(1,'fail',{});
    } finally {
    yield me.app.dataSource.releaseConnection(conn);
    return ret;
    }
    };
    }

Uniform return format

{
    resCode: 0,
    resReason: '',
    data: {
    }
    }
  • resCode: as a return code(subsection)
  • resReason: the message according to resCode
  • data: object that package business data

RDS Config

"mysql":{
    "host": "",
    "port": 3306,
    "user": "",
    "password": "",
    "database": "",
    "acquireTimeout": "10000",
    "waitForConnections" : true,
    "connectionLimit" :  10,
    "queueLimit" : 10000
    }

Run the Demo

cd bin;
    CONFIG_URL='../config/config.json' sh dev_start.sh

Test

in the file test/src/UserTest.js.

/**
    * Created by hujiabao on 6/17/16.
    */

    'use strict';

    require("babel-polyfill");
    import co from 'co';
    import request from 'superagent';
    import chai from 'chai';
    const assert = chai.assert;

    var urlBase = 'http://127.0.0.1:8899';

    var newUserId = 0;

    describe('UserTest', function () {

    before(function (done) {
    console.log("UserTest before");
    try {
    done();
    } catch (e) {
    done(e);
    }
    });

    it('create a new user',function (done){
    request.post(urlBase+'/user')
    .set('Content-Type','application/json;charset=utf-8')
    .send({"account":"hujb2000@163.com","accountid":"allen.hu","email":"hujb2000@163.com","phonenumber":"18657105763","salt":"12345","passwordsha":"1234561213123"})
    .accept('json')
    .end(function(err, res){
    // Do something
    assert( res.body.resCode === 0, 'expected to add new user success' );
    newUserId = res.body.data.id;
    done();
    });
    });

    it('open the page to create a new user',function (done){
    request.get(urlBase+'/add/user')
    .set('Content-Type','application/json;charset=utf-8')
    .accept('text/html')
    .end(function(err, res){
    // Do something
    console.log(res.text);
    done();
    });
    });

    it('update user',function (done){
    request.put(urlBase+'/user/1')
    .set('Content-Type','application/json;charset=utf-8')
    .send({"id":newUserId,"account":"hujb2000@163.com","accountid":"allen.hu","email":"hujb2000@163.com","phonenumber":"*****","salt":"12345","passwordsha":"1234561213123"})
    .accept('text/html')
    .end(function(err, res){
    // Do something
    assert( res.body.resCode === 0, 'expected to update user info success');
    done();
    });
    });

    it('get a user by id',function (done){
    request.get(`${urlBase}/user/${newUserId}`)
    .set('Content-Type','application/json;charset=utf-8')
    .accept('text/html')
    .end(function(err, res){
    // Do something
    assert( newUserId === res.body.data.id, 'expected req userid equal res userid');
    done();
    });
    });

    it('get user list',function (done){
    request.get(`${urlBase}/user/0/20`)
    .set('Content-Type','application/json;charset=utf-8')
    .accept('text/html')
    .end(function(err, res){
    // Do something
    assert( res.body.resCode === 0, 'expected res.body.resCode === 0');
    assert( res.body.data.data.length > 0, 'expected not empty');
    assert( res.body.data.data.length <= 20, 'expected not exceeded the limit per page');
    done();
    });
    });

    it('delete a user',function (done){
    request.delete(`${urlBase}/user/${newUserId}`)
    .set('Content-Type','application/json;charset=utf-8')
    .accept('text/html')
    .end(function(err, res){
    // Do something
    assert( res.body.resCode === 0, 'expected delete user success');
    assert( res.body.data.id == newUserId, 'expected delete the specified user ');
    assert( parseInt(res.body.data.id) === newUserId, 'expected delete the specified user ');
    done();
    });
    });


    after(function (done) {
    console.log("UserTest after");
    done();
    });

    });

cd ..
    npm test

Transaction operation

Trasaction Operation Template:

    buySomething(){
    var me = this;
    return function *(){
    try{
    var ret = {};

    var conn = yield me.app.dataSource.getConnection();
    yield* conn.beginTransaction()();

    ....
    yield* conn.commit()();
    }catch(e){
    EasyNode.DEBUG && logger.debug(` ${e},${e.stack}`);
    yield* conn.rollback()();
    }finally{
    yield me.app.ds.releaseConnection(conn);
    return ret;
    }
    }
    }

Multiple table correlation operation

It is saying that about Multiple table correlation operation is not as convenient as sequelize, so we ask you to be very proficient in SQL language, and all the SQL related statements to put in the same file.

Mainly focus on the use of the following two interfaces:

execQuery(sql, args = {})
    execUpdate(sql, args = {})

In the future.

ToDO: parameter validation and response validation based on JSON-Schema.

Checkout code

check out the code.

git clone https://github.com/easynode/easynode-template.git

    git branch -r   //show remote branchs

    git checkout origin/rds