NoSQL专题
Couchbase + Angular + Node.js教程
Couchbase + Angular + Node.js案例源码下载
开发步骤:
- 建模文本
- 创建视图Views
- 创建服务 Services
- 创建 UI
- 通过iteration提升应用
创建文本:
需要下面三种类型文档:
- 主题思路:介绍一个作者,标题和描述的想法
- 投票:笔者与一位留言 - 注意:这是一个选择,不把价值的选票,在第一个版本中,如果存在的选票,这意味着用户喜欢这个主意。
- 用户名:包含所有有关用户的信息(在这第一个版本的应用程序不使用)
将投票作为单独文档,而不是嵌入思路中作为一个元素,有下面好处:
- 没有并发访问,当用户想要投票,他不改变主意文件本身,所以没有必要使用乐观锁。
- 该文件的大小会更小,更容易在内存中缓存。
三个文档案例如下:
{
"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和数据类型。