Мой друг хочет создать простую систему для сбора идей и голосов. Даже если вы можете найти много онлайн-сервисов для этого, я думаю, что это хорошая возможность показать, насколько легко разрабатывать новые приложения с использованием Couchbase и Node.js.
Так как начать?
Некоторые из нас начнут с пользовательского интерфейса, другие — с данных, в этом примере я начинаю с модели. Основные шаги:
- Смоделируйте ваши документы
- Создать представления
- Создать Сервисы
- Создать пользовательский интерфейс
- Улучшите ваше приложение с помощью итерации
Источники этого примера приложения доступны в Gihub: https://github.com/tgrall/couchbase-node-ideas
Используйте следующую команду для локального клонирования проекта:
|
1
|
git clone https://github.com/tgrall/couchbase-node-ideas.git |
Примечание. Моя цель — не предоставить полное приложение, а описать основные этапы разработки приложения.
Смоделируйте ваши документы
Для этого приложения вам нужно 3 типа документов:
- Идеи: описывает идею с автором, названием и описанием
- Голосование: автор и комментарий — обратите внимание, что выбор не ставить значение для голосования, в этой первой версии, если голосование существует, это означает, что пользователю нравится идея.
- Пользователь: содержит всю информацию о пользователе (не используется в этой первой версии приложения)
Вы можете утверждать, что можно поместить голоса в виде списка элементов внутри документа идеи. В этом случае я предпочитаю использовать другой документ и ссылаться на идею при голосовании, поскольку мы не знаем, сколько голосов / комментариев будет. Использование разных документов также интересно в этом случае по следующим причинам:
- Нет «одновременного» доступа, когда пользователь хочет проголосовать, он не меняет сам документ идеи, поэтому нет необходимости устанавливать оптимистическую блокировку на месте.
- Размер документа будет меньше и его легче будет кэшировать в памяти.
Таким образом, документы будут выглядеть так:
|
1
2
3
4
5
6
7
|
{ "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"} |
|
1
2
3
4
5
6
|
{"type" : "user","id" : "user:434","name" : "John Doe","email" : "jdoe@myideas.com"} |
|
1
2
3
4
5
6
7
|
{"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!"} |
Что мне действительно нравится, так это то, что я могу быстро создать небольшой набор данных, чтобы проверить его правильность и помочь мне спроектировать представление. При этом я запускаю свой сервер, запускаю консоль администрирования Couchbase, создаю корзину и, наконец, вручную вставляю документ и проверяю модель и виды.
Создать представления
Теперь, когда я создал несколько документов, я могу думать о том, как я хочу получить информацию из базы данных. Для этого приложения мне нужно:
- Список идей
- Голоса по идеям
Список идей для этой первой версии очень прост, нам просто нужно указать название:
|
1
2
3
4
5
|
function (doc, meta) {if (doc.type == "idea") {emit(doc.title);}} |
Для голосов за идеи я выбираю создание сопоставленного представления, это даст мне несколько интересных вариантов, когда я представлю их в слое API / View. Я также за это представление использую функцию sum (), чтобы уменьшить количество голосов.
|
01
02
03
04
05
06
07
08
09
10
|
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;}} |
У меня есть свои документы, у меня есть некоторые представления, которые позволяют мне получить список идей, количество голосов по идеям и подсчитать количество голосов … Итак, я готов предоставить всю эту информацию приложению, используя простой уровень API.
Создать Сервисы
В последнее время я много играл с Node.js, просто потому, что приятно учиться новым вещам, а также потому, что его действительно легко использовать с Couchbase. Подумайте об этом. Couchbase любит JSON, а объектный формат Node.js — JSON, это означает, что мне не нужно выполнять маршалинг / демаршалинг.
Мой уровень API довольно прост, мне просто нужно создать набор конечных точек REST, чтобы иметь дело с:
- Операция CRUD для каждого типа документа
- Перечислите разные документы
Код услуги доступен в филиале 01-simple-services :
Вы можете запустить приложение с простыми сервисами, используя следующую команду:
|
1
2
|
> git checkout -f 01-simple-services> node app.js |
и перейдите в браузер, используя http://127.0.0.1:3000
О проекте
Для этого проекта я использую только 2 узловых модуля Express и Couchbase . Файл package.json выглядит так:
|
01
02
03
04
05
06
07
08
09
10
|
{ 'name': 'couchbase-ideas-management', 'version': '0.0.1', 'private': true, 'dependencies': { 'express': '3.x', 'couchbase': '0.0.11' }} |
После запуска установки давайте закодируем новый интерфейс API, как уже было сказано, прежде чем использовать итеративный подход, поэтому пока я не занимаюсь вопросами безопасности, я просто хочу заставить работать основные действия.
Я начинаю с конечных точек, чтобы получить и установить документы. Я создаю общие конечные точки, которые принимают тип в качестве параметра URI, позволяя пользователю / приложению получить / опубликовать / api / голосование, / api / idea. Следующий код фиксирует это:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
// get document app.get('/api/:type/:id', function(req, res) { if (type == 'idea' || type == 'vote' || type == 'user') { get(req, res, type); } else { res.send(400); }});// create new documentapp.post('/api/:type', function(req, res) { if (type == 'idea' || type == 'vote' || type == 'user') { upsert(req, res, type); } else { res.send(400); }}); |
В каждом случае я начинаю проверять, является ли URI одним из поддерживаемых типов (идея, голосование, пользователь), и в этом случае я вызываю метод get () или upsert (), который будет вызывать Couchbase.
Методы get () и upsert () используют более или менее одинаковый подход. Я проверяю, существует ли документ, правильный ли тип, и делаю операцию с Couchbase. Давайте сосредоточимся на методе upsert (). Я называю это upsert (), так как эта же операция используется для создания и обновления документа.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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); }} |
В этой функции я начинаю с проверки, содержит ли документ тип и соответствует ли он ожидаемому типу (строка 3). Затем я проверяю, присутствует ли идентификатор документа, чтобы узнать, нужно мне его создать или нет. Это одна из причин, почему мне нравится хранить идентификатор / ключ в документе, да, я дублирую его, но это делает разработку действительно легкой. Поэтому, если мне нужно создать новый документ, я должен создать новый идентификатор. Я решил создать счетчик для каждого типа. Вот почему я вызываю функцию incr (строка 7), а затем использую возвращенное значение для создания документа (строка 10).
Примечание: как видите, мои документы содержат идентификатор как часть атрибутов. Этот идентификатор совпадает со значением, использованным для установки документа («ключ»). Нет необходимости в хорошей практике дублирования этой информации, и во многих случаях приложение использует только сам ключ документа. Лично мне нравится помещать идентификатор в сам документ, потому что это сильно упрощает разработку.
Если идентификатор присутствует, я просто вызываю операцию обновления, чтобы сохранить документ. (строка 15)
Операция удаления эквивалентна операции get с использованием операции удаления HTTP.
Так что теперь я могу получить, вставить и обновить документы. Я все еще должен сделать некоторую работу, чтобы иметь дело со списками. Как вы можете догадаться, здесь мне нужно назвать мнения. Я не буду вдаваться в подробности простого списка идей. Давайте сосредоточимся на представлении, которое показывает результат голосования.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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("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); }); }); |
Для этой части приложения я использую небольшой трюк, чтобы использовать сопоставленный вид. / Api / results / call возвращает список идей с их названием и общим количеством голосов. Результат выглядит следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
[ { "id": "idea:0", "title": "Add new electric company cars", "value": 0 }, { "id": "idea:1", "title": "Develop new blog on Jekyll", "value": 3 }, { "id": "idea:2", "title": "Bring your own device project", "value": 1 }, { "id": "idea:3", "title": "Test the new Rasperry Pi", "value": 1 }] |
Обратите внимание, что также можно выбрать только одну идею, вам просто нужно передать идентификатор для вызова, например. Если вы посмотрите более подробно на функцию, я не только вызываю представление, но и создаю массив, в который помещаю идентификатор идеи, метку, а затем в следующем цикле добавляю число голосов. Это возможно, потому что представление является объединенным представлением идей и их голосов. Теперь у меня есть мои REST Services, включая расширенные возможности запросов. Настало время использовать эти сервисы и создать пользовательский интерфейс.
Создать пользовательский интерфейс
Для представления я использую AngularJS, что я упаковываю в то же приложение node.js по причине простоты
Простой пользовательский интерфейс без входа в систему / безопасности
Код приложения без логина доступен в ветке 02-simple-ui-no-login :
Вы можете запустить приложение с простыми сервисами, используя следующую команду:
|
1
2
|
> git checkout -f 02-simple-ui-no-login> node app.js |
Приложение основано на AngularJS и Twitter Boostrap.
Я использую базовую функцию и упаковку для Angular:
- /public/js/app.js содержит объявление модуля и все маршруты к различным представлениям / контроллерам
- /public/js/controllers.js содержит весь контроллер. Я покажу некоторые из них, но в основном это то, где я называю услуги, которые я создал выше.
- / views / partials / содержит различные страницы / экраны, используемые приложением.
Поскольку приложение довольно простое, я не делал никаких упаковок директив или других функций. Это верно для частей AngularJS и Node.js.
Фиктивное управление пользователями
В этой первой версии пользовательского интерфейса я еще не интегрировал логин / безопасность, поэтому я подделываю логин пользователя, используя глобальную переменную области видимости $ scope.user, которую вы можете видеть в контроллере AppCtrl (). Так как я еще не реализовал логин / безопасность, я добавил внизу страницы текстовое поле, где вы можете ввести «фиктивное» имя пользователя для тестирования приложения. Это поле вставляется на страницу /views/index.html.
Список просмотров и количество голосов
Домашняя страница приложения содержит список идей и количество голосов.
Посмотрите на контроллер EntriesListCtrl и файл view / index.html. Как вы можете догадаться, это основано на сопоставленном представлении Couchbase, которое возвращает список идей и количество голосов.
Создать / Редактировать идею
Когда пользователь нажимает на ссылку New в навигации, приложение вызывает view / view / partials / idea-form.html. Эта форма вызывается с использованием URL-адреса ‘/ # / idea / new’.
Просто посмотрите на контроллер IdeaFormCtrl, чтобы увидеть, что происходит:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
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 — строка 3). Если идентификатор присутствует, я вызываю REST API, чтобы получить идею и установить ее в
переменная $ scope.idea. Затем в строке 9 вы можете увидеть функцию $ scope.save (), которая вызывает REST API для сохранения / обновления идеи в Couchbase. Я использую строки 10 и 11, чтобы установить пользователя и тип данных для идеи.
Примечание. Интересно взглянуть на эти строки, добавив два атрибута (user & type), которые я изменяю «схеме» своих данных. Я добавляю в свой документ новые поля, которые будут сохранены в Couchbase. Еще раз, вы видите здесь, что я управляю типом данных из моего приложения. Я мог бы использовать другой подход и заставить тип в слое обслуживания. Для этого примера я решил поместить это на прикладной уровень, который должен отправлять правильные типы данных.
Другие взаимодействия
Для создания голосования, связанного с пользователем / идеей, используется тот же подход, что и в контроллере VoteFormCtrl. Я не буду вдаваться во все детали всех операций, я просто приглашаю вас взглянуть на код приложения и не стесняйтесь добавлять комментарии к этому сообщению в блоге, если мне нужно уточнить другую часть приложения
Итеративная разработка: добавление ценности к голосованию!
Код услуги доступен в филиале 01-simple-services:
Вы можете запустить приложение с простыми сервисами, используя следующую команду:
|
1
2
|
> git checkout -f 03-vote-with-value > node app.js |
Добавление поля в форму
Что мне действительно нравится в работе с AngularJS, Node и Couchbase, так это тот факт, что разработчик использует JSON из базы данных в браузер.
Итак, давайте реализуем новую функцию, где вместо того, чтобы иметь только комментарий, пользователь может дать оценку своему голосу от 1 до 5. Это довольно просто, вот шаги:
- Изменить интерфейс: добавление нового поля
- Измените представление Couchabe, чтобы использовать новое поле
Это оно! AngularJS имеет дело с привязкой нового поля, поэтому мне просто нужно отредактировать /views/partials/idea-form.html, чтобы добавить это. Для этого мне нужно добавить список значений в контроллере и выставить его в поле выбора в форме.
Список значений находится в переменной $ scope.ratings:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
$scope.ratings = [{ "id": "0", "label": "0 - No Interest", }, { "id": "1", "label": "1 - Low Interest", }, { "id": "2", "label": "2 - Medium", }, { "id": "3", "label": "3 - Good", }, { "id": "4", "label": "4 - Outstanding", }, { "id": "5", "label": "5 - Must be done. Now!", }]; |
Как только это будет сделано, вы можете добавить поле выбора в ваше представление, используя следующий код:
|
1
2
3
4
5
6
7
|
<div class="control-group"> <label class="control-label" >Rate</label> <div class="controls"> <select required ng-model="vote.rating" ng-options="value.id as value.label group by value.group for value in ratings"> </select> </div></div> |
Чтобы добавить поле выбора в форму, я просто использую функции AngularJS:
- список значений, описанных в моем контроллере с использованием атрибута ng-options
- привязка к объекту поля voice.rating с использованием атрибута ng-model.
Я добавляю поле в мою форму, я привязываю это поле к моему объекту Javascript; и ничего больше! Поскольку мой REST API просто потребляет объект JSON как есть, AngularJS отправит объект голосования с новым атрибутом.
Обновите представление, чтобы использовать рейтинг
Теперь, когда моя база данных имеет дело с новым атрибутом в голосовании, мне нужно обновить свое представление, чтобы использовать его в функции суммы. (Я мог бы также подсчитать среднее значение, но здесь я хочу получить сумму всех голосов / оценок).
|
01
02
03
04
05
06
07
08
09
10
|
function (doc, meta) { switch (doc.type){ case "idea" : emit([meta.id,0, doc.title],0); break; case "vote" : emit([doc.idea_id,1], (doc.rating)?doc.rating:2 ); break; } } |
Единственная строка, которую я изменил, — это строка № 7. Логика проста: если присутствует рейтинг, я его испускаю, если нет — 2, то это средний рейтинг идеи.
Это небольшой совет, который позволяет мне иметь рабочий вид / систему без необходимости обновлять весь существующий документ, если он у меня есть. Сейчас я остановлюсь здесь, и позже добавлю новую функцию, такую как «Аутентификация пользователя» и «Управление пользователями» с использованием, например, паспорта .
Управление версиями и обновлениями
Если вы внимательно изучили код приложения, при запуске приложения представления автоматически импортируются из файла app.js. Фактически я добавил небольшую функцию, которая проверяет текущую установленную версию и обновляет представления правильной версией, когда это необходимо. Вы можете посмотреть на функцию initApplication () :
- Загрузите номер версии из Couchbase (документ с идентификатором app.version)
- Проверьте версию, если это отличается
- Обновление / создание представления (здесь я делаю это в производственном режиме, в реальном приложении будет лучше использовать режим dev — просто добавьте идентификатор документа проекта с помощью dev_)
- Как только представление будет создано, обновите / создайте документ app.version с новым идентификатором.
Вывод
В этой статье мы увидели, как вы можете быстро разработать свое приложение / прототип и использовать гибкость NoSQL для разработчиков. Шаги, чтобы сделать это:
- Создайте свою модель документа и API (REST)
- Создайте пользовательский интерфейс, который использует API
- Измените модель, просто добавив поле в пользовательский интерфейс
- Обновите представление, чтобы адаптировать ваши списки к вашей новой модели
В дополнение к этому я также быстро объясню, как вы можете из своего кода контролировать версию своего приложения и автоматически развертывать новые представления (и другие вещи).
Ссылка: Простая разработка приложений с помощью Couchbase, Angular и Node.js от нашего партнера по JCG Тугдуала Граля в блоге Tug’s Blog .
