내 이름은 Eugene Obrezkov이고 오늘 나는 NodeJS라는 무시무시한 플랫폼에대해 이야기 해보려고 한다. "NodeJS는 어떻게 동작하는가?"라는 아주 복잡한 질문에 대한 답을 해나갈 것이다.

나는 마치 NodeJS가 존재하지 않는다고 생각하고 글을 써내려 갈 것이다. 이렇게하면 그 내부가 어떻게 동작하는지 이해하기 쉬워진다.

여기서 사용한 코드는 실제 NodeJS에서 발췌한 코드이며 이 글을 읽고나면 NodeJS에대해 더 편안합을 느낄 수 있을 것이다.


이렇게 하려면 무엇이 필요한가?
위 이야기를 보면 막상 "이렇게 하려면 무엇이 필요한가?"라는 질문이 먼저 떠오를 것이다.

Vyacheslav Egorov는 이렇게 말했다: "JS VM은 자바스크립트 소스를 0과 1로 만드는 미지의 블랙박스라 생각하고, 더 많은 사람들이 그것을 그만 들여다본다." 이 발상은 NodeJS에도 적용시켜보았다. "NodeJS는 저수준 API를 돌아가게 해주는 미지의 블랙박스라 생각하고, 더 많은 사람들이 그것을 그만 들여다본다."

그냥 해보자!
2009년으로 돌아가면 NodeJS가 만들어지기 시작하는 시점이다.

우리는 백엔드에서 자바스크립트를 실행시키고 싶었고, 자바스크립트가 저수준 API에 접근하고 싶었다. 또한 우리의 자바스크립트를 CLI와 REPL로부터 실행시키고 싶었다. 사실 자바스크립트로 뭐든 다 하고싶었던 것이다!

이게 어떻게 가능할까? 내 머릿속에 처음 드는 생각은 브라우저였다.

브라우저
브라우저는 자바스크립트를 실행시킬 수 있다. 따라서 브라우저를 우리 앱에 합쳐 자바스크립트를 동작시키게 할까?

설마! 아래에 반드시 답해야할 질문들을 보자.

브라우저가 저수준 API를 자바스크립트로 만들까? — 아니다!
다른 곳에서 자바스크립트를 돌릴 수 있을까? — 반반이다. 좀 더 복잡한 이야기이다.
브라우저가 제공하는 모든 DOM 기능이 필요할까? — 아니다!
브라우저가 조금이라도 필요할까? — 아니다!

브라우저는 필요 없다. 자바스크립트는 브라우저 없이 실행된다.

그럼 브라우저가 자바스크립트를 실행시키는게 아니라면 무엇이 자바스크립트를 실행할까?

가상머신(Virtual Machine-VM)
가상머신(VM)이 자바스크립트를 실행한다!

VM은 고수준 프로그래밍 언어라는 추상화를 제공한다.(시스템의 저수분 ISA 추상화와 비교해보아라)

VM은 추상화이고 플랫폼에 독립적인 프로그램 실행환경을 만들기 위해 단일 컴퓨터에서 동작하도록 설계되었다.

가상머신에는 Google의 V8, 마이크로소프트의 Chakra, 모질라의 SpiderMonkey, Apple의 JavaScriptCore 등 수많은 것들이 있다. 여기서 신중하게 고르지 않으면 남은 인생동안 분명 후회하며 살 것이다.

나는 V8을 선택하려 한다. 왜일까? 그 이유는 다른 VM보다 더 빠르기 때문이다. 당신도 아마 백엔드에서 실행속도는 중요하다는 것에 동의할 것이다.

이제 V8을 살펴보면서 이것이 NodeJS 구성에 어떤 도움을 줄 수 있는지 살펴보자.

V8 VM
V8은 어떤 C++ 프로젝트와도 합칠 수 있다. 단지 간단한 라이브러리처럼 V8 소스를 거기에 인클루드(include)시키면 된다. 이렇게만 하면 이제 자바스크립트 코드를 컴파일하고 실행할 수 있다.

V8은 C++을 자바스크립트에서 사용할 수 있게 해준다. 자바스크립트로 저수준 API를 사용할 수 있게 해주는 중요한 역할을 한다.

위 두가지 포인트가 "어떻게 자바스크립트로 저수준 API에 접근할 수 있는지"에대한 대략적인 구현을 생각해볼 수 있을것이다.

다음 챕터부터는 C++ 코드로 설명을 시작할 것이기 때문에 위의 것들을 한데모아 생각해보자. 가상머신을 선택하고(우리의 경우 V8을 선택했다) -> 우리 C++ 프로젝트에 그것을 통합시킨뒤 -> V8이 C++을 자바스크립트로 사용할 수 있게 한다.

그러나 어떻게 C++코드로 작성하고 그것이 자바스크립트에서 가능할까?

V8 템플릿
V8 템플릿들을 통해 가능하다!

한 템플릿은 자바스크립트에서의 함수와 객체를 위한 청사진이다. C++ 함수와 데이터 구조체를 자바스크립트로 감싸기 위해 템플릿을 사용한다.

예를 들어보자. 구글 크롬은 C++ DOM 노드를 자바스크립트 객체로 감싸고 전역에 함수를 만들어 두기 위해 템플릿을 사용한다.

여러분도 템플릿들을 만들어 그것을 사용할 수 있다. 따라서 당신이 원하는 만큼 템플릿을 만들면 된다.

그리고 V8은 두가지 타입의 템플릿을 가지고 있다: 함수 템플릿(Function Templates)객체 템플릿(Object Templates)이다.

함수 템플릿은 한 함수의 청사진이다. 당신이 자바스크립트 함수로 만들고 싶은 컨텍스트에서 템플릿의 GetFunction 메소드를 호출하여 자바스크립트 인스턴스의 템플릿을 만들어 낼 수 있다. 자바스크립트 함수 인스턴스가 호출될 때 함수 템플릿이 호출되는데, 이 함수 템플릿과 C++ 콜백을 연관시킬 수도 있다.

객체 템플릿은 객체 초기화에서 함수 템플릿과 함께 만들어진 객체를 구성하는데 익숙하다. 객체 템플릿은 두가지 C++콜백 타입과 연관이 가능하다: 접근자 콜백(accessor callback)인터셉터 콜백(intercepter callback). 접근자 콜백은 특정 객체 프로퍼티가 스크립트에의해 접근될 때 호출된다. 인터럽트 콜백은 어떤 객체 프로퍼티라도 스크립트가 접근하면 호출된다. 즉 C++ 객체/구조체를 자바스크립트 객체로 감쌀 수 있다.

간단한 예제를 살펴보자. 이것은 C++ 메소드인 LogCallback을 전역 자바스크립트 컨텍스트에서 사용하는 것이다.

두번째 라인에서 새 ObjectTemplate를 생성한다. 그리고 3번째 라인에 FunctionTemplate를 생성하고 그것을 LogCallback이라는 C++ 메소드와 연관시킨다. 그 다음 이 FunctionTemplateObjectTemplate에 세팅한다. 새 컨텍스트에서 자바스크립트를 실행시킬때 전역에서 log 메소드를 사용하도록 이 새로운 컨텍스트에 ObjectTemplate 인스턴스를 보낸다. 결과적으로 FunctionTemplate 인스턴스와 연관된 LogCallback C++ 메소드가 트리거 될 것이다.(As a result, C++ method, associated with our FunctionTemplate instance, LogCallback, will be triggered.)

여러분도 보았듯 C++에서는 자바스크립트에서 객체를 정의하는 것과 비슷하다.

그러나 이제 C++ 메소드를 어떻게 자바스크립트에 드러낼 것인지 배울 것이다. 방금처럼 수정된 컨텍스트에서 어떻게 자바스크립트 코드를 실행시키는지 살펴볼 것이다. 사실 단순하다. 그냥 요소를 컴파일하고 실행시키자.

V8 컴파일 && 자바스크립트 실행
우리가 만든 컨텍스트에서 자바스크립트를 실행하려면 V8에 컴파일(compile)과 실행(run)의 간단한 API만 호출하면 된다.

새 컨텍스트를 만들면서 자바스크립트를 실행시키는 이 예제를 살펴보자.

2번째줄을 보면 새로운 자바스크립트 컨텍스트를 만들었다(위에서 설명한 템플릿들과 함께 수정할 수 있다). 5번째줄은 자바스크립트 코드를 컴파일하고 실행하기 위해 컨텍스트를 활성화 시킨다. 8번째줄은 자바스크립트 소스로부터 새 문자열을 생성한다. 위처럼 하드코딩 할 수도 있고 파일을 읽거나 다른 방법으로도 가능하다. 11번째줄은 우리의 자바스크립트 소스를 컴파일한다. 14번째줄은 실제 실행시켜보고 결과를 기다린다. 이게 다다.

마침내 위에서 말한 테크닉들을 조합하여 우리는 간단한 NodeJS를 만들 수 있다.

C++ -> V8템플릿 -> 자바스크립트를 실행 -> ?
V8 머신을 만든다 -> 원하는 만큼 C++ 콜백과 연관된 FunctionTemplate를 만든다 -> ObjectTemplate 인스턴스를 만들고 그것에 만들어놓은 FunctionTemplate 인스턴스를 할당한다 -> 우리의 ObjectTemplate 인스턴스를 전역 객체로 한 자바스크립트 컨텍스트를 만든다 -> 이 컨텍스트에서 자바스크립트를 실행한다 -> NodeJS. 여기까지이다!

그러나 챕터 타이틀에 물음표부분의 "자바스크립트를 실행" 이후에는 무엇이 올까? 위 구현에는 약간의 문제가 있다. 굉장히 중요한 점을 하나 지나쳤다.

fs, http, crypto등과 함께 동작하는 수많은 C++ 메소드(약 10K SLOC)를 작성했다고 생각해보자. 우리는 [C++ 콜백들]을 ObjectTemplate에 할당하고 [FunctionTemplate]를 ObjectTemplate에 불러온다. 이 ObjectTemplate 자바스크립트 인스턴스를 쓰면 자바스크립트 전역에서 모든 FunctionTemplate 인스턴스에 접근할 수 있게 된다. 그리고 모든 것이 잘 동작하는 것처럼 보일 것이나..

당장에 fs이 필요없다면? crypto의 모든 기능이 필요 없다면? 전역에 모듈을 죄다 불러오지 않고 그들이 요구하는 것만 불러준다면 어떨까? 모든 C++ 콜백이 한 파일에 긴 C++ 코드를 작성하지 않는 것은 어떨까? 그래서 위 타이틀에서 "?"의 의미는..

모듈화이다!

각 C++ 모듈이 각 fs, http 나 다른 기능에 일치하기 때문에 이 모든 C++ 메소드는 모듈로 쪼개지고 각 다른 파일에 위치한다.(이 점이 개발을 쉽게 만들어준다) 자바스크립트 컨텍스트도 같은 로직이다. 전역에서 반드시 모든 자바스크립트 모듈에 접근할 수 있는게 아니라 요구에 따라 접근이 가능하다.

위의 방법을 기반으로 우리만의 모듈로더(module loader)를 구현해야 한다. C++ 코드에서 요구하는 데로 모듈을 가져오고 자바스크립트 컨텍스트도 마찬가지로 그러기 위해 모듈로더는 C++ 모듈과 자바스크립트 모듈을 불러오는 역할을 한다.

먼저 C++ 모듈부터 시작해보자.

C++ 모듈로더
이제 좀 많은 C++코드가 있을텐데, 마음 단단히 먹고 시작해보자 :)

모든 모듈로더의 기초부터 이야기 해보자면, 각 모듈 로더는 모든 모듈(혹은 그것을 어떻게 얻어내는지에대한 정보)을 담고 있는 변수를 반드시 가지고 있어야한다. C++ 모듈에 대한 정보를 가지고 있는 C++ 구조체를 정의하고 이 이름을 node_module이라 하자.

우리는 이 구조체 안에 현재 존재하는 모듈에 대한 정보를 담았다. 결과적으로 사용가능한 모든 C++ 모듈의 딕셔너리를 가진다.

위 구조체의 각 필드를 다 설명하진 않겠지만, 그중 몇개만 이야기 하고 싶다. nm_file에서는 어디서부터 그 모듈을 불러오는지 파일 이름을 저장하고 있다. nm_register_func와 nm_context_register_func에는 모듈이 필요할 때 호출하는 함수들을 담고있다. 이 함수들은 Template 인스턴스로 만들어질 것이다. 그리고 nm_modulename은 모듈 이름을 저장한다 (파일 이름이 아니다) .

다음으로 이 구조체를 도와주는 helper 메소드들이 필요하다. 우리 node_module 구조체에 정보를 저장할 수 있는 간단한 메소드를 만든 뒤 우리의 모듈 정의에서 이 메소드를 사용할 수 있다. 이것을 node_module_register라 부르자.

위에서 보이듯 여기서 하는 일이라곤 모듈 정보를 node_module 구조체에 저장하는 일 뿐이다.

이제 메크로를 이용해 이 과정을 간단하게 만들 수 있다. 당신의 C++ 모듈 안에 메크로를 정의하자. 이 메크로는 단지 node_module_register 메소드를 감싸는 용도이다.

첫번째 메크로는 node_module_register 메소드를 감싸는 메크로이다. 다른 하나는 첫번째 메크로에 미리 정의된 인자를 박아둔 메크로이다. 결론적으로 modnameregfunc라는 두 인자를 받는 메크로가 완성되었다. 이 메크로를 호출하면 새 모듈 정보를 우리의 node_module 구조체에 저장한다. 그럼 modnameregfunc는 무엇일까? 음.. modnamefs처럼 모듈의 이름을 뜻한다. regfunc는 이전에도 우리가 이야기한 모듈 메소드이다. 이 메소드는 V8 Template 초기화와 ObjectTemplate에 이것을 할당하는 역할을 한다.

앞에서 보았듯 각각의 C++ 모듈은 모듈 이름(modname)과 초기화 함수(regfunc)를 인자로 받는 메크로로 정의할 수 있는데, 이 메크로는 모듈이 필요할 때 호출되는 것이다. 우리에게 필요한 것은 단지 node_module  구조체로부터 정보를 읽고 regfunc 메소드를 호출할 수 있는 C++ 메소드를 만드는 것이다.

node_module 구조체에서 이름으로 모듈을 검색할 수 있는 간단한 메소드를 만들어보자. 이 메소드를 get_builtin_module이라 부를 것이다.

이 메소드는 nm_modnamenode_module 구조체의 이름이 일치하면 앞서 정의된 모듈을 반환한다.

node_module 구조체의 정보를 기반으로 C++ 모듈을 불러오고 우리의 ObjectTemplate에 V8 Template 인스턴스를 할당하는 간단한 메소드를 만들 수 있다. 그러면 이 ObjectTemplate는 자바스크립트 인스턴스에 따라 자바스크립트 컨텍스트에 보내질 것이다.

위 코드에 관해 몇가지 짚고 넘어가자면, Binding은 모듈 이름을 인자로 받는다. 이 인자는 당신이 메크로를 통해 주는 모듈 이름이다. 그리고 우리는 get_builtin_module 메소드에서 나온 이 모듈에 관한 정보를 찾아온다. 만약 찾으면 exports와 같은 유용한 인자들을 보내면서 이 모듈의 초기화 함수를 호출한다. exportsObjectTemplate 인스턴스이므로 exports에서 V8 Template API를 사용할 수 있다. 이 모든 동작이 끝나고 Binding ㅁ[소드의 결과물로 나온 exports 객체를 받는다. 여러분이 기억하는데로 ObjectTemplate 인스턴스는 자바스크립트 인스턴스와 Binding이 한 것을 반환할 수 있다.

마지막으로 해야할 것은 이 메소드를 자바스크립트 컨텍스트에서 사용할 수 있게 하는 것이다. 이것은 마지막 라인이 하는 일인데, FunctionTemplate에서 Binding 메소드를 감싸고 전역변수 process에 할당하는 일을 한다.

이 단계에서 process.binding('fs')를 호출하여 네이티브 바인딩을 할 수 있다.

단순함을 위해 로직을 뺀, 내장된 모듈의 예시이다.


위 코드는 process.binding('V8')를 호출하여 자바스크립트 컨텍스트로부터 이 자바스크립트 객체를 얻어내려고 자바스크립트 객체를 내보내는 "V8"이라는 바인딩을 만든다.

고맙게도 여러분들은 아직 잘 따라오고 있다.

이제 우리는 require('fs')와같이 깔끔하게 되도록 도와주는 자바스크립트 모듈 로더를 만들 것이다.

자바스크립트 모듈로더
좋다. 마지막 개선(improvements)에 감사하다. 우리는 process.binding()을 호출하고 자바스크립트 컨텍스트로부터 C++ 바인딩에 접근할 수 있게 되었다. 그러나 아직 자바스크립트 모듈에 관한 이슈는 해결된 바가 없다. 어떻게 자바스크립트 모듈을 작성하고 필요할 때 그것을 require 할 수 있을까?

먼저 모듈에는 두가지 타입이 있음을 이해해야한다. 그 중 하나는 C++ 콜백과 함께 작성된 자바스크립트 모듈이고 NodeJS에 내장된 fs, http 등과 같은 모듈들이다. 이 모듈을 NativeModule이라 부르자. 다른 모듈은 여러분이 작업하는 디렉토리 안에 있는 모듈이다. 이것을 그냥 Module이라 하자.

우리는 두가지 타입 모두 require 할 수 있어야한다. 이 말은 NodeJS로부터 NativeModule을 부르는 법과 작업 디렉토리에서 Module을 부르는 법을 알아야한다는 뜻이다.

NativeModule부터 먼저 이야기해보자.

자바스크립트에 있는 모든 NativeModule은 C++ 프로젝트로 다른 폴더에 위치한다. 이 말은 모든 자바스크립트 소스가 컴파일 시간을 가진다는 뜻이다. 이렇게하여 우리가 나중에 사용할 자바스크립트 소스를 C++ 헤더파일에 감싸 넣을 수 있게 한다.

이것을 위한 js2c.py(tools/js2c.py에 위치한)이라는 파이썬 툴이 있다. 이것은 자바스크립트 코드로 감싸진 node_natives.h 헤더파일을 생성한다. node_natives.h는 C++에서 자바스크립트 소스를 얻어내는 어떠한 C++ 코드에서도 포함될 수 있다.

이제 C++ 컨텍스트에서 자바스크립트 소스를 사용할 수 있다. — 한번 시도해보자. node_natives.h에서 자바스크립트 소스를 가져오고 그것을 ObjectTemplate 인스턴스에 할당하는 DefineJavaScript 메소드를 구현했다.

위 코드에서, 우리는 각 네이티브 자바스크립트 모듈들을 통해 돌면서 ObjectTemplate 인스턴스에 키로서 모듈이름을, 값으로서 모듈 자체를 넣었다. 마지막으로 우리가 할 일은 타겟으로 ObjectTemplate 인스턴스와 함께 DefineJavaScript를 호출하는 것이다.

이때 Binding 메소드가 유용하다. C++의 Binding 구현(C++ 모듈로더 부분)을 보면 하드코딩된 constantsnatives라는 두 바인딩이 있을 것이다. 그러므로 바인딩 이름이 natives이면 environmentexports 객체가 DefineJavaScript 메소드와 함께 호출될 것이다. 결과적으로 자바스크립트 NativeModuleprocess.binding('natives')가 호출될 때 반환될 것이다.

그래 좋다. 그러나 node.gyp 파일에서 GYP 작업을 정의하고 이것으로부터 js2c.py 툴을 호출하여 또다른 개선을 할 수 있다. 이것은 NodeJS가 컴파일 될 때 자바스크립트  소스가 node_natives.h 헤더파일로 감싸지기 때문에 이 개선을 만들 것이다.

이제부터 우리는 process.binding('natives')로 사용할 수 있는 NativeModule의 자바스크립트 소스들을 가지고 있을 수 있다. 이제 NativeModule을 위한 간단한 자바스크립트 껍데기를 만들어보자.

이제 모듈을 불러오기 위해 NativeModule.require() 안에 모듈 이름을 넣어 호출한다. 먼저 모듈이 이미 캐시에 있는지 확인한다. 있으면 캐시에서 꺼네오고 그렇지 않으면 모듈을 컴파일하고 캐시에 넣은 뒤 exports 객체로 반환한다.

이제 좀 더 아까이서 cachecompile 메소드를 살펴보자.

cache가 하는 일은 NativeModule에 위치한 스태틱 객체 _cacheNativeModule 인스턴스를 세팅한다.

compile 메소드가 더 흥미롭다. 먼저 (process.bind('natives')로 세팅해둔 이 스태틱 프로퍼티)_source에서 필요한 모듈의 소스를 꺼낸다. 그리고 wrap 메소드로 그 소스를 감싼다. 위 소스에서 볼 수 있듯 함수의 결과물은 exports, require, module, __filename, __dirname 인자를 받는다. 그 후 필요한 인자와 함께 이것을 호출한다. 결과적으로 NativeModule.exports를 가리키는 exports, NativeModule.require를 가리키는 requireNativeModule 그 자체를 가리키는 module, 현재 파일 이름의 문자열인 __filename을 가지는 영역에서 우리 자바스크립트 모듈이 감싸진다. 이제 여러분은 module이나 require이 자바스크립트 코드 어디에서부터 오는지 안다. 그것들은 단지 NativeModule 인스턴스를 가리키고 있다. 😃

다른 하나는 Module 로더 구현이다.

Module 로더의 구현은 기본적으로 NativeModule과 같다. 그러나 다른 점은 소스가 node_natives.h 헤더파일로부터 오는 것이 아니라 우리가 fs 네이티브 모듈이라 부르는 파일로부터 온다. 따라서 wrap, cache, compile 하는 일은 같지만 파일로부터 읽는 소스만 다르다.

좋다, 이제 우리는 어떻게 네이티브 모듈이나 여러분의 작업 디렉토리로부터 모듈을 요청하는지 안다.

마지막으로, 우리는 위의 일들을 사용하여 만든 NodeJS 환경을 실행(run)하고 준비(prepare)할 때 마다 실행되는 간단한 자바스크립트 모듈을 작성할 수 있다.

NodeJS 런타임 라이브러리?
런타임 라이브러리가 무엇일까? 이 라이브러리는 전역변수인 process, console, Buffer등을 세팅하여 코딩할 수 있는 환경을 갖추고, NodeJS CLI에 인자로 보내는 메인 스크립트를 실행한다. 이 라이브러리는 NodeJS 런타임시 모든 자바스크립트 코드가 실행되기 전에 실행되는 간단한 자바스크립트 파일로 아키브(achieve)될 수 있다.

첫번째 단계로는 전역에 모든 네이티브 모듈을 프록싱(proxying)하고 다른 전역 변수를 설정하는 것이다. 이 일은 단지 global.Buffer = NativeModule.require('buffer')global.process = process와 같은 일들이다.

두번째 단계는 NodeJS CLI에 인자로 보내는 메인 스크립트를 실행한다. 로직은 간단하다. process.argv[1]를 파싱하여 그 값을 객체 초기화때 값으로 Module 인스턴스를 생성한다. 따라서 Module은 파일로부터 소스를 읽을 수 있고 -> NativeModule이 했던 것처럼 미리 컴파일된 자바스크립트 소스로 캐시와 컴파일 해 둘 수 있다.

여기에 내가 더 추가할 수 있는게 없다. 굉장히 매우 간단하며, 만약 그대로 더 세부적인 것을 원한다면 노드 저장소의 src/node.js 파일을 한번 확인해보아라. 이 파일은 NodeJS 런타임시 실행되고 이 글에서 말한 모든 테크닉을 사용한다.

이것이 바로 NodeJS가 어떻게 당신의 자바스크립트 코드에서 저수분 API에 접근하며 실행하는지에관한 이야기였다. 멋지지 않는가?

하지만 아직 비동기 처리에대한 문제가 남았다. 여기서는 fs.readFile()같은 연산은 완전히 순차적으로 실행된다.

비동기 처리를 위해 어떤 것이 필요할까? 이벤트 루프이다.

이벤트 루프
이벤트 루프는 프로그램 내에서 이벤트나 메시지를 기다리고 디스패치(dispatch)하는 메시지 디스패처이다. 이것은 내부/외부 이벤트 프로바이더(보통 이벤트가 도착하기 전까지 요청을 블락시키는 것)에 요청을 만듦으로서 작업한다. 그리고 적절한 이벤트 핸들러를 호출한다(이벤트를 디스패처 한다). 선택되거나 채택될 수 있는 파일 인터페이스를 따르는 이벤트 프로바이더라면, 이벤트 루프는 리엑터(reactor)와 협력하여 사용할 수 있다. 이벤트 루프는 항상 메시지 제공자와 함께 비동기로 처리한다.

V8 환경을 생성하면 V8은 인자로 이벤트 루프를 받을 수 있다. 그러나 V8에 이벤트 루프를 세팅하기 전에 먼저 그것을 구현해놓아야한다.

이제 libuv라 불리는 그 구현을 이미 가지고 있다치자. libuv는 파일 읽기와 같은 모든 비동기 처리의 책임을 가지고 있다. libuv가 없는 NodeJS는 단지 순차적으로 자바스크립트/C++를 실행할 수 있는 도구일 뿐이다.

따라서 기본적으로 NodeJS에 libuv 소스를 인클루드 할 수 있고, 거기에 있는 libuv 기본 이벤트 루프와 함께 V8 환경을 만들 수 있다. 여기에 그 구현이 있다.

CreateEnviroment 메소드는 루프 인자로 libuv 이벤트 루프를 받는다. 우리는 V8 네임스페이스에서 Enviroment::New를 호출할 수 있고 libuv 이벤트 루프를 보낸 다음 V8 환경으로 구성할 수 있다. 여기까지가 NodeJS를 어떻게 비동기 처리 할 수 있는지에 관한 이야기였다.

libuv에대해 더 이야기하고 어떻게 동작하는지 말해주고 싶지만, 이 이야기는 다음으로 미루도록 하자. :)

Thanks!
이 글을 끝까지 읽어준 모든이에게 감사하다. 여러분이 여기까지 읽으면서 뭔가 즐겁게 배웠기를 바란다. 만약 뭔가 문제를 발견한다면 자유롭게 이 글에 커멘트해주면 된다. 그러면 가능한 빨리 내가 답변해주도록 하겠다.(옮긴이: 자유롭게 이 블로그에 댓글 달아주시면 됩니다!)

Eugene Obrezkov aka ghaiklor, Technical Leader at Onix-Systems, Kirovohrad, Ukraine.


신고

WRITTEN BY
canapio
개인 iOS 개발, canapio

받은 트랙백이 없고 , 댓글이 없습니다.
secret

옮긴이 : 종종 브라우저로 원문 링크에 들어가지지 않는 현상이 생깁니다. 이럴때는 Medium 앱으로 Architecture of Node.js Internal Codebase를 검색하여 들어가시면 됩니다.


첫째로 JavaScript라는 단어에 대해...

StackOverflow 공동 창시자인 Jeff Atwood가 Coding Horror 프로그래밍 블로그에 글을 올렸다.
"JavaScript로 작성될 수 있는 모든 앱들은 결국 JavaScript로 작성될 것이다."

JavaScript는 지난 몇년간 가장 인기있는 프로그래밍 언어 중 하나로 크게 확장되었다. 실제로 2016년 SO 개발자 조사에 따르면, StackOverflow에서 가장 인기있고 가장 기술력있는 언어로 1등을 달성하고, 다른 결과들도 좋은 기록을 세웠다.

Node.js는 바이너리 데이터 조작, 파일 시스템 I/O 오퍼레이션, 데이터베이스 접근, 컴퓨터 네트워킹 등과 같은 중요한 서버사이드 기능을 담은 기초(foundation)로서 JavaScript 환경의 서버사이드이다. 이것은 Django(Python), Laravel(PHP), RoR(Ruby) 등과 같은 현존하는 시도되고 테스트된 프레임워크 사이에서 특출난 특징을 가진다. Node.js는 PayPal, Tinder, Medium, LinkedIn, Netflix와 같은 기술 리더 회사들이 사용한다는 것이다. 몇몇은 Node.js가 1.0버전이 되기도 전에 이미 사용하고 있었다.

나는 최근에 StackOverflow에서 Node.js 내부 코드베이스의 아키텍처에 관한 질문에 답변을 달았었다. 그것이 이 글을 쓰도록 영감을 불어넣어 주었다.


사실 공식적인 문서에서는 Node.js가 무엇인지 설명하기에 크게 도움이 되지 않는다.


"JavaScript 런타임은 크롬의 V8 JavaScript 엔진으로 만들어졌다. Node.js는 event-driven, non-blocking I/O 모델이고..."

이 말을 이해하고 실제 숨어있는 힘을 이해하기 위해 Node.js 요소들을 쪼개어보고, 몇몇 중요 용어들을 상세히  설명 할 것이다. 다음 어떻게 다른 조각들이 서로 소통하여 Node.js를 강력하게 만들 수 있는지 설명할 것이다.

Node.js 아키텍처 (High-Level to Low-Level)

요소들/종속물(COMPONENTS/DEPENDENCIES)
V8 : 구글에의해 오픈소스화된 높은 퍼포먼스를 자랑하는 JavaScript 엔진이며 C++로 구현되어있다. 크롬에서 사용하는 엔진과 동일한 엔진이다. V8은 당신이 JavaScript로 작성한 코드를 받아서, 기계코드로 컴파일한 뒤(이렇기에 빠르다), 실행시킨다. 어떻게 V8이 그냥 빠를 수 있을까? StackOverflow의 답변을 확인해보아라.

libuv : 비동기적 기능을 제공하는 C 라이브러리이다. 이것은 다른 중요한 기능들 사이에서 이벤트 루프, 스레드 풀, 파일 시스템 I/O, DNS 기능, 네트워크 I/O를 관리한다.

다른 C/C++ 요소들/종속물 : c-ares, crypto(OpenSSL), http-parser, zlib. 이 요소들은 서버에서 네트워킹, 압축, 암호화와같은 중요한 기능을 수행하기 위해 low-level로 소통하는 방식을 제공한다. 

앱/모듈 : 이것은 JavaScript 코드가 존재하는 모든 곳이다. 당신의 앱 코드, Node.js의 코어 모듈, npm으로 설치한 모든 모듈, 당신이 작성한 모든 모듈까지 당신은 대부분의 시간을 여기서 보낼 것이다.

바인딩 : 당신은 아마 이 시간을 통해 Node.js가 JavaScript와 C/C++로 작성되었다는 것을 알게 되었을 것이다. 여기엔 수많은 C/C++ 라이브러리가 있는데 그 이유는 간단하다: 빠르기 때문이다. 그러나 어떻게 당신이 JavaScript로 짠 코드가 C/C++로 짠 코드와 자연스럽게 소통할까? 그것은 세개의 서로 다른 언어이지 않는가? 그렇다. 다른 언어로 짜여진 코드는 보통 서로 서통할 수 없다. 바인딩 없이는 안된다. 이름에서 이야기하듯 바인딩은 한 언어를 다른 언어에 '묶어(bind)' 코드를 접합시킨다. 그러면 서로 의사소통을 할 수 있게 된다. 이 경우(Node.js) 바인딩은 단순히 C/C++로 작성된(c-ares, zlib, OpenSSL, http-parser 등) Node.js 내부 코어 라이브러리들을 JavaScript에 연결한다. 바인딩을 작성하는데 첫번째 이유는 재사용이다: 만약 필요한 기능들이 이미 구현되있다면, 그것을 가져다 사용하면 된다. 단지 언어가 다르다는 이유로 그것을 다시 짤 필요는 없다. 왜 그냥 연결시켜 사용하지 않겠는가? 두번째 이유는 퍼포먼스이다: C/C++ 같은 시스템 프로그래밍 언어들은 일반적으로 고수준언어(Python, JavaScript, Ruby등) 보다 빠르다. 그러므로 CPU-집약 오퍼레이션 코드는 C/C++로 작성하는 것이 현명할 것이다.

C/C++ Addon : 바인딩은 단지 zlib, OpenSSL, c-ares, http-parser와 같은 Node.js 내부 라이브러리를 접합하는 기능만 한다. 만일 당신이 써드파티의 C/C++ 라이브러리(외부 C/C++ 라이브러리)를 넣고 싶으면, 스스로 라이브러리를 접합시켜야한다. 당신의 코드를 접합해주는 코드가 addon이라 불리는 것이다. 바인딩과 addon은 당신의 JavaScript 코드와 Node.js의 C/C++ 코드를 연결하는 다리라고 생각하면 편할 것이다.

용어들(TERMINOLOGIES)
I/O : Input/Output의 약자이다. 이것은 기본적으로 시스템 I/O의 하위 시스템에의해 주로 다뤄지는 모든 컴퓨터 오퍼레이션을 나타낸다. I/O 바운드( https://en.wikipedia.org/wiki/I/O_bound)오퍼레이션은 디스크/드라이브와의 상호작용을 포함한다. 예를들어 데이터베이스 접근과 파일 시스템 오퍼레이션을 포함한다. 연관된 다른 개념에는 CPU 바운드, 메모리 바운드 등이 있다. 오퍼레이션이 I/O 바운드에 속하는지 CPU 바운드에 속하는지 아니면 다른데 속하는지 구별하는 좋은 방법은, 특정 오퍼레이션이 더 많은 퍼포먼스를 내고 리소스를 많이 잡아먹는지 체크하면 된다. 예를들어 한 오퍼레이션이 눈에 띄게 빨라지고 CPU 파워가 증가했다면 그것은 CPU 바운드 이다. 

Non-blocking/비동기적 : 보통 리퀘스트가 들어오면 앱은 그 리퀘스트를 다루고, 그 리퀘스트의 작업이 끝날때까지 다른 모든 오퍼레이션을 멈춘다. 다음은 실제 일어날 수 있는 문제이다: 만일 한꺼번에 수많은 리퀘스트가 들어오면, 각 리퀘스트는 이전 리퀘스트의 작업이 끝날때까지 기다려야한다. 다른 말로는, 이전 오퍼레이션이 다음 것을 블럭(block)시킬 것이다. 최악의 상황은,  이전 리퀘스트가 긴 응답시간(eg. 먼저 1000개의 계산 후, DB로부터 3GB 데이터를 읽는 경우) 이라면, 다른 리퀘스트들은 긴 시간동안 멈춤/블럭이 되 있을 것이다. 이 문제를 해결하기 위한 방법에는 각 리퀘스트에 대해 멀티 프로세싱 혹은/그리고 멀티 스레딩이 있을 수 있다. Node.js는 이것을 좀 다르게 다룬다. 모든 새 요청에 대해 새 스레드를 만드는 것 대신, 요청들은 한 메인스레드에서 다뤄지며, 그것이하는 일의 대부분이다.

리퀘스트들을 다루기 : 리퀘스트에 포함된 모든 오퍼레이션(eg. 파일 시스템 접근, 데이터베이스 읽기/쓰기)은 백그라운드에서 (위에서 언급한)libuv에의해 관리되는 일꾼 스레드로 보내진다. 즉, 리퀘스트 안에 있는 I/O 오퍼레이션들은 메이스레드가 아닌 곳에서 비동기적으로 다뤄진다. 이 방법으로 무거운 것을 다른곳으로 보냄으로써, 메인스레드가 블럭되지 않게 해준다. 당신의 앱 코드는 한 순간에 한가지 일만 하고 있고, 그것은 메인스레드 안에 있을 것이다.  libuv의 스레드풀에 있는 모든 일꾼 스레드는 당신이 접근할 수 없게 만들어 놓았다. 여러분은 그들에게 직접 접근할 수 없으므로 그것에대해 걱정할 필요도 없다. Node.js가 당신을 위해 관리해주기 때문이다. 이러한 아키텍처는 I/O오퍼레이션을 특히 효율적이게 만들어준다. 그러나 이것에 단점이 없는 것은 아니다. 오퍼레이션에는 I/O 바운드 하나만 있는게 아니라 CPU바운드, 메모리 바운드 등이 더 있다. 조금만 더 이야기해보자면 Node.js는 I/O 작업을 위한 비동기 기능만을 제공한다. CPU 집약 오퍼레이션을 수행하는 방법이 있지만, 이 글의 논지가 아니니 넘어가도록 하자.

이벤트기반(Event-Driven) : 일반적으로 현대의 대부분 시스템은 메인 앱이 꺼져야 들어오는 리퀘스트에의해 프로세스가 초기화된다. 그러나 그들이 다르게 흘러간다면? 전형적인 구현은 리퀘스트를 다음 순서처럼 다룬다: 리퀘스트를 위한 스레드를 하나 만들고, 다른 오퍼레이션이 끝난뒤 그 오퍼레이션을 수행하며, 오퍼레이션이 느려지면 그 스레드에 있는 모든 오퍼레이션이 멈추거나 느려진다. 모든 오퍼레이션이 완료되면 리스폰을 돌려준다. 그러나 Node.js에서는 메인 앱에의한 것이든 리퀘스트에의한 것이든 모든 오퍼레이션이 트리거(trigger)를 기다리는 Node.js 이벤트로 등록된다.

런타임(시스템) : Node.js 런타임은 고수준, 저수준 둘 다 Node.js 앱 실행을 같이 도와주는 전체 코드베이스(위에서 언급한 요소들)이다.

모든것을 섞어보자. (PUTTING EVERYTHING TOGETHER)
이제 Node.js 요소의 고수준 관점에서 보자. 우리는 그것의 아키텍처를 더 잘 알기위해 그것의 워크플로우(workflow)를 알아보고, 어떻게 다른 요소들이 서로 소통하는지도 알아볼 것이다.

Node.js 앱이 실행되면, V8엔진이 당신의 앱 코드를 실행시킨다. 당신 앱의 객체들은 옵저버(이벤트로 등록할 수 있는 기능)의 목록에 넣어둔다. 이 옵저버는 각 이벤트들이 발생할 때 알림을 받는다.

한 이벤트가 발생되면, 그것의 콜백 함수가 이벤트 큐(event-queue)에 들어간다. 큐에 이벤트가 한개라도 남아있으면 이벤트 루프(event loop)는 큐에서 이벤트를 빼어 콜스택(call stack)에 집어넣는다. 이전 이벤트가 처리가 되면 이벤트 루프는 다음 이벤트를 콜스택에 넣는다는 점만 유의하면 된다.

콜스텍에서 우연히 I/O 오퍼레이션을 만나면, 처리하기위해 libuv에게 넘어간다. 기본적으로, libuv는 4개의 일꾼 스레드를 유지하고 있다. 물론 더 많은 스레드를 만들 수도 있다. 만약 요청이 파일 시스템 I/O나 DNS 관련이면, 처리를 위해 스레드 풀에 할당될 것이다; 다른경우, 네트워킹과 같은 다른 리퀘스트들에서는 특정 플랫폼의 매커니즘이 그 리퀘스트들을 알아서 배치할 것이다.

스레드 풀을 사용하게 만드는 I/O 오퍼레이션들(파일 I/O, DNS 등)은 데이터베이스 트렌젝션이나 파일 시스템 접근과 같은 오퍼레이션을 수행하기 위해 Node.js의 저수준 라이브러리로 일꾼 스레드가 작용할 것이다. 처리가 끝나고 libuv는 이벤트가 다시 메인스레드에서 처리될 수 있게 이벤트 큐에 다시 집어넣는다. 이 동안에는 libuv가 비동기의 I/O 오퍼레이션을 다루고 있고, 메인스레드는 마냥 앉아서 처리 결과를 기다리는 것이 아니라 자신의 할 일을 하고 있는다. libuv에의해 돌려받는 이벤트는 이벤트 루프에의해 콜스택으로 돌아왔을때, 다시 메인스레드에서 처리될 수 있다. 이것이 Node.js에서 이벤트 생명주기이다.

mbg는 기가막히게 Node.js와 레스토랑을 비유해 놓았다. 나는 Node.js 사이클을 더 쉽게 설명하기 위해 그의 이야기를 빌려 설명하겠다.

Node.js 앱을 스타벅스 카페라 생각해보자. 높은 효율과 숙련이 잘 된 웨이터(한 명이고 메인스레드라 할 수 있다)가 주문을 받는다. 만약 같은 시간대에 많은 고객이 들이닥치면 웨이터의 서비스를 받기위해 줄을 서게 된다(이벤트 큐에 넣는다). 한 고객이 웨이터로부터 서비스를 받으면 웨이터는 메니저(libuv)에게 주문을 넘겨준다. 각 주문을 어떤 바리스타(일꾼 스레드 혹은 플랫폼-특정 매커니즘)에게 주문을 할당할지 정한다. 바리스타는 다른 다른 재료들과 기계를 써서(저수준 C/C++ 요소들) 고객이 요청한 것들의 음료를 만든다. 보통 4명의 바리스타가 특정 라떼(파일 I/O, DNS 등)를 돌아가면서 만든다. 그러나 주문이 피크치에 달할 때는 더 많은 바리스타를 일시적으로 고용할 수 있다(개업한 날의 이야기이지, 점심시간대의 이야기는 아니다). 한 번 웨이터가 메니저에게 주문을 넘기면, 웨이터는 다른 고객을 서브하기 위해 커피 만드는 것을 기다리지 않는다. 대신에 다음 고객을 부른다(이벤트 루프에의해 다음 이벤트를 큐에서 꺼네어 콜스택에 넣는다). 이벤트가 콜스택 안에 있는 것은, 고객이 카운터에서 서브를 받으며 있는 것과 비슷하다. 커피가 다 만들어지면, 커피는 고객 줄의 끝으로 보내진다. 커피가 카운터로 나오면 웨이터는 고객의 이름을 부를 것이고, 고객은 주문한 커피를 받게된다. (비유가 다소 현실세계에서는 이상할 수 있지만, 프로그램 관점에서 프로세스를 고려할 때는 딱 들어 맞을 것이다)


고수준 관점의 전반적인 Node.js 내부 코드 베이스와 그것의 일반적인 이벤트 생명주기에대해 설명이 끝났다. 그러나 이 총람은 굉장히 일반적인 상황의 이야기이고 많은 이슈와 세부적인 요소에는 들어맞지 않을 수 있다는 점을 유의하길 바란다. 예를들어 CPU 바운드 핸들링이나 Node.js 디자인 패턴 등등이 있다. 더 많은 다른 글들의 토픽들이 그 부분을 채워줄 것이다. 


신고

WRITTEN BY
canapio
개인 iOS 개발, canapio

받은 트랙백이 없고 , 댓글이 없습니다.
secret

Node.js 쓰다보면 메모리를 점점 잡아먹는 현상을 발견할 있다. 이것은 굉장히 일반적인 현상이고, Node.js에서 쓰는 V8엔진은 기본으로 1.4GB 메모리를 한계로 잡아놓고(기본적으로 64비트는 1.4GB, 32비트는 512MB 한계로 잡는다.) 안에서 메모리를 점점 사용하게된다. 나의 경우는 아마존 프리티어를 사용하기때문에 메모리가 1GB 밖에 되지않아서 메모리 제한을 낮추어야했다.


--max-old-space-size=512

메모리 사용을 512MB 제한하라는 옵션이다. 아래와같이 사용할 있다.

혹은 forever 모듈을 사용하고있다면 아래와같이 사용하면 된다.


--expose-gc

가비지 컬랙터를 수동으로 전환한다. 내가 자바스크립트 코드에서 gc(); 호출하면된다. 적어도 30초에 한번씩은 가비지 컬랙팅을 하기를 추천 한다고 한다.


--max-new-space-size=2048

만약 서버 멈춤이 짧은것이 중요하다면 --max-new-space-size=2048 플래그를 사용하면 된다. 이것은 피크 퍼포먼스를 줄여버리기도하지만, 100ms정도로 서버 멈춤이 짧아질 것이다


--noincremental-marking

반면 피크 퍼포먼스가 중요하고 서버 멈춤이 오래 걸려도 괜찮다면 --noincremental-marking 플래그를 사용하면 된다. 플래그를 사용하면 1GB 1 정도의 서버 멈춤을 예상할 있다. 따라서 주로 작은 (heaps)이나 일괄 처리 테스크(순차 처리 테스크) 유용하게 사용된다.



참고

  • StackOverFlow : NodeJS / ExpressJS Memory Leak
    http://stackoverflow.com/questions/22507667/nodejs-expressjs-memory-leak
  • StackOverFlow : Is anybody making a Node-optimized V8?
    http://stackoverflow.com/questions/8263210/is-anybody-making-a-node-optimized-v8
  • 600k concurrent websocket connections on AWS using Node.js
    http://www.jayway.com/2015/04/13/600k-concurrent-websocket-connections-on-aws-using-node-js/
  • How does Node.js do memory allocation?
    https://www.quora.com/How-does-Node-js-do-memory-allocation


신고

WRITTEN BY
canapio
개인 iOS 개발, canapio

받은 트랙백이 없고 , 댓글이 없습니다.
secret

목표

- http://{username}.github.io에 Hello World 웹페이지가 나타나도록 만들것이다.
 +) 추가로 http://{username}.github.io가 아닌 http://profile.canapio.com 나만의 도메인을 적용시켜 볼것이다. 물론 도메인을 미리 구매하여 준비해둬야한다.

들어가며

Github는 깃을 사용하는 프로젝트를 지원하는 웹 기반의 호스팅 서비스이다. 여기서 홈페이지를 만들 수 있는 기능을 제공한다. 다르게 말하면 홈페이지를 구성하는 파일들을 Github에 올려놓고 홈페이지를 돌릴 수 있다는 것이다. 원래는 홈페이지를 만들기위해서는 서버까지 구축했었어야했는데, 요즘은 다양한 서비스에서 서버를 무료(또는 무료)로 제공해주고있다. 그중 하나가 Github인것이다.

"http://{username}.github.io"에 Hello World 페이지 띄우기

STEP 1 : 저장소를 판다.
저장소 이름은 {유저이름}.github.io로 한다. 저장소 이름이 나중에 웹페이지 도메인 네임이 될것이다.

STEP 2 : index.html 파일을 만든다.

STEP 3 : 확인한다.

나만의 도메인으로 바꾸기

STEP 1 : CNAME 파일을 생성하여 바꾸고자 하는 도메인을 넣어둔다.


이때 http://나 https://는 빼고 입력해야한다.

STEP 2 : 도메인 등록 사이트에 가서 A 레코드 설정을 한다.
Github의 DNS Provider IP주소는 아래 두가지이다. 둘중 하나 선택하여 등록해주면 된다.

  • 192.30.252.153
  • 192.30.252.154
위 사진은 닷넷코리아라는 도메인 사이트에서 등록할때의 모습이다.

이렇게 등록을 해주면 시간이 좀 걸릴수도 있다는데(10분정도?), 곧 확인해보면 원하는 주소에 나의 github 사이트가 연결되었음을 알 수 있을것이다.


신고

WRITTEN BY
canapio
개인 iOS 개발, canapio

받은 트랙백이 없고 , 댓글이 없습니다.
secret

원문 : https://blog.risingstack.com/fundamental-node-js-design-patterns/

Translated by canapio
Help by soojin

디자인패턴에 대해 이야기할 때 당신은 singleton, observer, factory들을 생각할 것이다. 이 글은 단지 그것들에 대해서만 이야기하는것은 아니고 dependency injection이나 middleware와 같은 다른 일반적인 패턴과 함께 다룰것이다.

디자인 패턴이란?

디자인 패턴은 흔히 발생하는 문제를 재사용가능하게 일반화하여 해결한다.

Singleton

singleton 패턴들은 해당 "클래스"의 인스턴스 갯수를 한개로 한정한다. Node.js에서는 require을 사용함으로써 꽤 쉽게 싱글톤을 만들 수 있다.

//area.js
var PI = Math.PI;

function circle (radius) { 
  return radius * radius * PI;
}

module.exports.circle = circle; 

당신의 응용프로그램에서 싱글턴 객체를 얼마나 사용하든 상관없이; 오직 하나의 객체로 존재하게 될 것이다.

var areaCalc = require('./area');

console.log(areaCalc.circle(5));

require의 동작 덕분에, 싱글톤들은 NPM모듈들 사이에서 가장 일반적인 Node.ja 디자인 패턴들일 것이다

Observer

한 객체는 상태가 바뀔때 dependents나 observer의 리스트를 자동으로 유지하고 그것들을 알린다. Observer 패턴을 구현하기 위해서는 EventEmitter를 끌어 사용해야한다.

// MyFancyObservable.js
var util = require('util');  
var EventEmitter = require('events').EventEmitter;

function MyFancyObservable() {  
  EventEmitter.call(this);
}

util.inherits(MyFancyObservable, EventEmitter);  

이것이 그 방법이다; 우리는 단지 옵저버가 가능한 객체를 만들었다! 이것을 유용하게 만들기 위해서는 몇가지 기능을 추가하면 된다.

MyFancyObservable.prototype.hello = function (name) { 
  this.emit('hello', name);
};

잘 했다. 이제 우리의 observable은 이벤트를 발생시킬 수 있다. 이제 사용해보자!

MyFancyObservable.prototype.hello = function (name) { 
  this.emit('hello', name);
};

Factory

팩토리 패턴은 생성자 대신 제네릭한 인터페이스를 만들어야 하는 creational pattern이다.
이 패턴은 만들려는 프로세스가 복잡할 때 굉장히 유용하게 쓰인다.

function MyClass (options) {  
  this.options = options;
}

function create(options) {  
  // modify the options here if you want
  return new MyClass(options);
}

module.exports.create = create;  

펙토리는 테스팅 또한 쉽게 만든다. 가령 이 패턴을 이용해 모듈에 dependency를 넣을 수 있다.

Dependency Injection

Dependency injection은 의존객체에 하나 이상의 dependency를 주입하거나 참조로 전달하는 소프트웨어 디지인 패턴이다.     

예를들어 데이터베이스에 의존적인 UserModel을 생성해보자.

function userModel (options) { 
  var db;

  if (!options.db) {
    throw new Error('Options.db is required');
  }

  db = options.db;

  return {
    create: function (done) {
      db.query('INSERT ...', done);
    }
  }
}

module.exports = userModel;

이제 이걸 이용해서 인스턴스를 만들 수 있다.

var db = require('./db');

var userModel = require('User')({  
  db: db
});

왜 이게 유용한가? 이것은 테스팅을 엄청나게 쉽게 만들어준다 -당신이 유닛테스트를 만들 때, 이 모델에 가짜 db 인스턴스를 쉽게 넣어줄 수 있다.

Middleware / pipeline

Middleware는 강력하지만 아주 심플한 컨셉이다: 한 유닛이나 한 함수의 결과값은 다음을 위한 인풋이다. 만약 당신이 이미 ExpressKoa를 사용했다면 이 컨셉을 이미 사용해보았다.

Koa가 어떻게 그것을 하는지 확인해보자:

app.use = function(fn){  
  this.middleware.push(fn);
  return this;
};

기본적으로 이 코드는 middleware를 추가하면 단순히 middleware 배열에 추가한다. 지금까지는 잘 되고 있다. 그러나 서버에 요청을 하면 어떨까?

var i = middleware.length; 
while (i--) { 
  next = middleware[i].call(this, next);
}

마법이 아니다 - 당신의 middleware는 줄줄이 호출된다. 

Streams

stream은 특별한 pipeline으로 생각할 수 있다. 이것은 객체가 아닌 bytes이지만 많은 양의 데이터 흐름을 처리하는 데 좋다.

process.stdin.on('readable', function () {  
    var buf = process.stdin.read(3);
    console.dir(buf);
    process.stdin.read(0);
});

$ (echo abc; sleep 1; echo def; sleep 1; echo ghi) | node consume2.js 
<Buffer 61 62 63>  
<Buffer 0a 64 65>  
<Buffer 66 0a 67>  
<Buffer 68 69 0a>  

Example by substack

stream에 대해 더 공부하고 싶으면 substack의 Stream Handbook을 확인해보자.

Further reading


신고

WRITTEN BY
canapio
개인 iOS 개발, canapio

받은 트랙백이 없고 , 댓글이 없습니다.
secret
사실 5분은 오버고..
암튼 엄청 빠르게 서버 푸시 시스템을 구축할 수 있다는 뜻입니다. 아래 링크에 들어가서 따라하시면 됩니다.


위 링크에서 필요한 내용이나, 추가할만한 내용을 뽑아봤습니다. 



nodejs 서버사이드 iOS 푸시

iOS용부터 설명하겠다. 링크 여기 강좌에 들어가면 정말 쉽게 푸시 구현하는 방법을 설명해 놓았다. 3분안에 푸시를 쏘아볼 수 있다! 내가 위 링크의 포스팅보다는 잘 쓸 자신이 없으므로 나는 배포하는 부분을 좀 이야기하겠다. 테스트만 해볼 분들은 링크만 참고하면 되지만 배포할때는 아래 코드로 수정해야한다.

1) 푸시 개발용, 푸시 배포용 
// Developer 
var options = { 
    gateway : "gateway.sandbox.push.apple.com", 
    cert: './keys2/cert_production.pem',
    key: './keys2/key_production.pem',
    production: false
};

// AppStore 배포, Adhoc 배포
var options = { 
    gateway : "gateway.push.apple.com",//"gateway.sandbox.push.apple.com", 
    cert: './keys2/cert_production.pem',
    key: './keys2/key_production.pem',
    production: true
};

바뀐 부분은 두 라인인데, gateway부분과 production부분이다. gateway 값을 바꿔주고 productiontrue로 수정해야한다. 이렇게하면 AppStore배포나 Adhoc배포에서 푸시가 날아간다. 그러니 저렇게 바꿔서 테스트를 해보고 싶으면 Adhoc으로 배포하여 푸시테스트를 해보면 된다. (필자는 Developer, Adhoc, AppStore Distribute 상황에서 모두 테스트해보았고 그 결과를 말하는 것이다.) 

2) 푸시 여러개 한번에 보내기
위에서 소개한 강좌에 들어가면 푸시를 한번에 하나밖에 보내지 못한다. 아래 소스는 푸시 날리는 부분에 푸시아이디값을 Array로 만들어 한꺼번에 여러개 날릴 수 있게 해준다. 

var myDeviceArray = [ ]
for (var i=0; i<results.length; i++) {
     var token = results[i]._id;//'앞에서 Xcode로 build 하면서 획득한 아이폰 디바이스 토큰을 입력한다.'
     var myDevice = new apn.Device(token);
     myDeviceArray.push(myDevice);
}
try {
     apnConnection.pushNotification(note, myDeviceArray);
} catch (e) {
     console.log("apn exception : " + e);
}


apnConnection.pushNotification(note, myDeviceArray);
이 부분이 푸시를 실제 날리는 부분이고, 애플에게 푸시를 쏘아달라고 요청하게된다.

여기서 원래 myDeviceArray가 아닌 myDevice라는 객체를 넣었었는데, 필자는 여기에 myDevice의 배열을 넣었다. 이렇게하면 한번에 20개, 30개씩 푸시를 날릴 수 있다. 




nodejs 서버사이드 android 푸시

안드로이드는 iOS보다 휠씬 간단하게 구현이 가능하다. 안드로이드는 인증서 이런게 없기때문에 그냥 코드 구현을 하고 키값만 넣어주면 된다. 
그런데 자료를 찾다보니 앞서 소개한 iOS푸시구현 블로그와 동일한 저자가 안드로이드푸시 저자가 동일하고, 정리가 정말 잘되있다.. 링크를 따라가면  구현할 수 있을 것이다. 

여기서는 링크에서 서버쪽 코드만 빼내서 설명하겠다.

우리가 사용할 nodejs모듈은 https://github.com/ToothlessGear/node-gcm이다. node파일이 위치한 디렉토리에 들어가서 npm install node-gcm 명령을 치면 된다.

상단에 node-gcm를 불러오고

var gcm = require('node-gcm');


// or with object values
var message = new gcm.Message({
     collapseKey: 'demo',
     delayWhileIdle: true,
     timeToLive: 3,
     data: {
          lecture_id:"notice",
          title:"제목입니다",
          desc: "설명입니다",
          param1: '첫번째파람',
          param2: '두번째파람'
     }
});

var server_access_key = '/*안드로이드 개발자가 넘겨준 서버키*/';
var sender = new gcm.Sender(server_access_key);
var registrationIds = [ ];     // 여기에 pushid 문자열을 넣는다.

registrationIds = ['/*안드로이드 단말기에서 나온 푸시 아이디*/'];

/*
for (var i=0; i<push_ids.length; i++) {
     registrationIds.push(push_ids[i]);
}
*/

// 푸시를 날린다!
sender.send(message, registrationIds, 4, function (err, result) {
     // 여기서 푸시 성공 및 실패한 결과를 준다. 재귀로 다시 푸시를 날려볼 수도 있다.
     console.log(result); 
});
이상 서버사이드 아이폰, 안드로이드 푸시 알림이였습니다. 

신고

WRITTEN BY
canapio
개인 iOS 개발, canapio

받은 트랙백이 없고 , 댓글이 없습니다.
secret
path없이 바로 /opt/nosql/mongodb/bin에 있는 명령을 실행하기위해 준비
# export PATH=/opt/nosql/mongodb/bin:$PATH

mongod 명령은 MongoDB 시스템을 위한 기본 데몬 프로세스이다. 데이터 요청, 데이터 접근, 백그라운드에서 동작할 수 있게하는 등의 기능을 수행한다.

1) 테스트를 위한 mongod 돌리기
1. mongod 명령 수행
# mongod

2. 새로운 터미널을 열어서 mongo 명령 수행
# mongo
>

3. 종료
mongo 종료 : 
> exit
mongod 종료 :
# cmd+c (혹은 # ctrl+c)


2) 서비스를 돌리기 위한 mongod를 데몬으로 돌리기(백그라운드에서 돌리기)
1. mongod 명령을 데몬으로 수행. 데몬으로 수행하면 fork를 하여 백그라운드에서 돌고 있다.
# mongod --fork --logpath /var/log/mongodb.log

2. mongo 명령 수행
# mongo
>

3. 종료
mongo 종료는 위와 동일
mongod 종료 :
# mongod --shutdown




MongoDB 클라이언트에 들어온 모습


신고

WRITTEN BY
canapio
개인 iOS 개발, canapio

받은 트랙백이 없고 , 댓글이 없습니다.
secret
OS : centos 32bit
mongodb : 3.0.2


1. 아래 링크에 들어가서 해당 OS의 MongoDB 다운로드 URL 준비한다.

2. 리눅스에 curl 명령을 실행하여 MongoDB 압축파일을 다운받는다.
# curl -O [1번에서가져온URL]
예) # curl -O https://fastdl.mongodb.org/linux/mongodb-linux-i686-3.0.2.tgz

3. 압축을 푼다 (# ls를 해보면 해당 디렉토리에 들어있는 파일을 볼 수 있다)
# tar -zxvf 압축파일이름
예) # tar -zxvf mongodb-linux-i686-3.0.2.tgz

4. 압축을 푼 폴더를 /opt/nosql 으로 옮긴다.
# mkdir /opt/nosql
# mv mongodb-linux-i686-3.0.2 /opt/nosql/mongodb

5. db가 저장될 폴더를 만든다.
# mkdir -p /data/db

6. mongodb 디렉터리로 들어가서 mongod를 실행시킨다.
# cd /opt/nosql/mongodb

(7. mongodb 클라이언트를 실행시켜본다.)
# export PATH=/opt/nosql/mongodb/bin:$PATH  // monodb 클라이언트를 바로 실행할 수 있게 해줌
# mongdb
>


빠르게 훑어보는 nodejs, mongodb 연동 : http://bcho.tistory.com/889


참고 


신고

WRITTEN BY
canapio
개인 iOS 개발, canapio

받은 트랙백이 없고 , 댓글이 없습니다.
secret
1. 아래 링크에 들어가서 해당 OS의 nodejs 다운로드 URL 준비한다.

2. nodejs 압축파일을 다운받고 압축을 푼다.
# wget http://nodejs.org/dist/v0.12.2/node-v0.12.2-linux-x86.tar.gz
# tar xfz node-v0.12.2-linux-x86.tar.gz

3. node 폴더를 옮긴다.
# mv ./node-v0.12.2-linux-x86 /usr/local/node

4. /etc/profile 파일에 들어가서 수정(:a)모드로 바꾸어 export를 두개 만들고 저장(:wq)한다. vi편집기 사용. (vi 편집기 사용법)

# vi /etc/profile

5. 방금 수정한 profile을 적용시킨다.
# source /etc/profile

6. node 명령을 실행해본다.

# node -v


연관 글

> [Node.js, MongoDB] Node.js 설치 및 실행

[Node.js, MongoDB] MongoDB 리눅스에 설치 및 실행

[Node.js, MongoDB] MongoDB 돌리기 (+백그라운드에서 돌리기)


(번역) Express.js 4, Node.js and MongoDB REST API 강좌

신고

WRITTEN BY
canapio
개인 iOS 개발, canapio

받은 트랙백이 없고 , 댓글이 없습니다.
secret


종종 좋은 것들은 한곳에 머무르지 않고, 그래서 모카(Mocha)와 Superagent를 이용한 테스트와 Mongoskin과 Express.js를 사용하여 Node.js와 MongoDB를 이용한 JSON REST API 서버를 만드는 튜토리얼을 Express.js 4버전이 출시됨과 함께 만들게 되었다. 최신의 Express.js 4, Node.js, MongoDB를 다루는 튜토리얼을 만나보자.
새 튜토리얼을 위한 소스코드는 github.com/azat-co/rest-api-express (master branch)에서 확인할 수 있다. 이전의 Express 3.x버전을 위한 튜토리얼 코드는  아직 작동하고 express3 branch에 있다.



Express.js4와 MongoDB REST API Tutorial은 아래 파트로 나뉘어 구성되어있다.
1. Node.js와 MongoDB REST API 개요
2. 모카와 Superagent를 이용한 REST API 테스트
3. NPM-ing Node.js Server Dependencies
4. Express.js 4.x Middleware Caveat
5. Express.js와 MongoDB (Mongoskin) 구현
6. Express.js 4 랩을 실행하고 모카를 이용한 MongoDB 테스팅
7. Express.js와 Node.js의 결론 및 확장성

만약 당신이 자장소와 그것이 무엇을 동작하는지로부터 코드의 동작에 관심이 있다면, 여기 REST API server가 어떻게 다운로드되고 동작하는지 간단한 설명이 여기있다.
$ git clone git@github.com:azat-co/rest-api-express.git
$ npm install
$ node express.js


$ mongod 와 함께 MongoDB를 시작한다. 그다음, 새 터미널창을 띄워서 모카 테스트를 실행한다. :

$ mocha express.test.js


아니면, 모카를 전역으로 설치하지 않은 경우. :

$ ./node_modules/mocha/bin/mocha express.test.js



1. Node.js와 MongoDB REST API 개요
Node.js, Express.js, MongoDB(Mongoskin) 튜토리얼은 모카와 SuperAgent를 사용해 테스트를 해나갈것이다. 이것은 Node.js의 JSON REST API 서버를 만들면서 테스트 주도 개발(Test-Driven Development)을 필요로 한다. 
서버 응용프로그램은 Express.js 4버전대 프레임워크와 MongoDB를 위한 Mongoskin 라이브러리를 필요로한다. 이 REST API 서버에서 우리는 CRUD(create, read, update and delete)기능을 실행하고 app.param( ), app.use( )와 같은 Express.js middleware 방식의 메소드를 실행할 것이다.

가장 처음에 할 것은, MongoDB를 설치하는 것이다. 이 링크를 따라가면 할 수 있을 것이다.
우리는 아래의 버전의 라이브러리를 사용하게 될 것이다.
  • express: ~4.1.1
  • body-parser: ~1.0.2
  • mongoskin: ~1.4.1
  • expect.js: ~0.3.1
  • mocha: ~1.18.2
  • superagent: ~0.17.0

만약 버전이 맞지 않다면 코드가 동작하지 않을 수도 있다 :-(



2. 모카와 Superagent를 이용한 REST API 테스트
시작하기 전에, 우리가 만들게될 REST API 서버에 HTTP 요청을 만드는 기능 테스트를 한번 적어보자. 만약 모카를 사용할 줄 알거나, 바로 Express.js 앱 구현을 해보고 싶은 사람들은 마음대로 해도 된다. 당신은 물론 터미널에서 CRUL테스트를 할 수도 있다.
우리는 이미 Node.js, npm, MongoDB를 설치했다고 가정하고, 새 폴더를 만들어보자.
$ mkdir rest-api
$ cd rest-api


우리는 모카, Expect.js, SuperAgent 라이브러리를 사용하게 될 것이다. 그것들을 설치하고, 프로젝트폴더에 들어가서 이 명령을 실행해라.

$ npm install mocha@1.18.2 --save-dev
$ npm install expect.js@0.3.1 --save-dev 
$ npm install superagent@0.17.0 --save-dev


Note: 당신은 물론 (명령에 -g를 넣고) 모카를 전역으로 설치할 수도 있다. 
이제 아까 그 폴더에 express.test.js파일을 만들자. 아래 6가지를 진행할 것이다.
  • 새 객체를 생성한다.
  • 객체의 ID를 가져온다.
  • 객체의 모든 정보를 가져온다.
  • ID를 이용해 객체를 업데이트한다.
  • ID를 이용해 객체를 제거한다.
HTTP 요청은 Super Agent’s와 연관된 (단지 테스트케이스를 넣기만 하면되는) 함수들을 사용하면 굉장히 식은죽먹기이다. 
이 강좌는 Express.js 4, MongoDB, Mocha를 사용하여 REST API를 만드는것이 목적이기 때문에 테스트케이스(test suits)에 대해 깊게 들어가지는 않겠다. 코드를 복붙하시오!

아래 코드는 express.test.js파일이다.
var superagent = require('superagent')
var expect = require('expect.js')

describe('express rest api server', function(){
  var id

  it('post object', function(done){
    superagent.post('http://localhost:3000/collections/test')
      .send({ name: 'John'
        , email: 'john@rpjs.co'
      })
      .end(function(e,res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(res.body.length).to.eql(1)
        expect(res.body[0]._id.length).to.eql(24)
        id = res.body[0]._id
        done()
      })    
  })

  it('retrieves an object', function(done){
    superagent.get('http://localhost:3000/collections/test/'+id)
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body._id.length).to.eql(24)        
        expect(res.body._id).to.eql(id)        
        done()
      })
  })

  it('retrieves a collection', function(done){
    superagent.get('http://localhost:3000/collections/test')
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(res.body.length).to.be.above(0)
        expect(res.body.map(function (item){return item._id})).to.contain(id)        
        done()
      })
  })

  it('updates an object', function(done){
    superagent.put('http://localhost:3000/collections/test/'+id)
      .send({name: 'Peter'
        , email: 'peter@yahoo.com'})
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body.msg).to.eql('success')        
        done()
      })
  })
  it('checks an updated object', function(done){
    superagent.get('http://localhost:3000/collections/test/'+id)
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body._id.length).to.eql(24)        
        expect(res.body._id).to.eql(id)        
        expect(res.body.name).to.eql('Peter')        
        done()
      })
  })    
  
  it('removes an object', function(done){
    superagent.del('http://localhost:3000/collections/test/'+id)
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body.msg).to.eql('success')    
        done()
      })
  })      
})

테스트를 하기위해, $ mocha express.test.js 커맨드를 날릴 것이다. (모카를 전역으로 설치하지 않은 경우 $ ./node_modules/mocha/bin/mocha espress.test.js)


3. NPM-ing Node.js Server Dependencies
이 튜토리얼에서 우리는 Mongoskin을 사용할 것이다. 또한, Mongoskin은 Mongoose나 shema-less보다 훨씬 가볍다. 자세한 내용은 Mongoskin comparison blurb를 확인해보기 바란다.
Express.js는 Node.js HTTP module 핵심 객체로 감싸져있다. Express.js 프레임워크는 Connect middleware의 상위층을 기반으로 만들어져있고, 어마어마하게 많은 편리함을 제공한다. 몇몇 사람들은 ...

만약 당신이 이전 섹션(Text Converage)에서 rest-api 폴더를 만들었다면, 어플리케이션 모듈을 설치하기 위해 아래 명령만 입력하면 된다. :
$ npm install express@4.1.1 --save
$ npm install mongoskin@1.4.1 --save


4. Express.js 4.x Middleware Caveat

슬프게도 NPM express만 하는 것으로는 Express.js와 함께 최소한의 REST API 서버를 구축하는게 불가능하다. 왜냐하면 4.버전대의 middlewares은 번들이 아니기 때문이다!(the middlewares are not bundled) 개발자들은 Express.js 4.x.왼쪽에 있는 express.static를 제외한 분리된 모듈들을 설치해야한다. 그리고 들어오는 정보를 파싱하기위해 body-parser를 추가해야한다:
$ npm install body-parser@1.0.2 --save


5. Express.js와 MongoDB (Mongoskin) 구현

제일 처음에 우리의 express.js 안에 우리의 dependencies를 정의해야한다. :
var express = require('express'),
  mongoskin = require('mongoskin'),
  bodyParser = require('body-parser')


3.x대 이후 버전(물론 v4도 마찬가지), Express.js은 앱 인스턴스의 객체를 간소화해서 가져온다, 아래 라인은 서버객체를 우리에게 제공할 것이다.(번역자:그냥 import정도로 생각하면 될듯):

var app = express()


요청의 바디로부터 파람들을 추출하기위해, 우리는 아래와같은 모양의 bodyParser() middleware를 사용할 것이다.:

app.use(bodyParser())


Middleware(여기, 다른 포럼)는 Express.js에서 강력하고 편리한 패턴이고 구성요소를 연결하며 코드의 재사용을 증진시킨다.

HTTP 요청의 바디객체 파싱의 넘사벽으로부터 구해주는 bodyParser()메소드와 같이, Mongoskin은 딱 한줄의 코드로 MongoDB 데이터베이스에 접속하는게 가능하다.:
var db = mongoskin.db('mongodb://@localhost:27017/test', {safe:true})


Note: 만약 당신이 원격으로 데이터베이스에 접속하고 싶다면(MongoHQ와 같은 것들..), 당신의 username, password, host and port의 값들을 스트링으로 치환하라. 여기 URI 스트링의 포맷이 있다:
mongodb://[username:password@]host1[:port1][, host2[:port2], …[, hostN[:portN]]][/[database][?options]].

app.param() 메소드는 또다른 Express.js middleware이다. 이것은 기본적으로 “요청 핸들러의 URL페턴에 어떤 값이 있으니 매 시간마다 뭔갈 처리해라”는 것을 말하고 있다. 우리의 경우 요청 패턴이 collectionName 스트링에 콜론이 점두사로 있을 때, 우리는 특정 콜랙션을 선택한다. 그러면 다음 요청 핸들러에서 사용할 수 있는 요청 객체(widespreadreq)의 프로퍼티(콜랙션이나 다른것일 수도 있다.)로써 콜랙션을 저장한다. (번역자:뭔소린지 모르겠다)
app.param('collectionName', function(req, res, next, collectionName){
  req.collection = db.collection(collectionName)
  return next()
})


단지 유저지향적으로, 메시지와 함께 루트 라우트를 넣자.:

app.get('/', function(req, res) {
  res.send('please select a collection, e.g., /collections/messages')
})


이제 진짜 할 일을 시작한다. 다수의 요소들중에 리스트를 어떻게 가져오는지 있다 (첫번째 파라메터는 빈 오브젝트{}이고 임의의 라는 뜻이다). 이 결과는 _id에(두번째 파라메터) 의해 정렬된 10개 제한으로 낼 것이다. find()메소드는 커서를 반환하고 우리는 toArray()를 불러 JavaScript/Node.js용 배렬로 만든다. :

app.get('/collections/:collectionName', function(req, res, next) {
  req.collection.find({} ,{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){
    if (e) return next(e)
    res.send(results)
  })
})


URL 패턴 파라메너에서 :collectionName 스트링에대해 언급한적이 있나? 이것과 이전 app.param() middleware는  req.collection 객체를 우리에게 준다. 이 객체는 우리의 데이터베이스에서 특정 콜랙션을 가르키고 있다.

우리는 단지 MongoDB에서 전체적인 페이로드를 지나온 이후로 마지막 시점에 만들어진 이 객체는 조금 이해하기 쉽게 해준다. 이 메소드는 서버나 다른 것들의 데이터베스가 어떠한 데이터 스트럭쳐도 받아드릴수 있기 때문에 종종 free JSON REST API라 불린다. Parse.com과 다른 백엔드 서버 제공자는 free JSON 접근을 만들어낸다. 우리 Express.js 앱에서는 이것을 위해 req.body를 사용할 것이다.
app.get('/collections/:collectionName', function(req, res, next) {
  req.collection.find({} ,{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){
    if (e) return next(e)
    res.send(results)
  })
})


함수들을 구하는 findByIdfindOne과같이 생긴 단일 객체는 find()보다 빠르다. 그러나 그것들은 조금 다른 인터페이스를 사용한다 (그것들은 커서 대신에 진짜 오브젝트를 반환한다). 그러므로 그것을 기억하고 있어라. 추가적으로, 우리는 Express.js 마법에 의해 req.params.id 경로의 :id 부분으로부터 ID를 가져올 것이다.

app.get('/collections/:collectionName/:id', function(req, res, next) {
  req.collection.findById(req.params.id, function(e, result){
    if (e) return next(e)
    res.send(result)
  })
})


PUT 요청 핸들러는 update()가 증가된 객체를 반환하기때문에  더 흥미로운 것을 가져온다.
또한 {$set:req.body}는 값을 저장하는 기능을 가진 특별한 MongoDB 기능이 (보통 달러표시로 시작한다).

두번째 {safe:true, multi:false} 파라메터는 MongoDB에 callback 함수가 실행되기 전까지 동작을 멈추고 오직 한가지(첫번째) 아이탬만 처리하라고 알리는 옵션을 가진 객체이다.
app.put('/collections/:collectionName/:id', function(req, res, next) {
  req.collection.updateById(req.params.id, {$set:req.body}, {safe:true, multi:false}, function(e, result){
    if (e) return next(e)
    res.send((result===1)?{msg:'success'}:{msg:'error'})
  })
})


마지막으로 DELETE HTTP 함수는 app.del()에 의해 실행된다. 요청 핸들러에서, 우리는 그것이 그 동작을 하는 것처럼 보이는 removeById()를 사용한다. 그리고 커스텀 JSON success 메시지를 제거과정에서 내보낼것이다.:

app.del('/collections/:collectionName/:id', function(req, res, next) {
  req.collection.remove({_id: req.collection.id(req.params.id)}, function(e, result){
    if (e) return next(e)
    res.send((result===1)?{msg:'success'}:{msg:'error'})
  })
})


Note: delete는 JavaScript의 연산자이고 대신에 Express.js는 app.del을 사용한다.

아래의 경우 서버 3000포트를 시작하는 마지막라인이다.
app.listen(3000)


단지 이 경우 뭔가 잘 실행되지 않을 수 있다. 여기 express.js 파일의 풀 소스가 있다.

var express = require('express') , mongoskin = require('mongoskin') , bodyParser = require('body-parser') var app = express() app.use(bodyParser()) var db = mongoskin.db('mongodb://@localhost:27017/test', {safe:true}) app.param('collectionName', function(req, res, next, collectionName){ req.collection = db.collection(collectionName) return next() }) app.get('/', function(req, res, next) { res.send('please select a collection, e.g., /collections/messages') }) app.get('/collections/:collectionName', function(req, res, next) { req.collection.find({} ,{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){ if (e) return next(e) res.send(results) }) }) app.post('/collections/:collectionName', function(req, res, next) { req.collection.insert(req.body, {}, function(e, results){ if (e) return next(e) res.send(results) }) }) app.get('/collections/:collectionName/:id', function(req, res, next) { req.collection.findById(req.params.id, function(e, result){ if (e) return next(e) res.send(result) }) }) app.put('/collections/:collectionName/:id', function(req, res, next) { req.collection.updateById(req.params.id, {$set:req.body}, {safe:true, multi:false}, function(e, result){ if (e) return next(e) res.send((result===1)?{msg:'success'}:{msg:'error'}) }) }) app.del('/collections/:collectionName/:id', function(req, res, next) { req.collection.removeById(req.params.id, function(e, result){ if (e) return next(e) res.send((result===1)?{msg:'success'}:{msg:'error'}) }) }) app.listen(3000)

코드를 저장하고 당신의 에디터를 닫아라, 우리의 소박한 Express.js REST API 서버가 완성되었다.


6. Express.js 4 랩을 실행하고 모카를 이용한 MongoDB 테스팅

이제 MongoDB가 설치되고 실행됬다는 가정($ mongod)하에 터미널에서 실행시켜볼 수 있다(modgod와 다른 창을 띄워라).
$ node express.js


그리고 다른 창에서 아래 명령을 쳐라(처음 창은 닫으면 안된다):

$ mocha express.test.js

혹은 모카를 전역으로 설치 하지 않은 경우.:

$ ./node_modules/mocha/bin/mocha express.test.js


만약 모카나 BDD 사용을 원치 않다면, CURL는 언제나 당신을 위해 있다. :-)

예를들어 POST 요청을 만들기 위해 CURL데이터이다. :
$ curl -X POST -d "name=azat" http://localhost:3000/collections/test13


그리고 결과는 아래처럼 나오게 될 것이다.:

{"name":"azat","_id":"535e180dad6d2d2e797830a5"}]


우리는 REST API 서버를 사용하기때문에 쉽게 이 객체를 확인할 수 있다.:

$ curl http://localhost:3000/collections/test13
Using CURL with Express 4 and MongoDB REST API


GET요청 또한 브라우저에서 동작할 수 있다. 예를들어, http://localhost:3000/collections/test13 링크를 당신의 로컬 서버가 포트 3000에서 돌아가고 있을때 열 수 있다.
혹은 서버의 결과를 신뢰하지 못한다면, MongoDB($ mongo)를 이용하여 데이터베이스를 확인할 수도 있다.:
> db.test13.find()


Note: 만약 데이터베이스의 이름을 test라는 이름 대신에 다른 이름으로 바꾸고 싶으면, 명령 앞에 > use your_database_name 을 써라.

이 튜토리얼에서, 우리 테스트들은 실제 동작하는 어플리케이션 코드보다 길다. 몇몇에게는 테스트-기반-개발(Test-Driven Development)을 포기하고싶게 만들지도 모르겠지만, 당신이 어떠한 크고 복잡한 응용프로그램을 개발할 때에도 좋은 TDD 습관이 당신의 개발 시간을 줄여줄것이라 믿는다. 



7. Express.js와 Node.js의 결론 및 확장성
Express.js 4와 MongoDB/Mongoskin 라이브러리는 간단하게 몇 줄 만에 REST API 서버를 구축하기에 정말 좋다. 후에, 당신이 라이브러리를 확장하고자 한다면, 그것들은 또한 당신의 코드를 구성하는 방법을 제공할 것이다.
NoSQL 데이터베이스는 MongoDB와같이 스키마를 정의할 필요 없고 어떠한 다양한 형태의 데이터를 넘기거나 저장할 수 있는 좋은 free-REST APIs이다.
express.test.js, express.js and package.json의 풀소스코드는 github.com/azat-co/rest-api-exrpess에 있다.
Express.js나 다른 자바스크립트 라이브러리에대해 더 공부하고 싶다면, 아래 Azat의 책들을 보라.
  • Practical Node.js: Building Real-world Scalable Web Apps
  • Express.js Guide: The Comprehensive Book On Express.js
..
..
..

연관 글

[Node.js, MongoDB] Node.js 설치 및 실행

[Node.js, MongoDB] MongoDB 리눅스에 설치 및 실행

[Node.js, MongoDB] MongoDB 돌리기 (+백그라운드에서 돌리기)


> (번역) Express.js 4, Node.js and MongoDB REST API 강좌

신고

WRITTEN BY
canapio
개인 iOS 개발, canapio

받은 트랙백이 없고 , 댓글이 없습니다.
secret