Web

[Web] MFA란(Micro Frontend Architecture)

Hoo_Dev 2024. 5. 3. 09:06

Micro Frontend Architecture 개념

장점

  • 작고, 응집력 있는 유지보수성을 가지는 코드베이스
  • 분리배포가 용이, 자율적인 팀 조직운영이 수월
  • 프론트엔드 개발을 점진적 업그레이드 또는 재작성이 수월

단점

  • 배포 번들 사이즈가 커짐
  • 서로간의 개발 환경의 차이로 복잡도 증가
  • 운영의 복잡도 증가

MFA 기법의 종류

  • 서버 템플릿 통합
    • 각 서버로 html 템플릿을 요청하고, 최종 응답서버에서 각 템플릿을 조합해서 응답을 보냄 서버측에서 최종 화면을 조합한다.
  • 빌드타임 통합
    • 단위 애플리케이션을 패키지로 배포하고, package.json에 명시한 후 컨테이너 애플리케이션에서 import하여 사용하는 방법 각 애플리케이션에 대한 런타임 대응이 안된다.  애플리케이션을 릴리즈하고 최종 애플리케이션에서 컴파일해야 한다. 
  • iframe 통합
    • 전통적인 방식이면서 가장 쉬운 방식이다. 애플리케이션 통합의 유연성 높다. 애플리케이션의 기술 종속성이 없다.  routing, history, deep-link같은 것이 복잡해질 수 있다.  컨테이너 애플리케이션과 iframe에 들어가는 단위 애플리케이션간의 통신규약도 필요하다.  UX가 iframe안에 갇히기 때문에 어색한 UI 표현을 가질 수 있다. 
  • Web Components를 통한 통합
    • HTML 커스텀 엘리먼트를 통한 통합방법, static, runtime 통합 둘 다 가능함. Javascript를 통한 런타임 통합과 유사하지만 "The web component way"를 지향한다. 클라이언트측에서 (브라우져) 통합한다.
  • Javascript를 통한 런타임 통합
    • iframe과 달리 유연한 통합이 가능하다. 현실적으로 가장 많이 사용하는 방식이다. 컨테이너 애플리케이션을 단위 애플리케이션 번들을 <script> 태그를 통합 다운로드 받고 약속된 초기화 메소드를 호출한다. 클라이언트측에서 (브라우져) 통합한다.

출처: https://mobicon.tistory.com/572

JS를 통한 런타임 통합

  • 웹 페이지를 불러온 시점에, 컨테이너 애플리케이션이 어떤 애플리케이션을 마운트할지 결정하고 관련 함수를 호출하여 애플리케이션에 렌더링할 시기와 위치를 알려주는 기법.
  • 장점
    • 빌드 타임 통합과 달리, 각 애플리케이션을 독립적으로 배포할 수 있다.
    • iframe과 달리, 애플리케이션 간의 통합을 원하는 대로 유연하게 구축할 수 있다.
    • 런타임에 동적으로 애플리케이션을 로드할 수 있어 유연성이 높다.
  • 단점
    • 런타임에 통합되는 과정에서, 애플리케이션 마다 중복된 코드(예: React)를 불러올 수 있다.
    • 런타임에 동적으로 애플리케이션을 로드하기 때문에, 해당 애플리케이션을 불러오지 못해 통합하지 못하는 문제가 발생할 수 있다.
    • 런타임에서 통합되기 때문에 빌드 타임에 타입 검사가 어려울 수 있으며, 빌드 타임에서는 문제가 발생하지 않았으나, 통합하는 과정에서 예상치 못한 문제가 발생할 수 있다.
  • 사용 할 수 있는 도구
    • 직접 애플리케이션을 불러오도록 구현하기
    • SystemJS을 사용해 구현하기
    • Import Maps을 사용해 구현하기
    • Module Federation을 사용해 구현하기

Module Federation(모듈 페더레이션)

https://maxkim-j.github.io/posts/module-federation-concepts

https://github.com/MaxKim-J/module-federation-example

  • 예제 프로젝트 실행 화면
  • 예제 프로젝트 실행 시 로그
    • $ yarn turbo:start
      • Packages in scope: @module-federation-example/micro-app-a, @module-federation-example/micro-app-b, @module-federation-example/micro-app-c
      • Running start in 3 packages
      • Remote caching disabled
      @module-federation-example/micro-app-c:start: cache miss, executing fb5ca07e4b706955
      @module-federation-example/micro-app-a:start: cache miss, executing cc0b762bac47a922
      @module-federation-example/micro-app-b:start: cache miss, executing 66734fa6b4c6502b
      @module-federation-example/micro-app-b:start: asset 156.js 134 KiB [emitted] (id hint: vendors)
      @module-federation-example/micro-app-b:start: asset remoteEntry.js 31 KiB [emitted] (name: microAppB)
      @module-federation-example/micro-app-b:start: asset main.js 30.3 KiB [emitted] (name: main)
      @module-federation-example/micro-app-b:start: asset 721.js 7.12 KiB [emitted]
      @module-federation-example/micro-app-b:start: asset 95.js 3.45 KiB [emitted]
      @module-federation-example/micro-app-b:start: asset 429.js 3.03 KiB [emitted]
      @module-federation-example/micro-app-b:start: runtime modules 41.6 KiB 28 modules
      @module-federation-example/micro-app-b:start: built modules 145 KiB (javascript) 126 bytes (consume-shared) 90 bytes (share-init) 6 bytes (remote) [built]
      @module-federation-example/micro-app-b:start:   javascript modules 145 KiB
      @module-federation-example/micro-app-b:start:     cacheable modules 145 KiB 12 modules
      @module-federation-example/micro-app-b:start:     container entry 42 bytes [built] [code generated]
      @module-federation-example/micro-app-b:start:     external "microAppC@http://localhost:3002/remoteEntry.js" 42 bytes [built] [code generated]
      @module-federation-example/micro-app-b:start:   consume-shared-module modules 126 bytes
      @module-federation-example/micro-app-b:start:     consume shared module (default) react@^18.2.0 (singleton) (fallback: ../.yarn/ca...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-b:start:     consume shared module (default) react-dom@* (singleton) (fallback: ../.yarn/__vi...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-b:start:     consume shared module (default) react@* (singleton) (fallback: ../.yarn/cache/re...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-b:start:   provide-module modules 84 bytes
      @module-federation-example/micro-app-b:start:     provide shared module (default) react-dom@18.2.0 = ../.yarn/__virtual__/react-do...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-b:start:     provide shared module (default) react@18.2.0 = ../.yarn/cache/react-npm-18.2.0-1...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-b:start:   remote microAppC/App 6 bytes (remote) 6 bytes (share-init) [built] [code generated]
      @module-federation-example/micro-app-b:start: webpack 5.87.0 compiled successfully in 472 ms
      @module-federation-example/micro-app-c:start: asset 156.js 134 KiB [emitted] (id hint: vendors)
      @module-federation-example/micro-app-c:start: asset remoteEntry.js 25.9 KiB [emitted] (name: microAppC)
      @module-federation-example/micro-app-c:start: asset main.js 25.6 KiB [emitted] (name: main)
      @module-federation-example/micro-app-c:start: asset 721.js 7.12 KiB [emitted]
      @module-federation-example/micro-app-c:start: asset 355.js 2.85 KiB [emitted]
      @module-federation-example/micro-app-c:start: asset 850.js 2.02 KiB [emitted]
      @module-federation-example/micro-app-c:start: runtime modules 35.8 KiB 23 modules
      @module-federation-example/micro-app-c:start: built modules 144 KiB (javascript) 126 bytes (consume-shared) 84 bytes (share-init) [built]
      @module-federation-example/micro-app-c:start:   javascript modules 144 KiB
      @module-federation-example/micro-app-c:start:     modules by path ../.yarn/ 143 KiB 9 modules
      @module-federation-example/micro-app-c:start:     modules by path ./ 808 bytes
      @module-federation-example/micro-app-c:start:       ./index.tsx 23 bytes [built] [code generated]
      @module-federation-example/micro-app-c:start:       + 2 modules
      @module-federation-example/micro-app-c:start:     container entry 42 bytes [built] [code generated]
      @module-federation-example/micro-app-c:start:   consume-shared-module modules 126 bytes
      @module-federation-example/micro-app-c:start:     consume shared module (default) react@^18.2.0 (singleton) (fallback: ../.yarn/ca...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-c:start:     consume shared module (default) react-dom@* (singleton) (fallback: ../.yarn/__vi...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-c:start:     consume shared module (default) react@* (singleton) (fallback: ../.yarn/cache/re...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-c:start:   provide-module modules 84 bytes
      @module-federation-example/micro-app-c:start:     provide shared module (default) react-dom@18.2.0 = ../.yarn/__virtual__/react-do...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-c:start:     provide shared module (default) react@18.2.0 = ../.yarn/cache/react-npm-18.2.0-1...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-c:start: webpack 5.87.0 compiled successfully in 480 ms
      @module-federation-example/micro-app-a:start: asset 156.js 134 KiB [emitted] (id hint: vendors)
      @module-federation-example/micro-app-a:start: asset main.js 30.2 KiB [emitted] (name: main)
      @module-federation-example/micro-app-a:start: asset 721.js 7.12 KiB [emitted]
      @module-federation-example/micro-app-a:start: asset 977.js 3.63 KiB [emitted]
      @module-federation-example/micro-app-a:start: asset index.html 105 bytes [emitted]
      @module-federation-example/micro-app-a:start: runtime modules 20.9 KiB 14 modules
      @module-federation-example/micro-app-a:start: orphan modules 918 bytes [orphan] 1 module
      @module-federation-example/micro-app-a:start: built modules 144 KiB (javascript) 126 bytes (consume-shared) 90 bytes (share-init) 6 bytes (remote) [built]
      @module-federation-example/micro-app-a:start:   javascript modules 144 KiB
      @module-federation-example/micro-app-a:start:     modules by path ../.yarn/ 143 KiB 9 modules
      @module-federation-example/micro-app-a:start:     modules by path ./*.tsx 1.23 KiB 2 modules
      @module-federation-example/micro-app-a:start:     + 1 module
      @module-federation-example/micro-app-a:start:   consume-shared-module modules 126 bytes
      @module-federation-example/micro-app-a:start:     consume shared module (default) react@^18.2.0 (singleton) (fallback: ../.yarn/ca...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-a:start:     consume shared module (default) react-dom@* (singleton) (fallback: ../.yarn/__vi...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-a:start:     consume shared module (default) react@* (singleton) (fallback: ../.yarn/cache/re...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-a:start:   provide-module modules 84 bytes
      @module-federation-example/micro-app-a:start:     provide shared module (default) react-dom@18.2.0 = ../.yarn/__virtual__/react-do...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-a:start:     provide shared module (default) react@18.2.0 = ../.yarn/cache/react-npm-18.2.0-1...(truncated) 42 bytes [built] [code generated]
      @module-federation-example/micro-app-a:start:   remote microAppB/App 6 bytes (remote) 6 bytes (share-init) [built] [code generated]
      @module-federation-example/micro-app-a:start: webpack 5.87.0 compiled successfully in 752 ms
      @module-federation-example/micro-app-b:start: Server is running on port 3001
      @module-federation-example/micro-app-c:start: Server is running on port 3002
      @module-federation-example/micro-app-a:start: Server is running on port 3000​
  • 프로젝트 폴더 구조

모듈 페더레이션은 Webpack의 기능 중 하나로, ModuleFederationPlugin을 사용하여 대규모 애플리케이션을 더 작고 관리하기 쉬운 모듈로 분리할 수 있게 해준다. 이를 통해 다른 애플리케이션의 모듈을 런타임에서 불러와 사용할 수 있으며, 이는 Code Splitting과 유사한 점이 있다.

그러나 Code Splitting과는 달리, 모듈 페더레이션은 별도의 Webpack 애플리케이션의 기능을 독립된 애플리케이션으로 분리할 수 있게 해준다. 덕분에 개별적으로 개발하고 배포할 수 있게 되어 유연성과 확장성을 높일 수 있다.

 

모듈 페더레이션의 주요 개념.

  1. Host: 원격 모듈을 불러오는 애플리케이션.
  2. Local Module: 로컬 모듈은 현재 빌드의 일부로, 일반적인 모듈. 이는 원격 모듈과 구별되며, 현재 애플리케이션 또는 프로젝트 내에서 정의되고 사용.
  3. Remote Module: 원격 모듈은 현재 빌드의 일부가 아닌, 원격 컨테이너에서 런타임에 로드되는 모듈.
  4. Exposes: 원격 모듈로 공개할 부분을 지정. 공개된 부분만 Host가 불러와 사용할 수 있다.
  5. Container: 컨테이너는 특정 모듈에 대한 비동기 접근을 노출하는 엔트리를 통해 생성. 컨테이너는 다른 컨테이너의 모듈을 사용할 수 있으며, 중첩 및 순환 의존성도 가능.
 

궁금증?

  • 예시 코드에서 micro-app-a가 호스트 프로젝트라고 했을 때, b와 c는 무조건 a의 프로젝트 폴더 안에 포함되어야 하는가? (호스트 프로젝트의 용량이 커지고, 파일 구조가 복잡해지지 않을까?)
  • 해당 코드는 react로만 되어있는데 vue나 다른 Framework 혹은 Library를 사용 할 땐 어떤 식으로 작성되어야 하는가?

'Web' 카테고리의 다른 글

[Web] Service Mesh  (0) 2024.05.07
[Web] 쿠키, 세션, 웹 스토리지의 차이점과 사용처  (0) 2023.02.08
[Web] HTTP와 WebSocket의 차이  (0) 2022.12.29