내 이름은 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
tucan.dev
개인 iOS 개발, tucan9389

,

원문 : 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
tucan.dev
개인 iOS 개발, tucan9389

,
사실 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
tucan.dev
개인 iOS 개발, tucan9389

,
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
tucan.dev
개인 iOS 개발, tucan9389

,
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
tucan.dev
개인 iOS 개발, tucan9389

,
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
tucan.dev
개인 iOS 개발, tucan9389

,