Мой друг хочет создать простую систему для сбора идей и голосов. Даже если вы можете найти много онлайн-сервисов для этого, я думаю, что это хорошая возможность показать, насколько легко разрабатывать новые приложения с использованием 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
|
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 document app.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 .