본문 바로가기

Client/Front-end

<MFE> Module Federation으로 알아보는 Micro Front-end

 

MFE(Micro Front-end)란 하나의 거대한 프론트엔드 앱을 독립하여 운영하는 아키텍처를 의미한다. FE 어플리케이션은 과거에 비해 굉장히 거대해졌고, 빠르게 변화한다. 이러한 상황에서 대규모 서비스라면 한번쯤 MFE 아키텍처 도입을 고려하게 될 것이다. 이를 구현하기 위한 여러가지 기술이 있지만 최근 주목받고 있는 기술인 Module Federation(MF)을 통해 MFE를 소개하고자 한다.

 

이번 포스팅에서는 MFE가 주된 주제이므로 Module Federation을 통한 구현방법이나 코드는 최소화할 예정이지만 이를 활용하기 좋은 사례를 자세히 설명하고, Module Federation을 도입할 때 고려할 사항도 함께 공유하고자 한다. 마지막으로는 FE생태계에서 MFE와 관련하여 무슨 일이 일어나고 있는지도 공유해보겠다.

 

1. Introduction

MFE에서 각 앱은 마이크로 앱이라 칭하며, 이들은 각각 개발-배포-운영을 독립시킬 수 있다. 이러한 MFE의 특징과 전략에 대해서 간단하게 알아보자.

 

Micro Front-end 특징

MFE에서의 가장 큰 특징이자 장점은 독립적으로 빌드 사이클과 Repository, 기술스택 등을 가질 수 있다는 것이다.

Micro Front-end 아키텍처

 

이러한 특징을 통해 조직 간의 병목을 제거할 수 있다. 하나의 앱을 여러 팀이 운영하게 된다면 서로간의 디펜던시가 생기기 때문에 서비스의 배포 주기를 짧게 가져가기 힘들다. 하지만 MFE를 도입한다면 각 팀이 별도의 배포 주기를 가져갈 수 있다. 게다가 기술스택도 별도로 가져갈 수 있으므로 레거시를 점진적으로 지원할 수도 있다.

 

결과적으로 기능 별 혹은 앱 별 오너십을 강화시키고 실험 속도나 안정성을 보장받을 수 있다. 추가적으로 각 팀이 거대한 앱을 함께 만들며 전체 앱에 대한 오너십이나 조직간 결속력을 증대시킬 수도 있다.

 

MFE Patterns

MFE 패턴 혹은 MFE 전략은 통합 시점에 따라서 구분하는 방법과 통합 위치에 따라서 구분하는 방법이 있다. 우선 통합 시점은 빌드타임과 런타임으로 나뉜다.

구분 특징 기술
Build-Time MFE 번들링 시점에 통합 주로 모노레포 활용
Run-Time MFE 앱 실행시 동적으로 통합 Module-Federation, iframe, script injection

 

빌드타임 MFE는 말그대로 빌드되는 시점에 다른 앱의 모듈을 불러와서 함께 빌드하는 방식이다. 이를 구현하기위한 가장 좋은 방법은 모노레포이다. 런타임 MFE는 앱 실행시 동적으로 다른 앱의 모듈을 불러오는 방식을 의미한다. 모던 MFE는 대체로 런타임 MFE를 의미하고 있기 때문에 사실상 통합 시점에 따른 구분보다 통합 위치에 따른 패턴을 일반적으로 더 많이 사용한다.

 

통합 위치에 따른 패턴 및 전략은 크게 Route Based MFE 와 Domain/Feature Based MFE가 있다.

 

Route Based MFE(Vertical Split Strategy)

이 패턴은 URL 경로에 따라 서로 다른 MFE이 담당하는 형태이다.

 

Vertical Split Strategy (출처: Vercel)

 

라우터 수준에서 조립하는 형태로 구현되며 이를 구현하기 위해서는 nginx, k8s ingress, nextjs multi-zones, module-federation 등의 기술을 주로 사용한다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: mfe-app-a-ingress
spec:
  rules:
    - host: mfe-test.com
      http:
        paths:
          - backend:
              service:
                name: mfe-app-a-svc
            path: /app-a
            pathType: Prefix
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: mfe-app-b-ingress
spec:
  rules:
    - host: mfe-test.com
      http:
        paths:
          - backend:
              service:
                name: mfe-app-b-svc
            path: /app-b
            pathType: Prefix

 

위의 ingress 설정은 "mfe-test.com/app-a" 로 갈 경우와 "mfe-test.com/app-b" 로 갈경우 각각 다른 앱을 볼 수 있도록 구성한 예시이다. MFE는 대부분 대규모 서비스에서 도입하고 있고, 그러한 서비스에서는 k8s(kubernetes)로 서비스를 구성하는 경우가 많으므로 위와 같은 형태의 ingress manifest를 작성하여 Route-Based MFE를 도입할 수 있다.

 

Domain/Feature Based MFE(Horizontal Split Strategy)

이 패턴은 말그대로 기능 단위 혹은 도메인 단위로 분할하는 것을 의미한다. 일반적으로 MFE를 떠올렸을 때 떠오르는 이미지일 것이다.

Horizontal Split Strategy (출처: Vercel)

 

Header, LNB&GNB, Notification 등의 기능을 별도로 분리하는 형태로 운영할 수 있는 전략이다. 이러한 서비스를 구현하기 위한 가장 적합한 기술이 Module Federation이며 다음 장에서 좀 더 자세히 소개하겠다.

 

2. Module Federation

Module Federation is an architectural pattern for the decentralization of JavaScript applications (similar to microservices on the server-side).
It allows you to share code and resources among multiple JavaScript applications (or micro-frontends).

 

공식문서에서는 모듈페더레이션을 JS앱을 분리시켜 운영할 수 있도록 하는 패턴이라고 소개하고 있다.

 

Terms

Module Federation을 간단하게 소개하기 위해 용어와 함께 코드를 통해 알아보자.

 

Consumer(Host)

 

Consumer는 다른 어플리케이션이 제공하는 모듈을 Import하는 어플리케이션을 의미한다. 코드에서 볼 수 있듯 동적으로 필요한 시점에 불러와야하므로 dynamic import 방식을 사용한다.

 

Producer(Remote)

 

Producer는 외부 어플리케이션이 사용할 수 있도록 모듈을 expose한다. remoteEntry.js를 통해 모듈을 제공하게 된다. MF에서는 이러한 Producer가 또 다른 Remote 모듈을 import하는 형태로도 구성할 수 있다. 즉 Remote 어플리케이션이면서 동시에 Host 어플리케이션일 수도 있다.

 

Shared

remote와 host가 공유해서 사용하는 의존성을 의미한다. react와 같은 라이브러리의 중복 로딩을 방지할 수 있다. 여러 옵션을 제공하므로 다양한 공유 전략을 세울 수 있다.

 

Use Case 01

첫번째 케이스는 Domain/Feature Based 서비스에 MF를 도입하는 상황이다. 아래의 예제 서비스는 쇼핑몰이며, 기본적으로 쇼핑몰 기능을 갖고 있다. 추가적으로 스타일 피드로 SNS기능을 제공하고 우측하단에는 챗봇을 넣어 제공하고 있다.

가상의 쇼핑몰 - Harry Style

이러한 서비스를 운영하고 있다고 생각해보자. 메인 기능이 아닌 도메인의 변경에도 새로 서비스를 빌드하고 배포해야하는 상황이 매우 잦을 것이다. 게다가 보면 알겠지만 쇼핑/SNS 의 카드 디자인도 조금 달라서 공통 컴포넌트가 거의 없는데도 같은 서비스라서 의존성이 생기게 될 것이다. 또 챗봇의 경우 최근 발전한 AI로 다양한 기능을 실험해보며 빠른 릴리즈 주기를 가져가고 싶을텐데 빌드 및 배포할때 쇼핑몰이 통째로 영향을 받는 상황이 생긴다.

Micro App 위치(좌) / 에러 대응(우)

여기에 만약 MF를 도입한다면 어떨까. 우선 별도 배포를 통해 독립적으로 운영할 수 있게될 것이다. 이를 통해 특정 도메인은 빠르게 테스트하고 릴리즈할 수 있다. 게다가 에러를 발생시키더라도 거의 영향을 주지 않는 형태로도 운영할 수 있다. host앱에서 remote를 불러오는 부분을 잘 구성한다면 우측과 같은 형태도 가능할 것이다. SNS 청크파일에 이슈가 있을 경우 인기상품목록을 한 줄 더 띄우거나, 챗봇에 문제가 생기면 비활성화 처리하는 등의 로직을 넣은 것이다. 이렇게 쇼핑몰을 운영한다면 문제가 발생하더라도 사용자에게는 정상적으로 동작하는 것처럼 보여줄 수 있다.

 

결과적으로 프로젝트에서는 조직을 굉장히 유연하게 운영할 수 있다. FE개발자들이 SNS팀이나 AI팀으로 분리되어 목적조직을 구성할 수도 있고, FE개발자로만 이루어진 기능 조직이더라도 유연하게 다른 조직과 협업할 수 있다.

 

Use Case 02

두번째 케이스는 Route Based 서비스에 MF를 도입해보자. 이번 예제 서비스는 배달을 관리하는 서비스이다. 기본 대시보드를 제공하는 루트(/)페이지와 함께 배달관리서비스(/delevery), 가게관리서비스(/store) 등을 제공한다.

가상의 배달 어드민 - 해리 배달 (좌측부터 각각 "/", "/delevery", "/store" 페이지)

이러한 서비스의 경우 공통 레이아웃 영역(LNB GNB)은 패키지로 관리하고 있을 것이다. 그리고 각 서비스는 별도 팀에서 별도 레포로 관리한다고 가정하자. 대규모 서비스를 운영하므로 k8s ingress 레벨에서 분기처리를 하여 각 서비스로 보내고 있다. 각 서비스를 이동할때에는 새로운 서비스로의 이동이므로 아래와 같은 로딩이 출력될 것이다.

페이지 이동시 전체 화면 로더 출력

이 서비스를 운영하면서 생기는 문제는 다양하다. 먼저 각자 운영 주기를 갖고는 있지만 공통 영역이 변경되어야할 경우 모든 서비스가 새로 빌드되고 배포되어야한다. 새로운 패키지 버전으로 업데이트하고 다시 빌드하고 모든 서비스는 각각 빌드되고 배포된다.

 

각 어플리케이션을 운영하다보면 결국 커다란 하나의 서비스에 속해있으므로 비슷한 로직을 작성할 가능성이 높다. 이러한 로직들이 중복되어 관리되게되고, 조금씩 다르게 작성되면 결과적으로 사용자에게는 같은 서비스인데도 조금씩 다른 UIUX를 경험하게 만든다. 사용자는 결국 이를 하나의 서비스라고 느끼지 않게될 것이다.

서비스 컨텐츠를 Micro App으로 구현

여기에 MF를 도입하면 위와 같은 형태로 구성할 수 있다. URL 경로에 따라 컨텐츠 부분을 마이크로 앱으로써 가져오도록 한 것이다. 이렇게하면 공통영역이 변경되더라도 서비스는 빌드하거나 배포를 할 필요가 없다. GNB LNB가 변경되면 서비스에서는 실시간으로 반영될 것이고, 이는 host 앱 입장에서도 마찬가지다. 배달 관리 어플리케이션이 새로 배포되면 그 즉시 반영될 것이다. MF 도입을 통해 비슷한 로직도 공유해서 사용할 수 있게 된다.

다른 라우트 서비스로 이동시 컨텐츠 부분만 로딩

MF도입 전과 달리 서비스를 이동할때에는 위와 같이 컨텐츠 부분에만 로딩UI를 출력시킬 수 있다. MF도입을 통해 하나의 서비스처럼 동작할 수 있게되었고 서비스간의 이동속도도 굉장히 빨라지게 된다. 결과적으로 사용자는 하나의 서비스라고 인식할 수 있게되고, 조직 입장에서도 조직간 결속력이 증대되고 서비스 오너십이 증가하는 효과를 가져올 수 있다.

 

3. Next Step

지금까지 MFE와 MF에 대해서 가볍게 살펴보고, Use Case를 통해 도입 효과를 자세하게 알아보았다. 굉장히 이점이 크지만 이를 도입하기 위해서는 고려할 사항이 다양하다. 이를 알아보고 프론트엔드 생태계에서 MFE가 현재 어떤 동향으로 흘러가고 있는지 알아보자.

 

고려할 사항

우선 MF를 도입하면 초기 개발 환경 구축이 어려울 수 있다. 우선 단일 앱이 개발모드로 확인할 수 있는 환경이 구축되어야 한다. 이게 무슨 뜻인지 의아할 수 있으므로 실제 개발을 하고있다고 가정하고 예시를 살펴보자.

npm run dev:micro-app-a

 

위와 같이 마이크로 앱 A를 개발서버로 열었을 때, 다른 마이크로 앱은 실제 A가 배포되었을때의 환경으로 열려야 한다. 만일 A를 개발하는데 B나 C를 개발 환경으로 열어야한다면 개발 서버를 띄우기도 힘들뿐만 아니라 실제 배포되었을때와 동작이 다를 수 있다. 게다가 이런 환경에서 IDE의 자동완성이나 타입추론을 제대로 사용하려면 여러가지를 생각해봐야 한다. 다행히 MF에서는 타입 추론을 위한 모듈을 제공하고 있다. 하지만 필자는 이러한 상황들을 고려했을때, 모노레포를 도입한다면 조금 더 쉽게 이러한 문제를 해결할 수 있다고 생각한다.

 

다음은 shared관리가 굉장히 복잡해질 수 있다는 점이다. 여러 마이크로앱을 런타임에 불러와서 개발하다보면 모듈 버전을 맞춰서 동일하게 가져가야하는 상황이 다수 존재한다.(ex. react) 그리고 context와 같은 전역 상태 의존성을 공유하려면 해당 모듈을 singleton 전략으로 설정해야하거나 스타일과 같은 라이브러리를 위해 eager 설정을 고려해야할 수 있다. 이러한 이슈를 쉽게 파악하고 전략을 세우기 위해 MF의 흐름을 심플하게 가져가는 것을 추천한다. remote앱이 host가 되거나 remote안에 또 remote 등이 존재하는 형태로 구현한다면 꼭 그렇게 구현해야할지 아키텍처를 다시 살펴보길 바란다.

 

마지막으로 고려할 사항은 서버사이드의 도입이다. MF는 초기에 SPA 중심으로 설계되었다. 마이크로 프론트엔드 컨셉 자체가 클라이언트 사이드에서의 앱 조합이므로 이러한 설계가 이상하다고 보이진 않는다. 물론 Server-Side를 지원할 수 있도록 구현은 되어있다. 하지만 복잡한 설정이 필요하며, nextjs-mf와 같은 서드파티 라이브러리를 써야할 수도 있다. 추가로 클라이언트 사이드와 싱크가 맞지 않을 수 있다. 이러한 상황은 Server-Side에 MF를 도입할 경우 고려해야할 부분이 매우 많아지는 것을 의미한다. 예를 들면 환경차이로 인한 동작 방식을 고려해야하거나 서버와 브라우저간의 캐시전략이 다르다거나 하는 등의 내용을 고려해야 한다.

 

생태계 동향

Module Federation v2가 나오면서 MF는 웹팩의 제한에 벗어나 MFE를 구축하기 위한 최선의 선택이 되었다. 이전 버전에서는 웹팩에서만 구현되는 기술이었다면 현재는 웹팩의 기술이아닌 MFE구현을 위한 개념과 같은 형태로 소개하고 있다.

공식문서(좌) 공식깃헙(우)

 

하지만 FE에서 가장 인기가 많은 프레임워크인 Next.js 에서는 손을 뗀 모습을 보여주고있다. 2024년 11월, MF의 메인테이너인 Zack Jackson 은 위와 같이 nextjs-mf를 deprecate 시켰고, 해당 깃헙 이슈에서 마이크로프론트엔드를 사용하려면 Next.js를 사용하지말라고 하거나, Vercel을 비판하는 내용의 글을 작성하였다.

Vercel 블로그 포스트 제목

 

비슷한 시기에 Vercel은 나름대로의 MFE를 구축한 모습을 보여주었다.

Vercel 블로그 포스트 내용

해당 Vercel 포스트를 읽어보면 Next.js 에 새로 추가된 multi-zones 스펙으로 구현하였다. 이 스펙은 라우트 기반이며, MF에 비해 소극적인 런타임 조합임을 알 수 있다. 이를 통해 알 수 있는 사실은 Vercel도 MFE에 관심이 있다는 것이다. 아마 최종적으로는 런타임 모듈 조합까지 도전할 것이라 생각한다.

 

(이 글을 발행하는 시점에는 Zack과 Vercel이 화해(?)하고 있는 모습도 보여주고 있으니 참고하길 바란다.)

화해(?)의 현장

 

 

지금까지 Module Federation을 통해 Micro Front-end 의 구현과 생태계를 전반적으로 알아 보았다. 서비스의 규모가 커질수록 앱의 규모도 커지게되고, 이러한 상황에서 MFE의 도입은 좋은 선택이다. MFE는 단순히 기술적인 부분뿐만아니라 프론트엔드 조직 문화와 개발 전략 전반에 영향을 줄 수 있는 아키텍처이다. 도입 시 기대효과와 함께 지금까지 언급했던 고려할 사항이나 FE 생태계 흐름까지 염두에 두고 선택한다면 만족할 만한 결과를 볼 수 있을 것이다.