나는 정말 React.js 가 싫다고요 (Feat. HTMX..)

공부는 혼자하는 거·2023년 9월 9일
4

잡담

목록 보기
9/11

오늘날 웹 개발에 대한 단상

패러다임의 변화.. SPA 프레임워크

미안하다. 제목은 어그로를 끌었다. 나는 사실 React.js 를 좋아한다. 제법 매력적이라고 생각한다. 어그로를 끄는 이유는 React.js로 대변되는 모던 SPA 프레임워크들이 과연 웹 개발에 있어서 더 나은 방향이라는 것에 의문을 던지는 데 있다. React.js를 처음 맞닥뜨리자마자, 기존의 웹 개발 패러다임과 확연히 다르다는 것을 바로 알아채릴 수 있었다. 생소한 것은 가상돔을 활용, 컴포넌트 기반의 웹 개발방식, JSX 등등, 이런 게 아니었다. 데이터를 주고받는 방식 자체에 있어서의 극단적인 이동이었다.

생각해보면, 나는 전통적인 MVC 기반의 웹 프레임워크 구조로 개발하는 데 사고가 익숙해져있었다. 그리고 MVC 기반의 웹 프레임워크의 철학은 각자의 레이어의 역할과 책임을 분명히 하도록 하는데 있다. 여기서 View Layer의 역할이란 단순히 서버의 상태를 반영하는 거울로서의 역할에 충실한다.

이러한 류의 웹 프레임워크에서는 대부분 성숙한 형태의 HTML Template Engine이 존재하고, 대부분의 랜더링은 서버에서 로직을 수행하며 완성된 형태의 페이지로(HTML)로 브라우저에 응답한다.(SSR) 일부 원활한 사용자 인터페이스를 위하여, 특정 부분은 클라이언트 사이드에서 AJAX 해서 완전한 페이지가 아닌 데이터만을 응답받고 돔을 직접 조작하여 업데이트하는 식으로,(CSR) 구현한다.

반면 SPA 프레임워크에서는 HTTP 기반 인터페이스를 사용한다는 측면에서는 동일하지만, 서버 API를 데이터 기반 API로 한정시켜버렸다.

하이퍼미디어 API vs 데이터 API

하이퍼미디어 API는 하이퍼미디어(일반적으로 HTML over)를 반환하는 API이다. 이와 구별되는 것은 하이퍼미디어를 반환하지 않는 데이터 API이다. 일반적으로 우리에게 익숙한 스타일인 JSON API 를 생각하면 된다.

오늘날 웹 API는 SPA 프레임워크의 등장으로 인해 데이터 지향 API로서 굳어졌다. SPA 프레임워크는 데이터를 받아와 화면의 랜더링 부분을 클라이언트 스스로를 통해서만 조작하도록 만들었다. 웹에 있어서 그림판은 브라우저다. 웹의 정의는 그보다 더 넓으나 현실적으로 우리는 대개 브라우저를 통해서 거미줄처럼 얽힌 인터넷 세계를 탐험한다. 그리고 알다시피 브라우저가 해석할 수 있는 언어는 JS가 유일하다. 물론 WASM 이야기를 꺼낼 수도 있지만 나중으로 미루도록 하자..

오늘날 웹 애플리케이션을 빌드하는 일반적인 접근 방식은 JSON 데이터 API를 만든 다음 React와 같은 JavaScript 프레임워크를 사용해 프런트 엔드 코드를 분리하고 연결짓는 것이다.

디커플링

디커플링은 종종 데이터 API의 장점으로 주장되기도 한다. 다만 때때로 디커플링은 단점이 되기도 한다. 더군다나 진짜로 디커플링해지냐도 봐야 될 문제다. 다음은 JSON API 엔드포인트의 응답의 예시다.

HTTP/1.1 200 OK

{
    "account_number": 12345,
    "balance": {
        "currency": "usd",
        "value": 100.00
     },
     "status": "good"
}

이 데이터 API는 웹 애플리케이션, 모바일 클라이언트, 데스크톱 앱, 타사 서버 등 HTTP 인터페이스를 사용할 수 있는 모든 클라이언트에서 사용할 수 있다. 특정 클라이언트와 긴밀하게 연결되지 않고 분리됨으로써 얻는 이점이다. 그러나 실제로 이 이점은 잘 작동할까?

내 개인적인 경험 + 여러가지 사례에서 느낌 것은 범용 API는 실제 비즈니스 로직에서 깔끔하게 구현하는 게 거의 불가능하다는 것이다. 거의 같은 도메인과 비즈니스 로직을 공유하는 앱과 웹의 컴포넌트라도 둘이 요구하는 데이터는 미묘하게 다르다. 필자의 사례를 생각해보면, 결국 하나의 API 엔드포인트에서 각각의 클라이언트가 필요한 모든 정보를 다 꾸겨넣어서 전달하도록 설계를 했다. 범용 API는 쉽게 뚱뚱해지고 더러워진다. 더군다나 사용하는 클라이언트 측 어느 한 쪽이 업데이트를 하게 되면, (알다시피 UI는 기획에 의해 급변한다..) 필요한 정보가 달라진다. 그렇다면 반대로 API들을 최소 단위로 쪼개면 어떨까.

결과는 단일 페이지를 렌더링하기 위해 수십개의 요청을 보내는 것으로 귀결됐다. 어떤 식으로든 만족스러운 답안은 아니다. 이 부분에 있어 하나의 가능한 솔루션은 GraphQL을 사용하는 것이다. 그러나 GraphQL은 내부적으로 여러가지 잠재적 위험요소가 존재하며, 프론트엔드에게 너무나 많은 권력을 쥐게 만든다. 그리고 일반적으로 클라이언트는 서버에 무지하므로, 서버의 성능과 속도를 위험하게 만들 가능성을 기하급수적으로 늘어나게 한다. 관리 포인트가 늘어나면 늘어날수록 복잡도의 증가는 늘어난다. 내게 있어 전혀 선택지가 아니었다.

나는 결국 특정 클라이언트에 특화된 버전의 API를 제공해주는 식으로 분리했다.

SPA 장점..?

일반적인 SSR은 서버에서 전체 페이지 리로드를 요구하기 때문에 투박하고 거친 UX를 가져다준다. SPA 프레임워크는 단일 페이지 내에서 컴포넌트 기반으로 랜더링을 수행하므로, 훨씬 부드러운 UX를 가져다준다. JS를 적극적으로 사용함으로서 일반적인 웹에서의 경험이 아닌 웹앱으로서의 기능을 우리에게 선사해줄 수 있다. 브라우저는 계속해서 강력해져가고 있으며 JS 기반 SPA 프레임워크는 그 기능을 적극적으로 활용하는 데 특화되어있다.

하지만 사실 SPA 프레임워크들의 장점들로 언급되는 것은(컴포넌트 기반 개발, 자체 추상화 랜더링 레이어 제공 등등..) 다른 접근 방식으로도 충분히 구현가능한 점이므로, 얘만의 특별한 장점이라고 말하기는 애매하다. 나의 생각은, 이러한 SPA 프레임워크를 사용함으로서 얻는 본질적인 큰 이점은 다른 것이라고 본다.

또 다시 디커플링

프론트엔드에게 더 많은 선택지가 생기게 된 것이다. 이제는 서버 사이드와 분리해서 자체적인 개발 및 유지 보수가 가능해지게 되었으며, 분리된 아키텍처에서는 각각의 컴포넌트를 독립적으로 스케일링하는 게 용이해졌다. 나는 이것이 근본적으로 SPA를 사용함으로서 얻는 이점이며 문제의 시초라고 생각한다. 프론트엔드가 독자적인 영역을 선점했다는 거 그 부분에 있다.

SPA 문제

1. 부풀어오른 JS 빌드 체인

리액트는 자기만의 독단적인 논리와 개발방식을 가지고 있다. 종종 리액트에서 제공하는 문법은 사람들의 혼란을 불러일으키다. 하지만 문제는 프레임워크(리액트가 프레임워크냐 라이브러리냐에 대한 논쟁이 있다는 것을 필자도 알고있다. 필자 생각에 테크니컬 적으로 그 자체는 라이브러리가 맞지만 실질적으로는 프레임워크로 봐야되지 않냐는 견해이다) 그 자체의 복잡성과 문법에 있는 것이 아니다. 예를 들어 스벨트나 Vue.js 같은 경우, 비교적 단순한 문법과 구조를 취하고 있으며 뛰어난 개발자 경험과 생산성을 가져다준다. 문제는 그 기반을 구축하는 JS 생태계 자체에 있다.

오늘날 SPA 프레임워크를 사용하는데 있어서, 필요한 JS 의존성 싸이클은 매우 복잡하다.

package.json
.eslintrc.json
.prettierrc
.babel.config.js
.webpack.config.js or vite.config.ts
jest.config.js
.env
.npmrc
.editorconfig
 ... 

Node.js만 설치한다고 다가 아니다. 기본적으로 빌드를 위해, 위와 같은 설정파일들이 필요하다. 심지어 이 과정은 통일되지 않았으며, 표준이라고 부를만한 것도 딱히 없다. TS를 사용하고자 친다면 또 별도의 Config 파일이 필요한 것은 물론이다. 아마 예전에 웹 개발을 하던 사람이 본다면 깜짝 놀랄만한 일일 것이다. 단순히 View Layer 하나를 위해 이렇게 많은 빌드 스텝이 필요하다고? 오늘날 자바 스크립트 생태계는 지나치게 복잡하며 불안정하며 클라이언트는 아주 쉽게 뚱뚱해진다.

그리고 이 모든 복잡성은 프론트엔드 개발과, 벡엔드 개발의 단절을 불러일으켰다.

벡엔드와 프론트엔드의 분리

이러한 생태계 구성은 결과적으로 웹 상에서 벡엔드 생태계와 프론트엔드 생태게를 떨어뜨리는 역할을 나눴다. 혹자는 웹이 점점 더 고도화되고 강력해짐에 따라, 전문 웹 프론트엔드 분야가 생겨났다고 하지만, 내가 생각하기에 진짜 이유는 다른 데 있는 게 아니다. Node.js의 등장과 함께, JS 에코 시스템의 과잉확장이 그 원인이다.

그리고 문제는 View쪽 레이어가 따로 떨어져나갔다는 것 그 자체에 있다. 누군가 웹 개발에 있어서 뭐가 제일 짜증나냐고 묻는다면, 나는 캐싱이라고 말할 것이다. 캐싱은 광범위한 용어이다. 그것이 서버상에서의 캐싱이 될 수도 있고, 클라이언트 측에서의 캐싱이 될 수도 있고, 컴파일러 상에서의 캐싱이 될 수도 있다. 아무튼 짜증나는 것은 상태를 일치시킨는 부분이다. 뭐든 개발할 때 캐싱 부분이 제일 짜증나고 버그도 많이 발생하는 부분이다. 정말 문제는 React.js 와 같은 SPA 프론트엔드 생태계가 이러한 부분을 강화시킨다는 것이다.

View Layer가 따로 떨어져 나감으로 인해 프론트엔드는 자신만의 독자적인 논리흐름과, 상태를 가지게 되었다. 클라이언트는 점점 더 두껍게 변해가고, 이 부분을 끈임없이 서버쪽 로직과 일치시켜야 하는 임무가 부여됐다. 벡엔드 개발자로서 가장 골치아픈 점은, 프론트엔드 개발자를 위한 API를 설계하는 것이다. 여기에는 수많은 커뮤니케이션의 미스와 함께, 적극적인 API 리팩토링을 주저하게 하는 요소가 다수 산재해 있다.

애플리케이션 수준에서 응답이 "프론트 엔드"와 더 긴밀하게 결합될 수록 서버는 더욱 공격적으로 전략을 개선하는 데 개입할 수 있다. 범용 API를 염두에 두지 않을수록, API를 일반화하고 애플리케이션에 필요한 콘텐츠만 생성해야 한다는 부담을 덜 수 있다. 엔드포인트는 도메인 모델에 대한 범용 데이터 액세스 모델이 아니라 특정 애플리케이션 UI/UX 요구사항을 지원하도록 최적화될 수 있다.

기억을 되짚어보자..

예전의 기억을 떠올려본다. 나는 처음 웹 개발을 PHP를 통해서 접하게 되었다. 당시 대학교 교양수업 중 하나가 PHP로 배우는 웹 개발? 뭐 비슷한 이름의 과목이 있어서, 그 과목을 수강하며 웹 개발을 처음으로 접하게 되었다. 교재의 내용을 따라 XAMPP를 설치하고, MySQL, Apache, PHP를 이용해서 코딩을 하였다. NO Framework, 순수히 PHP와 HTML, CSS를 섞어쓰면서 게시판을 완성해나갔다. php 코드와 HTML/CSS 코드가 더럽고 혼잡하게 엮어있었던 코드였지만, 적어도 그 과정에서 나는 완전한 주도권을 쥐고 있었다.

그 이후 시간이 꽤 지나, 본격적으로 스프링 MVC를 사용하여 웹 개발을 하기 시작했다. 프레임워크를 적극 사용하여 기존의 상용구 코드를 대체하고 MVC 구조를 따라, 최소한의 로직만을 View Layer에서 작성했다. 당시 View Layer로 mustache + JQuery를 사용했다. 복잡한 사이트는 아니었지만 그럭저럭 잘 돌아가는 사이트를 개발했다.

가끔 가다 나는 옛날의 MVC 패턴의 개발이 왜 대세가 되었는가를 생각해보곤 한다. 통제하고 관리하는 구획이 단일 코드 베이스 내에 정해져있다. 여기에는 새로운 요구 사항에 맞게 API를 완전히 재작업하는 데 부담이 없다. View는 단순히 서버의 상태를 비추는 거울에 지나지 않는다. 또한 골치 아픈 CORS 정책과 같은 보안 사항과 프론트엔드와 벡엔드간의 치열한 신경전, SEO의 까다로움들을 신경쓰지 않을 수 있었다.

나는 가끔 우리가 일반적인 단순한 웹 서비스에서조차 View Layer에 너무나 많은 권력을 부여한다고 생각한다.

대안..?

HTMX

HTMX는 현재 내가 가장 좋아하는 기술스택 중 하나다. HTML 요소에서 직접 AJAX 요청을 수행하고, CSS 전환을 트리거하고, WebSocket 및 서버 전송 이벤트를 호출을 도와주는 싱글파일 자바스크립트 라이브러리이다. 마치 예전의 서버 주도 웹 개발 방식을 현대적으로 탈바꿈한 형태로 느껴진다. 익숙히고 오래된 형태지만 더 간편해졌다. 내가 HTMX에서 정말로 좋아하는 점 한가지는 별도의 빌드단계를 지니지 않는다는 것이다. 실행하는 데 다른 자바스크립트 패키지가 필요하지 않고, 오직 JS 런타임 환경만 있다면 어디든 동작한다. 대부분의 브라우저와 호환되며 추가 설정없이 기존 프로젝트와 통합된다.

사용하는 것은 무척 간편하다, 단순히 파일의 위치를 SCRIPT 태그에 집어넣기만 하면 된다.

  <script src="https://unpkg.com/htmx.org@1.9.5"></script>

추상화된 AJAX 레이어를 제공해주며, 서버에서 템플릿 조각(html) 을 내려주면, 화면의 프레그먼트만 타겟으로 갈아끼울 수 있다. 기존의 SSR의 단점으로 언급되었던 전체 페이지 리로드를 요구하지 않는다. 문법은 매우 간단하며 직관적이다. 만약 본인이 원한다면 이 점을 활용해 SPA 로 만들 수도 있다.

기존의 클라이언트 사이드에서 JQuery를 사용해서 Dom을 조작했던 사람이라면, 그 과정을 HTMX가 더 HTML 친화적으로 추상화했다고 생각하면 될 것이다. 기존 MVC 웹 프레임워크에 아주 찰떡으로 딱 달라붙는다. SPA 프레임워크를 사용하는 사람이라면 굳이 쓸 필요가 없다. 하지만 그렇지 않다면, 꼭 사용해보길 추천하다. 아주 가벼운 라이브러리에 불과함으로, 여전히 다른 JS EcoSystem이 필요하다면, 갖다 쓰면 된다.

추천하는 방법은, 모던 JS Build chain에서 탈출하여, 전통의 방법대로 CDN을 이용해 라이브러리를 가져다 쓰는 걸 추천하다. TS에 집착하지 마라, 만약 MVC 프레임워크를 사용하며 MVC의 원칙대로 웹 개발을 하고 있다면, 내 생각에 View Layer는 별도의 로직과 상태를 가지고 있지않을 것이며, 사전 단계에서 버그를 감지해 줄 타입 안정성이 필요하지도 않을 것이다. 관리 포인트는 줄어둔다. 단순하게 접근한다. 최소한의 JS만을.

결론

각각의 개발방식은 장단점이 존재한다. 최근 트렌드란 트렌드일뿐, 모든 상황에서 더 나은 개발방향을 뜻하는 것은 아니다. MSA는 MSA만의 장단점이 있으며, SPA 프레임워크는 걔네들 나름대로의 장단점이 있다. 가끔씩은 전통적인 개발방법이 더 나은 방향으로 보일 때도 있다. 나는 가끔 우리들이 쓸데없이 어플레이키션을 점점 더 복잡화시키고 고도화시킨다는 생각에 잠기곤 한다. 중요한 건 판단 근거다. 과연 현재 나의 상황에서 그것이 정말 필요한 건지, 가지고 올 리스크는 무엇인지 스스로 판단할 수 있는 자기만의 근거가 있어야 한다고 생각한다.

참고

https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

https://max.engineer/server-informed-ui

https://htmx.org/essays/two-approaches-to-decoupling/

https://begin.com/blog/posts/2023-02-21-why-does-everyone-suddenly-hate-single-page-apps

profile
시간대비효율

1개의 댓글

comment-user-thumbnail
2023년 12월 22일

Backend + HTMX = FULLSTACK!

답글 달기