회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
AWS 이용 중이라면 최대 700만 원 지원받으세요
FEConf2023에서 발표된 ‘대형 웹 애플리케이션 Micro Frontends 전환기’/김종혁 flex 프론트엔드 엔지니어
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!
확인
FEConf2023에서 발표된 ‘대형 웹 애플리케이션 Micro Frontends 전환기’/김종혁 flex 프론트엔드 엔지니어
플렉스 팀은 1편에서 말한 검증들을 토대로 의사 결정을 끝내고, 플렉스 제품을 마이크로 프론트엔드 아키텍처 변경하는 작업을 2022년 9월부터 시작하여 2023년 3월까지 진행하게 되었습니다. 아래 내용처럼 총 4단계의 과정을 거쳐 마이그레이션이 진행되었습니다.
먼저 마이크로 프론트엔드라는 미지의 세계를 처음 접하면서 기술에 대한 이해도를 높였던 기간, 모듈 페더레이션 플러그인을 사용할 애플리케이션의 새 기반을 다졌던 과정, 빠른 기능 개발 속도를 유지하면서 아키텍처 마이그레이션을 진행했던 과정, 그리고 전사 구성원이 참여하는 QA를 통해 아키텍처 마이그레이션 과정을 마무리 지었습니다. 이 과정 하나하나의 결정들이 모여서 원활한 마이그레이션을 진행할 수 있었습니다. 4개의 과정에 대해 자세히 설명드리겠습니다.
먼저 마이크로 프론트엔드와 모듈 페더레이션에 대한 팀 차원의 지식수준을 같이 올리기 위해 다양한 데모 환경을 만들어 배포하고, 이를 통해 가설을 검증해 보면서 팀 전체가 성공에 대한 자신감을 가질 수 있도록 했습니다. 프론트엔드 엔지니어들의 지식수준을 올리기 위해 팀원들과 많은 자리를 만들었고, 팀 내부의 기술 공유 활동을 많이 진행했습니다. 코드 설명회를 개최하거나 여러 가지 도식화 문서를 제공하여 정보를 공유했습니다. 또, ‘무엇이든 물어보세요'라는 세션을 열어 궁금한 점이 있는 엔지니어들에게 다양한 질문을 받으며 답해주는 자리를 만들었습니다.
처음에 소개 드렸던 체험존 앱을 일회성으로 끝내지 않고, 프로덕션 환경의 코드를 가져와서 플렉스 서비스에 가까운 형태로 발전시켰습니다. 이 과정에서 시행착오를 많이 겪으며 체험존 앱은 온전한 형태의 앱으로 만들어졌습니다. 이 데모 앱을 사내 망에 배포하여, 앱이 어떤 방식으로 바뀌고 동작하는지 엔지니어들이 눈으로 볼 수 있게 하였고, 사내 인프라의 빌드, 배포 파이프라인을 그대로 이용해서 마이그레이션 이후 빌드, 배포 과정에서 생길 수 있는 이슈나 고려해야 할 점을 미리 알 수 있도록 했습니다. 이 과정에서 여러모로 자신감을 많이 올렸던 것 같습니다.
먼저 아키텍처 마이그레이션을 위해 Next.js에 존재하던 기존 코드들이 들어가서 살 새 집을 지어야 했습니다. 새로운 기반은 웹과 웹 팩 모듈 페더레이션 플러그인을 중심으로 구현되었고, 새 아키텍처가 가지고 있는 리스크를 완화하는데 집중했습니다. 이 리스크는 크게 2가지가 있었습니다.
먼저 모듈 페더레이션의 전방향 리스크부터 살펴보겠습니다. 아래 그림은 webpack config에 들어가는 웹 모듈 페더레이션 플러그인을 설정하는 코드의 일부분입니다. 각각 분리된 앱은 모듈 페더레이션을 사용해 설정 단계에서 remote와 exposes라는 두 개의 옵션을 사용합니다. remote는 해당 마이크로 앱에서 불러올 다른 마이크로 앱의 진입점 URL을 의미하고, exposes는 다른 앱에서 해당 마이크로 앱을 부를 때 사용할 수 있는 모듈의 경로를 의미합니다. 즉 하나의 마이크로 앱은 다른 앱을 호출할 수도 있고, 다른 앱에서 호출될 수도 있습니다.
다른 앱을 호출하는 마이크로 앱을 호스트, 다른 앱에서 호출되는 마이크로 앱을 리모트라고 하면, 아래 그림처럼 모든 마이크로 앱은 호스트이면서 동시에 리모트가 될 수 있습니다. 따라서 각각의 마이크로 앱들끼리 자유로운 의존성을 맺을 수 있습니다. 이 특성을 바로 전방향 적인 특성이라고 합니다.
하지만 이런 방법으로 서비스를 운영하면 런타임 간 의존성이 상당히 복잡해지고 앱의 구조를 쉽게 파악하지 못하는 비효율이 생길 수 있습니다. 그래서 플렉스 앱은 전역의 호스트를 하나만 두고, 호스트가 여러 리모트를 불러올 수 있는 2 depth 구조를 만들었고, 이것을 코드 베이스 단의 config로 강제했습니다.
다음으로 런타임 에러 리스크에 대한 내용입니다. 런타임 리스크란 런타임에 앱들이 동적으로 합쳐지는 과정에서 식별되지 않은 에러가 발견되어 유저가 앱을 원활히 사용하지 못한다는 것이었습니다. 따라서 각 리모트 앱에서 발생한 에러는 격리시켰고, 각 리모트에서 에러가 발생하더라도 앱의 다른 기능을 사용할 수 있도록 만전을 기했습니다.
이를 위해 로컬 환경에 두 가지 개발 환경을 제공해서 런타임 에러에 대한 위험성을 더 잘 알 수 있게 하고, 개발 생산성에도 문제가 없게 조치를 취했습니다. 아래 그림의 dev:multiple 환경은 호스트 GNB와 함께 여러 앱을 각각 다른 로컬 포트에 실행시켜 여러 앱이 하나로 합쳐져 있는 프로덕션의 모습을 볼 수 있게 한 화면입니다. 하지만 개별 스쿼드 작업자는 개발을 할 때 모든 앱을 띄울 필요 없이 하나의 앱만 개발하는 경우도 많습니다. 따라서 dev:standalone 환경도 제공하여 단일 앱을 개발할 수 있게 했습니다. 개발시 필요하지 않은 앱은 실행시키지 않고 하나의 앱만 실행시켜 개발할 수 있으니 개발 서버의 성능이나 속도를 온전히 활용해 생산성을 올릴 수 있습니다.
아키텍처 마이그레이션은 굉장히 큰 변경인데, 기능 개발을 멈추고 진행했을지 궁금하셨을 것 같습니다. 플렉스 팀은 기능 개발을 멈추지 않았고, 마이그레이션과 기능 개발을 동시에 진행했습니다. 어떻게 기존 기능 개발 속도를 최대한 유지하면서 아키텍처 마이그레이션을 함께 진행할 수 있었는지에 대해 설명드리겠습니다.
먼저 기존 플렉스 앱을 점진적으로 마이그레이션 하기는 힘든 상황이었습니다. 왜냐하면 기존의 Next.js 앱에서 마이그레이션된 앱으로 이동할 때 html을 한번 렌더링 해야 했기 때문입니다. 이 렌더링 시간에 로딩 서클 등의 UI를 넣는다고 해도, 예상하지 못한 로딩 화면을 보게 되었고 이로 인해 앱 이동 경험이 안 좋았습니다. 따라서 한 번에 모든 앱을 마이크로 프론트엔드 아키텍처로 마이그레이션해야하는 상황이었습니다.
기존 플렉스 팀의 기능 개발 속도는 굉장히 빠른 편이었습니다. 많은 코드가 자주 바뀌고, 주요 업데이트가 있을 때 아래와 같은 업데이트 노트를 발행합니다. 한 달에 평균 두 번 이상 새로운 기능이나 메이저한 개선 사항이 배포되고, 웹 제품은 매주 정기 배포를 통해 기능 개선이 이루어집니다. 아래의 그림처럼 변경 사항이 매우 많다는 걸 알 수 있습니다. 이 빠른 기능 개발 속도를 최대한 유지해야 했습니다.
위에서 설명한 기존 개발 속도 유지와 마이그레이션을 함께 진행하기 위해 두 가지 아키텍처를 동시에 빌드 할 수 있는 환경을 구성했고, 이 환경을 4개월 정도 유지했습니다. 프레임워크에 의존하지 않는 리액트 컴포넌트에 해당하는 코드들을 모노레포(monorepo) 내부 패키지로 분리하고, 기존 웹 패키지와 함께 새로운 웹 패키지를 동시에 의존하도록 구성하여 빌드를 성공시키는 방식입니다. 이를 위해 마이그레이션 초반에 각 스쿼드의 프론트엔드 엔지니어들과 함께 프레임워크에 의존하지 않는 리액트 컴포넌트들을 코드에서 분리하는 작업을 진행했습니다.
플렉스의 홈페이지로 예를 들면, 홈페이지에 해당하는 리액트 컴포넌트 코드를 분리해서 Next.js 앱에서는 디렉토리 라우팅 방식으로 넣어주고, 마이크로 프론트엔드 앱에서는 SPA 라우팅 방식으로 넣어주면 페이지가 두 앱 모두에서 제대로 작동하여 빌드가 성공하는 방식입니다. Next.js 앱에서는 분리된 페이지에서 계속 기능 개발이 진행되고, 기존 웹에서는 개발된 기능에 대한 QA, 프로덕션 배포가 계속 이뤄졌습니다. 마이크로 프론트엔드 앱에서는 분리된 패키지에서 개발된 기능을 그대로 반영함과 동시에 마이그레이션에 필요한 작업이나 QA를 계속 진행할 수 있었습니다.
위 방식을 계속 진행하면서 마이그레이션이 완료된 기능에 대해서는 기존 앱을 중단시키고 코드 베이스에서도 삭제하여 새로운 앱을 프로덕션으로 전환했습니다.
앞서서 프레임워크에 의존하지 않은 부분을 따로 분리했다고 했는데, 그렇다면 아래 코드는 어떻게 처리해야 할까요? 플렉스 팀은 원래 Next.js에서 사용하는 라우터의 리액트 훅들을 모노레포의 내부 패키지로 한번 래핑을 해서 사용하고 있습니다. 이 때문에 프레임워크에 의존하지 않는 부분을 분리해도, 분리된 컴포넌트 내부에 기존 Next.js의 의존성이 그대로 남아있게 됩니다.
위와 같이 물리적으로 떼어낼 수 없는 코드가 존재했고, 이런 코드들은 빌드 시점에 바꿔주었습니다. 넥스트 라우터를 래핑 하는 패키지에 대응하기 위해서 SPA 라우터인 react-router-dom을 래핑 하는 패키지를 만들었고, 기존 패키지와 동일한 기능을 하도록 구현했습니다. 그리고 마이크로 앱이 빌드 될 때 webpack config의 resolve, alias 설정을 통해 코드를 직접 수정하지 않고 빌드 타임에 마이크로 앱 안에 선언된 넥스트 라우터를 새로운 패키지로 대체하여 빌드가 성공할 수 있게 했습니다.
이번 내용은 마이그레이션의 단계 중 마지막 내용입니다. 전사 구성원이 참여했던 마이그레이션 QA가 어떤 방식으로 이루어졌는지, QA 과정을 효율화하기 위해 어떤 전략을 취했는지 소개합니다.
마이크로 프론트엔드로 바뀐 새로운 앱의 QA는 프로덕트 팀의 모든 구성원이 참여할 수 있는 형태로 진행했습니다. 플렉스 앱을 배포할 때는 데브 환경에 코드를 올려 검증하고, 그다음 QA 환경에서 검증한 후 프로덕션 환경에 코드를 올려 릴리즈 하고 있습니다.
아래 그림을 보면 각 환경 별로 아키텍처 변경을 한 날짜가 표시되어 있습니다. 이러한 제품 배포 과정 중에서 QA 환경을 먼저 마이크로 프론트엔드 아키텍처로 대체하는 방식을 진행했습니다. 이런 방식의 환경 대체를 통해서 제품 기능에 대한 QA와 아키텍처 변경에 대한 QA가 동시에 자연스럽게 일어나도록 환경을 설계했습니다. 이 과정을 통해 점점 프로덕션에 가깝게 만들었고 마이그레이션에 대한 자신감을 높였습니다.
앞서 점진적 마이그레이션이 힘들었고, 환경 전체를 마이크로 프론트엔드로 바꾸는 방식으로 마이그레이션을 진행한다고 말씀드렸는데, 기존 앱에 대한 QA도 같이 진행해야 했기 때문에 두 가지 환경을 모두 접속할 수 있도록 브라우저 쿠키의 앱 타입이라는 값을 통해 인프라 단의 조치도 합께 진행했습니다.
이러한 조치 덕분에 기능 QA와 함께 마이그레이션에 대한 QA를 동시에 가능한 환경이 구성됐습니다. 즉, 많은 분들이 참여하여 QA를 효율적으로 진행할 수 있는 상황이 만들어졌습니다. 전사의 디자이너, PM, 엔지니어 분들이 버그에 대한 이슈 티켓을 만들어주면, 각 스쿼드의 프론트엔드 엔지니어가 이 문제를 어느 파트의 문제인지 식별해 주고, 기능의 문제이면 스쿼드에서 해결을, 마이그레이션에 대한 문제이면 FE Lab에서 해결을 진행했습니다.
지금까지의 과정은 전사의 구성원들 모두 함께 노력한 QA였습니다. 이러한 마이그레이션 방식 덕분에 새로운 아키텍처에 대한 적용과 함께 일하는 환경도 마이그레이션 되었고, 모든 구성원이 새로운 앱에 점진적으로 적응하고 기존과 달라진 배포 단위에 대한 이해도도 높일 수 있었습니다. 결과적으로 23년 3월 23일에 웹 제품에 대한 마이크로 프론트엔드 아키텍처 마이그레이션이 성공적으로 끝났습니다.
마지막으로 이번 마이그레이션으로 얻은 성과를 알아보겠습니다.
유저에게 하나의 앱을 쓰는 경험을 제공할 수 있게 되었습니다. 엔지니어는 눈에 보이는 UI 컴포넌트는 모두 별도의 배포 단위로 분리할 수 있게 되었고, 필요한 부분만 프로덕션에 내보낼 수 있는 환경이 만들어졌습니다. 이를 통해 각 스쿼드는 더욱 독립적으로 일할 수 있게 됐습니다. 또, 에러를 격리한 덕분에 전체 장애나 전체 롤백 상황도 많이 줄었고, 앱을 작은 단위로 나누었기 때문에 빌드 시간이 많이 줄었습니다. 실제 빌드 시간이 한 앱당 20분에서 5분으로 줄어들었습니다.
이러한 성과가 있었지만, 다양한 문제들 또한 남아있습니다.
Next.js에서 제공하는 편리한 개발 환경을 쓰지 못하게 되었기 때문에, 이러한 편리한 개발 환경과 생산성을 보장하기 위해 노력하고 있습니다. 그리고 런타임 리스크도 여전히 존재하고 있습니다. 잘게 나누어진 앱들의 상태 값에 대한 의존성 문제, 앱의 배포 순서 등에 대한 고려가 아직 더 많이 필요합니다. 이러한 리스크들을 줄여가고 있고, 더 잘 디커플링 하기 위한 여러 과제들이 남아있습니다. 마지막으로 아까 말했던 서버 리소스를 활용한 클라이언트 앱의 SSR을 구현해야 합니다.
위와 같은 문제들을 해결하기 위해 플렉스 팀은 우리만의 프레임워크를 만들었습니다. 바로 포도JS라는 마이크로 프론트엔드 웹 프레임워크입니다. 기존에 마이그레이션한 코드들을 효율적으로 통합하여 하나의 패키지로 정리했습니다. 이제는 이 통합된 코드로 하나의 프레임워크 패키지를 구성하고, 남은 문제들을 중앙에서 효과적으로 해결할 수 있도록 작업 중입니다. 플렉스 팀은 이 프레임워크를 통해 Next.js와 유사한 웹 서버 및 로컬 개발 서버를 제공하는 역할을 수행하고 있습니다.
이름이 포도인 이유는 포도알 하나하나가 마이크로 앱들을 의미하기 때문입니다. 포도알이 모여서 포도 한 송이가 되는 것처럼 각각의 앱들이 따로 빌드 되어 배포되지만 런타임에 하나의 앱이 되는 것을 생각하여 붙인 이름입니다. 또한 각 앱에 대한 오너십은 독립적이지만 결국 하나의 팀처럼 일하고자 하는 플렉스 팀의 철학과 의지를 담은 이름이기도 합니다.
지금까지의 내용을 정리하며 글을 마치겠습니다. 플렉스 제품과 코드 베이스는 고객사의 HR 생애 주기 전 경험을 커버하고 통합의 경험을 주기 위해 아주 크고 복잡합니다. 기존 아키텍처에서는 유저에게 하나의 앱을 쓰는 경험을 주지 못했고, 스쿼드가 각자의 리듬에 맞춰 독립적으로 일하기가 힘들었습니다. 그래서 마이크로 프론트엔드 아키텍처가 이 문제를 해결해 줄 것이라 판단했고, 모듈 페더레이션으로 이를 구현하고자 했습니다.
PoC 과정에서 기술을 가시화하고 데모 배포를 통해 여러 가지 가설을 검증하는 과정을 거쳤습니다. 마이그레이션 과정에서는 팀 차원의 기술 지식을 높이고, 런타임 통합의 위험성을 고려하여 새로운 구조를 고민했습니다. 기존 기능 개발을 지연시키지 않기 위해 방법을 보완하여 전사 구성원이 참여하는 QA를 진행했고, 이를 통해 다양한 문제를 해결했습니다.
결과적으로 8개월 만에 마이그레이션을 성공적으로 마무리했습니다. 마이그레이션 이후의 앱은 하나의 앱을 쓰는 경험을 제공할 수 있게 되었고, 성공적으로 스쿼드가 독립적으로 일할 수 있는 기반을 만들었습니다. 그리고 마이그레이션 이후 남은 문제들을 해결하기 위해 포도JS라는 프레임워크를 만들었고 앞으로도 이 프레임워크를 더 발전시켜 나갈 것입니다.
팀원들의 도움이 없었다면 마이그레이션 과정은 굉장히 힘들었을 것 같습니다. 이 글을 통해 플렉스 팀 모두에게 감사함을 전하고 싶습니다. 앞으로 남은 문제가 많이 있고 이 해결 과정은 아주 어려울 것 같습니다. 하지만 혼자가 아니라 팀 전체가 함께 고민하고 풀어나가고 있기 때문에 힘들지만 좋은 결과를 만들어낼 수 있을 거라 생각합니다. 감사합니다.
글 FEConf
편집 오신엽 객원 에디터
요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.