뒤로가기
마이크로 프론트엔드, 우리 팀에 필요한가

June 23, 2022

frontend

팀 리드가 어느 날 슬랙에 링크를 하나 공유했다. 마이크로 프론트엔드에 대한 기술 블로그 글이었다. "이거 재밌네. 한번 검토해볼까?" 가벼운 톤이었다. 나는 그 링크를 보자마자 흥분했다. 마이크로 프론트엔드. 이름부터 멋지지 않나. 마이크로서비스의 프론트엔드 버전이라니.

당시 우리 팀 상황은 이랬다. 프론트엔드 개발자 세 명, 모놀리식 Next.js 앱 하나. 앱이 점점 커지면서 빌드 시간이 3분을 넘기기 시작했고, 서로 작업한 코드가 충돌하는 일이 잦아졌다. "이 문제를 마이크로 프론트엔드로 풀 수 있지 않을까?"라는 생각이 들었다.

일주일간의 리서치#

나는 POC를 맡았다. 일주일 동안 마이크로 프론트엔드에 대해 파봤다. 점심시간에도 관련 글을 읽었고, 퇴근 후에도 유튜브에서 관련 발표 영상을 찾아봤다. 새로운 아키텍처를 탐구하는 건 언제나 재미있다. 특히 이게 팀의 공식적인 업무로 주어진 거라면 더.

Module Federation, Single-SPA, iframe 기반, Web Components 기반. 접근 방식이 여러 개였다. Webpack 5의 Module Federation이 가장 많이 언급됐다. 리모트 앱에서 컴포넌트를 노출하면 호스트 앱에서 런타임에 불러오는 구조. 개념은 이해가 됐다.

간단한 데모를 만들어봤다. host 앱이 있고, remote 앱이 있고, remote 앱의 Header 컴포넌트를 host에서 불러오는 구조.

js
// remote app - webpack.config.js
new ModuleFederationPlugin({
  name: 'remoteApp',
  filename: 'remoteEntry.js',
  exposes: {
    './Header': './src/components/Header',
  },
  shared: ['react', 'react-dom'],
})
js
// host app - webpack.config.js
new ModuleFederationPlugin({
  name: 'hostApp',
  remotes: {
    remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
  },
  shared: ['react', 'react-dom'],
})

데모는 30분 만에 돌아갔다. "오 이거 되네?" 하면서 팀에 공유했다. 근데 문제는 데모가 아니라 프로덕션이었다.

현실의 벽들#

데모에서 프로덕션으로 가는 길에 장애물이 쌓이기 시작했다.

첫 번째, 우리는 Next.js를 쓰고 있었다. Module Federation은 기본적으로 Webpack 설정을 직접 제어해야 한다. Next.js의 Webpack 설정을 커스텀하는 것도 가능은 한데, SSR과 Module Federation을 같이 쓰려면 @module-federation/nextjs-mf 같은 서드파티 플러그인이 필요했다. 이 플러그인의 GitHub 이슈를 보는데, 열린 이슈가 백 개가 넘었다. Next.js 버전 올리면 깨지는 케이스, SSR에서 hydration 미스매치 나는 케이스, 개발 모드에서만 동작하는 케이스.

두 번째, 공유 상태 관리. 우리 앱은 Zustand로 전역 상태를 관리하고 있었다. 마이크로 프론트엔드로 쪼개면 이 상태를 어떻게 공유하나? Custom Event로? 공유 스토어를 별도로 만들어서? 어떤 방식이든 기존의 심플한 구조보다 복잡해졌다.

세 번째, 스타일 충돌. 앱 A의 CSS가 앱 B의 DOM에 영향을 주는 문제. Shadow DOM을 쓰면 해결되지만, React와 Shadow DOM은 이벤트 전파 문제가 있다. CSS Modules로 스코핑하는 방법도 있는데, 이미 Tailwind를 쓰고 있어서 클래스명 충돌이 생길 수 있었다.

네 번째, 배포. 각 마이크로 앱이 독립 배포된다는 게 장점이라고 하는데, 독립 배포를 하려면 각 앱마다 CI/CD 파이프라인이 필요하다. 프론트엔드 세 명인 팀에서 여러 개의 배포 파이프라인을 관리하는 게 과연 효율적인가?

팀 미팅에서의 논의#

POC 결과를 팀에 공유하는 미팅을 했다. 데모를 보여주고, 장단점을 정리해서 발표했다. 시니어가 질문을 하나 했다.

"지금 우리가 겪고 있는 문제가 뭐야?"

빌드 시간이 길다. 코드 충돌이 자주 난다.

"빌드 시간은 Next.js 14에서 Turbopack 쓰면 많이 개선될 것 같은데. 코드 충돌은 폴더 구조를 좀 정리하면 줄어들지 않을까?"

맞는 말이었다. 우리가 겪고 있는 문제를 해결하는 방법이 반드시 마이크로 프론트엔드일 필요는 없었다. 그건 여러 해결책 중 하나였고, 가장 비용이 큰 해결책이었다.

팀 리드가 정리했다. "마이크로 프론트엔드는 여러 팀이 하나의 페이지를 독립적으로 개발/배포해야 할 때 의미가 있다. 우리는 한 팀이고, 한 프로덕트를 만들고 있다. 지금은 아닌 것 같다."

대신 한 것들#

마이크로 프론트엔드를 도입하는 대신, 기존 모놀리스를 정리하는 데 시간을 썼다.

폴더 구조를 feature 기반으로 재정리했다.

text
src/
  features/
    auth/
      components/
      hooks/
      api/
    dashboard/
      components/
      hooks/
      api/
    settings/
      components/
      hooks/
      api/
  shared/
    components/
    hooks/
    utils/

이전에는 components/, hooks/, utils/ 같은 기술 기반 폴더 구조였는데, feature 기반으로 바꾸니까 서로의 작업 영역이 명확해졌다. 내가 auth를 작업하는 동안 동료가 dashboard를 건드려도 충돌이 거의 안 났다.

빌드 시간은 Next.js를 업데이트하고 Turbopack을 개발 환경에 적용하면서 체감상 절반으로 줄었다. 프로덕션 빌드도 불필요한 의존성을 정리하고 dynamic import를 적극적으로 쓰면서 개선됐다.

결과적으로, 마이크로 프론트엔드 없이도 원래 겪던 문제가 대부분 해결됐다.

추가로, barrel export(index.ts에서 feature별 public API만 내보내는 패턴)를 도입하면서, feature 간 의존성을 명시적으로 관리할 수 있게 됐다. dashboard/components/SomeChartauth 폴더에서 직접 import하는 걸 ESLint 규칙으로 막았다. feature 경계를 코드 레벨에서 강제하니까, 모놀리스인데도 느슨한 결합이 어느 정도 가능했다.

이 과정에서 깨달은 건, 폴더 구조와 import 규칙만 잘 잡아도 코드베이스의 복잡도를 상당히 낮출 수 있다는 거다. 마이크로 프론트엔드가 해결하려는 문제의 일부를 훨씬 적은 비용으로 해결한 셈이었다.

마이크로 프론트엔드가 필요한 팀#

이후에 커뮤니티에서 마이크로 프론트엔드를 실제로 쓰는 팀의 이야기를 들었다. 대부분 이런 특징이 있었다. 프론트엔드 팀이 여러 개다. 각 팀이 서로 다른 배포 주기를 갖는다. 하나의 페이지에 여러 팀의 코드가 들어간다. 쿠팡 같은 대형 이커머스의 상품 상세 페이지 같은 경우. 상단 영역은 A팀, 리뷰는 B팀, 추천 상품은 C팀.

이런 조직 구조에서는 마이크로 프론트엔드가 말이 된다. 조직적 문제를 기술로 풀어야 하는 상황이다. 하지만 프론트엔드 개발자가 세 명이고 커피 한잔 하면서 "나 이 파일 수정할 건데 괜찮지?" 할 수 있는 팀이라면, 그건 그냥 대화로 풀면 되는 문제다.

기술 선택에 대한 교훈#

이 경험에서 배운 건, 기술을 도입할 때 "이 기술이 멋진가"가 아니라 "이 기술이 지금 우리 문제를 풀어주는가"를 먼저 물어야 한다는 거다. 뻔한 말처럼 들리겠지만, 실제로 그 순간에는 쉽지 않다. 새로운 기술은 늘 매력적이다. 기술 블로그에 올리면 조회수도 잘 나온다. 이력서에도 한 줄 추가할 수 있다.

팀 리드가 그 링크를 공유했을 때 "한번 검토해볼까?"라고 한 건, 도입하자는 게 아니라 정말 검토만 해보자는 뜻이었다. 검토한 결과 "안 하는 게 낫다"가 나왔고, 그게 올바른 결론이었다. 기술 검토의 결과가 항상 도입일 필요는 없다. "안 한다"도 충분히 가치 있는 결론이다.

가끔 그때 만든 Module Federation 데모 레포를 열어본다. 잘 돌아가는 데모. 근데 프로덕션에는 안 갔다. 그래서 좋았다고 생각한다. 언젠가 팀이 커지고 조직이 분화되면 그때 다시 꺼내볼 수도 있겠지. 그전까지는 서랍 속에 잘 모셔두는 거다.