레거시 프로젝트의 코드베이스를 다른 코드베이스로 마이그레이션 하는 것은 서비스의 안정성 측면에서, 작업의 규모 측면에서 상당히 부담스러운 작업입니다. 프로젝트 전체를 한번에 마이그레이션 해야한다면 코드 마이그레이션 작업 자체로도 부하가 올 뿐 만 아니라, QA 측면에서도 세세한 부분까지 다시 검증해야하기 때문입니다.
사용자 입장에서는 어떨까요? 사실 마이그레이션은 사용자 입장에서는 어떠한 변화도 느끼지 못합니다. 내부적인 코드가 바뀔 뿐이니까요. 그런데 전체 마이그레이션을 진행해버리는 경우 내부적으로는 엄청 바쁜 과정을 통해 진행되지만 겉으로는 사용자에게 전혀 제공되는 이점이 없는셈입니다. 그러나 그런 선택을 했다는 것은 장기적인 측면에서 반드시 진행해야 하는 이유들이 있었기 때문입니다.
따라서 사용자에게 충분한 변화를 주면서 마이그레이션을 줄 수 있는 방법은 한번에 하는 것이 아니라 점진적으로 진행하는 것입니다. 레거시에서의 기능개발을 진행하면서도, 일부 페이지에 대해서는 마이그레이션 하는 방식으로 접근할 수 있다면 서비스의 성장과 품질을 모두 챙길 수 있을 것입니다.
Migration을 진행하는 이유
기존 제품은 21년도에 Vue2로 개발되어 현재까지도 서비스 중인 매장관리서비스입니다. 아래와 같은 이유들 때문에 Vue2에서 React로 마이그레이션을 결정하게 되었습니다.
Vue2의 지원 종료
2023년 12월 31일 기준 공식문서에 의하면 Vue2에 대한 지원은 공식적으로 종료되었습니다. 현재의 Vue는 Vue3을 공식으로 지원하고 있습니다. 따라서 Vue3나 React나 결국 마이그레이션이 필요한 상황에 처해있었습니다.
제품 복잡도
Vue는 데이터가 양방향으로 흐르도록 설계되어 있는데요. 제품 초기에는 빠른 개발속도와 직관적인 데이터 흐름으로 인해 장점으로 느껴졌었습니다. 그러나 제품이 성장함에 따라 다양한 데이터들이 컴포넌트에 따라 흐르게 되면서, 데이터 흐름을 추적하기가 점차 난해해지고 있는 문제가 있었는데요. 반면에 React에서는 부모에서 자식 컴포넌트로 데이터가 단방향으로 흐르기 때문에 여러 파일을 뒤져보지 않더라도 컴포넌트 구조를 따라 데이터 흐름을 파악하기 용이하였습니다. 이러한 측면으로 인해서 장기적으로 더 복잡해질 해당 제품의 코드 복잡도를 낮출 수 있겠다고 판단되었습니다.
생태계 및 커뮤니티 지원
Vue의 생태계에서도 충분히 지원되는 라이브러리나 도구들이 존재하지만, React가 가지고 있는 방대한 생태계에서는 확실히 부족해보입니다. Redux, Recoil, Jotai, Zustand 등 다양한 전역상태 관리 도구들이나, TanStack Query 같은 Data Fetching 라이브러리, 커뮤니티에서 찾아볼 수 있는 좋은 예제들과 프로젝트들이 React를 더욱 매 력적으로 보이게 하는 요소들이였습니다.
그 외 사내의 상황
- 프로젝트 구조가 Monorepo 환경으로 개편되면서, 공통 모듈을 원활하게 사용할 수 있게 되었습니다.
- React로 개발된 디자인 시스템을 적용할 수 있어 브랜드에 맞는 디자인을 유지하면서도 개발 호율을 높일 수 있게 됩니다.
- 타입스크립트로의 마이그레이션하여 정적 타입 분석을 통해 안정성을 더욱 높일 필요가 있었습니다.
- 프론트엔드의 코드베이스 단일화가 필요합니다.
점진적으로 마이그레이션 아이디어 (Module Federation)
말머리에서 왜 마이그레이션을 점진적으로 진행해야 하는지는 설명하였습니다. 그런데 어떻게 하면 Vue로 작성된 코드들을 React로 점진적으로 마이그레이션 할 수 있을까요? 해당 문제에 대한 해결책은, 페이지 별로 구분하여 점진적으로 React로 바꾸어나가는 것인데요. 결론적으로 말하자면, Module Federation을 활용하여 Vue 프로젝트 안에서, 일부 페이지들을 런타임에 통합하여 제공하는 방식으로 접근하면 가능하게 됩니다.
Module Federation
Module Federation은 마이크로프론트엔드 아키텍쳐로 서비스를 제공할 수 있는 핵심 아이디어인데요. 하나의 코드베이스에서 프로젝트를 배포하는 것이 아니라, Feature를 기준으로 서비스를 독립적으로 배포하고, 느슨하게 연결해줄수 있는 구조입니다. 즉 하나의 서비스를 여러 마이크로 서비스로 나누고, 하나의 웹사이트에서 하나의 서비스 처럼 동작할 수 있도록 해줍니다.
핵심 컨셉
- 기술 독립성: 각 단위는 기술적으로 독립적이여야 한다.
- 컨텍스트 독립성: 각 단위는 애플리케이션을 자제척으로 구축해야하며, 다른 곳의 상태를 공유하거나 변수에 의존하지 않도록 한다.
- 네임스페이스를 활용한 분리: 각 작동의 단위 격리가 불가능한 경우, 네이밍 컨벤션에 따라 prefix 등으로 네임스페이스를 활용한다.
- 기본 브라우저 기능 활용: 단위간의 통신을 위해 자체 시스템을 구축하기 보다는 브라우저 이벤트, 커스텀이벤트를 활용하고 필요한 경우 최대한 간단하게 유지한다.
- 탄력적인 웹 디자인 구축: 자바스크립트가 에러가 있거나 실행할 수 없어도, 기능은 사용가능해야 하며, 범용렌더링과 점진적 향상을 통해 성능을 향상시킬 수 있다.
도입시 장점
- 확장성
- 독립적 개발 및 배포
- 더 빠른 배포
- 팀 자율성 강화
- 업데이트 및 유지관리 용이
도입시 단점
- 통합의 복잡성
- 전체 앱 번들 크기가 증가, 로드 시간 증가로 성능문제
- 배포, 모니터링, 확장을 위한 오버헤드가 발생하고, 네트워크 비용증가
- 일관된 코딩 표준을 유지하고 팀 간의 의존성 관리에 강력한 거버넌스가 필요하며 이에 따라 의사결정이 느려질 수 있음.
즉 요약하면, 제품 자체의 크기가 매우 크고 무거우며, 다양한 팀이 속해 있을 때 마이크로프론트엔드 아키텍쳐를 통해서 다양한 문제를 해결할 수 있다는 것인데요. 사실 저희 제품과 회사가 처한 상황과는 거리가 멉니다. (하나의 팀, 그렇게 크지 않은 서비스)
그럼에도, 하나의 코드베이스로 국한될 수 있는 상황에서 일부 페이지들을 독립적으로 분리하여 개발하고 런타임에서 통합시킬 수 있다는 특징과 하나의 Host App과 하나의 Remote App만 가지고 있으므로, 통합에서의 복잡도도 크지 않기 때문에 React로의 마이그레션 과정에서 임시로 사용할만한 해결책으로 채택하게 되었습니다.
Vue2에서 React 렌더링하기
React로 마이그레이션 할 것이기 때문에 당연히 마이그레이션 대상이 되는 페이지들을 React로 구현해야 합니다. 페이지 구현이 완료되었다면, 이제 런타임에서 해당 페이지를 보여주어야 합니다.
Module Federation 설정하기
Webpack이나 Vite의 플러그인으로 제공되는 Module Federation을 이용해 설정할 수 있습니다. 저희 예제에서는 Vite를 사용하였습니다.
Vue와 React에서 아래의 플러그인 설치하세요.
npm install -D @originjs/vite-plugin-federation