Skip to main content

대형 웹 애플리케이션 Micro Frontends 전환기

About 2 minNode.jsReact.jsArticle(s)blogyozm.wishket.comnodenodejsreactreactjs

대형 웹 애플리케이션 Micro Frontends 전환기 관련

React.js > Article(s)

Article(s)

대형 웹 애플리케이션 Micro Frontends 전환기 (1) | 요즘IT
이번 글에서는 플렉스 팀이 기존 제품을 마이크로 프론트엔드 아키텍처로 전환했던 8개월간의 과정에 대해서 소개합니다. 왜 플렉스 웹앱을 마이크로 프론트엔드 아키텍처로 전환했는지, 어떤 고난과 역경을 겪었는지 그리고 어떤 방식으로 극복해나갔는지에 대해 이야기하겠습니다. 아키텍처를 바꾸면서 겪었던 그 자세한 내용에 대해 소개하고, 불확실성이 높은 기술을 도입하면서 이를 검증하고 설득하는 과정, 그리고 성공적인 마이그레이션을 위해서 신경 썼던 것들을 위주로 설명하겠습니다.

FEConf2023에서 발표한 <대형 웹 애플리케이션 Micro Frontends 전환기open in new window>[1]를 정리한 글입니다. 발표 내용을 2회로 나누어 발행합니다. 1회에서는 HR플랫폼 플렉스(flex) 제품의 기존 문제점과 이를 해결하기 위한 가설 검증을 살펴봅니다. 2회에서는 실제 아키텍처 전환 과정을 살펴보고 성과와 남은 문제점들을 알아봅니다. 본문에 삽입된 이미지의 출처는 모두 이 콘텐츠와 같은 제목의 발표자료로, 따로 출처를 표기하지 않았습니다. 발표자료는 FEConf2023 홈페이지open in new window에서 다운받을 수 있습니다.

안녕하세요. ‘대형 웹 애플리케이션 마이크로 프론트엔드 전환기’에 대해 소개하게 된 플렉스 팀의 김종혁입니다. 제가 속해 있는 플렉스 팀은 SaaS 기반 올인원 HR 플랫폼을 만들고 있고, 저는 FE Labs라는 팀에서 프론트엔드 엔지니어로 근무하고 있습니다. FE Labs라는 팀 이름이 특이할 수 있는데, 실험실이라는 이름 그대로 동료 프론트엔드 개발자들이 일을 더 잘할 수 있도록 개발 플랫폼에 관한 업무를 수행하고, 빌드 환경, 인프라 등 제품의 기반이 되는 여러 도구들을 연구하고 개선해나가는 일을 하고 있습니다.

이번 글에서는 플렉스 팀이 기존 제품을 마이크로 프론트엔드 아키텍처로 전환했던 8개월간의 과정에 대해서 소개합니다. 왜 플렉스 웹앱을 마이크로 프론트엔드 아키텍처로 전환했는지, 어떤 고난과 역경을 겪었는지 그리고 어떤 방식으로 극복해나갔는지에 대해 이야기하겠습니다.

플렉스 팀이 더 잘 일하기 위해, 그리고 더 좋은 제품을 만들기 위해 아키텍처를 바꾸면서 겪었던 그 자세한 내용에 대해 소개하고, 불확실성이 높은 기술을 도입하면서 이를 검증하고 설득하는 과정, 그리고 성공적인 마이그레이션을 위해서 신경 썼던 것들을 위주로 설명하겠습니다.

이번 글에서 살펴볼 내용은 다음과 같습니다.

  1. flex 제품 & 코드 베이스 소개
  2. 문제 탐구의 시작
  3. Proof of Concepts (PoC)
  4. Actions
  5. 마이그레이션 완료

플렉스

먼저 플렉스 웹 제품에 대한 소개를 드리겠습니다. 글의 제목에서 ‘대형 웹'이라고 했으니 얼마나 큰 서비스인지 보셔야겠죠? 아래 그림이 플렉스의 홈 화면입니다. ‘올인원 HR 플랫폼’이라는 플렉스의 가치관을 표방하는 화면입니다.

1. 플렉스 홈 화면
1. 플렉스 홈 화면

회사 내 인사와 관련된 여러 가지 복잡하고 손이 많이 가는 업무를 내부 구성원과 HR 관리자가 쉽게 처리할 수 있게 도와줍니다. 이로 인해 본질적으로 회사의 성장에 더 집중할 수 있도록 도와주고 있습니다. 올인원 플랫폼을 표방하는 만큼 기능도 많습니다. 홈 화면의 왼쪽에 위치한 것이 GNB라고 부르는 영역인데, 이 영역을 통해 플렉스 제품의 여러 가지 기능들을 넘나들며 사용할 수 있습니다.

저희가 만들고 있는 대표적인 기능들은 아래 그림처럼 아주 다양하고, 인사 업무의 여러 영역을 포함하고 있습니다.

2. 플렉스가 커버하는 생애주기
2. 플렉스가 커버하는 생애주기

플렉스 제품은 회사 구성원의 생애 주기에서 일어나는 이벤트를 모두 커버합니다. 다시 말하면 구성원의 입사부터 퇴사까지 일어나는 모든 일을 기능으로 만들고 있습니다. 이것을 플렉스 팀에서는 ‘통합의 경험'이라고 말하고 있습니다. 플렉스는 모든 HR 관련 업무를 효과적으로 처리할 수 있는 올인원 HR 플랫폼을 표방하고, 이를 통해 통합의 경험을 제공합니다. 이는 상당히 중요한 컨셉으로, 통합의 경험이 제품에 어떻게 구현되었는지를 자세히 알아보겠습니다.

회사가 신규 구성원을 채용했다면 플렉스를 통해 구성원을 초대하고, 구성원이 정보를 입력하면 플렉스에 기록됩니다. 그러고 나면 근로계약서를 생성해 주는 기능을 활용하여 근로 계약을 합니다. 근로 계약까지 마쳤다면 이제 일을 해야겠죠? 그 구성원은 근무를 하기 위해 플렉스 앱을 통해 출퇴근 시간을 기록하고 휴가를 신청합니다. 일을 했으니 급여일에 급여를 받고 연말에는 연말정산도 플렉스 앱을 통해 진행합니다.

이 모든 활동들이 플렉스 제품 한곳에서 일어납니다. 이러한 통합의 경험으로 인해 플렉스 제품은 각각의 앱보다 더 많은 가치를 줄 수 있습니다. 왜냐하면 각 과정에서 발생한 데이터들이 또 다른 기능에서도 사용되기 때문입니다. 구성원의 근로계약 정보와 회사의 근무 정책에 따라 근무 기록을 남기고 이 데이터를 바탕으로 급여 정산까지 할 수 있습니다. 즉, 플렉스를 사용하는 고객사는 하나의 앱에서 복잡한 HR 업무를 손쉽게 처리할 수 있습니다.

3. 플렉스의 통합의 경험
3. 플렉스의 통합의 경험

그렇다면 엔지니어 입장에서 이 제품의 특성은 어떨까요? 앞서 설명드린 통합의 경험을 제공하기 위해 도메인과 결합된 여러 가지 데이터, UI, 그리고 코드들이 각 기능 여기저기서 상당히 많이 엮이게 됩니다. 가령 근무 계약 정보를 바탕으로 구성원의 근무 유형이 달라지게 되고, 이를 위해 근무 기능에서 계약한 근무 정책에 대한 데이터가 필요합니다. 또 근무하는 시간에 따라서 급여가 달라진다면 급여 기능에서 근무와 관련된 데이터가 필요합니다.

이러한 기능 단위의 엮임은 엔지니어 입장에서는 엔지니어링 난이도를 상당히 증가시키는 지점입니다. 근태, 급여 등의 각 개별 기능 개발 난이도도 복잡해 보이는데, 이게 서로 엮여 있으니 제품의 코드 베이스는 아주 방대해지고 의존성이 복잡해질 수밖에 없습니다. 게다가 이 모든 기능이 하나의 앱을 쓰는 것처럼 인식되어야 하기 때문에 개발 난이도가 더욱 증가합니다.

4.엔지니어 관점에서의 특성
4.엔지니어 관점에서의 특성

위와 같은 상황을 토대로, 플렉스 제품의 프론트엔드 엔지니어들이 개발하는 앱은 17개의 Next.js 앱이 있었고, 모노레포 위에 앱의 개수 보다 훨씬 많은 공용 패키지를 사용하고 있었습니다. 제품 전체를 다 합치면 200개 이상의 URL과 페이지를 가지고 있고, 모노레포 코드 베이스는 의존성 관련한 파일들을 제외하면 8.7기가 바이트의 용량을 가지고 있었습니다.

5. 방대한 크기의 기존 플렉스
5. 방대한 크기의 기존 플렉스

이렇게 제품 하나하나가 엄청 크고 복잡하기 때문에 각 하나의 제품들은 각 제품에 대한 오너십을 가진 디자이너, 엔지니어, PM으로 구성된 스쿼드 조직에서 문제를 풀어나가고 있습니다.

6. 스쿼드 조직
6. 스쿼드 조직

플렉스의 문제

그렇다면 플렉스 제품에는 무슨 문제가 있었을까요? 기존 아키텍처는 각각 제품에 해당하는 여러 Next.js 앱들이 존재하고 이를 인프라 레벨에서 라우팅해주는 형태였습니다. 아래 그림처럼 time-tracking이라는 URL로 접속하면 근무 앱으로 접속되고, digicon이라는 URL로 접속하면 전자계약 앱으로 접속되는 방식입니다.

7. 기존 아키텍처
7. 기존 아키텍처

처음의 플렉스 앱은 모놀리식 Next.js 앱이었지만 앱이 점점 커짐에 따라 빌드 배포 시간이 아주 오래 걸리게 되었습니다. 이로 인해 앱을 나눠서 개발할 필요성을 느꼈고, 각 스쿼드에서 페이지 별로 앱을 나눠서 개발했습니다. 앞서 설명한 GNB와 같은 모든 앱에 공통적으로 들어가는 UI들은 빌드 과정에 포함해 같이 배포되고 있었습니다. 다시 말하면 페이지 묶음별로 전체 앱이 나눠져 있었다고 보면 될 것 같습니다.

이런 아키텍처에는 다음 두 가지 문제가 있습니다.

  1. 유저에게 하나의 앱을 쓰는 경험을 전달할 수 없다.
  2. 스쿼드 독립적, 효율적인 개발이 힘들다.

하나의 앱을 쓰는 경험을 전달하기 어렵다

첫 번째 문제부터 살펴보겠습니다. 각각의 앱은 모두 HTML, 자바스크립트 번들, CSS와 같은 모든 리소스를 가지고 있고, 다른 앱으로 접속하면 또다시 모든 리소스를 가져오는 형태입니다. 즉, 플렉스 제품 내에서 다른 기능으로 넘어간 것뿐인데, 모든 자원을 다시 가져와야 합니다.

8. 페이지 이동시 모든 리소스를 다시 준비하는 플렉스
8. 페이지 이동시 모든 리소스를 다시 준비하는 플렉스

물론 페이지에 존재하는 공통 UI들은 다른 앱으로 넘어가도 유지되기도 하지만, 다른 앱의 기능으로 넘어갈 때마다 로딩 서클을 다시 보게 되고, 이전 앱의 기능에서 사용한 기능들도 다시 불러와 사용하게 됩니다. 이런 상황은 최적화와 거리가 멀었고, 하나의 앱을 사용하는 완벽한 경험을 줄 수 없었습니다.

스쿼드 독립적, 효율적인 개발이 힘들다

모든 앱의 빌드에 포함되는 GNB와 같은 UI들도 불편함이 있습니다. 기존 아키텍처에서는 GNB 패키지를 기능 내부 패키지에서 임포트 문을 통해 각 앱들에 모두 넣어주는 형태입니다. 따라서 여러 개의 앱을 한 번에 배포할 때 GNB에 문제가 있다면 해당 버전의 GNB가 포함된 모든 앱을 롤백 해야 했습니다.

9. 기존 아키텍처의 문제
9. 기존 아키텍처의 문제

복잡한 UI들이 임포트 문으로 빌드 타임에 통합되는 문제 때문에 각 스쿼드가 독립적으로 개발하기 힘들었습니다. GNB가 수정되면 모든 앱에 영향을 미치기 때문에 한 스쿼드에서 진행한 변경이 너무 쉽게 다른 앱에 영향을 미치게 되는 것입니다.


마이크로 프론트엔드 아키텍처

위에서 소개한 두 가지 문제들에 대해 마이크로 프론트엔드 아키텍처가 문제를 해결할 수 있는 가능성이 있어 보였습니다. 저희가 검토한 기술은 모듈 번들러인 웹팩이 플러그인으로 기능을 제공하는 ‘모듈 페더레이션'입니다.

모듈 페더레이션

모듈 페더레이션은 마이크로 프론트엔드 아키텍처의 구현 방식 중 하나로, 하나의 앱을 독립적인 배포가 가능한 모듈 단위로 나누어 브라우저의 런타임 시점에 통합하는 방식입니다.

10. 모듈 페더레이션
10. 모듈 페더레이션

이는 빌드 타임이 아닌 런타임에 각각의 분리된 앱을 합치는 방식으로, 분리된 앱들이 각각의 개발 프로세스를 거쳐서 자바스크립트 번들 형태로 배포됩니다. 각각의 마이크로 앱들은 호스트라고 불리는 앱에서 번들 형태로 로드되어 각각 따로 렌더링 됩니다.

이러한 아이디어를 활용한다면 앱 단위가 아니라 화면에 보이는 모든 UI들을 컴포넌트 단위로 배포할 수 있게 됩니다. 또, 앱 전환 시 모든 자원을 처음부터 다시 불러올 필요가 없게 되고, 런타임 환경에서 하나의 앱을 사용하는 것처럼 만들 수 있습니다.

11. 모듈 페더레이션 워크플로우
11. 모듈 페더레이션 워크플로우

실제 플렉스 앱에서 홈페이지를 예로 들면 아래와 같은 구조라고 생각하면 될 것 같습니다.

12. 플렉스와 모듈 페더레이션
12. 플렉스와 모듈 페더레이션

배포 단위를 호스트, GNB 그리고 홈이라는 3가지로 나누고, 각각의 앱들은 따로 개발되어 빌드, 배포될 수 있는 환경을 만듭니다. 실제 브라우저에서는 앱의 초기 로딩 시에 먼저 호스트가 로드 되고, 호스트가 각각 따로 배포된 UI 컴포넌트인 GNB와 홈을 런타임에 로드하여 렌더링 되는 방식입니다.


플렉스와 마이크로 프론트엔드

마이크로 프론트엔드, 모듈 페더레이션은 저희가 겪고 있던 두 가지 문제를 모두 해결할 수 있는 솔루션이라고 생각했습니다. 배포 단위는 분할되지만 런타임에 하나의 앱으로 합쳐져 유저에게 하나의 앱을 쓰는 경험을 전달할 수 있습니다. 그리고 배포 단위를 페이지 이하의 단위로 쪼개서 필요한 부분만 개발하고 배포하는 방식으로 바꾼다면 빌드 타임에 통합되는 복잡한 UI로 인해 스쿼드 독립적인 개발이 힘들다는 점을 해결할 수 있습니다.

문제 해결 외에 매력적인 장점도 많았습니다. 첫 번째로, 앱 내의 리액트 컴포넌트를 쪼개서 배포 단위를 만들고 번들로 빌드 하여 배포하면 앱 내부에서는 코드 스플리팅을 한 것처럼 자연스럽게 통합할 수 있습니다. 또, iframe과 같은 통합을 위한 수단이나 상태 공유 방법에 대한 고려가 필요 없고, 컴포넌트를 쪼개듯 배포 단위를 쪼갤 수 있다는 점도 장점이었습니다. 플렉스 팀은 배포 단위를 더 잘게 나누고 동시에 하나의 앱을 사용하는 경험을 제공하고자 했고, 이는 굉장히 좋은 선택으로 보였습니다.

13. 런타임 통합 마이크로 프론트엔드 아키텍처의 장점
13. 런타임 통합 마이크로 프론트엔드 아키텍처의 장점

PoC ( 검증 )

저희 팀은 모듈 페더레이션 관련 리서치 내용을 바탕으로 모노레포 코드 베이스 안에 아래와 같이 디렉토리를 하나 만들어 실행시켜 볼 수 있는 작은 SPA 앱을 만들어 ‘체험존 개장’이라는 문서를 통해 프론트엔드 엔지니어들에게 공유했습니다. 이 체험존 앱은 모노레포 내부에 있기 때문에 플렉스 제품에서 사용하는 공용 패키지들을 가져와서 사용할 수 있고, 기존 플렉스의 GNB와 유사하게 만들어서 페이지를 이동할 때 모듈이 어떻게 동적으로 로드 되는지, 유저는 어떻게 앱을 사용할 수 있는지에 대한 정보를 제공합니다.

14. 모듈 페더레이션 체험 앱
14. 모듈 페더레이션 체험 앱

기존 아키텍처와 모듈 페더레이션

기존 아키텍처에서 모듈 페더레이션을 사용할 수 있는지 확인하기 위해, 기존의 Next.js 앱과 모듈 페더레이션의 궁합을 보는 PoC도 진행했습니다. Next.js에서 모듈 페더레이션을 사용할 수 있게 해주는 플러그인이 있었고, 이를 플렉스 앱에 적용해 봤습니다. 앱이 동작하는 것은 확인했지만, Next.js의 기타 레포지토리에 이슈가 발생했고, 리서치 결과 Next.js에서는 공식적으로 마이크로 프론트엔드 아키텍처에 대해 모듈 페더레이션 방식을 지원할 계획이 없다고 판단되었습니다. 또한 플렉스라는 커다란 서비스를 검증되지 않은 커뮤니티 기반의 플러그인에 의존하는 것은 리스크가 크다고 생각했습니다. 따라서 Next.js와 모듈 페더레이션을 함께 사용할 수 없다는 판단을 내렸습니다.

15. Next.js와 모듈 페더레이션
15. Next.js와 모듈 페더레이션

Next.js와의 이별, SPA로 전환

플렉스 팀은 마이크로 프론트엔드 아키텍처를 구현하기 위해 Next.js를 뜯어내게 되고, 플렉스 앱을 SSR 없이 CSR만 사용하는 SPA로 전환하기로 결정했습니다. Next.js와 같은 프레임워크를 사용하면서 누릴 수 있는 가장 큰 장점은 SSR를 통해 서버 리소스를 쉽게 활용할 수 있다는 점입니다. Next.js를 사용하지 않으면 당장은 이러한 기능 사용도 힘들어집니다. 그렇다고 마이그레이션 과정에서 SSR을 직접 구현하기에는 시간과 리소스도 부족했습니다.

그러나 B2B SaaS라는 플렉스 제품 특성상 Next.js의 SSR 방법의 이점인 SEO 등에 대한 중요성이 적었고, 일부 기능에서만 SSR을 사용했기 때문에 여러 요소를 감수하고 전면 SPA로 전환하는 결론을 내릴 수 있었습니다.

16. Next.js 분리
16. Next.js 분리

아키텍처 변경에 대한 트레이드 오프

물론 아키텍처 변경에 대한 우려도 존재했습니다. SPA로 전환하게 되면 렌더링에 필요한 리소스를 유저의 브라우저에서 로드하기 때문에, 초기 렌더링 속도가 늦어집니다. 즉, LCP(Largest Contentful Paint, 사용자가 URL을 요청한 시점부터 페이지 내에서 시각적으로 가장 큰 콘텐츠를 그리는데 걸리는 시간) 등의 성능 지표에 악영향을 미칠 가능성이 컸습니다. 플렉스 서비스의 유저는 명확한 목적을 가지고 앱을 사용하기 때문에, 앱에 접근하는 초기 렌더링 성능 못지않게 앱을 사용하는 경험도 중요합니다. 따라서 하나의 앱을 사용하는 경험을 제공해 서비스 사용 경험을 매끄럽게 만든다는 목표를 명확하게 정했습니다. SSR 이외의 성능 병목도 이미 제품에서 존재하고 있었기 때문에 성능 개선은 아키텍처 변경과 별개로 진행할 테스크라고 판단했습니다.

또, 단기적으로는 SSR을 사용하지 않는 선택을 했지만, 추후에는 필요할 수도 있다는 점을 인식했습니다. 추후에 SSR을 사용하기 위해 Next.js처럼 클라이언트 리소스를 전달하는 서버를 같이 구현하는 방법으로 확장성을 고려하여 아키텍처 변경을 진행하기로 했습니다.

17. 아키텍처 변경에 따른 트레이드 오프
17. 아키텍처 변경에 따른 트레이드 오프

아키텍처 변경에 대한 불확실성과 걱정

검증 단계와 의사결정 단계에서 많은 시간을 사용했지만, 여전히 불확실성은 높았습니다. 모듈 페더레이션에 대한 기술 레퍼런스가 부족했고, 널리 검증된 기술이 아니었기 때문에 예상한 그대로 동작한다는 보장이 없었습니다. PoC를 통해 모든 위험을 해소했다고 할 수도 없었습니다.

이러한 걱정은 ‘플렉스 팀이 도입하는 것은 모듈 페더레이션이 아니라 마이크로 프론트엔드다', ‘앞으로 플렉스 앱은 계속해서 커질 것이기 때문에 결국 우리 제품은 마이크로 프론트엔드라는 구조로 가야 한다' 와 같이 목표를 명확히 인식하는 것으로 극복했습니다. 즉, 모듈 페더레이션이 실패하더라도 다른 방법을 찾아서 적용하면 된다고 생각하면서 아키텍처 변경을 계속해서 진행할 수 있었습니다.

그리고 의사 결정 트리를 만들어 계획이 잘 되었을 때는 계속해서 진행하고, 실패했을 때는 다른 방법을 찾을 수 있도록 했습니다. 즉, 대안을 미리 세워두고 팀원들과 공유하는 방법으로 업무를 가시화 하였고. 이 의사 결정 과정에 따라 실패 하게 되면 빨리 다른 방법을 찾을 수 있도록 로드맵을 설정하여 진행했습니다.

다음 글에서는 위 검증을 바탕으로 실제 마이크로 프론트엔드 마이그레이션 실행 과정에 대해 알아보겠습니다.


대형 웹 애플리케이션 Micro Frontends 전환기 (2) | 요즘IT
먼저 마이크로 프론트엔드라는 미지의 세계를 처음 접하면서 기술에 대한 이해도를 높였던 기간, 모듈 페더레이션 플러그인을 사용할 애플리케이션의 새 기반을 다졌던 과정, 빠른 기능 개발 속도를 유지하면서 아키텍처 마이그레이션을 진행했던 과정, 그리고 전사 구성원이 참여하는 QA를 통해 아키텍처 마이그레이션 과정을 마무리 지었습니다. 이 과정 하나하나의 결정들이 모여서 원활한 마이그레이션을 진행할 수 있었습니다. 4개의 과정에 대해 자세히 설명드리겠습니다.

Actions ( 실행 )

플렉스 팀은 1편에서 말한 검증들을 토대로 의사 결정을 끝내고, 플렉스 제품을 마이크로 프론트엔드 아키텍처 변경하는 작업을 2022년 9월부터 시작하여 2023년 3월까지 진행하게 되었습니다. 아래 내용처럼 총 4단계의 과정을 거쳐 마이그레이션이 진행되었습니다.

  1. 팀 차원의 자신감 향상시키기 ( 2022.09 ~ 2022.10 )
  2. 새 애플리케이션 기반 다지기 ( 2022.10 ~ 2022.11 )
  3. 기능 개발 속도 최대한 유지하기 ( 2022.11 ~ 2022.12 )
  4. 전사가 참여하는 마이그레이션 QA ( 2023.01 ~ 2023.03 )

먼저 마이크로 프론트엔드라는 미지의 세계를 처음 접하면서 기술에 대한 이해도를 높였던 기간, 모듈 페더레이션 플러그인을 사용할 애플리케이션의 새 기반을 다졌던 과정, 빠른 기능 개발 속도를 유지하면서 아키텍처 마이그레이션을 진행했던 과정, 그리고 전사 구성원이 참여하는 QA를 통해 아키텍처 마이그레이션 과정을 마무리 지었습니다. 이 과정 하나하나의 결정들이 모여서 원활한 마이그레이션을 진행할 수 있었습니다. 4개의 과정에 대해 자세히 설명드리겠습니다.

팀 차원의 자신감 향상시키기 ( 2022.09 ~ 2022.10 )

먼저 마이크로 프론트엔드와 모듈 페더레이션에 대한 팀 차원의 지식수준을 같이 올리기 위해 다양한 데모 환경을 만들어 배포하고, 이를 통해 가설을 검증해 보면서 팀 전체가 성공에 대한 자신감을 가질 수 있도록 했습니다. 프론트엔드 엔지니어들의 지식수준을 올리기 위해 팀원들과 많은 자리를 만들었고, 팀 내부의 기술 공유 활동을 많이 진행했습니다. 코드 설명회를 개최하거나 여러 가지 도식화 문서를 제공하여 정보를 공유했습니다. 또, ‘무엇이든 물어보세요'라는 세션을 열어 궁금한 점이 있는 엔지니어들에게 다양한 질문을 받으며 답해주는 자리를 만들었습니다.

처음에 소개 드렸던 체험존 앱을 일회성으로 끝내지 않고, 프로덕션 환경의 코드를 가져와서 플렉스 서비스에 가까운 형태로 발전시켰습니다. 이 과정에서 시행착오를 많이 겪으며 체험존 앱은 온전한 형태의 앱으로 만들어졌습니다. 이 데모 앱을 사내 망에 배포하여, 앱이 어떤 방식으로 바뀌고 동작하는지 엔지니어들이 눈으로 볼 수 있게 하였고, 사내 인프라의 빌드, 배포 파이프라인을 그대로 이용해서 마이그레이션 이후 빌드, 배포 과정에서 생길 수 있는 이슈나 고려해야 할 점을 미리 알 수 있도록 했습니다. 이 과정에서 여러모로 자신감을 많이 올렸던 것 같습니다.

1.데모 환경 발전시키며 남은 가설 검증
1.데모 환경 발전시키며 남은 가설 검증

새 애플리케이션 기반 다지기 ( 2022.10 ~ 2022.11 )

먼저 아키텍처 마이그레이션을 위해 Next.js에 존재하던 기존 코드들이 들어가서 살 새 집을 지어야 했습니다. 새로운 기반은 웹과 웹 팩 모듈 페더레이션 플러그인을 중심으로 구현되었고, 새 아키텍처가 가지고 있는 리스크를 완화하는데 집중했습니다. 이 리스크는 크게 2가지가 있었습니다.

  1. 모듈 페더레이션이 가진 전방향적인 특성 때문에 생기는 리스크
  2. 런타임 에러 리스크

먼저 모듈 페더레이션의 전방향 리스크부터 살펴보겠습니다. 아래 그림은 webpack config에 들어가는 웹 모듈 페더레이션 플러그인을 설정하는 코드의 일부분입니다. 각각 분리된 앱은 모듈 페더레이션을 사용해 설정 단계에서 remote와 exposes라는 두 개의 옵션을 사용합니다. remote는 해당 마이크로 앱에서 불러올 다른 마이크로 앱의 진입점 URL을 의미하고, exposes는 다른 앱에서 해당 마이크로 앱을 부를 때 사용할 수 있는 모듈의 경로를 의미합니다. 즉 하나의 마이크로 앱은 다른 앱을 호출할 수도 있고, 다른 앱에서 호출될 수도 있습니다.

2. 웹팩 컨피그 파일의 플러그인 설정 코드
2. 웹팩 컨피그 파일의 플러그인 설정 코드

다른 앱을 호출하는 마이크로 앱을 호스트, 다른 앱에서 호출되는 마이크로 앱을 리모트라고 하면, 아래 그림처럼 모든 마이크로 앱은 호스트이면서 동시에 리모트가 될 수 있습니다. 따라서 각각의 마이크로 앱들끼리 자유로운 의존성을 맺을 수 있습니다. 이 특성을 바로 전방향 적인 특성이라고 합니다.

하지만 이런 방법으로 서비스를 운영하면 런타임 간 의존성이 상당히 복잡해지고 앱의 구조를 쉽게 파악하지 못하는 비효율이 생길 수 있습니다. 그래서 플렉스 앱은 전역의 호스트를 하나만 두고, 호스트가 여러 리모트를 불러올 수 있는 2 depth 구조를 만들었고, 이것을 코드 베이스 단의 config로 강제했습니다.

3. 전방향 리스크
3. 전방향 리스크

다음으로 런타임 에러 리스크에 대한 내용입니다. 런타임 리스크란 런타임에 앱들이 동적으로 합쳐지는 과정에서 식별되지 않은 에러가 발견되어 유저가 앱을 원활히 사용하지 못한다는 것이었습니다. 따라서 각 리모트 앱에서 발생한 에러는 격리시켰고, 각 리모트에서 에러가 발생하더라도 앱의 다른 기능을 사용할 수 있도록 만전을 기했습니다.

4. 런타임 에러 리스크
4. 런타임 에러 리스크

이를 위해 로컬 환경에 두 가지 개발 환경을 제공해서 런타임 에러에 대한 위험성을 더 잘 알 수 있게 하고, 개발 생산성에도 문제가 없게 조치를 취했습니다. 아래 그림의 dev:multiple 환경은 호스트 GNB와 함께 여러 앱을 각각 다른 로컬 포트에 실행시켜 여러 앱이 하나로 합쳐져 있는 프로덕션의 모습을 볼 수 있게 한 화면입니다. 하지만 개별 스쿼드 작업자는 개발을 할 때 모든 앱을 띄울 필요 없이 하나의 앱만 개발하는 경우도 많습니다. 따라서 dev:standalone 환경도 제공하여 단일 앱을 개발할 수 있게 했습니다. 개발시 필요하지 않은 앱은 실행시키지 않고 하나의 앱만 실행시켜 개발할 수 있으니 개발 서버의 성능이나 속도를 온전히 활용해 생산성을 올릴 수 있습니다.

5. 2가지 개발 환경을 제공
5. 2가지 개발 환경을 제공

기능 개발 속도 최대한 유지하기 ( 2022.11 ~ 2022.12 )

아키텍처 마이그레이션은 굉장히 큰 변경인데, 기능 개발을 멈추고 진행했을지 궁금하셨을 것 같습니다. 플렉스 팀은 기능 개발을 멈추지 않았고, 마이그레이션과 기능 개발을 동시에 진행했습니다. 어떻게 기존 기능 개발 속도를 최대한 유지하면서 아키텍처 마이그레이션을 함께 진행할 수 있었는지에 대해 설명드리겠습니다.

먼저 기존 플렉스 앱을 점진적으로 마이그레이션 하기는 힘든 상황이었습니다. 왜냐하면 기존의 Next.js 앱에서 마이그레이션된 앱으로 이동할 때 html을 한번 렌더링 해야 했기 때문입니다. 이 렌더링 시간에 로딩 서클 등의 UI를 넣는다고 해도, 예상하지 못한 로딩 화면을 보게 되었고 이로 인해 앱 이동 경험이 안 좋았습니다. 따라서 한 번에 모든 앱을 마이크로 프론트엔드 아키텍처로 마이그레이션해야하는 상황이었습니다.

6. 점진적 마이그레이션이 힘든 이유
6. 점진적 마이그레이션이 힘든 이유

기존 플렉스 팀의 기능 개발 속도는 굉장히 빠른 편이었습니다. 많은 코드가 자주 바뀌고, 주요 업데이트가 있을 때 아래와 같은 업데이트 노트를 발행합니다. 한 달에 평균 두 번 이상 새로운 기능이나 메이저한 개선 사항이 배포되고, 웹 제품은 매주 정기 배포를 통해 기능 개선이 이루어집니다. 아래의 그림처럼 변경 사항이 매우 많다는 걸 알 수 있습니다. 이 빠른 기능 개발 속도를 최대한 유지해야 했습니다.

7. 플렉스 팀의 빠른 기능 개발속도
7. 플렉스 팀의 빠른 기능 개발속도

위에서 설명한 기존 개발 속도 유지와 마이그레이션을 함께 진행하기 위해 두 가지 아키텍처를 동시에 빌드 할 수 있는 환경을 구성했고, 이 환경을 4개월 정도 유지했습니다. 프레임워크에 의존하지 않는 리액트 컴포넌트에 해당하는 코드들을 모노레포(monorepo) 내부 패키지로 분리하고, 기존 웹 패키지와 함께 새로운 웹 패키지를 동시에 의존하도록 구성하여 빌드를 성공시키는 방식입니다. 이를 위해 마이그레이션 초반에 각 스쿼드의 프론트엔드 엔지니어들과 함께 프레임워크에 의존하지 않는 리액트 컴포넌트들을 코드에서 분리하는 작업을 진행했습니다.

8. 두 가지 환경에서 빌드 성공시키기
8. 두 가지 환경에서 빌드 성공시키기

플렉스의 홈페이지로 예를 들면, 홈페이지에 해당하는 리액트 컴포넌트 코드를 분리해서 Next.js 앱에서는 디렉토리 라우팅 방식으로 넣어주고, 마이크로 프론트엔드 앱에서는 SPA 라우팅 방식으로 넣어주면 페이지가 두 앱 모두에서 제대로 작동하여 빌드가 성공하는 방식입니다. Next.js 앱에서는 분리된 페이지에서 계속 기능 개발이 진행되고, 기존 웹에서는 개발된 기능에 대한 QA, 프로덕션 배포가 계속 이뤄졌습니다. 마이크로 프론트엔드 앱에서는 분리된 패키지에서 개발된 기능을 그대로 반영함과 동시에 마이그레이션에 필요한 작업이나 QA를 계속 진행할 수 있었습니다.

위 방식을 계속 진행하면서 마이그레이션이 완료된 기능에 대해서는 기존 앱을 중단시키고 코드 베이스에서도 삭제하여 새로운 앱을 프로덕션으로 전환했습니다.

9. 두 가지 환경에서 동시에 빌드 성공시키기. 기존 코드 삭제
9. 두 가지 환경에서 동시에 빌드 성공시키기. 기존 코드 삭제

앞서서 프레임워크에 의존하지 않은 부분을 따로 분리했다고 했는데, 그렇다면 아래 코드는 어떻게 처리해야 할까요? 플렉스 팀은 원래 Next.js에서 사용하는 라우터의 리액트 훅들을 모노레포의 내부 패키지로 한번 래핑을 해서 사용하고 있습니다. 이 때문에 프레임워크에 의존하지 않는 부분을 분리해도, 분리된 컴포넌트 내부에 기존 Next.js의 의존성이 그대로 남아있게 됩니다.

10. 호환 패키지 대체하기: 분리한 패키지의 컴포넌트에서 Next.js 구현체를 Import하는 경우 -> Micro App에서 빌드 에러
10. 호환 패키지 대체하기: 분리한 패키지의 컴포넌트에서 Next.js 구현체를 Import하는 경우 -> Micro App에서 빌드 에러

위와 같이 물리적으로 떼어낼 수 없는 코드가 존재했고, 이런 코드들은 빌드 시점에 바꿔주었습니다. 넥스트 라우터를 래핑 하는 패키지에 대응하기 위해서 SPA 라우터인 react-router-dom을 래핑 하는 패키지를 만들었고, 기존 패키지와 동일한 기능을 하도록 구현했습니다. 그리고 마이크로 앱이 빌드 될 때 webpack config의 resolve, alias 설정을 통해 코드를 직접 수정하지 않고 빌드 타임에 마이크로 앱 안에 선언된 넥스트 라우터를 새로운 패키지로 대체하여 빌드가 성공할 수 있게 했습니다.

전사가 참여하는 마이그레이션 QA ( 2023.01 ~ 2023.03 )

이번 내용은 마이그레이션의 단계 중 마지막 내용입니다. 전사 구성원이 참여했던 마이그레이션 QA가 어떤 방식으로 이루어졌는지, QA 과정을 효율화하기 위해 어떤 전략을 취했는지 소개합니다.

마이크로 프론트엔드로 바뀐 새로운 앱의 QA는 프로덕트 팀의 모든 구성원이 참여할 수 있는 형태로 진행했습니다. 플렉스 앱을 배포할 때는 데브 환경에 코드를 올려 검증하고, 그다음 QA 환경에서 검증한 후 프로덕션 환경에 코드를 올려 릴리즈 하고 있습니다.

아래 그림을 보면 각 환경 별로 아키텍처 변경을 한 날짜가 표시되어 있습니다. 이러한 제품 배포 과정 중에서 QA 환경을 먼저 마이크로 프론트엔드 아키텍처로 대체하는 방식을 진행했습니다. 이런 방식의 환경 대체를 통해서 제품 기능에 대한 QA와 아키텍처 변경에 대한 QA가 동시에 자연스럽게 일어나도록 환경을 설계했습니다. 이 과정을 통해 점점 프로덕션에 가깝게 만들었고 마이그레이션에 대한 자신감을 높였습니다.

앞서 점진적 마이그레이션이 힘들었고, 환경 전체를 마이크로 프론트엔드로 바꾸는 방식으로 마이그레이션을 진행한다고 말씀드렸는데, 기존 앱에 대한 QA도 같이 진행해야 했기 때문에 두 가지 환경을 모두 접속할 수 있도록 브라우저 쿠키의 앱 타입이라는 값을 통해 인프라 단의 조치도 합께 진행했습니다.

11. 각 환경별 마이그레이션
11. 각 환경별 마이그레이션

이러한 조치 덕분에 기능 QA와 함께 마이그레이션에 대한 QA를 동시에 가능한 환경이 구성됐습니다. 즉, 많은 분들이 참여하여 QA를 효율적으로 진행할 수 있는 상황이 만들어졌습니다. 전사의 디자이너, PM, 엔지니어 분들이 버그에 대한 이슈 티켓을 만들어주면, 각 스쿼드의 프론트엔드 엔지니어가 이 문제를 어느 파트의 문제인지 식별해 주고, 기능의 문제이면 스쿼드에서 해결을, 마이그레이션에 대한 문제이면 FE Lab에서 해결을 진행했습니다.

12. 이슈 해결 과정
12. 이슈 해결 과정

지금까지의 과정은 전사의 구성원들 모두 함께 노력한 QA였습니다. 이러한 마이그레이션 방식 덕분에 새로운 아키텍처에 대한 적용과 함께 일하는 환경도 마이그레이션 되었고, 모든 구성원이 새로운 앱에 점진적으로 적응하고 기존과 달라진 배포 단위에 대한 이해도도 높일 수 있었습니다. 결과적으로 23년 3월 23일에 웹 제품에 대한 마이크로 프론트엔드 아키텍처 마이그레이션이 성공적으로 끝났습니다.


결과 및 성과

성과

마지막으로 이번 마이그레이션으로 얻은 성과를 알아보겠습니다.

  1. 유저 경험 개선 - 하나의 앱을 쓰는 경험
  2. 배포 단위 세분화 - 전체 장애/롤백 상황 최소화
  3. 개발 생산성 개선 - 빌드 시간 평균 70% 개선 (20분 -> 5분)

유저에게 하나의 앱을 쓰는 경험을 제공할 수 있게 되었습니다. 엔지니어는 눈에 보이는 UI 컴포넌트는 모두 별도의 배포 단위로 분리할 수 있게 되었고, 필요한 부분만 프로덕션에 내보낼 수 있는 환경이 만들어졌습니다. 이를 통해 각 스쿼드는 더욱 독립적으로 일할 수 있게 됐습니다. 또, 에러를 격리한 덕분에 전체 장애나 전체 롤백 상황도 많이 줄었고, 앱을 작은 단위로 나누었기 때문에 빌드 시간이 많이 줄었습니다. 실제 빌드 시간이 한 앱당 20분에서 5분으로 줄어들었습니다.

남은 문제들

이러한 성과가 있었지만, 다양한 문제들 또한 남아있습니다.

  1. 개발자 경험 (DX) - 프레임워크가 제공하는 편리한 기능의 부재
  2. 아직 존재하는 런타임 리스크 - 앱 간 상태 분리, 배포 순서
  3. SSR 구현 및 서버 리소스 활용 - 정적 자원 서빙 역할만 하고 있음

Next.js에서 제공하는 편리한 개발 환경을 쓰지 못하게 되었기 때문에, 이러한 편리한 개발 환경과 생산성을 보장하기 위해 노력하고 있습니다. 그리고 런타임 리스크도 여전히 존재하고 있습니다. 잘게 나누어진 앱들의 상태 값에 대한 의존성 문제, 앱의 배포 순서 등에 대한 고려가 아직 더 많이 필요합니다. 이러한 리스크들을 줄여가고 있고, 더 잘 디커플링 하기 위한 여러 과제들이 남아있습니다. 마지막으로 아까 말했던 서버 리소스를 활용한 클라이언트 앱의 SSR을 구현해야 합니다.


포도JS

위와 같은 문제들을 해결하기 위해 플렉스 팀은 우리만의 프레임워크를 만들었습니다. 바로 포도JS라는 마이크로 프론트엔드 웹 프레임워크입니다. 기존에 마이그레이션한 코드들을 효율적으로 통합하여 하나의 패키지로 정리했습니다. 이제는 이 통합된 코드로 하나의 프레임워크 패키지를 구성하고, 남은 문제들을 중앙에서 효과적으로 해결할 수 있도록 작업 중입니다. 플렉스 팀은 이 프레임워크를 통해 Next.js와 유사한 웹 서버 및 로컬 개발 서버를 제공하는 역할을 수행하고 있습니다.

13. 플렉스 팀의 포도JS. 포도 한 알이 각각의 Micro App, 모여서 한 송이를 이루는 것이 현재의 flex와 비슷. 각각 앱에 대한 오너십은 독립적이면서도 원팀(한 송이)로 일하고자 하는 팀의 의지
13. 플렉스 팀의 포도JS. 포도 한 알이 각각의 Micro App, 모여서 한 송이를 이루는 것이 현재의 flex와 비슷. 각각 앱에 대한 오너십은 독립적이면서도 원팀(한 송이)로 일하고자 하는 팀의 의지

이름이 포도인 이유는 포도알 하나하나가 마이크로 앱들을 의미하기 때문입니다. 포도알이 모여서 포도 한 송이가 되는 것처럼 각각의 앱들이 따로 빌드 되어 배포되지만 런타임에 하나의 앱이 되는 것을 생각하여 붙인 이름입니다. 또한 각 앱에 대한 오너십은 독립적이지만 결국 하나의 팀처럼 일하고자 하는 플렉스 팀의 철학과 의지를 담은 이름이기도 합니다.


정리

지금까지의 내용을 정리하며 글을 마치겠습니다. 플렉스 제품과 코드 베이스는 고객사의 HR 생애 주기 전 경험을 커버하고 통합의 경험을 주기 위해 아주 크고 복잡합니다. 기존 아키텍처에서는 유저에게 하나의 앱을 쓰는 경험을 주지 못했고, 스쿼드가 각자의 리듬에 맞춰 독립적으로 일하기가 힘들었습니다. 그래서 마이크로 프론트엔드 아키텍처가 이 문제를 해결해 줄 것이라 판단했고, 모듈 페더레이션으로 이를 구현하고자 했습니다.

PoC 과정에서 기술을 가시화하고 데모 배포를 통해 여러 가지 가설을 검증하는 과정을 거쳤습니다. 마이그레이션 과정에서는 팀 차원의 기술 지식을 높이고, 런타임 통합의 위험성을 고려하여 새로운 구조를 고민했습니다. 기존 기능 개발을 지연시키지 않기 위해 방법을 보완하여 전사 구성원이 참여하는 QA를 진행했고, 이를 통해 다양한 문제를 해결했습니다.

결과적으로 8개월 만에 마이그레이션을 성공적으로 마무리했습니다. 마이그레이션 이후의 앱은 하나의 앱을 쓰는 경험을 제공할 수 있게 되었고, 성공적으로 스쿼드가 독립적으로 일할 수 있는 기반을 만들었습니다. 그리고 마이그레이션 이후 남은 문제들을 해결하기 위해 포도JS라는 프레임워크를 만들었고 앞으로도 이 프레임워크를 더 발전시켜 나갈 것입니다.


마치며

팀원들의 도움이 없었다면 마이그레이션 과정은 굉장히 힘들었을 것 같습니다. 이 글을 통해 플렉스 팀 모두에게 감사함을 전하고 싶습니다. 앞으로 남은 문제가 많이 있고 이 해결 과정은 아주 어려울 것 같습니다. 하지만 혼자가 아니라 팀 전체가 함께 고민하고 풀어나가고 있기 때문에 힘들지만 좋은 결과를 만들어낼 수 있을 거라 생각합니다. 감사합니다.


이찬희 (MarkiiimarK)
Never Stop Learning.

  1. FEConf2023에서 발표된 ‘대형 웹 애플리케이션 Micro Frontends 전환기’/김종혁 flex 프론트엔드 엔지니어 ↩︎