Pixabay 님의 사진, 출처: Pexels
Pixabay 님의 사진, 출처: Pexels

밀당 IHFB 프론트엔드의 모든 것

Sungmin Park
IHFB  R&D 팀블로그
11 min readOct 14, 2021

--

들어가며

밀당영어의 프론트엔드는 유저에 따라 분류되어 학생, 어드민, 매니저용 웹이 존재합니다.

  • 학생 : 유일하게 고객에게 노출되는 사이트로, 주로 학생이 문제를 풀고, 단어를 암기하는 등 학습을 할 수 있는 공간입니다.
  • 매니저(온택트 선생님) : 선생님이 학생의 학습 현황을 관리할 수 있는 공간입니다.
  • 어드민(컨텐츠 제작자): 학습에 쓰이는 지문, 영상등의 컨텐츠를 제작하는 공간입니다.

여타 다른 초기 스타트업과 마찬가지로, 특성상 짧은 기간내에 작동하는 앱이 필요했기 때문에 소수 인력이 프론트엔드, 백엔드 그리고 데브옵스까지 맡아 오랫동안 빠른 속도에 맞춰서 개발해오다보니 상당한 기술부채가 존재하는 상황이었습니다.

레거시를 다루기 위해 한꺼번에 재작성이 가능하다면 이 방법이 제일 깔끔할 것입니다. 그러나 일정 규모 이상의 코드베이스의 경우 상당한 시간이 필연적으로 소요되기 때문에 저희는 점진적으로 마이그레이션을 해나가며 기능 추가와 기존 코드베이스 개선이 동시에 이루어질 수 있는 방법을 채택했습니다.

이와 관련해서는 저희의 이전 포스트인 Rush로 프론트엔드 모노레포 도입하기에서 상세하게 다룬바가 있습니다.

이러한 방식의 마이그레이션의 가장 큰 장점은 기존 서비스를 그대로 유지한 채 애플리케이션에서 새롭게 개발되는 부분에 대해서는 그린 필드 프로젝트를 개발하는 경험과 동일한 DX를 가져갈 수 있다는 것입니다.

해당 포스트에서는 모던 웹 개발 환경을 새롭게 꾸미면서 도입하게 된 기술 스택 중 유용한 것들 몇 가지에 대해서 간단히 소개하겠습니다.

디자인 시스템

프론트엔드 전반에 걸쳐 시각적 일관성을 얻기 위해 우리는 디자인 시스템을 개발합니다. 예컨대, 이상적인 제품은 오직 한 종류의 Button 집합, 통일된 Typography 세트를 가지며 UI 요소는 어떤 개발자가 작업 했는가에 상관없이 동일하게 보여야 합니다.

밀당영어의 경우, 팀의 규모가 상대적으로 작았기에 디자인 시스템 운영으로 얼마만큼의 이득을 볼 수 있을지 미지수였습니다. 하지만 아래의 장점들을 놓치고 싶지 않았고, 팀의 규모가 커질수록 더욱 빛을 발하게 될 것이라는 믿음이 있었기 때문에 지금은 열심히 가꿔나가는 중입니다.

1. Workshop : 메인 앱 흐름과 분리된 환경에서 독립적인 컴포넌트 개발. 단순히 테스트 용이성과 비쥬얼을 쉽게 확인할 수 있다는 점을 차치하고서라도, 통합된 스크린에서 하나의 부품으로서 개발하는 것과 컴포넌트 하나만 놓고 집중해서 가능한 state variant들을 나열해가며 permutation을 개발해 나가는 것은 멘탈 모델 자체가 다릅니다. 더 다양한 상황, 재사용성을 고려하게 되고 앱의 Context 내에서 재현하기 어려운 상태까지 쉽게 다룰 수 있게 됩니다.

2. Styleguide: UI 컴포넌트의 문서화. 누구나 한번쯤은 Components 폴더 하위에 사용되지 않는 정체모를 컴포넌트를 발견했거나, 기껏 새로운 컴포넌트를 만들었는데 똑같은 기능을 하는 도플갱어를 보고 허탈했던 적이 있을 것 입니다. UI 카탈로그를 만드는 것은 이런 문제들을 방지하고 팀내 지식공유를 원활하게 합니다.

Mildang UI 카탈로그

컴포넌트의 분류 기준

  • Atom: 더 이상 분할이 불가능한 기본 요소가 되는 컴포넌트 (Color, Shadow, Typo…)
  • Molecule: Atom 의 조합으로 독자적으로 기능을 하는 컴포넌트(Button, Input…)
  • Pattern: 보다 작은 단위의 컴포넌트들을 조합하며 UI 패턴을 가지고 있는 컴포넌트
  • Screen: 말 그대로 페이지 하나에 대응되는 컴포넌트.

Pattern 부터는 재사용성이 떨어지며, Data Fetching 등의 비즈니스 로직이 섞이게 됩니다. 너무 많은 단계의 분류 기준을 가질 경우 어떤 카테고리에 넣을 것인지 고민하는 것 자체로 스트레스가 되므로 (확실한 기준이 없을 경우) 저희는 최대한 실용적인 분류방법을 적용하고 있습니다.

밀당영어는 Component workshop & Styleguide 의 용도로 Storybook 을 사용하고 있습니다.

스타일링

(대부분의) CSS in JS 는 기본적으로 JS로 스타일을 작성하면 유니크한 클래스명이 생성되어 런타임에 DOM으로 스타일 코드를 주입시키는 기술 일컫습니다. Christopher Chedeau 가 기존 CSS 의 확장성 문제를 대두시킨 이후로 현재까지 관련 생태계가 발전해오고 있습니다.

React.js 로 일반적인 SPA를 개발하는 팀이라면 CSS in JS 는 가장 무난한 선택지입니다. 문서의 구조와 로직에(JSX) 스타일링 코드(CSS) 까지 같은 위계에 위치시켜 높은 응집성을 가지기 때문에 서로 다른 Technology Context 를 오가며 멘탈 오버헤드를 증가시키지 않고 개발하는 컴포넌트 그 자체에 집중하게 해줍니다.

밀당영어는 Emotion 을 사용합니다. css prop 은 스타일링을 더 쉽게 접근하게 해줍니다. 동적 스타일과 타입스크립트를 함께 사용하는 것도 쉽고 직관적입니다. 번들 크기에 심각하게 집착한다면 고민이 될 수도 있지만, 제공하는 기능들을 고려해 봤을 때 다른 CSS in JS 솔루션 보다는 괜찮은 편이라고 생각됩니다. 성능보다는 DX 에 초점을 맞추었지만, 그럭저럭 사용하기에 나쁘지 않은 올라운더 라이브러리입니다.

재사용 가능한 디자인 시스템 컴포넌트를 만들때는 Emotioncss prop을 활용하지만 1회성 스타일 오버라이드(One-off customization)를 할적에 MUIsx prop을 광범위하게 활용합니다. sx prop은 대부분의 UI 프레임워크에서 지원하는 기능으로(Atomic CSS 컨셉으로 부터 파생) Theme 기반 스타일링을 좀 더 간결한 문법(shorthand)으로 할 수 있게 해줍니다.

개인적으로는 사소한 스타일 변경에 매번 스타일 코드를 선언하고 네이밍을 붙이는 번거로움을 사이다처럼 해결해주어 개발속도를 끌어올리는 점을 가장 가치있게 보고있습니다. 물론 너무 남용한다면 가독성을 해칩니다. Configurable 한 디자인 시스템을 만들어서 대부분의 디자인을 커버하고, 세부 디테일을 구현하는 용도로 sx prop을 활용하는 것이 합리적인 방향이라고 생각합니다.

테스팅

복잡한 함수(컴포넌트)를 유닛 테스트 없이 작성한다면 어떨까요? 아마도 매번 기능을 수정할때마다 수동으로 모든 Use case 들을 직접 확인해봐야 할 것 입니다.

하지만 UI 테스팅은 테스트할 대상을 분리시키기 어렵고, 시각적으로 Verify할 수 없다는 점 때문에 많은 개발팀이 테스트 유지보수를 포기하고 종종 위와 같은 방식으로 개발하곤 합니다.

밀당영어에서는 Storybook 의 하나의 Story 를 모든 종류의 테스트 케이스의 원천으로 보고, 적극적으로 활용하고 있습니다. Story는 위에서 언급한 Jest와 같은 Node 환경에서 동작하는 테스트 러너들이 커버해주지 못하는 점들을 보완합니다. Story는 분리된 하나의 Use case(또는 상태)이며, 실제 브라우저에서 동작하는 UI 를 눈으로 확인해볼 수 있는 테스트 케이스이기 때문입니다.

더 나아가서, Story(Component Story Format)를 그대로 유닛 테스트 환경에 가져다가 RTL(React Testing Library)을 사용해서 Interaction 테스트를 자동화 할 수도 있습니다(곧 출시될 play function 기능으로 Interaction 테스트를 실제 브라우저에서도 실행하는 것도 가능해집니다). 이렇게 하면 이미 특정 상태가 적용된 컴포넌트가 준비된 환경에서 테스트 케이스를 작성하게 되므로 Assertion 을 작성할 때 어떤 것을 Verify 할지 명확해지는 효과가 있습니다.

모킹

Backend API 의 구현 여부와 독립적으로 컴포넌트를 개발하기 위해서 MSW(Mock Service Worker)를 사용합니다.

MSW 는 표준 Service Worker API를 사용하기 때문에 네트워크 수준에서 http 요청을 가로채어 사용자가 정의한 Express 스타일의 route handler 의 내용대로 응답을 받아오는 것이 가능합니다. 그렇기 때문에 Mocking 경험을 상당히 매끄럽게 해줍니다. 아마 많은 분들이 API 응답값을 테스트 하기 위해 임의의 가짜 데이터를 반환하도록 애플리케이션 코드를 수정하거나, test case 내에서 MockProvider 같은 것들을 제공해서 fetch 함수를 Mocking 해본 경험이 있을 겁니다. 하지만 MSW 를 사용하게 되면 Mocking 여부와 상관없이 기존 코드 그대로 실제 사용자가 사용하는 방식처럼 테스트가 가능한 것이 큰 장점입니다.

저희는 MSW로 제작한 Mock API Server 를 Storybook 에 필요한 data 를 공급할 때, 그리고 테스트 케이스를 작성할 때 등에 유용하게 사용하고 있습니다.

GraphQL

One missing piece: Data Requirement

앞서 CSS in JS 란에서 JSX + Styling Logic 으로 높은 응집성을 갖는 컴포넌트에 대해서 언급했었습니다. GraphQL 은 여기에 나아가서 컴포넌트에 Data Layer를 추가해서 유지보수 용이성을 한층 더 업그레이드 할 수 있게 해줍니다.

각 컴포넌트가 독자적으로 데이터 요구사항을 선언하는 것은 중앙에서 fetch 하여 분배하는 것보다 훨씬 유연하고 우월합니다. 만약 어떤 컴포넌트의 데이터 요구사항이 변경되면 다른 곳을 찾아 헤맬 필요없이 실제로 데이터를 소비하는 해당 컴포넌트에서만 변경하면 됩니다. 그러면 GraphQL Client 가 컴포넌트 트리를 따라 요구사항을 읽어가면서 하나의 커다란 쿼리로 합쳐 네트워크 요청을 보낼때 자동으로 반영되게 됩니다. 이러한 과정들은 Fragment 를 적극적으로 활용함으로써 더 자연스럽게 일어나게 됩니다.

저희는 SDL(Schema Definition Language)로 정의한 쿼리를 .graphql 확장자 파일에 정의하고, 파일들을 바탕으로 타입 제너레이션을 한 후 컴포넌트에서 소비하는 방식을 사용합니다. 아래는 파일 구조의 예시입니다.

SignIn 폴더 하위의 파일들

Free End-to-End Type Safety

밀당영어에서는 프론트엔드 전반에 TypeScript를 사용합니다. 일반적으로 Rest API 를 사용할 때, 따로 응답의 타입을 지정해주지 않는다면, fetch 되는 data 자체는 하나의 커다란 any 타입 덩어리와 같습니다. 타입을 개발자가 수동으로 지정해준다고 하더라도 변화하는 API에 맞춰서 동기화를 하는 것은 너무나 비효율적입니다.

GraphQL 은 자체적으로 타입을 가지고 있습니다. 그리고 이 타입 시스템으로 서버와 클라이언트 사이의 계약(Contract)인 스키마를 작성합니다. 그렇다면 타입을 가진 GraphQL 스키마와 클라이언트에서 어떤 필드를 가져올지가 정의되어있는 GraphQL Operation(Query, Mutation…) 에 대한 정보가 있다면, 이것들을 TypeScript 의 타입으로 정의할 수 있지 않을까요?

GraphQL Code Generator 는 서버에서 생성된 GraphQL Schema, 그리고 클라이언트의 Operation Usage를 스캔해서 완벽히 타입이 정의된 Query 와 Mutation, 부수적인 타입들을 생성해줍니다. 결론적으로, 서버에서 정의된 타입이 클라이언트로 까지 연결되는 종단간 타입 안전성(End-to-End Type Safety)을 공짜로 얻게 되는 것입니다.

마치며

여기까지 밀당영어에서 새롭게 도입한 기술 스택들 몇 가지를 소개해 보았습니다. 밀당영어가 개발하는 웹도 결국은 일반적인 범주의 SPA이기 때문에, 사용하는 툴이나 개발환경이 현재의 프론트엔드 분야 트렌드와 크게 다르진 않은 것 같습니다. 따라서 대부분의 다른 팀에 적용하기에도 괜찮게 잘 맞을거라고 생각합니다.

밀당영어에 아직 프론트엔드 팀이 구성된 지 오래되지 않았고, 새로운 팀멤버 채용과 개발환경을 이제 막 조성하는 단계라서 앞으로 더욱 개선할 부분이 많습니다. 이 글이 새로운 기술스택을 고려하려는 분들께 유용하게 읽혔기를 바라며 글을 마치겠습니다. 읽어주셔서 감사합니다!

프론트엔드 팀에서 동료를 찾고 있습니다!

밀당 영어가 최근 급성장하여 에듀테크 유니콘이 되기 위해 새로운 시스템을 개발하고 있습니다. 데이터 파이프라인 구축부터 인프라 개선 등 기존 레거시에 존재하는 다양한 문제를 해결, 개선하고 있습니다. 새로운 도전을 즐기시는 분들과 함께 하면 교육의 혁신을 더욱 빠르고 멋지게 이룰 수 있을 것 같습니다!

밀당과 함께할 동료를 찾습니다.

--

--

Sungmin Park
IHFB  R&D 팀블로그

I Hate Flying Bugs에서 프론트엔드 개발을 맡고 있습니다.