대규모 시스템을 위한 모듈 페더레이션 아키텍처

일 리포지토리(Monorepo)의 빌드 시간이 20분을 넘어가고, 사소한 CSS 변경이 전체 서비스의 배포 파이프라인을 점유하기 시작했다면 아키텍처의 한계에 도달한 것입니다. 마이크로 프론트엔드(Micro-Frontends)는 단순한 트렌드가 아니라, 프론트엔드 애플리케이션의 복잡도가 기하급수적으로 증가함에 따라 필연적으로 요구되는 확장성(Scalability) 솔루션입니다. 본고에서는 Webpack 5의 Module Federation을 중심으로 런타임 통합 전략과 기술적 트레이드오프를 분석합니다.

1. 런타임 통합과 모듈 페더레이션의 메커니즘

과거의 iframe 방식이나 npm 패키지를 통한 빌드 타임 통합은 사용자 경험(UX) 저하와 의존성 지옥(Dependency Hell)이라는 치명적인 단점을 안고 있었습니다. Webpack 5에서 도입된 Module Federation은 이 문제를 해결하는 핵심 기술로, 서로 다른 빌드 결과물이 런타임에 동적으로 코드를 로드하고 실행할 수 있게 합니다.

이 아키텍처의 핵심은 Host(컨테이너)와 Remote(마이크로 앱)의 관계입니다. Host는 런타임에 Remote의 remoteEntry.js를 참조하여 필요한 모듈을 비동기로 가져옵니다. 이때 가장 중요한 기술적 도전 과제는 의존성 공유(Shared Dependencies)입니다. React나 Vue와 같은 프레임워크 코어가 중복 로드되는 것을 방지하기 위해 싱글톤 패턴을 강제해야 합니다.

Architecture Note: Module Federation은 네트워크 요청(Network Waterfall)을 발생시킵니다. 따라서 초기 로딩 성능(FCP/LCP)을 방어하기 위해 Host 애플리케이션의 셸(Shell) 최적화와 Remote 모듈의 Preload 전략이 필수적입니다.

2. Webpack 설정과 싱글톤 관리 전략

Module Federation 설정 시 가장 빈번하게 발생하는 이슈는 Uncaught Error: Shared module is not available for eager consumption 또는 다중 인스턴스 충돌입니다. 이를 방지하기 위해 shared 옵션의 정밀한 제어가 필요합니다.


// webpack.config.js (Host & Remote 공통 설정 패턴)
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('./package.json').dependencies;

module.exports = {
  // ... 기타 웹팩 설정
  plugins: [
    new ModuleFederationPlugin({
      name: 'app1', // 유니크한 앱 이름
      filename: 'remoteEntry.js', // 엔트리 포인트 파일명
      remotes: {
        // 호스트에서 사용할 리모트 앱 정의
        app2: 'app2@http://localhost:3002/remoteEntry.js',
      },
      exposes: {
        // 리모트로서 외부에 노출할 컴포넌트
        './Button': './src/Button',
      },
      shared: {
        ...deps,
        react: {
          singleton: true, // 단일 인스턴스 강제
          requiredVersion: deps.react,
          eager: false, // 비동기 로딩 권장 (앱 크기에 따라 true 고려)
        },
        'react-dom': {
          singleton: true,
          requiredVersion: deps['react-dom'],
        },
      },
    }),
  ],
};

위 설정에서 singleton: true는 React 컨텍스트가 여러 개 생성되어 애플리케이션이 충돌하는 것을 방지합니다. 또한 requiredVersion을 통해 시맨틱 버저닝(Semantic Versioning) 규칙 내에서 호환되는 버전을 공유하도록 강제하여 런타임 안정성을 확보해야 합니다.

3. 스타일 격리와 상태 관리의 한계점

마이크로 프론트엔드 도입 시 기술적 부채가 가장 많이 발생하는 지점은 스타일 충돌(CSS Collision)전역 상태 관리입니다. 각 팀이 독립적으로 배포하더라도, 브라우저의 전역 스코프는 하나이기 때문입니다.

3-1. 스타일 격리(Isolation) 전략

CSS 클래스 이름의 충돌을 막기 위해 BEM과 같은 네이밍 컨벤션에 의존하는 것은 불가능에 가깝습니다. 다음과 같은 기술적 격리 장치가 필요합니다.

  • CSS Modules / CSS-in-JS: 빌드 타임에 고유한 해시(Hash)를 생성하여 클래스 충돌을 원천 차단합니다. 런타임 오버헤드가 발생할 수 있으나 가장 현실적인 대안입니다.
  • Shadow DOM: 완벽한 격리를 제공하지만, React의 이벤트 위임(Event Delegation) 메커니즘이나 전역 모달(Modal) 구현 시 복잡도가 급격히 상승합니다.
  • Prefixing: postcss-prefix-selector 등을 사용하여 빌드 시점에 모든 CSS 선택자에 네임스페이스를 강제 주입합니다.
Warning: 마이크로 앱 간의 상태 공유를 위해 Redux Store를 전역으로 노출하는 것은 안티 패턴(Anti-Pattern)입니다. 이는 앱 간의 결합도(Coupling)를 높여 독립 배포의 이점을 파괴합니다. 필요한 경우 Custom Events나 브라우저의 URL, LocalStorage와 같은 플랫폼 네이티브 API를 활용하십시오.

4. 독립적 배포 파이프라인과 대안 비교

각 마이크로 앱은 독립적인 CI/CD 파이프라인을 가져야 합니다. 특정 앱이 배포되었을 때 Host 앱이 이를 즉시 반영하기 위해서는 캐싱 전략(Cache Busting)이 중요합니다. 일반적으로 remoteEntry.js에는 캐시를 비활성화(no-cache)하고, 청크 파일들은 불변(immutable) 캐싱을 적용합니다.

비교 항목 모놀리스(Monolith) 마이크로 프론트엔드(MFE)
배포 단위 전체 애플리케이션 기능 단위 (모듈별)
빌드 속도 느림 (프로젝트 규모에 비례) 빠름 (독립적 빌드)
복잡도 낮음 (코드 레벨 복잡도) 높음 (인프라 및 오케스트레이션)
일관성 보장하기 쉬움 디자인 시스템/가이드 강제 필요
장애 영향도 전체 장애 가능성 높음 장애 격리(Fault Isolation) 용이

결론: 기술이 아닌 조직을 위한 선택

마이크로 프론트엔드는 콘웨이의 법칙(Conway's Law)을 기술적으로 구현한 결과물입니다. 팀 간의 커뮤니케이션 비용이 빌드 및 배포 비용을 초과할 때 도입을 고려해야 합니다. 단순히 최신 기술 스택을 도입하기 위함이라면, 모놀리스 아키텍처 내에서 모듈화를 잘하는 것이 성능과 유지보수 측면에서 훨씬 효율적일 수 있습니다. 초기 도입 시에는 점진적 마이그레이션(Strangler Fig Pattern)을 통해 레거시 시스템의 특정 페이지부터 분리해 나가는 전략을 권장합니다.

Post a Comment