Node.js 위에 기본적으로 Express.js를 많이 사용하지만 좀 더 추상화 되고 MVC 다운 프레임워크를 사용해 보고자. Express.js 및 Socket.io 와 다양한 데이터베이스를 추상화해서 사용할 수 있는 기능을 갖춘 Sails.js Framework을 사용해 보도록 한다.
1. 사용 배경
- Enterprise 급 MVC Framework : Express.js + Socket.io 기본 설정되어 사용
- Waterline 이라는 adapter를 통하여 다양한 Database의 호환성 보장
+ 흐름 : Model 객체 <-> Waterline <-> Databases
+ 지원 데이터베이스 : MySql, PostgreSql, MongoDB, etc
+ 데이터베이스 연결을 하지 않으면 파일 또는 디스크에 백만개 밑으로 key:value 저장하는 node-dirty의 어뎁터를 사용한다
- 손쉬운 설치 및 RESTful Web Services 개발
- socket.io 및 redis 통합
- SPA (Single Page Application) 개발을 위한 client framework (backbone, ember, angular)의 adapter 제공
// MySQL is the world's most popular relational database.
// Learn more: http://en.wikipedia.org/wiki/MySQL
mysql: {
module: 'sails-mysql',
host: 'YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS',
user: 'YOUR_MYSQL_USER',
password: 'YOUR_MYSQL_PASSWORD',
database: 'YOUR_MYSQL_DB'
}
};
+ generator-sails-angular 를 yeoman통하여 설치하였다면 SPA를 개발하는 것이므로 실제로 ejs 쓸 일이 거의 없고, Client 단에서 AngularJS 정의에 따라 Routing과 View Control이 발생한다. 따라서 Controller를 통하여 수행할 action에서 response.view() 하는 것이 아니라, response.json(<JSON Object>) 만을 응답으로 주면 될 것으로 보인다. 또한 업무적인 처리는 action에서 구현하면 된다
Smart Dashboard라는 장난감을 하나 만들면서 서버단을 Node.js로 코딩을 하고 있다. Node.js에서 가장 햇갈리는 부분 그리고 인식의 전환을 해야하는 것들을 "Art of Node"에서 잘 정리해 주고 있다. Node.js의 핵심은 무엇인지 알아보자.
1. Callback
- Node.js 는 I/O Platform 이다 : filesystm과 network I/O를 위한 플랫폼이다. Gateway와 같다고 생각해 보자~~
- Callback은 node.js에서 나온 개념이 아니라 이미 있던 개념 : C언어에서 "함수포인터"와 같다
- Async가 기본이기 때문에 결과에 대한 처리를 요청하는 Callback function을 파라미터로 넘긴다
- 다음 예는 undefined가 나온다. Async이기 때문에 addOne() 호출(invoke)하고 바로 console.log(myNumber)으로 이동해 버린다. File I/O보다 Memory I/O가 훨씬 더 빠른다.
varfs=require('fs')// require is a special function provided by nodevarmyNumber=undefined// we don't know what the number is yet since it is stored in a filefunctionaddOne(){fs.readFile('number.txt',functiondoneReading(err,fileContents){myNumber=parseInt(fileContents)myNumber++})}addOne()console.log(myNumber)// logs out undefined -- this line gets run before readFile is done
- 다음 예가 정확한 예이고 Closure개념을 결합하여 정보를 받아서 출력하고 있다. callback을 addOne 함수의 파라미터로 넘겨서 fs.readFile내부의 doneReading Callback 펑션에서 Closure객체로 callback()을 호출하는 것이다
- Callback을 하다보면 간혹 이런 가독성이 떨어지는 문구를 볼 수 있다. 이에 대한 해결은 Defer/Promise 를 사용한다. Java 진영에서는 Future라고 한다. 주로 Async 프로그래밍시 Callback을 사용할 때의 문제점을 serial하게 해결할 수 있는 do -> then -> error 처리가 가능함
a(function(){b(function(){c()})})
2. Events
- Node.js는 Even Machine (about ruby on rails or twisted of Python) 이다
- on 펑션을 통하여 Subscribe 하고, emit 펑션을 통하여 Publish 한다
- 하기 예는 Subscribe하여 처리할 Handler를 Callback 펑션 정의(Definition)을 미리한다. 그리고 connect() 펑션에 인자로 넣는다
varchatClient=require('my-chat-client')functiononConnect(){// have the UI show we are connected}functiononConnectionError(error){// show error to the user}functiononDisconnect(){// tell user that they have been disconnected}functiononMessage(message){// show the chat room message in the UI}chatClient.connect('http://mychatserver.com',onConnect,onConnectionError,onDisconnect,onMessage)
- 위 예를 다른 형태로 변경하면, 즉, connect() 메소드에서 작용하는 원칙은 다음과 같다. on(<EventName>, <Callback Function>) 를 통하여 Subscribe하는 것과 동일하다
varchatClient=require('my-chat-client').connect()chatClient.on('connect',function(){// have the UI show we are connected})chatClient.on('connectionError',function(){// show error to the user})chatClient.on('disconnect',function(){// tell user that they have been disconnected})chatClient.on('message',function(){// show the chat room message in the UI})
3. Stream
- Stream I/O 를 통하여 끊김없이 file system, network I/O를 수행할 수 있다. 성능의 병목없이 물흐르듯 데이터가 흘러간다.
- 하기 코드는 data.txt 파일읽는 것이 다 끝나면 Callback의 data로 전달이 된다. 이때 data.txt파일이 크다면 그만틈의 메모리를 사용하게 되고 병목이 생길 수 있다. Async 방식을 고려한다면 성능상의 문제를 야기할 수 있겠다. data.txt파일 읽으면서 http응답 병목발생.
.pipe() is just a function that takes a readable source stream src and hooks the output to a destination writable stream dst: - 읽을 소스 스트림 src가 있고 쓸 스트림으로 output을 연결해 주는 펑션일 뿐이다
- 모듈개념으로 npm(Node Package Manager)를 통하여 필요한 것을 설치한다 : 34,000 개가량의 모듈이 등록되어 있음
- 모듈 찾기 npm search <ModuleName> : npm search pdf
- package.json 안에 해당 모듈이 사용하는 다른 모듈 정보와 모듈의 일반 정보(이름, 버전, 저장소등)가 있다 : npm init
- node코드에서 require('some-module'); 를 찾는다. require 동작 순서
if a file called some_module.js exists in the current folder node will load that, otherwise - 현재 디렉토리에 있는지 찾는다
node looks in the current folder for a node_modules folder with a some_module folder in it - 현재 디렉토리에 node_modules 디렉토리가 있는지 찾고 있으면 그 밑으로 some_module 폴더가 있는지 찾는다
if it doesn't find it, it will go up one folder and repeat step 2 - 위 두개다 찾지 못하면 윗쪽 폴더로 가서 두번째 처럼 다시 node_module 폴더를 찾아간다
서비스 또는 솔루션을 만들기 위해 Node.js와 Express.js를 선택하였다면 그 다음 고민은 애플리케이션 개발을 위하여 필요한 스택을 선정하는 일이다. 크게는 로깅, 환경설정, 통신등 기본적인 부분들을 직접 개발하지 말고 이미 만들어진 바퀴를 공짜로 구해서 달아보자
Web2.0 에 따라 많은 서비스들이 SPA 로 개발되고 Open API화 하면서 이러한 API를 모아서 관리할 수 있는 apigee 와 같은 스타트업이 생겨나고 있다. 큰 사상의 흐름에 따라 기술 또한 성숙하고 이에 따라 하나 둘씩 서비스가 생겨나면서 새로운 생태계가 탄생하였다. 물론 미쿡 이야기지만 우리나라에서도 카카오 플랫폼이 협력업체들과 폐쇄적으로 Open API 협업을 하긴 하는 듯하다. 하지만 생태계를 만들어 간다는 차원에서 그들만의 리그가 아닐까 싶다. 다른 나라에서 카카오의 플랫폼을 활용하려고 몇달씩 대기자 명단을 받으며 기다리고 함께 만들어 가고 싶어할까? 아마도 그것은 개발자 사이트와 Open API 정책 결여 그리고 기타등등 여러 사정이 있겠지만 역시 카카오 같은 모습은 앞으로 지향해 가야할 스타트업 회사들의 과제가 아닐까 싶다.
AMD를 사용하지 않으면 엔터프라즈급의 웹앱을 만들 수가 없다. 요즘 Java 에서 Spring Framework이 엔터프라이즈 구현에 핵심이듯이 서로의 의존관계를 관리하고 주입하여 준다. 전통적인 방식은 HTML에서 <script> 태그를 사용하여 모듈들을 순차적으로 코딩한다. 그런 순차적 코딩이 아닌 모듈개념으로 어떻게 전환할 수 있는지 알아보자
Node.js 에서 가장 많이 사용하는 Express 프레임워크를 다시 둘러보면서 Connect를 살펴보았다. 데이터 스트림속에서 특정 객체를 가르키는 기술을 쿼리라고 본다. 예로 jQuery는 DOM내에서 특정 노드 객체를 쉽게 찾을 수 있고, Backbone은 el를 통하여 객체를 Fragment화해서 특정 위치에 Render해 준다
Node.js 에서 Express를 사용하면서 일관된 축약 언어 컴파일러로 JavaScript 코딩은 CoffeeScript를 사용하고, HTML은 Jade를 사용하고 CSS는 Stylus를 사용할 때 가장 기본적인 뼈대가 되는 코드를 만들어 주는 프레임워크 Cham을 알아보자.
//////////////////////////////////////////
// vowtest.js
var vows = require('vows'),
assert = require('assert');
// Create a Test Suite
vows.describe('Division by Zero').addBatch({
'when dividing a number by zero': {
topic: function () { return 42 / 0 },
'we get Infinity': function (topic) {
assert.equal (topic, Infinity);
}
},
'but when dividing zero by zero': {
topic: function () { return 0 / 0 },
'we get a value which': {
'is not a number': function (topic) {
assert.isNaN (topic);
},
'is not equal to itself': function (topic) {
assert.notEqual (topic, topic);
}
}
}
}).run(); // Run it
Node.js를 설치하고 MongoDB를 사용하여 REST API를 만들어보자. native MongoDB driver를 사용하며 REST API를 쉽게 만들기 위하여 Node.js의 웹애플리케이션 프레임워크인 Express를 이용한다. 원문의 내용을 보고 수행한 결과를 요약한다
1) Node.js 설치
- http://nodejs.org/dist/ : 사이트에서 최신 버전을 선택하여 다운로드한다
Fri Feb 1 22:30:46 [initandlisten] options: { dbpath: "/home/mongodb/aggregation" }
Fri Feb 1 22:30:46 [initandlisten] Unable to check for journal files due to: boost::filesystem::basic_directory_iterator constructor: No such file or directory: "/home/mongodb/aggregation/journal"
Fri Feb 1 22:30:46 [initandlisten] couldn't unlink socket file /tmp/mongodb-27017.sockerrno:1 Operation not permitted skipping
Fri Feb 1 22:30:46 [initandlisten] waiting for connections on port 27017
Fri Feb 1 22:30:46 [websvr] admin web console waiting for connections on port 28017
// coffee에서 컴파일된 javascript 소스
// Generated by CoffeeScript 1.4.0
(function() {
var x;
x = 4;
if ((0 <= x && x <= 10)) {
console.log("true");
}
}).call(this);
// -b 옵션을 주었을 경우
(function() {
var x;
x = 4;
if (0 <= x && x <= 10) {
console.log("true");
}
}).call(this);
// -b 옵션을 주지 않았을 경우
(function(){var x;x=4;if(0<=x&&x<=10){console.log("true")}}).call(this);
This task will create one or more files in the current directory, based on the environment and the answers to a few questions. Note that answering "?" to any question will show question-specific help and answering "none" to most question
will leave its value blank.
"jquery" template notes:
Project name should not contain "jquery" or "js" and should be a unique ID not already in use at plugins.jquery.com. Project title should be a human-readable title, and doesn't need to contain the word "jQuery", although it may. For
example, a plugin titled "Awesome jQuery Plugin" might have the name "awesome-plugin". For more information please see the documentation at https://github.com/jquery/plugins.jquery.com/blob/master/docs/manifest.md
Node.js를 리안달이 만들고 난후 프로그래밍의 패러다임이 바뀌고 있다. 스타트업을 위한 모바일 서비스 또는 솔루션의 기본 플랫폼으로 사용되고 있다. 특히 요즘은 Client + Server + DB 가 인터넷에서 하나로 연결되고 있다. 인터넷이 하나의 운영체제처럼 변해가고 있는 것이다.
- JavaScript : Client + Server + DB 프로그래밍을 할 수 있는 랭귀지로 자리 잡고 있다.
- JSON : MongoDB를 사용하면 Client + Server + DB 를 하나의 데이터로 통일 할 수 있다.
- Socket.io : 인터넷에 물려있는 모든 Tier의 통신을 HTTP 프로토콜을 통하여 Bi-Direction 양방향 통신을 할 수 있다.
1) 인터넷에 연결된 모든 부분이 JSON을 통하여 데이터를 주고 받는다.
2) Node.js 생태계에 나타난 Framework들 : Realtime쪽에는 전부 Socket.io 가 핵심 모듈로 사용되고 있다.
1) 우선 require를 사용하여 모듈패턴에 의한 모듈을 얻는다. 모듈로 얻음으로 namespace가 분리되므로 서로간의 충돌을 방지 할 수 있다. 물론 접근은 module.<exports Name> 으로 접근한다
var express = require('express')
, routes = require('./routes') // index.js 파일
, user = require('./routes/user') // user.js 파일
, dowon = require('./routes/dowon') // dowon.js 파일
, http = require('http')
, path = require('path');
2) ./routes/dowon 으로 require 했으므로 routes 디렉토리 밑에 dowon.js 파일을 생성한다. hi 라는 펑션을 exports 객체에 설정하여 모든 인스턴스가 공유할 수 있게 한다. 물론 모듈안에서 공유가 된다. 단, function(req, res) {...} 로 구현하자.
exports.hi = function(req, res){ // dowon 모듈에서 hi 펑션 공유
3) res.render('dowon', ...) 설정에서 dowon이라고 key를 넣었으므로 views 폴더 밑에 dowon.jade 파일을 생성한다. jade syntax에 맞추어 코딩을 하고 값은 #{key} 형식으로 코딩하면 json에서 값을 읽어 자동 셋팅하고, html 파일로 변환된다
- var dododo = function() { return 'dododo young!'; };
4) app.js 에서 get 요청에 대하여 서비스 되는 모듈의 exports 펑션을 지정한다. 즉, /dowon uri가 호출되면 hi 펑션이 수행함
app.get('/', routes.index);
app.get('/users', user.list);
app.get("/dowon", dowon.hi);
Node의 require(<js 파일>) 호출을 통하여 모듈을 로딩하고, 모듈로 로딩된 js 파일안에 모듈에서 공유할 펑션을 정의하였다. 해당 펑션에는 jade 파일명을 지정하여 서비스 한다. 결국, Node의 require, exports Global객체를 적절히 사용하여 구성하였다. 하기와 같은 과정을 거치는 것이다