본문으로 바로가기

웹 프레임워크(Web Framework)

웹 개발에 필수적인 요소들을 묶어 개발자들이 쉽게 쓸 수 있게 하고자 하는 시도가 생겨났고, 이를 웹 프레임워크라고 부릅니다.

 

데이터베이스에 연결을 설정하고 데이터를 관리하거나, 세션을 맺고 유지하는 등의 동작을 정해진 방법과 추상화된 인터페이스로 제공합니다. 이 방법들이 프레임워크 사용자의 자유도를 제약한다고 생각할 수 도 있지만 프레임워크에서 표준으로 제시하는 방법을 이용하면 쉽고 빠르게 안정적인 애플리케이션을 구축할 수 있습니다.

 

웹 프레임워크에는 다음과 같은 것들이 있습니다.(백엔드와 프론트엔드 구분X)

프레임워크 개발언어 설명
React JavaScript
TypeScript
SPA(Single Page Application)나 모바일 앱 개발에 사용된다.
최근 몇 년간 가장 인기있는 프론트엔드 프레임워크
가상 DOM(Virtual Document Object Model)을 사용한다.
페이스북이 주도하는 공동체에서 유지 보수되고 있다.
Vue.js JavaScript
TypeScript
React.js와 함께 인기있는 프론트엔드 프레임워크
SPA를 구축할 수 있다.
MVVM 패넡에서 VM(View Model)에 해당하는 라이브러리
속도가 빠르다
Angular TypeScript SPA를 위한 프레임워크면서 SSR을 지원한다.
모듈/컴포넌트 기반으로 작성하며 재사용성 높은 SW를 만들 수 있다.
2021년 말 Angular 1.x인 AngularJS의 LTS 지원이 중단되었다.
Svelte JavaScript
TypeScript
React, Vue.js를 제치고 최근 인기순위 1위로 등극한 프론트엔드 프레임워크
가상 DOM을 사용하지 않는다.
적은 코드로 구현이 가능하고, 이로 인한 유지 보수성이 좋다.
Express JavaScript
TypeScript
가장 많은 사용자를 가지고 있는 Node.js 기반 백엔드 프레임워크
가볍게 서버를 구동시킬 수 있다.
NestJS와 같이 Express를 기반으로 하는 프레임워크도 존재한다.
Spring Java
Kotlin
국내에서 인기가 높은 Java기반 프레임워크
전자정부프레임워크(eGov Framework)를 이용하는 프로젝트를 수행하기 위해서는 스프링을 알아야 하기 때문에 그 영향력이 크다.
IoC, DI, AOP와 같은 객체 지향 프로그래밍 기법을 쉽게 적용할 수 있다.
STRUTS Java Java기반의 JSP만을 위한 프레임워크
MVC 모델 기반의 웹 애플리케이션을 쉽게 작성할 수 있게 해준다.
(한 때 JSP가 많이 사용되었으나, 지금은... 추천하지 않는다.)
Django Python 파이썬 기반 웹 프레임워크 표준이라 할 수 있다.
MVC 패턴을 사용한다.
Gin Go Golang 기반의 웹 프레임워크
Ruby on Rails Ruby 간결한 문법
뛰어난 생산성으로 인기를 얻었다.
ActiveRecord를 이용하여 쿼리를 쉽게 다룰 수 있게 해준다.

이외에도 많은 프레임워크가 존재한다.

위에서 소개해 드린 것들은 국내에서 많이 사용되거나 인기있는 프레임워크입니다.

 

* 웹 프레임워크를 선택 시 고려사항
1. 개발 문서: 쉽게 이해할 수 있게 잘 쓰여진 개발 문서는 사용자의 생산성을 증대시켜 줍니다.
2. 사용자 수: 사용자 수가 많다는 것은 그 만큼 안정적으로 운용된다는 반증입니다.
3. 활성 커뮤니티: 요즘에는 언어나 특정 기술에 대한 개발자 커뮤니티가 많이 있습니다. 페이스북, 슬랙, 디스코드와 같은 채널 외에도 질문/답변 서비스를 제공하는 스택 오버 플로우에도 해당 기술을 키워드로 검색하면 얼마나 많은 사람들이 활동하고 있는지 가늠해 볼 수 있습니다.
4. 깃허브 스타 수와 이슈 대응: 대부분의 프레임워크는 오픈 소스로 개발하고 소스 코드가 깃허브에 공개되어 있습니다. 깃허브 스타 수는 그만큼 사람들이 인정하고 있다는 뜻입니다. 또 사용자들이 리포트하는 이슈가 얼마나 잘 대응되고 있는 지도 중요한 요소입니다. 개발이 멈춘 프로젝트는 최신 언어 트렌드와 아키텍처를 따라잡지 못하고 있을 수 있습니다.

 

Node.js

Nest는 Node.js를 기반으로 동작합니다. 정확히는 Nest로 작성한 소스코드를 Node.js 기반 프레임워크인 Express나 Fastify에서 실행 가능한 자바스크립 소스코드로 컴파일 해주는 역할을 합니다.

 

Node.js는 2009년에 릴리즈되었다.

Node.js는 NPM(Node Package Manager)이라고 하는 패키지(또는 라이브러리) 관리 시스템을 가지고 있습니다. 누구나 자신이 만든 Node.js 기반 라이브러리를 등록하여 다른 사람들이 사용하게 공개할 수 있습니다. 공개하기는 싫지만 NPM을 이용하여 사내에서 패키지를 관리하고자 한다면 돈을 지불하고 비공개(Private)로 등록도 가능합니다.

 

단일 쓰레드에서 구동되는 non-blocking I/O 이벤트 기반 비동기 방식

작업 요청이 한꺼번에 들어올 때 각 작업을 처리하기 위한 쓰레드를 만들고 할당하는 방식을 멀티 쓰레드 방식이라고 합니다.

멀티 쓰레드 방식은 여러 작업을 동시에 처리하므로 작업 처리속도가 빠른 장점이 있지만, 공유 자원을 관리하는 노력이 많이 들고

잘못 작성된 동기화로 인해 락에서 빠져나오지 못하는 경우가 발생하기 쉽습니다. 쓰레드가 늘어날 때 마다 메모리를 소모하게 되므로 메모리 관리 역시 중요합니다.

 

이에 비해 Node.js는 하나의 쓰레드에서 작업을 처리합니다.

사실 애플리케이션 단에서는 단일 쓰레드이지만 백그라운드에서는 쓰레드 풀을 구성해 작업을 처리합니다.

개발자가 직접 쓰레드 풀을 관리하지 않고 플랫폼, 정확히는 Node.js에 포함된 libuv가 그 역할을 하기 때문에 개발자는 단일 쓰레드에서 동작하는 것처럼 이해하기 쉬운 코드를 작성할 수 있습니다. 웹 서버를 운용할 때는 코어(CPU)를 분산해서 관리하므로 실제 작업은 여러개의 코어에서 별개로 처리됩니다.

 

* libuv: Node.js에서 사용하는 비동기 I/O 라이브러리다. 이 라이브러리는 C로 작성되었고 윈도우나 리눅스 커널을 추상화한다. 커널을 사용하여 처리할 수 있는 비동기 작업을 발견하면 바로 커널로 작업을 넘겨버린다. 이후 이 작업들이 종료되어 커널로부터 시스템 콜을 받으면 이벤트 루프에 콜백을 등록한다. 만약 커널이 지원하지 않는 작업일 경우 별도의 쓰레드에서 작업을 처리한다.

 

Node.js의 장단점

장점 단점
단일 쓰레드 이벤트 기반 비동기 방식은 서버의 자원에 크게 부하를 가하지  않습니다. 쓰레드를 하나만 사용하기 떄문에 하나의 쓰레드에 문제가 생기면 애플리케이션 전체가 오류를 일으킬 위험이 있다.
대규모 네트워크 애플리케이션을 개발하기에 적합 컴파일 언어의 처리속도에 비해 그 성능이 떨어진다.
하나의 쓰레드로 동작하는 것처럼 코드를 작성할 수 있다는 점은 개발자에게 큰 장점입니다. 이벤트 기반 비동기 방식으로 복잡한 기능을 구현하다보면 여러 이벤트를 동시에 처리하는 경우 콜백 지옥에 빠지는 경우가 있습니다. 코드가 대각선으로 쭉 들여쓰여 작성되면서 가독성이 떨어지고 이해하기 어려운 코드가 양산되는 경우가 빈번했습니다.
ECMAScript 2015(ES6)에서 Promise가 도입되면서 간결한 표현으로 작성할 수 있게 되었습니다.  
ECMAScript 2017에서는 async/await 기능이 추가되면서 비동기 동작을 마치 동기 처리로 처리하는 것처럼 코드를 작성할 수 있게 되었습니다.  

 

 

이벤트 루프

이벤트 루프는 시스템 커널에서 가능한 작업이 있다면 그 작업을 커널에 이관합니다. 자바스크립트가 단일 쓰레드 기반임에도 불구하고 Node.js가 non-blocking I/O 작업을 수행할 수 있도록 해주는 핵심 기능입니다.
공식문서: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
Paul Shan의 블로그: https://www.voidcanvas.com/nodejs-event-loop/

이벤트 루프에는 6개의 단계(Phase)가 있습니다.

각 단계는 단계마다 처리해야 하는 콜백 함수를 담기 위한 큐를 가지고 있습니다.

화살표는 각 단계가 전이되는 방향을 뜻하지만 이후에 설명하듯 반드시 다음 단계로 넘어가는 것은 아닙니다.

자바스크립트 코드는 idle & prepare 단계를 제외한 어느 단계에서나 실행될 수 있습니다. 

nextTickQueue microTaskQueue는 이벤트 루프의 구성요소는 아니고 이 큐에 들어 있는 작업 역시 이벤트 루프가 어느 단계에 있든지 실행될 수 있습니다. 

node main.js 명령어로 Node.js 애플리케이션을 콘솔에서 실행하면 Node.js는 먼저 이벤트 루프를 생성한 다음 메인 모듈인 main.js를 실행합니다.

이 과정에서 생성된 콜백들이 각 단계에 존재하는 큐에 들어가게 되는데 메인 모듈의 실행을 완료한 다음 이벤트 루프를 계속 실행할 지 결정합니다.

만약 큐가 모두 비어서 더 이상 수행할 작업이 없다면 Node.js는 루프를 빠져나가고 프로세스를 종료합니다.

Timer 단계

이벤트 루프는 Timer 단계에서 시작합니다.

Timer 단계의 큐에서는 setTimeout이나 setInterval과 같은 함수를 통해 만들어진 타이머들을 큐에 넣고 실행합니다.

now - registeredTime ≥ delta인 값을 가지는 타이머들이 큐에 들어갑니다.

여기서 deltasetTimeout(() => {}, delta)과 같이 타이머가 등록된 시각에서 얼마만큼 시간이 흐른 후에 동작해야 하는 지를 나타내는 값입니다. 즉, 대상 타이머들은 이미 실행할 시간이 같거나 지났다는 뜻입니다.

타이머들은 최소 힙으로 관리됩니다. 힙을 구성할 때 기준으로 실행할 시각이 가장 적게 남은 타이머가 힙의 루트가 됩니다. Timer 단계에서 최소 힙에 들어 있는 타이머들을 순차적으로 찾아 실행한 후 힙을 재구성합니다.

 

* 힙(Heap): 최댓값 또는 최솟값을 찾아내는 연산을 빠르게 하기 위해 고안된 완전이진트리(complate binary tree)를 기본으로 한 자료구조(tree-based structure)

 

Pending (i/o) 콜백 단계

이 단계의 큐에 들어있는 콜백들은 현재 돌고 있는 루프 이전의 작업에서 큐에 들어온 콜백입니다.

예를 들어 TCP 핸들러 내에서 비동기의 쓰기 작업을 한다면, TCP 통신과 쓰기 작업이 끝난 후 해당 작업의 콜백이 큐에 들어옵니다.

또 에러 핸들러 콜백도 pending_queue로 들어오게 됩니다.

 

Timer 단계를 거쳐 pending 콜백 단계에 들어오면 이전 작업들의 콜백이 pending_queue에서 대기중인지를 검사합니다.

만약 실행 대기 중이라면 시스템 실행 한도에 도달할 때까지 꺼내어 실행합니다.

 

Idle, Prepare 단계

Idle 단계는 매 틱(Tick, 매 단계가 이동하는 것을 의미)마다 실행됩니다.

Prepare 단계는 매 폴링마다 그 전에 실행됩니다.

이 두 단계는 Node.js의 내부 동작을 위한 것입니다.

 

Poll 단계

이벤트 루프 중 가장 중요한 단계입니다.

Poll 단계에서는 새로운 I/O 이벤트를 가져와서 관련 콜백을 수행합니다.

예를 들어 소켓 연결과 같은 새로운 커넥션을 맺거나 파일 읽기와 같이 데이터 처리를 받아들이게 됩니다.

이 단계가 가지고 있는 큐는 watch_queue입니다.

이 단계에 진입한 후 watch_queue가 비어있지 않다면 큐가 비거나 시스템 실행 한도에 다다를 떄까지 동기적으로 몯느 콜백을 실행합니다.. 만약 큐가 비게되면 Node.js는 곧바로 다음 단계로 이동하지 않고 check_queue(Check 단계의 큐), pending_queue(Pending 단계의 큐), closing_callbacks_queue(Close 단계의 큐)에 남은 작업이 있는지 검사한 다음 작업이 있다면 다음 단계로 이동합니다.

만약 큐가 모두 비어서 해야할 작업이 없다면 잠시 대기를 하게 됩니다.

이때 대기시간은 타이머 최소 힙의 첫번째 타이머를 꺼내어 지금 실행할 수 있는 상태라면 그 시간만큼 대기한 후 다음 단계로 이동합니다.

이렇게 하는 이유는 바로 타이머 단계로 넘어간다고 해도 어차피 첫번째 타이머를 수행할 시간이 되지 않았기 때문에 이벤트 루프를 한 번 더 돌아야 하므로 Poll 단계에서 시간을 보내는 것입니다.

 

Check 단계

Check 단계는 setImmediate의 콜백만을 위한 단계입니다.

역시 큐가 비거나 시스템 실행 한도에 도달할 때 까지 콜백을 수행합니다.

 

Close 콜백 단계

socket.on('close', () => {})과 같은 close나 destroy 이벤트 타입의 콜백이 여기서 처리됩니다.

이벤트 루프는 Close 콜백 단계를 마치고 나면 다음 루프에서 처리해야 하는 작업이 남아 있는지 검사합니다.

만약 작업이 남아 있다면 Timer 단계부터 한 번 더 루프를 돌게 되고 아니라면 루프를 종료합니다.

 

nextTickQueue과 microTaskQueue

nextTickQueue는 process.nextTick() API의 콜백들을 가지고 있으며,

microTaskQueue는 Resolve된 Promise의 콜백을 가지고 있습니다.

이 두 개의 큐는 기술적으로 이벤트 루프의 일부가 아닙니다. 즉, libuv 라이브러리에 포함된 것이 아니라 Node.js에 포함된 기술입니다.

이 두 큐에 들어 있는 콜백은 단계를 넘어가는 과정에서 먼저 실행하게 됩니다.

nextTickQueue가 microTaskQueue보다 높은 우선순위를 가지고 있습니다.

 

 

참고: NestJS로 배우는 백엔드 프로그래밍

반응형