[번역] Let's Program Like It's 1999 | Lee Byron
안녕하세요 여러분! 오늘 여러분과 웹 구축에 대해 이야기하게 되어 정말 기쁘고, 웹에 대해 이야기하게 되어 정말 좋은 시간입니다! 올해 초에 들으셨겠지만, 사실 저희는 큰 기념일을 맞이했습니다. 바로 웹이 30주년을 맞이한 것입니다. 팀 버너스 리는 1989년 3월 'Information Management: A Proposal'라는 제목의 논문에서 World Wide Web에 대한 비전을 공유했습니다. 당시 그의 상사는 ‘모호하지만 흥미진진하다’고 평가하기도 했습니다. 그리고 그는 이렇게 말했습니다. “웹의 꿈은...”이라고요. 정보를 공유하여 소통하는 공통의 정보 공간, 그 보편성은 필수적입니다.
하이퍼텍스트 링크가 개인, 로컬, 글로벌 등 어디든 가리킬 수 있다는 사실에 착안한 그는 초안이든 고도로 다듬어진 것이든 간에 이 아이디어를 몇 년 동안 CERN에서 연구했습니다. 그리고 1993년, 무료 모자이크 브라우저의 출시와 함께 우리 모두가 함께 플레이할 수 있게 되었고, 그 후 몇 년 동안 모자이크는 엄청난 발전을 거듭했습니다. 물론 지금은 웹을 기반으로 수많은 웹사이트, 새로운 경험과 아이디어, 회사가 생겨났고, 그 중 대부분은 여러분도 들어보셨거나 잘 모르겠지만 한 회사에서 일하고 있을지도 모릅니다.
지난 몇 년 동안 브라우저 전쟁이 시작되었습니다. 어떤 운영체제를 사용하든 웹마스터가 될 수 있도록 새로운 웹브라우저가 많이 출시되었습니다. 그리고 이러한 브라우저는 웹이 점점 더 강력해지는 데 일조하고 있습니다.
그리고 지난 몇 년 동안 자바스크립트와 CSS 플러그인이 도입되었습니다. 플래시 애니메이션과 같은 새로운 종류의 작업을 수행하고 모바일 및 가상 현실 장치와 같은 미래 경험을 지원하게 되었습니다. 이 모든 것을 고려하면 시장은 우리를 더 이상 사랑할 수 없을 것 같습니다.
그래서 오늘은 이 모든 것을 가능하게 해준 새로운 기술의 하이라이트와 1999년 현재, 그리고 웹의 미래를 엿볼 수 있는 몇 가지 주요 내용을 공유하고자 합니다.
먼저 웹이란 무엇이며 어떻게 작동하는지 기본부터 알아볼까요? 웹 페이지는 웹 언어인 HTML로 작성된 리치 텍스트 문서로, 일반적인 파일에 불과합니다. 그리고 여기에는 다른 웹 페이지를 참조하는 클릭 가능한 하이퍼링크가 포함되어 있습니다. 그리고 이러한 하이퍼링크는 URL의 형태로 제공되는데, 첫 번째 부분은 웹사이트이고 두 번째 부분은 웹 페이지입니다. 그리고 약간의 마법으로 브라우저는 해당 웹사이트의 인터넷에 연결된 실제 물리적 컴퓨터를 찾습니다. 그런 다음 요청된 웹 페이지를 요청합니다. 이 컴퓨터는 웹 서버라는 소프트웨어를 실행하여 요청된 웹 페이지를 파일 시스템에 저장된 실제 파일로 변환합니다. 웹 서버는 해당 파일을 열고 HTTP라는 프로토콜을 사용하여 인터넷을 통해 브라우저로 다시 보냅니다.
지난 몇 년 동안 정적 파일이나 웹사이트를 제공하는 것에서 동적 프로그램이나 웹 애플리케이션으로 진화하는 것을 목격했습니다. 이를 통해 사용자 컴퓨터가 아닌 웹 서버에서 프로그램이 실행되는 완전히 새로운 종류의 경험을 제공할 수 있게 되었습니다. 그리고 이러한 진화는 CGI에서 시작되었습니다. CGI는 매우 간단하면서도 매우 영리합니다. 요청된 파일이 특정 디렉터리(일반적으로 'cgi-bin'을 사용)에 있으면 그 파일이 프로그램이라고 말함으로써 웹 서버가 할 수 있는 일을 단순히 파일을 읽는 것 이상으로 확장합니다. 그리고 단순히 파일을 여는 것이 아니라 HTTP 요청에서 설정한 몇 가지 환경 변수를 사용하여 파일을 실행한 다음 프로그램의 표준 출력을 HTTP를 통해 브라우저로 다시 보냅니다. 이제 말 그대로 모든 프로그램이 웹 애플리케이션이 될 수 있기 때문에 매우 멋진 기능입니다. 셸 스크립트부터 복잡하게 컴파일된 C++ 프로그램까지 무엇이든 웹 애플리케이션이 될 수 있습니다. 물론 셸 스크립트에서는 간단한 작업 외의 다른 작업을 수행하는 것이 매우 번거롭고 C++는 마스터하는 데 몇 년이 걸리고 컴파일하는 데도 거의 비슷하게 오래 걸립니다. Perl과 같은 스크립팅 언어는 훌륭한 절충안이며 CGI와 함께 꽤 인기를 얻고 있지만 Perl은 실제로 웹을 염두에 두고 설계되지 않았습니다.
PHP의 급부상이 바로 여기서 시작되었습니다. PHP는 1995년 몇 년 전에 만들어졌지만, 작년 PHP 3의 출시와 함께 웹 커뮤니티의 진정한 관심을 받기 시작했죠. PHP는 오픈소스 프로그래밍 언어이자 현재 웹을 시작하는 데 있어 최고의 방법이라고 할 수 있습니다. 재미있는 점은, PHP의 창시자가 이것을 프로그래밍 언어로 만들 의도가 전혀 없었다고 했다는 거예요. 물론 튜링 완전한 언어이긴 하지만요. 웹을 위해 특별히 설계된 최초의 언어였기 때문에 엄청난 인기를 얻었습니다. 원래 목적은 웹 폼을 처리하고 웹 페이지를 생성하는 것이었는데, 이 두 가지 일을 정말 잘 해냈죠. PHP가 혁명적으로 느껴진 이유는 웹 페이지로서의 파일과 웹 애플리케이션으로서의 프로그램 사이의 경계를 허물었기 때문입니다. 예를 들어, 일반적인 HTML 파일은 그 자체로 유효한 PHP 프로그램이 됩니다. 물론 아직은 특별한 기능은 없고 프로그램이라기보다는 웹 페이지에 가깝지만, PHP 태그를 삽입하는 순간 애플리케이션 영역으로 들어서게 되죠.
예를 들어, 제출된 폼이나 쿠키에서 상태 값을 가져와서 데이터베이스를 생성하고, 새로운 데이터가 포함된 HTML로 바로 돌아갈 수 있습니다. 제가 PHP를 특히 좋아하는 이유 중 하나는 배우기가 매우 쉽다는 점입니다. HTML을 알고 있다면, 기존 HTML 파일에 동적 로직을 점진적으로 추가할 수 있고, 이는 Perl로 할 수 있는 모든 작업까지 확장될 수 있습니다.
PHP는 또한 무료이며 오픈소스이고, 최근에 LAMP 스택이라고 불리는 것의 일부입니다. LAMP는 무료 오픈소스 운영체제인 Linux, CGI와 MySQL, PHP를 지원하는 무료 오픈소스 웹 서버인 Apache를 의미하는데, 이것들이 바로 웹 애플리케이션을 구축하고 서비스하는 데 필요한 모든 도구입니다. 오픈소스 소프트웨어에 익숙하지 않으신 분들에게는 대단해 보이지 않을 수 있지만, 이는 PHP와 나머지 LAMP 스택이 웹 커뮤니티의 기여를 통해 지속적으로 발전하고 있다는 것을 의미합니다. 누구나 비용이나 라이선스 문제 없이 사용할 수 있죠.
사실, 저는 웹에 대한 최근의 열광이 미래 세대들의 프로그래밍 학습 의욕을 고취시킬 것이며, PHP와 LAMP 스택이 이를 쉽게 만들어줄 것이라고 예측합니다. 그리고 앞을 내다보면서 Tim의 꿈을 되돌아볼 수 있는데, 이 꿈에는 두 번째 부분도 있습니다. 웹이 우리의 일하고, 놀고, 교류하는 방식을 현실적으로 반영하거나 실제로 이를 구현하는 주요 수단이 될 정도로 보편적으로 사용되면, 우리의 상호작용이 온라인 상태가 되었을 때 컴퓨터를 활용하여 이를 분석하고, 우리가 무엇을 하고 있는지, 우리 개개인이 어디에 적합한지, 그리고 어떻게 하면 더 잘 협력할 수 있는지를 이해할 수 있게 될 것입니다.
이 두 번째 부분은 아직 실현되지 않았는데, 이는 제가 10년 혹은 20년 후의 웹이 어떤 모습일지 궁금하게 만듭니다. 자, 이야기가 너무 깊어지기 전에 제 소개를 하는 것이 좋겠네요. 제 이름은 Lee Byron이고, 금융 시스템에 대한 접근성을 민주화하고 있는 Robin Hood에서 웹 엔지니어링을 이끌고 있습니다. 저는 GraphQL의 창시자 중 한 명이자 GraphQL 재단의 이사입니다. 또한 Facebook의 React, Relay 및 다른 소프트웨어의 초기 기여자였으며, Jest, Check, Immutable.js와 같이 여러분이 들어보셨을 수 있는 다양한 오픈소스 프로젝트를 만들었습니다.
저는 특히 이번 React Conf에서 여러분들께 연설하도록 초대받은 것이 정말 기쁩니다. 이 컨퍼런스는 제 마음속에 특별한 자리를 차지하고 있는데요. 4년 전 React Conf에서 제가 처음으로 Immutable JS에 대한 제 작업을 공유했고, 제 팀 동료들이 GraphQL의 첫 모습을 선보였기 때문입니다. 두 프로젝트 모두 수년에 걸쳐 상당히 성장했고, 이를 통해 저는 개인적으로 오픈소스 커뮤니티와 관계를 구축할 수 있었습니다. 그리고 아니요, 저는 실제로 시간 여행자가 아닙니다. 다만 가끔 현실과 동떨어질 뿐이죠.
저는 지금이 2019년이라는 것을 잘 알고 있습니다. 이는 우리가 최근에 웹 제안 30주년을 기념했다는 것을 의미하며, 이는 곧 우리의 오픈소스 웹 개발자 커뮤니티가 20년이 훌쩍 넘었다는 뜻이기도 합니다. 제가 1999년을 돌아보는 것이 재미있는 이유는, 그때가 바로 제가 처음 웹에 관심을 갖게 되어 PHP를 독학하고, 아시다시피 LAMP 스택으로 포켓몬 팬 사이트를 만들면서 프로그래밍을 배우기 시작했던 시기이기 때문입니다. 1999년 이후 10년 동안, 웹과 PHP는 정말로 폭발적인 인기와 중요성을 얻게 되었죠.
무료로 사용할 수 있고, 배우기 쉽고, 확장성이 있다는 사실로 인해 LAMP로 구축된 닷컴 버블 이후의 새로운 기업들이 등장했는데, 여기에는 현재의 거대 기업들도 포함됩니다: 2001년의 위키피디아, 2003년의 워드프레스, 2004년의 페이스북이 그렇죠. PHP가 정당한 비판을 많이 받긴 하지만, 여전히 유용하며 아마도 여러분이 생각하는 것보다 훨씬 더 인기가 있을 것입니다. 오늘날 80% 이상의 웹사이트가 여전히 PHP로 구동되고 있으며, 실제로 전체 웹의 30%를 차지합니다.
워드프레스만으로도 구체적으로 이 정도의 웹사이트가 운영되고 있습니다. 정말 놀랍죠! 저는 웹의 30년 역사 중 마지막 시기를 페이스북에서 보내면서 PHP, LAMP, 그리고 복잡한 웹 앱의 진화를 지켜봤습니다. 제가 가장 흥미롭게 생각하는 점은, 20년이 지난 지금도 웹의 프로그래밍 모델과 멘탈 모델이 이러한 도구들이 처음 도입되었을 때와 크게 다르지 않다는 것입니다.
물론 동시에, 우리는 증가하는 복잡성을 다루어왔고, 훨씬 더 풍부한 경험을 구축했으며, 커뮤니티를 천 배 이상 성장시켰고, 웹에서 수십억 명이 연결될 수 있도록 확장했습니다. 그리고 저는 이러한 발전 뒤에 있는 하나의 패턴을 보게 되는데요. 세 가지 종류의 발전, 즉 더 나은 강력한 추상화, 더 명확하고 표현력 있는 구문, 그리고 개선되고 정제된 멘탈 모델 사이의 관계가 매우 중요합니다. 실제로 이 세 가지는 일종의 순환 구조로 서로에게 영향을 미쳐왔습니다.
우리는 더 나은 라이브러리와 추상화를 구축하여 새로운 기능과 더 나은 성능을 이끌어냅니다. 이러한 추상화를 더 잘 표현하기 위해 새로운 프로그래밍 패턴, API, 그리고 궁극적으로는 새로운 구문을 우리의 언어에 도입합니다. 이는 명확성을 만들어내고, 이를 통해 우리가 무엇을 만드는지에 대한 생각하는 방식, 즉 멘탈 모델을 개선할 수 있게 됩니다.
오늘 저는 이 과정을 페이스북에서 제가 목격한 몇 가지 이야기와 구체적인 예시들을 통해 설명하고자 합니다. 우리 모두에게 익숙해진 세 가지 문제, 즉 뷰 렌더링, 상태 관리, 그리고 데이터 페칭을 중심으로 말이죠. 이 모든 것이 귀에 익숙하게 들리실 텐데, 우리가 하루 종일 이에 대해 이야기를 나눴기 때문입니다. 저는 프론트엔드 엔지니어로서 우리가 가장 많이 집중하는 부분인 뷰 렌더링부터 시작하고 싶습니다.
PHP는 정말 대단한 킬러 기능을 가지고 있었죠. PHP는 웹의 공용어인 HTML로 작성된 정적 콘텐츠와 동적 콘텐츠가 혼합된 웹 앱이라는 멘탈 모델과 아주 잘 어울렸고, 이는 템플릿 언어의 시대를 열었습니다. 그 이후로 더 많은 것들이 등장했는데요. 2004년의 임베디드 루비(ERB)와 2009년의 Mustache 템플릿, 그리고 훨씬 더 많은 것들이 있었습니다. 이러한 도구들이 한 일은 정적 콘텐츠로 시작해서 필요에 따라 시간이 지나면서 동적 값들을 도입하기 쉽게 만든 것이었죠, 뭐든 복잡한 것을 만들어야 할 때 말이에요.
그래서 곧 완전한 튜링 완전성을 가진 로직이 필요하다는 것을 깨닫게 되는데, 이는 이러한 단순한 템플릿 언어들이 PHP처럼 자체적인 특이한 프로그래밍 언어로 발전하거나, 아니면 Mustache처럼 제한적이고 한정된 용도로 남게 되었다는 것을 의미합니다. 풍부한 로직을 갖춘 템플릿조차도 전역 변수에 의존하지 않고는 현대화하거나 재사용하기가 어렵거나 불편했고, 결국 템플릿 문법의 장점 대부분을 잃게 되었습니다. 게다가 보안 측면은 대부분 개발자의 몫으로 남겨졌죠. 그 결과, 단순해 보이는 순진한 PHP 코드는 대개 심각한 SQL 인젝션이나 크로스 사이트 스크립팅 취약점을 갖게 되었고, 이는 특히 초기 페이스북을 괴롭히는 지속적인 골칫거리였습니다.
이러한 문제들을 해결하기 위해 페이스북은 UI 컴포넌트라는 프레임워크를 만들었습니다. 지금은 이것이 일반적인 용어가 되었다는 게 흥미롭네요. UI 컴포넌트는 당시의 MVC 프레임워크와 브라우저의 DOM API에서 큰 영감을 받았습니다. 예를 들어, 버튼은 UI_button_component
라고 불렸는데, 이는 UI_component
를 인스턴스화하거나 상속받았죠.
이것을 사용하기 위해서는 new
키워드로 인스턴스를 생성했습니다. 메서드 체이닝을 통해 설정을 하고, 자식 UI 컴포넌트들을 추가하고, DOM에서 영감을 받은 API로 클라이언트 측 동작을 구현한 다음, render
를 호출하여 완성된 HTML을 얻을 수 있었습니다. 이를 통해 템플릿과 관련된 모든 문제들을 해결할 수 있었죠.
이제 UI를 만드는 것은 단순히 코드를 작성하는 것이 되었습니다. 새로운 튜링 완전 언어를 발명할 필요가 없어진 거죠. 모듈화와 재사용을 위해 설계된 API를 가지게 되었고, 중요한 점은 우리가 여기서 만드는 것이 문자열이나 연결된 문자열의 스트림이 아닌 UI 컴포넌트 인스턴스들의 트리라는 것입니다. 이로 인해 페이지 내 콘텐츠가 나타나는 위치에 따라 자동으로 적절한 이스케이프 처리를 함으로써 보안 문제를 체계적으로 해결하기가 훨씬 쉬워졌습니다. 그리고 CSRF 토큰이 무엇인지 몰라도 폼에 항상 CSRF 토큰을 포함시키는 등 기본적으로 보안 문제들이 해결되었죠.
재사용성은 또한 표준 컴포넌트 라이브러리로 이어졌고, 이는 제품 디자인의 일관성을 향상시켰습니다. 이 모든 것이 정말 훌륭하게 들리지만, 한 가지 큰 단점이 있었습니다. 템플릿에서 UI 컴포넌트로 코드를 리팩터링할 때마다, 새로운 코드가 더 나빠졌다는 느낌을 지울 수 없었던 거죠. 코드가 더 장황해지고 읽기 어려워졌으며, 사람들이 DOM API를 직접 사용하는 것보다 HTML을 작성하는 것을 선호했던 데는 분명한 이유가 있었습니다. 다행히도 그 와중에 페이스북에서는 더 나은 성능을 달성하고 기술 부채를 해소하기 위해 사이트를 처음부터 다시 작성하는 비밀 프로젝트가 진행 중이었습니다.
이는 물론 전혀 성공하지 못한 끔찍한 아이디어였고, 아마도 다시 시도해서는 안 될 것입니다. 하지만 오늘 앞서 들었던 현재의 시도처럼, 이 노력은 매우 흥미로운 새로운 기술들과 아이디어들을 다수 만들어냈습니다. 그 중 하나가 XHP라는 PHP 확장이었죠. XHP는 실패한 e4x 자바스크립트 프로젝트에서 영감을 받아 PHP에 XML과 유사한 문법을 도입했습니다.
하지만 e4x와는 달리, XHP는 XML 작업을 위한 새로운 런타임 동작을 추가하려 하지 않았습니다. 대신 XHP는 일반적인 PHP 클래스를 인스턴스화하는 것에 대한 문법적 설탕에 불과했습니다. 클래스 인스턴스들의 트리 구조를 훨씬 쉽게 표현할 수 있게 해주었고, 기본적으로 HTML만큼이나 읽기 쉬웠죠.
이는 UI 컴포넌트와 완벽하게 들어맞았습니다. 구현 세부사항은 대체로 동일하게 유지되었지만, 문법은 우리가 템플릿에서 좋아했던 것들을 되살려주었습니다. 게다가 이제는 닫히지 않은 태그와 같은 오류들을 프로덕션 환경에서 깨진 뷰나 유효하지 않은 HTML로 발견하는 것이 아니라, 개발 단계에서 문법 오류로 잡아낼 수 있다는 추가적인 이점도 있었죠. 자, 빠르게 손을 들어볼까요: 여러분의 사이트 마크업 오류를 체크해주는 HTML 검사기 앱들을 기억하시나요? 지난 1년 동안 이런 것들을 사용해보신 분 계신가요? 어머, 아무도 안 드시네요! 저도 마찬가지입니다.
저는 수년간 그런 것을 사용하지 않았습니다. 이 문제는 사라졌죠. UI 컴포넌트라는 더 나은 추상화와 XHP의 더 나은 문법을 통해, 모듈식 UI를 다른 모듈식 UI들로 정의하면서 UI를 구축한다는 우리의 멘탈 모델이 더욱 명확해졌습니다.
이를 통해 명령형이고 가변적이며 지저분한 스타일의 API를 더 선언적이고 함수형인 API로 대체할 수 있었습니다. 여러분은 XHP에 대해 들어보셨을 수도, 아닐 수도 있지만, 설령 들어보지 않았다 하더라도 지금 이 이야기를 듣고 JSX를 떠올리실 것 같네요. 잘 모르실 수도 있지만, React의 초기 버전에는 JSX가 포함되어 있지 않았습니다. React의 원작자인 Jordan Walke는 페이스북에 입사하기도 전에 React의 기본 아이디어들을 연구하고 있었는데, 당시에는 코드 라이브러리를 도입하면서 동시에 언어의 문법을 변경한다는 생각은 전혀 하지 않고 있었죠.
하지만 페이스북의 다른 프론트엔드 엔지니어들이 처음 React를 접했을 때, 그들은 즉각적으로 회의적인 반응을 보였습니다. 그들의 멘탈 모델은 이미 XHP에 의해 변화되어 있었고, JSX를 도입하기 전까지는 React가 페이스북 내에서 채택되지 않았습니다. 그래서 2013년에 React를 오픈소스로 공개했을 때 사람들이 싫어한다는 것을 알고 우리가 얼마나 놀랐는지 상상해보세요. 특히 그들은 JSX를 싫어했죠. 우리가 어떻게 그렇게 잔인할 수 있냐고 했습니다. 페이스북 엔지니어들은 XHP 덕분에 이미 개발에 대한 사고방식을 조정했지만, 웹 커뮤니티의 나머지 사람들이 JSX를 받아들이기까지는 몇 년이 더 걸렸습니다.
그리고 저는 이러한 추상화, 문법, 멘탈 모델의 개선이 Vue와 Svelte 같은 React 이후의 UI 라이브러리들에서도 계속되고 있다고 생각합니다. 이들은 모든 HTML 조각이 유효한 컴포넌트가 될 수 있다는 원래의 매력적인 아이디어로 완전히 되돌아갔고, 이는 모든 HTML이 유효한 PHP였던 1999년의 단순한 PHP 코드처럼 어떤 면에서 친숙하게 느껴집니다. 그래서 초기 웹 앱들은 실제로 아주 적은 양의 자바스크립트만을 사용했죠.
사실 웹의 첫 15년 정도는 상태 관리에 있어서 부러울 정도로 단순한 모델을 가진 애플리케이션들이 주를 이뤘습니다. 페이지를 로드하고, 무언가를 클릭하면 다른 페이지가 로드되거나, 혹은 폼을 제출하면 또 다른 페이지가 로드되는 방식이었죠. 이러한 패턴에서 시간에 따른 상태와 상호작용하는 프로그래밍 모델 역시 단순했습니다. 폼 값이나 쿠키를 읽고, 데이터베이스를 업데이트하는 정도였죠. 상태라고 해봐야 쿠키를 작성하고 동일한 HTML을 작성하는 것이 전부였습니다.
주목할 만한 점은, PHP 애플리케이션들이 오래 실행되는 상태 유지형 서비스로 취급되지 않았다는 것입니다. 각각의 요청은 깨끗한 상태에서 새로 시작되었죠. 각 URL 라우트는 실행될 서로 다른 PHP 파일과 연결되어 있어서, 라우팅과 상태 관리가 더욱 단순화되었습니다. 이는 독특하고 새로운 런타임 방식이었는데, 최근에 이르러서야 서버리스, Lambda, XJS와 같은 기술들에서 이러한 특성들을 다시 볼 수 있게 되었습니다.
여러분의 눈앞에 펼쳐진 이 모든 것이 바로 진정한 의미의 최초 단방향 데이터 흐름이었습니다. 물론, 이는 모든 상태 전환이 전체 페이지 로드를 수반한다는 것을 의미했고, 1999년에는 그것이 다이얼업 연결을 통해 이루어져 고통스러울 정도로 느렸죠. 또한 이러한 방식으로는 채팅과 같은 리치 클라이언트 사이드나 이벤트 기반 앱을 구현할 수 없다는 한계도 있었습니다.
페이스북 채팅은 메신저의 전신이었으며, 페이스북에서 최초로 클라이언트 사이드 자바스크립트를 본격적으로 활용한 서비스였습니다. 2008년에 개발되었고, 당시 잘 정립되어 있던 MVC 패턴을 따랐죠. 그리고 주목할 만한 점은, 이는 Backbone.js가 나오기 2년 전의 일이었습니다.
하지만 수년간 같은 버그가 계속해서 발생했는데, 우리는 이를 "좀비 알림"이라고 불렀습니다. 페이스북에서 마치 새 메시지가 온 것처럼 알림이 표시되고, 여러분이 그것을 클릭하면 성공적으로 처리된 것으로 나타났습니다. 하지만 실제로는 아무것도 없었죠, 새 메시지도 없었습니다. 그리고 오늘 초기 Adam Wolfe의 라이트닝 토크를 보신 분들은 아시겠지만, 이것이 실제로 제 오랜 매니저였던 그가 페이스북에 처음 입사했을 때 작업했던 최초의 프로젝트 중 하나였습니다.
그는 현재 제가 일하고 있는 Robin Hood의 엔지니어링 VP인데, 여러분도 우리와 함께 즐거운 팀의 일원이 되면 좋겠습니다. 하지만 실제로, 그가 가장 먼저 하고 싶었던 것은 이 문제의 정확한 원인이 무엇인지 이해하는 것이었습니다. 당시에는 자동화된 테스트가 전혀 없었고, 다양한 출처에서 오는 여러 이벤트 스트림을 구독하는 방식에 의존하고 있었기 때문에 테스트하기가 상당히 어려웠습니다. 이로 인해 우리는 모킹을 핵심 개념으로 삼는 새로운 종류의 자바스크립트 프레임워크를 구축하게 되었습니다.
우리는 이것을 오픈소스로 공개하기 전까지 거의 4년 동안 페이스북 내부에서만 사용했습니다. 그리고 회귀를 잡아내는 테스트들이 구축되면서, 우리는 문제를 더 잘 이해할 수 있게 되었습니다: 채팅 UI를 업데이트하게 만드는 이벤트들은 서버 푸시 이벤트, 메타데이터, 페이지 전환, 또는 사용자가 직접 채팅과 상호작용하는 것 등 다양한 곳에서 발생할 수 있었죠. 그리고 채팅 자체도 최소 세 군데에 나타났습니다: 메시지 뷰, 채팅바, 그리고 알림 아이콘이 있었죠.
상단 모서리에 있던 각각의 뷰들은 이벤트를 처리하고 자신을 최신 상태로 유지하기 위한 고유한 로직을 가지고 있었습니다. 이런 것들을 머릿속으로 전부 파악하는 것은 불가능했고, 코드에서 엣지 케이스들을 놓치는 바람에 반복적으로 불일치가 발생했습니다. 그래서 해결책은 새로운 아키텍처로 채팅을 다시 작성하는 것이었습니다.
그들은 단순한 서버 사이드 상태의 멘탈 모델을 가져와서, 이를 전부 클라이언트 사이드 자바스크립트로 구현했습니다. 상태를 변경할 수 있는 모든 이벤트는 이벤트 이미터에서 액션으로 표현 방식이 바뀌었는데, 액션은 단순히 무엇이 변경되어야 하는지에 대한 선언적 설명이었습니다. 이 선언적 액션은 마치 단순한 상태 유지 웹 서버에 POST 요청을 보내는 것처럼 스토어에 디스패치되었죠. 그리고 그들은 이 아키텍처를 Flux라고 불렀습니다.
물론, 이러한 단순한 웹 서버 스타일 접근방식이 완성되려면, 액션을 디스패치한 결과가 웹 요청에 대한 응답과 비슷해야 했습니다: 전체 페이지가 그 변경 결과를 포함한 새로운 응답으로 교체되어야 하는 것이죠. 하지만 이는 당시 우리가 클라이언트 사이드 UI를 구축하던 방식과는 매우 달랐습니다. 새 메시지 이벤트에 대한 콜백을 살펴보면, 새로운 DOM 노드를 직접 구성하고 채팅 창에 appendChild
를 호출하는 방식이었죠. 이러한 방식에서 전체 뷰를 교체하는 방식으로 바꾸는 것이 반드시 더 나은 것은 아니었습니다. 속도가 느려질 수 있고, UI가 깜빡거릴 수 있으며, 스크롤 위치나 현재 입력 중이던 내용을 잃어버릴 수 있었기 때문입니다. 특히 채팅에서는 이러한 요소들이 매우 중요했죠.
물론 이러한 것들이 React를 만들게 된 주요 동기가 되었습니다. Flux와 React를 함께 사용하면서 상태 관리를 위한 훨씬 더 나은 추상화가 가능해졌죠. 90년대의 단순한 PHP 앱들이 가지고 있던 단방향 데이터 흐름을 오늘날의 동적인 자바스크립트 클라이언트에 도입할 수 있게 되었습니다. 물론 지금은 5년 전에 소개되었던 그대로의 Flux를 사용하는 사람은 거의 없습니다.
그 이후로 우리는 더 나은 추상화, 더 명확한 API와 문법, 그리고 더 정제된 멘탈 모델을 통해 이 순환을 계속해왔습니다. 이는 우리를 Redux로 이끌었고, React 컴포넌트에 connect 데코레이터를 사용하게 되었으며, 상태를 리듀서 함수로 생각하게 되었고, 심지어 Reason이나 Elm 같은 함수형 프로그래밍 언어까지 활용하게 되었습니다. React 자체도 이러한 순환을 계속했는데, 클래스 문법을 도입하고 컴포넌트 생명주기로 멘탈 모델을 정제했으며, Fiber로 추상화를 개선했고, 함수형 컴포넌트와 React 훅으로 문법을 더욱 단순화했습니다. 훅을 사용하는 React 함수형 컴포넌트는 1999년의 PHP 코드처럼 친숙하게 느껴집니다. 당시에는 그저 상태를 선언하고 템플릿으로 완성된 뷰를 렌더링하는 함수를 작성하는 것이 전부였죠.
돌이켜보면, LAMP가 그토록 강력했던 이유 중 하나는 MySQL 데이터베이스를 설정하고 사용하여 데이터 기반 뷰를 만드는 것이 매우 쉬웠다는 점입니다. 초기의 단순한 웹 앱들은 따라하기 쉬운 명령형 코드를 가지고 있었죠. SQL로 데이터베이스를 조회하고, 그 결과를 변수에 저장한 다음, 해당 데이터로 템플릿을 렌더링하는 방식이었습니다. 이는 웹 앱이 단일 인스턴스로만 구동되던 시절에 아주 잘 작동했습니다. PHP와 MySQL이 하나의 기기에 설치되어 있었을 뿐인데, 굉장히 단순하면서도 잘 작동했고 유지보수도 꽤 쉬웠죠.
하지만 이는 수천 명의 사용자나, 대규모 개발팀, 또는 복잡하고 데이터가 풍부한 애플리케이션으로는 확장하기 어려웠습니다. 그리고 Facebook은 뉴스피드 같은 새로운 기능을 개발하기 시작하면서 이 세 가지를 모두 해결해야 했죠. 뉴스피드는 데이터 가져오기를 까다롭게 만들었는데, 매우 다양한 종류의 스토리로 구성된 피드에 필요한 모든 것을 가져올 수 있는 성능 좋은 SQL 쿼리가 없었기 때문입니다.
설령 그런 쿼리가 있었다 하더라도, Facebook은 성능 개선을 위해 Memcache를 사용했습니다. 이는 캐시 가능한 키-값 쌍이 필요했죠. 그래서 Memcache로 뉴스피드를 구축하면 악명 높은 "n+1 문제"가 발생했습니다. 키 목록에서 여러 값을 각각 독립적으로 로드하려고 시도하다 보니, 페이지 로드마다 많은 차단 쿼리가 한 번에 하나씩 실행되는 문제였죠.
이로 인해 속도가 매우, 매우 느려질 수 있었고, 실제로 초기 Facebook 페이지들은 이 문제 때문에 아주 느렸습니다. 그래서 Facebook은 이 문제를 해결하기 위해 "Preparable"이라는 라이브러리를 만들었습니다. Preparable은 사실 단순한 상태 머신에 불과했습니다. 현재 상태를 나타내는 인자를 전달받아 switch문을 포함한 prepare
메서드를 가진 클래스 인스턴스였죠.
따라서 각 단계는 가져와야 할 데이터를 큐에 넣고 true
를 반환하거나(이는 해당 데이터가 사용 가능해지면 다음 상태로 진행해야 함을 의미), 추가 데이터를 가져온 후 false
를 반환했습니다(이는 마지막 단계이며 완료된 것으로 간주할 수 있음을 나타냄). 그래서 preparer는 페이지와 관련된 모든 preparable들의 목록을 수집했습니다. 뉴스피드의 스토리당 하나씩, 알림이 있다면 그것까지 포함해서요. 그리고 나서 이 모든 것에 대해 prepare
를 호출했습니다. 모든 preparable이 데이터를 기다리며 멈춰있을 때, 그 시점까지 요청된 모든 것에 대해 memcache로 단일 일괄 요청이 전송되었습니다.
그런 다음 각 preparable의 상태를 반환하고 진행시키며, 모든 preparable이 완료될 때까지 이런 식으로 루프를 계속 진행했습니다. 이는 N+1 문제를 해결했는데, 웹페이지가 아무리 복잡하더라도 각 페이지 요소가 독립적인 로직을 유지하면서도 최소한의 일괄 memcache 요청만으로 처리할 수 있게 되었기 때문입니다. 이를 통해 Facebook은 좋은 성능과 모듈화된 유지보수 가능한 코드베이스를 유지하면서도 훨씬 더 복잡하고 데이터가 풍부한 서비스로 성장할 수 있었습니다.
이 방식은 꽤 잘 작동했지만, 예상하셨겠지만 이는 우리에게 잦은 버그의 원인이 되었습니다. switch문에서 return이나 break문을 실수로 빼먹기 쉬웠고, 리팩토링 후에 숫자를 빼먹는 일도 있었죠. 하지만 우리는 이 방식을 고수했고 코드 리뷰를 통해 이런 종류의 문제들을 최대한 잡아내려 노력했습니다.
저는 제너레이터 함수에 대해 알기 전까지는 이것이 매우 독창적이고 영리한 방법이라고 생각했습니다. 제너레이터는 처음부터 끝까지 한 번에 실행된 후 호출자에게 반환하는 대신, 원하는 만큼 여러 번 제어권을 양보(yield)할 수 있는 함수입니다. 그러면 호출자는 다음 메서드를 호출하여 함수를 계속 실행하도록 요청할 책임을 갖게 됩니다.
제너레이터는 1970년대부터 어떤 형태로든 존재해왔지만, 2001년 Python에서 대중화되었고, 이후 2006년 Mozilla가 ES4의 제안 기능으로 JavaScript에 도입했습니다. ES4는 수년간의 작업 끝에 취소되었습니다. Harmony는 ES4의 일부 기능을 되살리려는 시도였지만, 이는 영원히 걸리는 것 같았고, 결국 이 노력은 후에 ES6으로 이름이 바뀌었습니다.
결과적으로, 제너레이터 함수가 Mozilla의 첫 버전에서 모든 브라우저에 탑재되기까지는 약 10년이 걸렸습니다. 따라서 당시 JavaScript 개발자들이 Harmony에 대해 무관심했고 신뢰하지 않았던 것은 어쩌면 당연했습니다. 적어도 브라우저에 실제로 탑재되어 사람들이 실제로 사용하게 될 것이라고는 믿지 않았죠.
그래서 개발자들은 브라우저 지원이 가능해지기 전에 언어를 확장할 수 있는 도구들을 직접 만들기 시작했습니다. 여러분은 아마 이런 도구 중 하나인 Babel을 사용했거나 최소한 들어봤을 것입니다. Babel은 원래 'six-to-five'라고... 아니, 정확히는 'ES6를 ES5로 변환한다'는 의미로 '6 to 5'라고 불렸습니다. 하지만 이 프로젝트 이전에, 이러한 변환기 중 최초의 것 중 하나가 바로 'regenerator'였습니다.
regenerator는 특별히 제너레이터 함수를 모든 브라우저에서 실행할 수 있는 JavaScript로 변환했는데, 이는 제가 충격을 받은 일이었습니다. 저는 제너레이터가 너무나 새로운 런타임 개념이라 이런 것이 불가능할 거라고 생각했거든요. 그래서 당연히, 이것이 어떻게 작동하는지 알아야만 했습니다.
간단한 제너레이터 함수를 작성하고 제너레이터에 통과시켰더니 어떤 결과가 나왔을까요? 바로 prepare ball이었습니다! 제너레이터 함수는 결국 상태 머신을 설명하는 더 깔끔한 문법에 불과하며, 우리가 이미 prepareBalls
에서 사용하던 것과 정확히 같은 종류의 switch문으로 변환될 수 있다는 것을 알게 되었죠. 마치 누군가가 prepareBalls
를 만들 때 제너레이터 함수에 대해 알고 있었지만, 제게 말해주지 않은 것 같았습니다. 이후 페이스북에서는 Parables를 개선하면서 중첩된 자식 prepare ball들을 추가하고, 오류를 처리하고, 반복문과 기타 더 복잡한 상태 전환을 추가하는 작업을 진행했습니다.
이 모든 것이 제너레이터 함수와 정확히 일치합니다. yield*
같은 문법, try-catch
, 그리고 for
와 while
반복문 같은 단순하고 평범한 제어 흐름까지도요. 제너레이터 함수는 시간이 지남에 따라 여러 값을 생성하기 위한 상태 머신의 문법을 제공하지만, parables의 경우에는 단일 최종 상태로 이어지는 복잡한 비동기 로직에 관한 것입니다. 그리고 이는 사실 다른 ES6 기능인 프로미스(promises)와 훨씬 더 잘 맞아떨어집니다.
따라서 우리의 prepare bowl의 각 단계는 자바스크립트 프로미스와 콜백의 연쇄처럼 생각될 수 있습니다. 2012년에 우리는 더 나은 해결책을 발견했는데, 바로 C#이 도입한 async 함수였습니다. 이는 제너레이터 함수의 상태 머신 모델링 문법과 프로미스(C#에서는 태스크라고 불렀죠)의 비동기 추상화를 결합한 획기적인 조합이었습니다. 이에 아이디어가 떠올랐고, 페이스북은 다시 한 번 PHP 문법을 확장하여 async와 await를 포함시켰으며, 콜백을 이해하기 쉽고 오류가 훨씬 적은 async 함수로 대체하는 작업을 시작했습니다.
이러한 확장들은 나중에 Hack 프로그래밍 언어가 되었는데, 여기서 비동기 프로그래밍에 대한 혁신적인 개선은 실제로 올해 초까지도 계속되었습니다. 비동기 프로그래밍을 위한 더 멋진 기능들이 추가되었죠. 이는 멘탈 모델을 더욱 명확하게 했고, 이후 async 함수와 XHP가 합쳐져 Gen XHP가 만들어졌습니다. 데이터 가져오기와 컴포넌트 렌더링이 함께 위치하게 되면서, 복잡한 데이터 기반 컴포넌트들의 캡슐화가 더 잘 이루어지게 되었습니다.
이러한 최종 진화는 우리가 GraphQL 작업을 시작할 무렵에 일어났으며, GraphQL 쿼리의 문법과 실행 모두에서 async 함수와 parables의 영향을 볼 수 있습니다. 사실, 나중에 우리는 parables가 얼마나 중요했는지 깨달았습니다. GraphQL을 통해 우리는 자바스크립트에서도 같은 n+1 문제를 해결하는 데 도움이 되는 오픈소스 데이터 로더를 만들게 되었죠. Gen XHP에서 영감을 받은 페이스북의 프론트엔드 엔지니어들은 이러한 멘탈 모델을 바탕으로 Relay를 구축했는데, 이는 GraphQL을 통해 UI 컴포넌트와 데이터 요구사항을 함께 배치하는 아이디어를 서버에서 클라이언트로 가져온 것입니다. 이처럼 우리는 더 나은 추상화, 더 명확한 문법, 그리고 정제된 멘탈 모델을 통해 여러 번의 진화를 거쳤고, 이는 Gen XHP, GraphQL, Relay, React, 그리고 suspense와 같은 아이디어로 이어졌습니다.
1999년의 단순한 PHP 코드처럼 이건 제게 친숙하게 느껴집니다. 당시에는 필요한 것을 요청하고 원하는 것을 렌더링하기만 하면 됐죠. PHP가 웹 앱을 만들기 위한 표현력 있는 문법과 설득력 있는 멘탈 모델을 제공했기 때문에 LAMP가 크게 인기를 얻게 된 것 같습니다.
그리고 20년이 지난 지금, 어떻게 보면 우리는 다시 처음 시작했던 지점으로 돌아온 것 같습니다. 이는 더 나은, 더 강력한 추상화와 더 명확하고 표현력 있는 문법, 그리고 개선되고 정제된 멘탈 모델이 꾸준히 발전해 온 덕분입니다. 이를 통해 우리는 더 풍부하고, 더 빠르며, 더 복잡한 앱들을 만들 수 있게 되었고, 우리 커뮤니티를 수백만으로 성장시켰으며, 수십억의 사람들을 웹으로 연결할 수 있었습니다. 그리고 지금 뒤돌아보니, 페이스북이 이러한 아이디어들을 추구하는 데 많은 책임을 져왔다는 것이 사실 그리 놀랍지 않게 느껴집니다.
페이스북은 원래 저처럼 1999년에 LAMP 스택으로 웹을 위한 것들을 만들면서 프로그래밍을 배운 사람들이 만들었습니다. 페이스북이 제품의 복잡성과 인기, 회사 규모 면에서 성장하면서, 이러한 과제들을 마주하게 되었고, 기존 솔루션들에 한계를 느끼게 되었으며, 초기 웹의 단순함에서 영감을 얻었습니다. 그리고 이는 XHP, React, JSX를 통한 더 나은 뷰 렌더링으로 이어졌고, 단방향 데이터 흐름, Redux, 그리고 현재의 React를 통한 더 나은 상태 관리로 이어졌습니다.
Hooks와 prefetch, Abel 스타일의 배칭, async 함수, GraphQL, 그리고 이제는 React Suspense를 통한 더 나은 데이터 페칭까지 발전했습니다. 하지만 저는 우리가 아직 완성에 근접했다고 생각하지 않습니다. 여전히 더 나은 추상화, 문법, 그리고 멘탈 모델의 발전을 통해 개선될 수 있는 많은 문제들이 남아있습니다. 아마도 수십 년 전의 아이디어들에서 다시 한번 영감을 얻을 수 있을 것 같습니다.
그래서, 우리가 다시 미래를 바라볼 때, 웹에 대한 팀 버너스 리 경의 꿈을 살펴볼 수 있습니다. 그는 웹 30주년이라는 이정표를 돌아보며 이렇게 말했습니다. "오늘날 전 세계의 절반이 온라인에 접속해 있습니다. 이는 우리가 얼마나 멀리 왔는지를 축하할 순간이면서도, 동시에 우리에게 얼마나 많은 길이 남아있는지 돌아볼 기회이기도 합니다.”
웹은 공공광장, 도서관, 병원, 상점, 학교, 디자인 스튜디오, 사무실, 영화관, 은행, 그리고 그 이상의 많은 것이 되었습니다. 물론, 새로운 기능과 새로운 웹사이트가 생길 때마다 온라인에 접속할 수 있는 사람들과 그렇지 못한 사람들 사이의 격차가 커지고 있어, 모든 사람이 웹을 이용할 수 있도록 하는 것이 더욱 시급한 과제가 되고 있습니다. 웹은 모든 사람을 위한 것이며, 우리는 집단적으로 이를 변화시킬 수 있는 힘을 가지고 있습니다.
쉽지는 않겠지만, 우리가 조금만 꿈꾸고 열심히 노력한다면, 우리가 원하는 웹을 만들어낼 수 있을 것입니다. 이 모든 것이 저로 하여금 10년 또는 20년 후의 웹은 어떤 모습일지 궁금하게 만듭니다.