GitLab은 Vue를 어떻게 사용하는가: 일 년 후

How we do Vue: one year later

이 글은 GitLab에서 공개한 How we do Vue: one year later를 번역한 글입니다. 글에 관한 모든 권리는 GitLab에 있습니다. 이 글을 읽기 전 GitLab은 왜 Vue.js를 선택했나도 함께 읽어보시면 좋습니다.


Vue에 관한 글을 작성한 지 꽤 지났다. 우리는 1년 넘게 Vue를 사용하고 있었고 굉장히 만족하고 있다. 이 글을 작성하라고 상기시켜 준 @lnoogn님에게 감사드린다!

우리의 상황은 "스칼라는 천천히 죽어가는 중인가요?"에 대해 누군가 말했던 답변을 생각나게 한다.

스칼라하는 사람들은 레딧이나 블로그에 글 올릴 시간이 없습니다. 문제를 해결하느라 바쁘거든요.

우리 상황에도 딱 맞는 말이다. 스칼라처럼 Vue도 적절하게 사용할 때 정말 정말 잘 동작한다. Vue는 한 때 유행어가 아닌 성실한 일꾼이다. 우리의 수많은 문제가 우리 또는 다른 이들에 의해 해결됐다. 여전히 여러 문제가 남아있지만 우리에겐 적용가능한 "Vue 작성법"이 있다.

지난 글 이후, 우리는 확장성 있는 Vue 스타일 가이드를 공개했고 우리의 가이드에서 영감을 받아 Vue에서도 스타일 가이드를 공개했다. 우리가 Vue를 사용하면서 점점 더 나은 방법을 찾으면서 스타일 가이드도 여러 차례 업데이트되었다. 이 글에서는 우리가 찾은 것 중 몇 가지를 정리해보았다.

그냥 VueX를 사용하자

VueX를 사용할 때 일이 한결 편해졌다. 중간 혹은 큰 기능을 작성할 때는 VueX를 사용하라. 작은 기능을 작성할 때는 사용하지 않아도 괜찮다. 우리는 큰 기능을 작성할 때 VueX를 사용하지 않는 실수를 했었다. 예로 현재 저장소의 파일 뷰를 대체할 멀티 파일 에디터(작업중)를 살펴보자. 여러 파일을 쉽게 편집할 수 있게 해주는 기능이다.

처음에는 VueX를 사용하지 않고 대신 스토어 패턴을 이용해 이 기능을 작성했다. Vue 문서에는 스토어 패턴은 이를 엄격하게 지키도록 전념해야 잘 동작할 거라고 말한다. 하지만 그러느니 VueX에 시간을 투자하는 편이 더 나을 것이다. VueX는 처음에는 다소 장황해 보이지만 훨씬 더 확장성(scalability) 있으며 장기적으로 봤을 때 상당한 시간을 절약해준다. 우리는 데이터를 여러 곳에서 변경할 때 발생했는데, VueX를 사용하면 중심부 한 곳에 있는 데이터의 변경에만 집중하면 된다. 이를 사용하지 않는다면 여기저기에서 발생하는 예기치 못한 버그를 추적해야 했을 것이다.

좋은 코드를 작성하라

VueJS와 VueX 둘 다 멋진 도구지만, 다른 모든 코드가 그렇듯이 여전히 나쁜 Vue 코드를 작성할 가능성은 존재한다. 잘 동작하는 코드라 하더라도 코드의 지속성(longevity)과 확장성(scalability)에는 문제가 있을지 모른다. 성능에도 문제가 생길 수 있다. Vue 코드는 작성하기 굉장히 단순하기 때문에 Vue를 사용하면 잘 동작하는 완벽한 코드를 작성하기 쉬운 듯 보이기도 한다. 지속성 문제가 있으면 처음에 잘 작동하던 코드를 여러분이나 다른 사람이 업데이트하려고 할 때 굉장히 힘들어진다. 성능 문제 또한 작은 데이터에서는 나타나지 않을지 모르지만 대용량 데이터를 다루다보면 문제가 생길 수 있다. 코드는 망가지기 쉽다. 여러분이 작성한 코드에서도 나쁜 냄새가 날 수 있다. 그리고 이 나쁜 코드 냄새는 Vue를 사용할 때도 날 수 있다.

data 객체나 store에 Vue가 추적할 무언가를 추가할 때, Vue는 데이터 객체를 재귀적으로 훑으며 모든 것을 추적한다. 따라서 데이터의 계층이 심하게 깊고 데이터의 크기가 크며 자주 변경해야 한다면(mousemove에 반응할 때처럼) 골칫거리를 만들 수 있다. Vue로 하여금 대량의 데이터 세트를 관찰하도록 하는 것은 나쁘지 않다. 하지만 반응을 위해 반드시 그 데이터를 감시할 필요가 있는지는 확실히 해야한다. Vue를 사용하면 설사 그럴 필요가 없을 때도 무엇이든 쉽게 반응성 있는 것으로 만들 수 있다.

이 때문에 우리는 Vue 코드를 작성할 때 원칙을 철저히 따른다. 반드시 문서를 따라야 하고, 필요할 때만 Vue 코드를 작성하고 과도할 때는 Vue를 사용하지 않는다.

우리가 새로 작성하는 Vue 코드는 Flux 아키텍처를 따른다. VueX 역시 Flux를 따르고 있으며 사실 이 부분도 우리가 VueX를 사용하는 한 가지 이유이다. 앞서 언급한 "스토어 패턴(store pattern)"을 사용할 수도 있지만, VueX는 모든 것이 규칙을 따르도록 강제하고 있으므로 VueX를 사용하는 쪽이 더 나은 선택이다. 통제에서 벗어나게 되면 이러한 규칙을 스스로 지켜야 하고 어쩌면 실수를 할 수도 있다. 신경 써야 할 일은 적을수록 좋다. 잘 작성된 Vue 앱의 좋은 예로는 이미지 목록 레지스트리가 있다.

jQuery를 Vue와 함께 사용하고 싶다면

새로 개발하던 중에 이런 질문이 떠올랐다.

jQuery하고 VueJS를 섞어서 써도 괜찮을까?

jQuery 라이브러리인 Select2에 관한 얘기는 아니다. DOM을 직접 선택할 필요가 있는지에 대한 얘기이다. 우리는 jQuery의 사용에 대해 토론할 때 다음과 같은 제안이 나왔었다.

jQuery를 사용하는 것은 좋다. 하지만 DOM을 선택하는 것에 한해서만 사용하자.

처음에는 jQuery와 Vue의 혼합 사용에 관해 수차례 토론했었다. 어떤 사람은 DOM 선택에만 사용한다면 괜찮을 거라고도 했다. 하지만 검토 결과, 우리는 jQuery와 Vue를 함께 사용하는 건 그리 좋은 생각이 아니라는 결론에 이르렀다. 항상 더 나은 방법은 있기 마련이다. 만약 누군가에게 Vue 아키텍처 안에서 DOM을 직접 선택해야 할 필요가 생겼다면 그건 그 사람이 무언가를 잘못하고 있다는 뜻이다.

jQuery를 DOM 선택이 필요한 아주 적은 상황에서만 사용하겠다고 한다면 이러한 상황을 계량화해야 한다. 대신 Vue 안에서는 DOM을 직접 질의하지 않아야 한다.

DOM을 질의하는 대신 store와 서버 사이드 코드를 조합해서 사용하는 편이 일반적으로 훨씬 더 간단하다. 서버는 클라이언트에서는 제공할 수 없는 데이터를 제공할 수 있다. 우리의 경험에 비추어보면 대개 클라이언트에서 다루어야 할 데이터는 적을 수록 좋다. 클라이언트에서 데이터를 조작하면 절대 안된다는 뜻은 아니지만 대체로 그보다 더 명확한 방법이 존재한다. GitLab에서 우리는 메인 엘리먼트의 data 속성을 통해 엔드 포인트를 구하기 위해 DOM 질의를 사용한다. 하지만 이 작업에는 jQuery가 아닌 el.dataset을 사용한다. GitLab에서 우리(프론트엔드 개발자)는 백엔드 개발자들과 대화하며 우리가 사용할 데이터의 구조를 명확히 했다. 이러한 방식으로 프론트엔드 팀과 백엔드 팀을 모두 관리할 수 있었다.

상황 예시

다음 문제를 살펴보자.

현재 모든 이슈 댓글은 Vue에서 렌더링된다. 위는 우리가 jQuery를 사용하고 싶어 했던 상황의 예인데, 사용자의 가장 최근 댓글을 편집하는 기능을 다시 작성할 때 일어났던 일이다. 사용자가 빈 새 댓글 textarea(화면 제일 아래)에서 키를 누르면 슬랙처럼 자신이 작성한 가장 최근 댓글을 수정할 수 있도록 했다. 전체의 가장 최근 댓글이 아니라, 해당 사용자가 작성한 가장 최근 댓글임에 주의하자. 위 이미지에서 가장 최근 댓글은 빨간색 사각형으로 표시해두었다. 물론 시간은 촉박했다. 누군가 다음과 같이 말할 법도 했다.

일단 그냥 되게만 해두고 나중에 수정하면 안 되나?

물론 이 기능을 위해 DOM 질의를 사용할 수도 있다. 하지만 이 경우 더 나은 방법은 백엔드 개발자에게 백엔드에서 보내주는 JSON에 사용자의 가장 최근 댓글을 표시해서 보내달라고 하는 것이다. 백엔드 개발자들은 데이터베이스에 직접 접근할 수 있기 때문에 관련 코드도 최적화할 수 있다. 이 경우 클라이언트에서는 따로 해야 할 일이 없어진다. 서버에서 데이터를 받고 나면 store에 댓글이 저장되어 접근하기 쉬운 상태가 된다. 이를 사용해 뭐든 할 수 있다. 세상에 못 할 것이 없다.

적절한 Vue 앱

Vue 번들에는 언제나 한 개의 스토어와 서비스, 그리고 꼭 필요한 진입점(entry point)도 하나 포함되어 있어야 한다. 진입 컴포넌트는 컨테이너 컴포넌트일 뿐이고 나머지 다른 컴포넌트이 표현을 담당한다(presentational). 이 정보는 Vue 문서에도 나와 있다.

우선은 div 한 개로 할 수 있다.

<!--HAML-->
.js-vue-app{ data: { entrypoint: 'foo' }}

<!--HTML-->
<div class="js-vue-app" data-entrypoint="foo"></div>

진입점은 data 속성을 사용해 전달할 수 있다. 그러면 Vue는 HTTP 클라이언트에서 이 진입점을 호출한다.

클라이언트 사이드 자바스크립트에서 URL을 만들고 싶진 않을 것이다. 서버에서 작성된 URL이 진입점에 전달되도록 하자. Vue 코드를 작성할 때는 서버가 해야 할 일은 서버에 맡겨두는 것이 중요하다.

성능 개선

우리는 최근 Vue를 사용해 이슈 댓글을 새로 작성했다. 이전에는 Haml, jQuery, Rails로 작성됐었는데, 댓글을 비동기적으로 읽어들이지 않았기 때문에 병목 현상이 있었다. 간단하게 Ajax를 사용해서 댓글을 읽어들이고 페이지를 전부 읽어 들인 후에 댓글을 나타내도록 처리했다. 페이지 로딩을 더 빠르게 하는 방법 하나는 무거운 항목을 나중에 읽어 들여서 페이지 로딩을 방해하지 않는 것이다.

마음에 들었던 점은 이 새로운 댓글을 공개했을 때 어떤 사람들은 우리가 그걸 리팩토링했다는 사실을 전혀 눈치채지 못했다는 것이다. 리팩토링의 결과 이슈 페이지는 훨씬 빨라졌고 사용자 경험도 조금 개선되었다.

지금은 이슈 페이지의 댓글 로딩이 더 간소화되었고 각 이슈 페이지는 훨씬 빨리 읽어 들여진다. 예전에는 이슈 페이지 하나에 수만 개의 이벤트 리스너가 붙기도 했다. 예전 코드가 이벤트 리스너를 제대로 관리하고 지우지 않았기 때문이다. 어마어마한 양의 이벤트 리스너는 (다른 많은 문제와 더불어) 사용자 인터페이스의 성능을 저하했기 때문에, 댓글이 많은 페이지를 스크롤 하면 버벅대곤 했다. 우리는 jQuery를 제거하고 Vue를 추가하면서 성능 개선에 집중했다. 해당 페이지를 본다면 확연히 빨라졌음을 느낄 수 있을 것이다. 하지만 성능 개선 작업은 이제 막 시작했을 뿐이다. 이번에 코드를 뒤집어엎으면서 훨씬 유지보수가 쉬운 코드가 되었기 때문에 성능을 쉽게 개선할 수 있는 토대가 마련됐다. 이전 코드는 유지보수가 어려웠다. 지금의 이슈 댓글 코드는 적절하게 분할되고 "컴포넌트화"되었다.

이러한 개선 사항과 함께 스크롤 시 이미지 로딩과 같은 다른 부분도 개선되었고 덕분에 이슈 페이지를 훨씬 빠르게 만들 수 있었다.

리팩토링은 생초짜 개발자가 입사 첫날 모든 코드를 Angular로 다시 작성하자고 제안할 때 사용하는 말이다. GitLab에서는 일어나지 않았던 일이지만. 다행히도 우리 프론트엔드 개발자들은 매우 보수적이다. 생각해보자. 왜 모든 이들은 항상 리팩토링을 하고 있는 것처럼 보일까? 그들은 이루려는 목표가 뭘까? 내가 말할 수 있는 건 GitLab에 관해서다. 리팩토링을 통해 우리가 이루려는 것은 무엇인가? 현실적으로 리팩토링은 큰 비용을 필요로 한다. 여기서 비용이란:

  1. 리팩토링을 위한 비용
  2. 변경 사항 테스트를 위한 비용
  3. 테스트와 문서를 업데이트하는 비용

을 의미한다. 그뿐만 아니라 위험 요소도 있다.

  1. 버그를 만들어 낼 위험
  2. 완료하지 못할 큰 작업을 다루어야 할 위험
  3. 의도한 만큼 품질이나 개선을 이루지 못할 위험

우리의 목표는 다음과 같았다.

목표 #1 코드를 유지보수가 더 쉽도록 바꾼다. 앞으로는 새 기능을 더 쉽게 추가하고 싶었다. 장기적으로 보면 이번 리팩토링은 우리의 시간을 아껴줄 것이다. 하지만 리팩토링에 들인 시간을 보상하려면 상당히 많은 시간이 필요할 것이다. 불편한 진실일 수 있지만, 일반적으로 리팩토링은 여러분의 시간을 아껴주지 않는다. 하지만 스트레스는 줄여줄 수 있을 것이다.

목표 #2 할 수 있는 것을 올바르게 하면 개발자들이 행복해진다. 행복하고 즐겁게 일하는 개발자만큼 팀의 능률을 올려주는 건 없다. 스트레스받는 개발자는 코딩을 피하려 하지만 즐거운 개발자는 그렇지 않다. 행복한 개발자는 시간을 아껴준다.

이런 목표를 달성하기 위한 다음 단계는 머지 요청(merge requset) 댓글 부분을 리팩토링하는 것이다. 머지 요청 댓글은 댓글이 상당히 많은 머지 요청에서는 엄청나게 느리다. 대략 200개의 댓글이 있으면 댓글은 느려지고 응답성도 떨어진다. Diff도 마찬가지로 느려진다. 이런 현상의 원인은 많은데 그중 하나는 자바스크립트가 너무 자주 리플로우(reflow)를 유발하기 때문이다. 이 부분은 이미 리팩토링을 마쳐서 수정해두었지만 장기적인 해결책은 될 수 없다. 규모가 큰 머지 요청의 경우, 한 번에 8초 이상 걸리는 리플로우를 유발하는 코드도 있다! 지금은 수정된 문제지만 말이다. 이 이미지를 보면 속도를 느리게 만드는 다른 요인도 알 수 있다. 두말할 것도 없이 할 일이 엄청 많을 것이다. 가장 큰 문제는 우리의 코드가 유지보수하기 쉽지 않아서 수정 작업이 오래 걸릴 거라는 점이다. Vue를 사용한 리팩토링은 초기 속도를 상당히 개선하고 나중에 쉽게 코드를 개선할 수 있는 기반을 마련해 줄 것이다.

GitLab에는 할 일이 많다. GitLab의 엉망진창인 지하 묘지를 탐험해보고 멋진 코드를 작성해보고 싶다면, 그리고 우리 프론트엔드 팀에 도움이 되고 싶다면 우리 회사에 지원해보길 바란다.

댓글을 남겨주세요