NoSQL专题

Couchbase + Angular + Node.js教程

 Couchbase + Angular + Node.js案例源码下载 

开发步骤:

  1. 建模文本
  2. 创建视图Views
  3. 创建服务 Services
  4. 创建 UI
  5. 通过iteration提升应用

创建文本:

需要下面三种类型文档:

  1. 主题思路:介绍一个作者,标题和描述的想法
  2. 投票:笔者与一位留言 - 注意:这是一个选择,不把价值的选票,在第一个版本中,如果存在的选票,这意味着用户喜欢这个主意。
  3. 用户名:包含所有有关用户的信息(在这第一个版本的应用程序不使用)

将投票作为单独文档,而不是嵌入思路中作为一个元素,有下面好处:

  1. 没有并发访问,当用户想要投票,他不改变主意文件本身,所以没有必要使用乐观锁。
  2. 该文件的大小会更小,更容易在内存中缓存。

三个文档案例如下:

{
"type" : "idea",
"id" : "idea:4324",
"title" : "Free beer during bug hunt",
"description" : "It will be great to have free beer during our test campaign!",
"user_id" : "user:234"
}

{
"type" : "user",
"id" : "user:434",
"name" : "John Doe",
"email" : "jdoe@myideas.com"
}

{
"type" : "vote",
"id" : "vote:usr:434-idea:4324",
"idea_id" : "idea:4324",
"user_id" : "user:434",
"comment" : "This is a great idea, beer is excellent to find bugs!"
}

创建视图

需要下面功能:

1. 主题思路idea的列表

实现:

function (doc, meta) {
if (doc.type == "idea") {
emit(doc.title);
}
}


2.关于这些主题的投票

function (doc, meta) {
switch (doc.type){
case "idea" :
emit([meta.id,0, doc.title],0);
break;
case "vote" :
emit([doc.idea_id,1],1);
break;
}
}

创建服务

Couchbase和Node.js的对象格式是JSON,这意味着没有任何序列/反序列化或封送/拆封处理。

API层是相当简单,我只需要创建一套REST端点处理:

  • 在每个类型的文档的CRUD操作
  • 列出不同的文件

代码如下:

var express = require('express'),
        driver = require('couchbase');

dbConfiguration = {
        "hosts": ["localhost:8091"],
        "bucket": "ideas"
};

 

driver.connect(dbConfiguration, function(err, cb) {
        if (err) {
                throw (err)
        }

 

        var app = module.exports = express();
        // Configuration
        app.configure(function() {
                app.use(express.bodyParser());
        });

        function get(req, res, docType) {
                cb.get(req.params.id, function(err, doc, meta) {
                        if (doc != null && doc.type) {
                                if (doc.type == docType) {
                                        res.send(doc);
                                } else {
                                        res.send(404);
                                }
                        } else {
                                res.send(404);
                        }
                });
        };

 

        function upsert(req, res, docType) {
                // check if the body contains a know type, if not error
                if (req.body != null && req.body.type == docType) {
                        var id = req.body.id;
                        if (id == null) {
                                // increment the sequence and save the doc
                                cb.incr("counter:"+req.body.type, function(err, value, meta) {
                                        id = req.body.type + ":" + value;
                                        req.body.id = id;
                                        cb.set(id, req.body, function(err, meta) {
                                                res.send(200);
                                        });
                                });
                        } else {
                                cb.set(id, req.body, function(err, meta) {
                                        res.send(200);
                                });
                        }
                } else {
                        res.send(403);
                }
        }
       
       
        app.get('/api/results/:id?', function(req, res) {
                var queryParams = {
                        stale: false,
                        group_level : 3
                };
                if (req.params.id != null) {
                        queryParams.startkey = [req.params.id,0];
                        queryParams.endkey = [req.params.id,2];
                }

                cb.view("dev_ideas", "votes_by_idea", queryParams, function(err, view) {
                        var result = new Array();
                        var idx = -1;
                        var currentKey = null;
                        for (var i = 0; i < view.length; i++) {
                                key = view[i].key[0];
                                if (currentKey == null || currentKey != key ) {
                                        idx = idx +1;
                                        currentKey = key;
                                        result[idx] = { id : key, title : view[i].key[2], value : 0 };
                                } else {
                                        result[idx].value = view[i].value;
                                }
                        }
                        res.send(result);
                });               
        });
       
       
        // get document
        app.get('/api/:type/:id', function(req, res) {
                if (req.params.type == 'idea' || req.params.type == 'vote' || req.params.type == 'user') {
                        get(req, res, req.params.type);
                } else {
                        res.send(400);
                }
        });

 

        // create new document
        app.post('/api/:type', function(req, res) {
                if (req.params.type == 'idea' || req.params.type == 'vote' || req.params.type == 'user') {
                        upsert(req, res, req.params.type);
                } else {
                        res.send(400);
                }
        });

       
        app.get('/api/idea', function(req, res) {
                cb.view("dev_ideas", "by_title", {
                        stale: false
                }, function(err, view) {
                        var keys = new Array();
                        for (var i = 0; i < view.length; i++) {
                                keys.push(view[i].id);
                        }
                        cb.get(keys, null, function(errs, docs, metas) {
                                res.send(docs);
                        });
                });               
        });
       
       
        appServer = app.listen(3000, function() {
                console.log("Express server listening on port %d in %s mode", appServer.address().port, app.settings.env);
        });

});

运行:node app.js

浏览http://127.0.0.1:3000

其中upsert() 是创建修改功能:

function upsert(req, res, docType) {
   // check if the body contains a know type, if not error
   if (req.body != null && req.body.type == docType) {
      var id = req.body.id;
      if (id == null) {
         // increment the sequence and save the doc
         cb.incr("counter:"+req.body.type, function(err, value, meta) {
            id = req.body.type + ":" + value;
            req.body.id = id;
            cb.add(id, req.body, function(err, meta) {
               res.send(200);
            });
         });
      } else {
         cb.replace(id, req.body, function(err, meta) {
            res.send(200);
         });
      }
   } else {
      res.send(403);
   }
}

在这个函数中,检查文档是否包含一类型,如果是(第三行),然后检查文档的ID是否存在,是否需要创建一个,这就是为什么在文档中保留有id/key ,是的,重复两个了,但是能够使开发容易,如果我必须创建新文档我会产生新的id,为每个类型创建一个计数器,这就是为什么在第7行调用函数,然后用它在第10行返回创建文档。如果ID存在,在第15行更新。

创建界面

界面项目下载:https://github.com/tgrall/couchbase-node-ideas/tree/02-simple-ui-no-login

> git checkout -f 02-simple-ui-no-login
> node app.js

这是基于 AngularJS 和 Twitter Boostrap

  • /public/js/app.js 包含模板和所有路由到不同的视图/控制器 views/controllers
  • /public/js/controllers.js 包含所有控制器,将调用上面创建的服务
  • /views/partials/ 包含不同的页面屏幕

Create/Edit an idea

IdeaFormCtrl控制器实现创建修改

function IdeaFormCtrl($rootScope, $scope, $routeParams, $http, $location) {
  $scope.idea = null;
   if ($routeParams.id ) {
      $http({method: 'GET', url: '/api/idea/'+ $routeParams.id }).success(function(data, status, headers, config) {        
            $scope.idea = data;
         });
   }

   $scope.save = function() {     
      $scope.idea.type = "idea"; // set the type
      $scope.idea.user_id = $scope.user;
      $http.post('/api/idea',$scope.idea).success(function(data) {
         $location.path('/');
      });
   }
   $scope.cancel = function() {
      $location.path('/');
   }

}
IdeaFormCtrl.$inject = ['$rootScope', '$scope', '$routeParams','$http', '$location'];

控制器在第三行调用URL的标识 ($routeParams.id) .如果ID存在,调用这个REST API得到Idea然后设置到
$scope.idea 变量. 第9行, 看到 $scope.save() 函数调用REST API来保存修改idea到Couchbase. 第10 11行是设置idea中的user和数据类型。