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 정책 결여 그리고 기타등등 여러 사정이 있겠지만 역시 카카오 같은 모습은 앞으로 지향해 가야할 스타트업 회사들의 과제가 아닐까 싶다.
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를 리안달이 만들고 난후 프로그래밍의 패러다임이 바뀌고 있다. 스타트업을 위한 모바일 서비스 또는 솔루션의 기본 플랫폼으로 사용되고 있다. 특히 요즘은 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객체를 적절히 사용하여 구성하였다. 하기와 같은 과정을 거치는 것이다
npm (Node Package Manager)을 이용하여 설치해 보자 (-g 옵션은 nodejs 설치 전체에 적용된다. -g 옵션을 안 주면 설치 디렉토리내애서만 영향을 미친다)
설치가 완료되면 nodejs가 global 참조영역인 Application Data/npm/node_modules/express 라고 설치가 된다
express 명령으로 HelloExpress라고 만들어 보자. 그러면 기본적인 Web Application 골격이 생긴다. (WAS의 Context war 파일 생성되는 것으로 비유하면 되겠다. 예로 > routes/*.js 파일은 web.xml의 url-mapping 설정과 유사하다 > views/*.js 는 템플릿 엔진의 파일들이 위치한다 (ejs 또는 jade를 사용한다. ejs는 JSP 코딩과 유사하여 친숙하고, jade는 간결함이 좋다) > public/* 퍼블리싱 하는 static 파일들 > app.js Express의 main 파일이다
HelloExpress 디렉토리로 이동하여 express가 의존관계에 있는 외부 modules를 설치한다
설치가 완료된 후 node app 로 Express 프레임워크를 수행한다 (http server listening 이 포함되어 있음)
app.js 코드를 보자
> 외부모듈 express를 얻어와서 app변수에 정적 서버환경을 셋팅하고 있다.(default jade 템플릿 셋팅) > URL의 호출에 대한 맵핑을 담당하는 rounting 설정을 한다 > 맨 마지막줄에 http server 를 시작한다
http://localhost:3000/dowon 이라는 url 셋팅을 해본다. 우선 app.js 에 routing 정보를 넣는다
HelloExpress/routes/user.js 안에 exports.dowon 을 추가한다
HelloExpress/app.js 서버를 재시작 하고, 브라우져에서 호출한다.
▶ Node를 위한 Jade Template 사용하여 표현하기
routing하기 위하여 user.js에 하드 코딩된 html 코드를 jade 템플릿으로 서비스하는 방법을 알아보자. 우선 views 디렉토리에 dowon.jade 파일을 만든다 (jade는 템플릿 엔진이다)
routes/index.js 안에 정보를 넣으면 app.js 안에서 routes.dowon으로 호출을 할 수 있다
routes/index.js 파일안에 설정한 index, dowon은 app.js 에서 routes.index 와 routes.dowon 으로 호출한다
재시작하고 브라우져에서 http://localhost:3000/dowon2 로 호출하면 jade 내역이 html로 변환되어 서비스 된다
간단하게 Express Framework을 Node.js에 얻져서 사용하는 방법을 보았다. 사용하며 느낀것은 서비스를 위한 앞단의 url-mapping과 유사한 route 개념은 새롭게 개발할 필요없이 적용할 수 있어서 좋다. 그러나 domain 모델쪽의 개발과 persistence 영역의 개발에 대한 부분이 Express에 있는지 없다면 어떤 외부 module을 통하여 Express와 통합을 해야하는지 찾아 보아야 겠다.