Express를 사용해야 할 이유 (2) - Microframework

이 글은 microframework라는 개념과 express의 용도를 연관지어 생각해본다. 이 글은 기술적으로 사실이라고 검증되지 않은 내용이 포함되며 데이터에 근거한 결론보다 생각 위주로 작성됨을 미리 알린다.


대략 2주 전 Express를 사용해야 할 이유 (1)을 쓰면서 왜 Express를 많이 쓰는걸까? 생각을 많이 해봤지만 결론을 내리지 못 했다. 아래는 당시 글에 작성했던 intro이다.

잠시나마 사용해본 Express는 내게 React 같았다. 무엇이든 할 수 있어 보였으나 직접 하기에는 매우 불편하고, 그러다보니 REST API를 작성할 때 이런 것까지 해야 돼? 혹은 이런 기능이 없어서 불편하네 등이 많았는데…

오늘이 되어서야 왜 Express가 기능이 적은지 알게 되었는데, 그 마법의 키워드는 바로 Microframework이다. (진지하게 이 키워드에 대해 오늘 이전에 들어본 적이 단 한 번도 없었다.)

Microframework란?

1. 정의: 최소 기능을 갖는 웹 애플리케이션 프레임워크 <-> full-fledged framework (필요한 기능은 대부분 갖춘 프레임워크를 의미)

2. 기능: Microframework는 서비스 개발 시 “주로“, “일반적으로“ 사용되는 공통적인 기능들을 제공하지 않는다. 제공하지 않는다는 그 기능들이란 대체로 아래와 같다.

  • 인증, 인가
  • ORM 혹은 DB 관련 기능
  • 입력값 검증 / 보안 (Validation, Sanitation)
  • 템플릿 엔진

3. 목적: Microframework는 작은 API 서버를 제작하는게 목적이다.

정리하자면 Microframework는 기능의 다양성이나 설계의 편리성보다 기능의 단순성이 더 우선된 프레임워크이다.

따라서 규모 있게 Monolith로 제작하는 경우 full-fledged framework를 사용하는 게 맞다고 본다. 실제로 Walamrt는 Node.js 기반으로 백엔드를 구성했지만 Commerce 기업이라 Hapi.js(full-fledged framework)를 직접 만들어서 사용하기도 하고 말이다.


Microframework의 종류

아래는 주요 언어의 Microframework의 목록의 일부다.

(더 많은 목록은 위키 백과 Microframework 문서 (EN)를 참고 바람.)

예상한 대로 Express.js도 microframework로 등재돼있다. Express 소개문에 “Fast, unopinionated, minimalist web framework for Node.js“ 라고 괜히 되어 있는 것이 아니다.

Netflix에서는 Restify라는 프레임워크를 예전부터 사용 중인데, Restify도 Microframework이다. Restify는 Semantically correct RESTful 을 지향한다. 온전히 REST API를 위한 기능만 제공하므로 Express 등에서 제공하는 템플릿 엔진 조차 없는데 기능의 단순함 측면에서 더 매력적이라고 할 수 있다. Restify는 또한 Connect 미들웨어를 지원하므로 Express 미들웨어와 호환된다.

아래는 Restify 공식 홈페이지에 나와있는 소개이다.

Meet Restify - A Node.js web service framework optimized for building semantically correct RESTful web services ready for production use at scale. restify optimizes for introspection and performance, and is used in some of the largest Node.js deployments on Earth.


Microframework가 Microservices에 좋을까?

아직 MSA에 대해 공부한 적 없기도 하고 이런 지식들이 쉬운 편이 아니라 좀 배워야 키워드 검색이 가능하기 때문에 Youtube와 여러 글들을 읽어보면서 느낀 점을 적으려 한다.

왜 restify를 사용하는지 추측해보자면 굉장히 많은 컨테이너에서 돌아가는 MSA를 구축할 때 작은 서비스가 유리하기 때문이 아닐까? Netflix는 (다른 기업들이 응당 그러하듯) MSA로 개발할 때 기능 단위로 분리하는데, Monolithic Service에 들어갈 많은 기능들이 기본 제공되지 않는 프레임워크가 가벼워지는데 유리한 것이다. (Netflix는 의사 결정 시 성능에 우선순위를 두는 듯 하다. 13년도에 Java에서 Node.js로의 이주를 시작한 것만 봐도.)


Node.js를 쓰는 대부분의 use case가 microframework가 필요해서일까?

이전 글 내용 중 다운로드 수를 비교한 자료가 있었는데 Express가 압도적이었다. 같은 microframework이며 벤치마크(hello world)도 더 우수한 Koa가 그렇게 많이 사용되지 않는 점(약 21배 차이)은 이전 글에서도 다뤘듯 async/await 문법의 지원과 커뮤니티의 차이 때문임으로 보인다.

다운로드 수에 대한 요즘 생각:

  1. 러닝 커브. 많은 사람들이 Javascript가 배우기 쉽다고 말하며 그렇게 진입하는 사람이 적지 않다. 그저 React.js와 Express.js를 기초적인 수준에서 사용하는데 머무는 사람들이 정말 많다고 생각한다.

  2. Netflix처럼 애초에 Managed로 환경 구성이 잘 된 경우라면 npm에서 다운로드 수 집계가 제대로 되지 않을 것 같다. (Proxy를 통한 캐싱 등) 즉 npm 다운로드가 현업 개발 시의 실제 사용 빈도를 정확히 나타내는 것도 아닐 수 있을 것이다.

따라서 단순히 npm 다운로드 수로 비교하는 것보단 실제로 돈을 벌고 많은 트래픽을 처리하는 기업에서 무슨 스택을 사용하는지가 더 중요할 것으로 보인다.

Hapi Koa Nest Express
img img img img

(참고로 restify는 12만, fastify는 19만 정도 된다. - fastify의 경우 Nest.js에서 사용되는 면이 있으니 참고)


다운로드 수를 유일한 척도로 삼고 맹신해서는 안 될 것 같다.

Express의 점유율이 매우 높다는 것을 microframework를 사용하는 숫자가 Node.js 백엔드 개발자 중에서 대다수라고 받아들이면 안 될 듯하다. Netflix의 경우는 MSA를 도입했기 때문이지만 트래픽이 많지 않은 기업들의 경우 아직 Monolithic이거나 작은 규모의 분산 시스템이면 충분할 거라고 생각하기 때문이다.

Node.js의 경우 …

  • Javascript를 프론트엔드와의 공용어로 사용할 수 있다는 점
  • SSR 시 코드 재사용이 가능하다는 점
  • 백엔드 언어와 Javascript 간의 Context Switching이 사라진다는 점
  • Interpreter 언어여서 Startup이 매우 빠르다는 점
  • 모듈 생태계가 크다는 점이 장점

이런 장점 속에서 굳이 프레임워크에서 가치를 찾지 않으려는 경우도 많지 않을까?

다운로드 수가 많다고 해서 Express를 사용할 이유는 없다. 1편에서도 밝혔듯이 Javascript와 Node.js의 장점 자체도 이미 많으며 Express에서 조금 고생하면서 Monolithic 서비스 개발하는 것은 점진적으로 러닝 커브가 올라가는 형태라고 생각하고, 딱 그 정도 수준이 필요한 기업도 많을 거라고 생각한다.

아직 결론은 내릴 수 없을 것 같다.

아직 Node.js의 정수를 다 배우지 못 했기 때문에, 아직 MSA를 배우지 못 했기 때문에 정확한 판단을 내릴 수가 없다. 이것들을 어느 정도 습득하고 나서 정말 왜 Express가, Express만이 이렇게 잘 팔리는 이유를 분석할 수 있으면 좋겠다.


What I Learned

  1. Netflix는 기술 블로그와 컨퍼런스를 통해 정말 많은 기술적인 내용들을 공유한다는 걸 오늘 리서치하면서 배웠다. Youtube의 경우 여러 채널에 Video가 산재돼있는데 이 Playlist가 좋은 것 같다.

  2. 아직도 Node.js 생태계에 대해 제대로 이해하지 못 하고 있다는 점을 또 알게됐다. 아직 해결되지 못한 질문들에 대해 데이터를 찾아서 반드시 답을 내리고 싶다.

Express를 사용해야 할 이유 (1) - 생태계 조사

잠시나마 사용해본 Express는 내게 React 같았다. 무엇이든 할 수 있어 보였으나 직접 하기에는 매우 불편하고, 그러다보니 REST API를 작성할 때 이런 것까지 해야 돼? 혹은 이런 기능이 없어서 불편하네 등이 많았는데 이번에 알아보려고 한다. 얼마나 많이 Express를 사용하며, 왜 Express를 사용하는지 팩트 위주로 체크해봤다.


1. Node와 Express의 장점을 헷갈리면 안 된다.

대부분의 웹사이트에서 소개하는 Express의 장점들은 Javascript, Node.js의 장점들이었다. 많은 글을 읽어보아도 Express의 장점을 소개하는 글은 많이 없었고 대부분 Node.js의 장점을 소개하고 있었다.

Express가 Node 기반인 게 큰 장점이라는 걸까… 그래서 Node.js와 같은 목적으로 생성된 프레임워크/런타임을 조사해보았다.


2. Reactor Pattern을 구현한 프레임워크/런타임

A. 역시 Node.js만 있는 것은 아니었다. Javascript를 깊게 배우고 생태계를 옮겨 탈 바에 기존에 사용하던 언어로 작업하는 게 현실적이긴하다.

Lang Sync Framework Async Framework
Java Spring Web MVC Spring WebFlux (Reactor Pattern), Vert.x (JVM 기반)
Python Flask, Django FastAPI, Tornado, Sanic, … (꽤 많다.)
Javascript - *

다른 언어에 대해선 찾아보지 않았지만 Java, Python이 점유율이 큰 언어들이므로 충분하다고 생각한다. 벤치마크를 찾아보진 않았지만 같은 패턴을 기반으로 제작됐기 때문에 실제 서비스로 구현했을 땐 성능 면에서도 비슷할 것으로 예상된다.

다만 Node.js의 장점이라면, 선천적으로 비동기 API가 장려되어왔기 때문에 비동기 API로 작성된 라이브러리 활용 면에서 낫지 않을까 생각한다.

Spring Web Flux 구조 - Node.js EventLoop과 유사

출처:


3. Node.js 백엔드 프레임워크 간의 점유율/만족도 비교

제대로 비교하기 전에 통계 자료부터 확인하자.


점유율 요약 (아래 그림):

  • Express의 점유율이 압도적이다.
  • Koa, Hapi 라는 네임드의 점유율이 꽤 낮다.
  • 서비스 개발에 가장 유리할 거라고 생각했던 Nest.js의 점유율이 13%밖에 안돼서 의문이다.

2020 점유율 순위

Hapi Koa Nest Express

물론 Express를 기반으로 하는 다른 프레임워크 등이 어느 정도 반영됐을 것이긴 하다. Nest도 처음에는 Express 기반이었으니까. 그래도 다운로드 수의 큰 차이를 보면 나머지 프레임워크의 시장성이 의심되긴 한다.


만족도 비교 (아래 그림):

  • Express는 점유율에 이어 만족도도 최상위권이다.
  • Nest.js가 5% point 정도의 차이가 있지만 준수한 편이다.
  • Koa의 만족도가 76%인 점인 이유는 장점이었던 동기식 코딩 방식인 async-await이 표준화됐기 때문임으로 보인다.
  • Hapi는 만족도가 매우 낮은 것으로 보아 사용할 수 없겠다는 생각이 들었다. (추후 조사를 해봐야겠다.)

2020 만족도 순위

출처: 2020 State Of JS (한국어 번역)


4. 왜 이렇게 Express를 많이 쓰는 걸까?

정말 Express가 좋은걸까?

다른 언어의 프레임워크를 비교해봤을 때 솔직히 좋다고 하진 못 할것 같다.


1. 단순함 (+0)

정말 많은 블로그에서 Express의 최장점을 단순함으로 꼽고 있었는데 장점보다는 목적에 가까운 것이라 생각한다. 목표에 따라 단순함은 장점이 될 수도, 단점이 될 수도 있기 때문이다. 단순함을 장점으로 꼽는 경우 둘 중 하나이다.

  • Rich Framework를 감당할 만큼 숙련된 개발자로 채우기 어려운 조직이거나
  • 애초에 큰 규모의 서비스를 작성하기 위해 Express를 사용하지 않거나

만약 서비스 개발을 위해 Express를 사용한다면 단순함은 직접적인 단점이 된다.

  • 기본적인 의존성만 담은 Boilerplate(1.4k stars)만 보더라도 같이 깔아야 할 라이브러리들이 많아 학습 곡선이 가팔라진다.

  • 처음 입문하는 경우 미들웨어들을 직접 찾는 추가적인 일을 하게 된다. (한 프레임워크 내에서 찾는 것과 대조적.)

  • Rich Framework 들에 비해 설계를 너무 근본적인 것들부터 해야 해 오히려 설계 측면에선 난도가 높다. (DI/IOC가 없고 여러 라이브러리를 비교 분석 후 사용해야 함.)

다만 적절한 Boilerplate를 찾으면 이 문제가 어느 정도 해소된다는 점과 이후 단락에서 소개할 내용들을 통해 단순함의 단점을 상쇄할 수 있다.

Node.js는 출시 후 아직까지도 작은 서비스를 만드는 데 적합하다는, 프로토 타이핑 위주라는 인식이 남아 있는 것 같고, 그런 용도로 채택하여 단순함이 종종 장점이 되는 것 같기도 하다.


2. Express Middleware (+0)

어떤 언어, 프레임워크로 웹 개발을 하더라도 Express에 미들웨어에 해당하는 계층에서 확장성을 가져가는 것은 기본이지 특별한 기능은 아니다. 또한 Express에서 제공하던 자체 Middleware들은 모두 Connect 미들웨어 라이브러리옮겨갔다. Next.js에서는 이 미들웨어들을 지원하는데, 그럼 다른 프레임워크에서도 의도하기만 하면 재사용 할 수 있는 셈이다. (의존성이 req, res, next 인자 밖에 없으니.)


3. Community (+3)

Express는 꽤 많은 사용자 풀을 보유하고 있다. 이미 사용자가 많아 검색을 통한 문제 해결이 비교적 원활하다.

아래는 StackOverFlow 트렌드인데 koa(js), hapi(js)는 태그로 잡히지도 않아서 비교가 불가능했다. 이는 생태계 조성이 거의 전무하다는 뜻인데 koa나 hapi는 출시된 지 시간이 지났음에도 이정도이며 특히 Hapi는 정말 작은 사용자 풀을 보여준다(사용하지 마세요).

StackOverFlow Trends: Express vs Nest vs Next


4. Async-await을 workaround로 쓸 수 있다. (+1)

Express v5 부터는 Response Handler 및 Middleware에서 async/await을 사용할 수 있지만 아직 Release 되지 않은 관계로 사용할 수는 없다.

Express QnA 이슈의 답변이다.

Q. How to use async/await in express 5?

A: There is one main difference between v4 and v5 when it comes to async/await and promises in general. In v5, if you return a promise from a response handler (or middleware), if that promise rejects and is not handled elsewhere, then Express will handle the error. It handles the rejection by passing the rejection reason to next for you.

v4에서도 async-await을 쓸 수 있는데, 아주 간단한 미들웨어 express-async-handler로 한 번 감싸주면 된다. (원리는 이 설명 참고) (같은 원리로 Promise도 처리 가능)

1
2
3
4
5
6
7
8
9
10
11
12
13
// find a user by id
router.get(
'/:id',
asyncHandler(async (req, res) => {
if (req.user.id === req.params.id) {
return res.status(403).send(FORBIDDEN);
}
const user = await UserRepository.findUserById(req.params.id);
if (!user) return res.status(404).send(NOT_FOUND);

res.json(user);
}),
);

5. Koa나 Express나 둘 다 개발은 하지 않는다. (+0)

Koa나 Express나 발전을 멈춘지 좀 됐다.

KoaJS는 2013년에 시작해 제너레이터 기반으로 미들웨어를 쉽게 작성하기 위해 나온 프레임워크인데, async-await 표준이 2017년 초부터 Node.js에서 공식적으로 지원되면서 그 의미가 퇴색되지 않았나 생각이 든다.

Express 역시 Documentation 위주의 유지보수, v5를 6-7년 째 안 내고 있긴 하다. (14, 15년도 쯤까지만 일한듯)


5. 서비스 개발 측면에선 NestJs가 더 낫지 않을까? (추후 보강 예정)

Express, Koa는 현재 사실상 유지보수가 되고 있지 않다. 프로젝트에서 돈을 벌지 못하기 때문인 것으로 보이는데, 기업 스폰서가 없으며 프레임워크도 간단해 기술 지원이 불가능해 수익 모델이 없다. (Hapi는 Walmart에서 사용 중이긴 하지만 너무 마이너하다. 왜 인기가 없을까?)

NestJS는 구조가 Angular의 영향을 받았다고 돼있지만 Spring과 유사한 구조와 개발자 경험을 제공한다고 생각하며, Spring은 그 기능과 복잡성을 통해 기술 지원으로 돈을 벌고 있기 때문에 NestJS가 이 모델을 구현한다면 긴 시간 유지보수를 해나갈 수 있을 것 같다.


정확히 무슨 벤치마크를 했는진 모르겠지만 성능 측면에서 NestJs-Fasitfy[현재 버전]가 Express보다 낫다고 한다. (출처)

Framework Req/sec Trans/sec Req/sec DIFF Trans/sec DIFF
Nest-Express 15370 3.17MB +4.38% +4.23%
Nest-Fastify 30001 4.38MB +2.20% +2.23%
Express 17208 3.53MB +8.38% +8.31%
Fastify 33578 4.87MB +6.55% +6.53%

NestJS에 대해선 추후 더 조사하려고 한다.


TODO

1. NestJS

NestJS는 다루는 양이 방대하기도 하고 앞의 리서치에서 시간을 너무 많이 사용해서 따로 시간을 내서 리서치하진 못 해서 다음 기회에 꼭 하도록 한다.

2. Fasify

한 번 조사해봐야 할 것 같다. async-await도 지원하며 제대로 관리되고 있는 것 같다.

아래는 README에 게시된 벤치마크인데 성능도 역시 좋고.

Framework Version Router? Requests/sec
Express 4.17.1 15,978
hapi 19.1.0 45,815
Restify 8.5.1 49,279
Koa 2.13.0 54,848
Fastify 3.0.0 78,956
-
http.Server 12.18.2 70,380

3. HapiJS

Hapi도 개발이 계속 진행 중이고 Nest처럼 Rich한 Framework를 목표로 하는 것 같고, Walmart에서 실제로 사용하면서 주도적으로 개발하다가 작년 중순부터 Community-driven으로 간다고 한다. 성장 가능성이 꽤 있는 것 같아서 시간이 나면 조사하면 좋을 것 같다. Facebook이 React를 만들어 프론트엔드 생태계를 많이 바꿔낸 것처럼.

4. Express In Action (2016)

이 책을 좀 더 읽어보고 Express의 가치를 발견하다면 정말 좋을 것 같다.

5. 기타

Promise, Async-await이 성능이 CPS 패턴에 비해 느리다는 의견이 종종 나왔는데 왜 그런지 확인해보기

D2에서 Node.js는 Socket.IO 때문에 떴다고 하던데 정말인지 확인해보기