예전에는 웹 페이지에서 구동되는 애플리케이션은 모두 SSR(server-side renering)방식으로 동작했습니다. 하지만 시간이 지날 수록 웹 기술은 발전했고 기존 방식으로 작성하기에는 개발자의 시간과 노력이 많이 필요했습니다. 이 때 자연스럽게 웹 개발에 필수적인 요소들을 묶에 개발자들이 쉽게 쓸 수 있게 하고자 하는 시도가 생겨났습니다. 그 결과물을 웹 프레임워크라 부릅니다.
SSR은 서버에서 사용자에게 보여줄 페이지를 모두 구성하여 사용자에게 페이지를 보여주는 방식입니다.(SSR 사용시 서버를 이용해서 페이지를 구성하기 때문에 클라이언트에서 구성하는 CSR보다 페이지를 구성하는 속도는 늦어지지만 사용자에게 보여주는 콘텐츠 구성이 완료되는 시점이 빨라진다는 장점이 있습니다.)
예를 들어 프레임워크는 데이터베이스에 연결을 설정, 관리, 세션을 맺고 유지하는 등의 동작을 정해진 방법과 추상화된 인터페이스로 제공합니다. 이 방법들이 프레임워크 사용자의 자유도를 제약한다고 할 수 있지만 제시하는 방법을 이용하면 쉽고 빠르게 안정적인 애플리케이션을 구축할 수 있습니다.
프레임워크는 뼈대, 골조라는 뜻인데 미리 뼈대를 세우고 살을 붙여가는 작업을 하기만 되기 때문에 붙여진 이름입니다.
| Express | 자바스크립트 / 타입스크립트 | 가장 많은 사용자를 가지고 있는 Node.js 기반의 프레임워크, 가볍게 서버를 구동할 수 있다. NestJS와 같이 Express를 기반으로 하는 프레임워크도 존재함 |
|---|---|---|
| Spring | 자바 / 코틀린 | 자바기반 프레임워크, IOC, DI, AOP 같은 객체지향 프로그래밍 기법을 쉽게 적용 |
| react | 자바스크립트 / 타입스크립트 | SPA 이나 앱 개발에 사용 |
최근 리액트가 인기를 얻으며 SPA 기반으로 구축되었습니다. SPA는 SSR방식과 다르게 서버로부터 매 요청에 대해 최소한의 데이터 응답으로 받고 화면 구성 로직을 프런트엔드에서 처리합니다. 따라서 깜빡거리는 어색한 화면이 줄어듭니다. 하지만 첫 화면 진입시 애플리케이션 다운로드로 인해 초기 로딩 속도가 오래 걸립니다.
웹 프레임워크를 선택할 때 고민이 된다면 다음 사항들을 고려하면 좋습니다.
Nest는 Node.js를 기반으로 동작합니다. 정확히는 Nest로 작성한 소스코드를 Node.js 기반 프레임워크인 Express나 Fastify에서 실행가능한 자바스크립트 코드로 컴파일하는 역할을 합니다.
여러 작업 요청이 한꺼번에 들어올때 각 작업을 처리하기 위해 스레드를 만들고 할당하는 방식을 멀티스레딩이라고 합니다. 멀티스레딩은 여러 작업을 동시에 처리함으로 작업 처리 속도가 빠르다는 장점이 있지만 공유자원을 관리하는 노력이 많이 들고 동기화를 잘못 작성하면 락에서 빠져나오지 못하는 경우가 발생할 수 있습니다. 또한 스레드가 늘어날 때마다 메모리를 소모하게 되므로 메모리 관리 역시 중요합니다.
Node.js는 하나의 스레드에서 작업을 처리합니다. 애플리케이션 단에서는 단일 스레드이지만 백그라운드에서는 스레드 풀을 구성해 작업을 처리합니다. 개발자 대신 Libuv에서 스레드 풀을 관리하기 때문에 단일스레드에서 동작하는 것처럼 쉬운 코드를 작성할 수 있습니다.
노드는 작업이 끝날 때까지 기다리지 않고 비동기로 처리하여 Node.js는 단일 스레드 논블로킹 이벤트기반 비동기 방식입니다.
단일 스레드 기반 비동기 방식은 서버의 자원에 크게 부하를 가하지 않습니다. 이는 대규모 네트워크 애플리케이션을 개발하기 적합합니다. 물론 스레드를 하나만 사용하기 때문에 하나의 스레드에 문제가 생기게 되면 애플리케이션 전체가 오류를 일으킬 위험이 있습니다.
하나의 스레드로 동작하는 것처럼 코드를 작성할수 있는 것은 큰 장점입니다.(교착상태: deadlock에 빠지지 않음)
단점은 컴파일러 언어의 처리 속도에 비해 성능이 떨어진다느 점입니다. 하지만 이점은 엔진속도성능이 항샤되고 있습니다.
혹은 콜백지옥에 빠질 수 있지만 ECMA 스크립트 2015에서 프로미스가 도입되면서 간결한 표현으로 작성할 수 있게 되었고, async/await가 추가되면서 비동기 코드를 동기식으로 처리하는 것처럼 작성할 수 있습니다.
이벤트루프는 시스템 커널에서 가능한 작업이 있다면 그 작업을 커널에 이관합니다. 자바스크립트가 단일 스레드 기반임에도 Node.js가 논블로킹 I/O를 수행할 수 있도록 해주는 핵심 기능입니다.
이벤트 루프에는 6개의 단계가 있습니다. 반드시 다음 단계로 넘어가는 것은 아니고 ㅓ각 단계에는 해당 단계에서 실행되는 작업을 하는 큐가 있습니다. 또한 이벤트 루프의 구성요소는 아니지만 nextTickQueue와 microTaskQueue가 존재합니다.
nextTickQueue와 microTaskQueue에 들어 있는 작업은 이벤트루프가 어느 단계에 있던지 실행될 수 있습니다.

자바스크립트 코드는 유후, 준비 단계를 제외한 어느 단계에서나 실행될 수 있습니다. nextTickQueue와 microTaskQueue는 이벤트 루프의 구성요소는 아니고, 이 큐에 들어 있는 작업역시 이베트 루프가 어느 단계에 있든지 실행될 수 있습니다.
node main.js 명령어로 node.js 애플리케이션을 콘솔에서 실행하면 Node.js 는 먼저 이벤트 루프를 생성한 다음 메인 모듈인 Main.js를 실행합니다.
이 과정에서 생성된 콜백들이 각 단계에 존재하는 큐에 들어가게 되는데, 메인 모듈의 실행을 완료 했다면 다음 이벤트 루프를 계속 실행할지 결정합니다.
큐가 모두 비어서 더 이상 수행할 작업이 없다면 Node.js는 루프를 빠져나가고 프로세스를 종료합니다.
이벤트 루프는 타이머 단계에서부터 시작합니다. 타이머 단계의 큐에는 setTimeout 이나 setInterval같은 함수를 통해 만들어진 타이머들을 큐에 넣고 실행합니다. now - registeredTime >= delta(setTimeout (()=>{}, delta)와 같이 타이머가 등록된 시각에서 얼마큼 시간이 흐른 후 동작해야하는지를 나타내는 값입니다.) 인 타이머 들이 큐에 들어갑니다.
대상 타이머들은 이미 실행할 시간이 지났다는 뜻입니다. 타이머들은 최소 힙으로 관리됩니다. 힙을 구성할 때 기준으로 실행할 시간이 가장 적게 남은 타이머가 힙의 루트가 됩니다. 이 단계에서는 최소 힙에 들어 있는 타이머들을 순차적으로 찾아 실행한 후 힙을 재구성합니다.
대기 단계의 큐에 들어 있는 콜백들은 현재 돌고 있는 루프 이전의 작업에서 큐에 들어온 콜백입니다.
유후 단계는 틱(프로그램 실행 시 최소 간격, '틱 카운트' 라고 합니다. 이벤트 루프에서는 각 단계가 이동하는 것을 뜻합니다.)마다 실행됩니다. 준비 단계(prepare phase)는 매 폴링 직전에 실행됩니다.
해당 단계는 Node.js의 내부동작을 위한 것입니다.
이벤트 루프에서 가장 중요한 단계가 폴 단계 입니다. 폴 단계에서는 새로운 I/O 이벤트를 가져와서 관련 콜백을 수행합니다. (ex : 소켓 연결, 새로운 커넥션, 파일 I/O)와 같이 데이터 처리를 받아들이게 됩니다. 해당 단계에서 가지고 있는 큐는 watch_queue 입니다.
폴 단계 진입 후 watch_queue가 비어있지 않다면 큐가 비거나 시스템 실행 한도에 다다를 때까지 동기적으로 모든 콜백을 실행합니다.
만약 큐가 비게 된다면 check_queue(다음 체크 단계의 큐), pending_queue(대기 콜백 단계의 큐), closing_callbacks_queue(종료 콜백 단계의 큐)에 남은 작업이 있는지 확인하고, 다음 작업이 있다면 다음 단계로 이동합니다.
만약 큐가 모두 비어 해야할 작업이 없다면 잠시 대기를 하게 됩니다.
체크 단계는 setImmediate 의 콜백만을 위한 단계입니다. 역시 큐가 비거나 시스템 실행 한도에 도달할 때까지 콜백을 수행합니다.
일부코드를 비동기적으로 실행하고 싶지만 가능한 빨리 실행하려는 경우 setImmediate() 를 사용할 수 있습니다.
종료 콜백 단계에서는 socket.on('close', ()=> {}) 과 같은 close나 destory 이벤트 타입의 콜백이 처리됩니다. 이벤트 루프는 종료 콜백을 마치고 나면 다음 루프에서 처리해야하는 작업이 남아 있는지 검사합니다. 만약 작업이 남아 있다면 타이머 단계부터 한 번 더 루프를 돌게 되고, 아니라면 루프를 종료합니다.
nextTickQueue는 process.nextTick() API의 콜백들을 가지고 있으며, microTaskQueue는 resolve 된 Promise 콜백을 가지고 있습니다. 이 두개의 큐는 기술적으로 이벤트 루프의 일부가 Node.js에 포함된 기술입니다. 이 두 큐에 들어있는 콜백은 단계를 넘어가는 과정에서 먼저 실행됩니다.
nextTickQueue가 microTaskQueue보다 높은 우선순위를 가지고 있습니다.
시맨틱 비저닝은 패키지의 버전명을 숫자로 관리하는 방법으로 많이 사용되고 있습니다. 비저닝 규칙은 다음과 같습니다.
[Major].[Minor].[Patch]-[label]
ex) 1.2.3-beta
package-lock.json 파일은 package.json에 선언된 패키지들이 설치될 때의 정확한 버전과 서로 간의 의존성을 표현합니다. 즉 팀원들 간에 정확한 개발환경을 공유할 수 있습니다.
만약 소스코드 내에 package-lock.json파일이 이미 존재한다면 npm install 명령을 수행할 때 이 파일을 기준으로 패키지들을 설치하게 됩니다.
타입스크립트는 마이크로소프트에서 개발한 언어입니다. 자바스크립트 코드에 타입 시스템을 도입하여 런타임에러가 발생할 가능성이 있는 코드를 정적 프로그램 분석으로 찾아줍니다.
타입스크립트가 제공하는 타입추론은 타입오류로 인해 런타임에 발생할 오류를 컴파일타임에 잡아줍니다. IDE에서는 소스 코드에 에러를 표시해주므로 일일이 컴파일을 실행하지 않아도 됩니다.
데커레이터를 잘 사용하면 횡단 관심사를 분리하여 관점 지향 프로그래밍을 적용한 코드를 작성할 수 있습니다.
클래스. 메서드, 접근자, 프로퍼티, 매개변수에 적용가능합니다. 각 선언부 앞에 @로 시작하는 데커리에터를 선언하면 데커레이터로 구현된 코드를 함께 실행합니다.
nestJS에서는 experimentalDecorators 옵션을 켜야 데커레이터를 사용할 수 있습니다.
{
"compilerOptions": {
...
"experimentalDecorators": true,
...
}
}
@뒤에 오는 선언은 런타임과 함께 호출되는 함수여야 합니다. 메서드 데커레이터로 사용하기 위해서는 이렇게 정의해야합니다.
function deco(target: any, propertykey: string, descriptor : PropertyDescriptor){
console.log('데커레이터가 평가됨');
}
class TestClass{
@deco
test(){
console.log('함수 호출됨')
}
}
const t = new TestClass();
t.test();
만약 데커레이터에 인수를 넘겨 데커레이터의 동작을 변경하고 싶다면 리턴하는 함수를 만들면 됩니다.
function deco(value: string){
console.log('데커레이터가 평가됨');
return funtion (target: any, propertykey: string, descriptor : PropertyDescriptor){
console.log(value);
}
}
class TestClass{
@deco
test('Hello'){
console.log('함수 호출됨')
}
}
const t = new TestClass();
t.test();
여러 개의 데커레이터를 사용한다면 수학의 함수 합성과 괕이 데커레이터를 합성하면 됩니다. f(g(x))와 같습니다.
@f
@g
test
클레스 데커레이터는 클래스이 생성자에 적용되어 클래스의 정의를 읽거나 수정할 수 있습니다. 선언 파일과 선언 클래스 내에서는 사용할 수 없습니다.
메서드의 속성 설명자에 적용되고 메서드의 정의를 읽거나 수정할 수 있습니다. 메서드 데코레이터는 3개의 인수를 가집니다.
접근자의 속성 설명자에 적용되고, 접근자의 정의를 읽거나 수정할 수 있습니다.
클래스의 속성 바로 앞에 선언됩니다.
