카테고리 없음

[FE] 프론트엔드계의 MSA. Micro Frontends.

Frorong 2024. 2. 19. 22:52
728x90

[FE] 프론트엔드계의 MSA. Micro Frontends.

 

MicroServiece Architecture.

MSA. MicroServiece Architecture의 약자이다. 

커다란 소프트웨어를 작고 독립적인 각개의 서비스로 분해하여 개발하고 배포하는 아키텍처이다.

 

기존의 Monolithic Architecture는 소프트웨어믜 모든 구성요소가 한 프로젝트에 통합되어 있는 형태이다. 각각의 모듈별로 개발 이후에 완성된 어플리케이션을 하나로 패킹하여 배포한다. 그런데, 이런 monolithic 방식은 한계가 존재한다. 서비스가 커질 수록 monolithic 어플리케이션에서는 다양한 문제가 발생하는 것이다.

 

Monolithic Architecture에서는 다음과 같은 문제가 발생할 수 있다.

1. 어플리케이션의 크기가 커질 수록 코드의 복잡성이 증가한다.

2. 전체 어플리케이션을 한 번에 배포하기 때문에 배포 프로세스가 오래 걸리고 복잡해진다.

3. 전체 어플리케이션이 동일한 기술을 공유하기 때문에 기술 스택의 다양화가 어렵다.

4. 일부 부분적인 오류가 전체 시스템으로 확산될 가능성이 높다.

 

이러한 monolithic architecture의 문제 때문에 MSA라는 개념이 등장한 것이다.

보통 MSA는 서버 측에서 사용되는 아키텍처적인 접근 방식이다. 프론트엔드에서는 Micro Frontends라는 유사한 개념이 존재한다.

 

Micro Frontends

MFE(micro frontends)는 웹 어플리케이션을 작은 독립적인 부분으로 분해하야 개발하고 관리하는 아키텍처이다. MSA와 같은 접근 방식이다.

 

MSA는 백엔드 기능을 개별적으로 나눠 개발하고 관리하지만, MFE는 UI를 작은 단위로 나눠 개발하고 관리한다. MSA와 동일하게 필요에 따라 다른 기술 스택을 사용할 수도 있다.

 

아이언맨 3에서는 손, 팔, 다리 개별적으로 동작하는 슈트가 등장한다. 고장이 난다면 고장난 부분만 수리하여 사용할 수 있다. 물론, 합쳐진다면 하나의 슈츠로 동작한다.

MFE의 패러다임과 비슷하다.

 

여기서 이런 의문점이 생길 수도 있다. 서비스를 분리하여 관리한다면 monorepo와 비슷한 개념이 아닐까? 당연히 엄청난 것들이 다르다.

monorepo는 여러 개로 분산되어있는 서비스들을 단일 레포지토리에서 관리하는 것이고, MFE는 하나로 묶여있는 서비스를 분해하여 관리하는 것이다.

추가적으로 monorepo는 프론트엔드에 한정되지 않고, MFE는 레포지토리에 한정되지 않는다.

 

이제 micro frontends가 뭔지는 알겠는데.. 어떻게 구현할 수 있을까?

Module Federation

module federation은 webpack의 기능 중 하나이다.

여러 개의 독립적인 번들을 하나의 어플리케이션으로 통합할 수 있는 기술이다. 각각의 MFE 어플리케이션이 독립적인 웹팩 번들로 빌드되고, 이를 하나의 어플리케이션으로 통합한다. module federation은 주로 MFE에서 사용되지만, 국한되지는 않는다고 한다.

 

module federation은 런타임에 javascript를 통해 각 어플리케이션이 서로 코드 공유가 가능하도록 한다. 빌드타임에 통합한다면, 개별 배포가 가능한 MFE의 장점이 사라지게된다.

 

먼저 module federation에서 사용되는 용어들을 알아보자.

 

  • host : 원격 모듈을 불러오는 어플리케이션이다. 다른 모듈들을 동적으로 가져와 사용한다.
  • local module : 일반적인 모듈이다. 현재 어플리케이션이나 프로젝트에서 정의되고 사용된다.
  • remote module : 원격 컨테이너에서 런타임에 동적으로 로드되는 무듈이다.
  • exposes : host 어플리케이션에게 모듈의 공개할 부분을 지정한다. 
  • container : 원격 모듈을 호스팅하고 런타임에 동적으로 제공한다. 외부의 모듈을 host에 제공한다.

 

자, 이제 어떻게 module federation을 하는지 알아보자.

먼저 공유할 어플리케이션에서 webpack.config.js 파일을 구성한다.

name은 모듈의 이름이 들어가고, filename은 번들링 시 생성되는 파일의 이름이다. expose에는 공개할 부분이 들어간다.

shared를 설정하여 공통된 패키지를 중복으로 불러오는 것을 방지한다.

const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
    .
    .
    .
    plugins: [
        new ModuleFederationPlugin({
            name: 'moduleName',
            filename: 'remoteEntry.js',
            exposes: {
                './Component': './src/Component',
            },  
            shared: {
                react: {
                    eager: true,
                },
            }, 
        }),
    ],
};

 

그리고, host할 어플리케이션에서 webpack.config.js 파일을 구성한다.

name에는 Host 어플리케이션의 이름이 들어가고, remotes에는 불러올 remote 모듈들이 들어간다.

const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
    .
    .
    .

    plugins: [
        new ModuleFederationPlugin({
            name: 'hostName',
            remotes: {
                moduleName: 'app1@http://localhost:3001/remoteEntry.js',
            },
        }),
    ],
};

 

이제 host 어플리케이션에서 import문을 사용하여 remote 모듈을 가져올 수 있다.

import Component from 'moduleName/Component';

 

컴포넌트를 제외하고도 다양한 것들을 expose 할 수 있다.

 

typescript issue

이런 module federation은 장점만 있는 것이 아니라 typescript 사용시에 치명적인 단점이 생길 수 있다고 생각했다. 여러 레퍼런스를 찾아봤는데 실제로 그런 사례들이 있었다.

 

remote module에서 가져온 컴포넌트의 props type을 어떻게 typescript가 유추할 수 있겠는가?

 

module federation은 타입 정보를 동적으로 가져오는 기능을 제공하지 않기 때문에, 이러한 정보들을 처리할 방법이 필요하다.

.d.ts를 사용하여 타입을 정의하거나, monorepo의 경우 tsconfig의 paths 속성을 사용할 수 있다.

 

 

마치며

여러 거대한 기업들에서 MFE를 선택하고 있다고 알고있다. 대규모 프로젝트에 적합한 만큼, 굉장한 복잡도를 가지고 있다. 하지만 그만큼 매력적인 기술이 아닐까 싶다. 특히 점진적으로 변화하는 특성을 가진 어플리케이션에 적용한다면 엄청난 효과가 나올 것 같고, 모노레포에서 사용한다면 특히 좋은 시너지가 생길 것이라 예상한다. 꼭 취업하기 전에 MFE + Monorepo를 사용해서 서비스를 개발해보고싶다.

728x90