디자인패턴, 설계

(설계)Micro Frontend

흥부와놀자 2021. 9. 13. 10:24

마이크로 프론트엔드란?

특정 기준으로 프론트엔드를 나누는 개발방식으로 프론트앱에 여러 기능과 섹션이 존재하여 통합해서 관리하기 힘들때 사용

 

https://medium.com/bb-tutorials-and-thoughts/how-to-implement-micro-frontend-architecture-with-react-5ab172a0fec7

위 그림은 여러 앱으로 나누어서 백엔드 또는 서로간에 소통하는 방식을 나타낸다.

 

 

마이크로 서비스 vs 마이크로 프론트엔드

 

개발방식

https://sathyalog.wordpress.com/2021/01/03/micro-frontends/

배포방식

모놀리식 배포
마이크로 프론트엔드 식 배포

 

 

마이크로 프론트엔드 장점

 

앱을 이해하기 쉬워진다.

각 앱의 규모가 작아지기에 이해하기 쉬워진다.

 

각 앱이 독립적이 된다.

분리되서 개발하기에 서로 독립적이다.

 

더 쉽게 개발하고 배포할 수 있다.

각 앱마다 단일팀에서 개발 가능하기 때문에 개발과 배포가 쉽다.

 

개발 속도가 빨라진다.

각 앱이 별도의 팀으로 구성될 수있기에 개발 속도가 빨라진다.

 

CI/CD가 쉬워진다.

각 앱을 개별적으로 통합하고 배포할 수 있으므로 CI/CD 프로세스가 훨씬 쉬워진다.

 

독립 스택 및 버전 사용

각 앱마다 서로 같은 기술스택을 사용하더라도 부분적으로 최신버전을 도입하여 유연하게 테스트 할 수 있다.

 

코드를 공유하지 않는다.

서로간에 공유되는 코드가 많을 수록 버그 발생률이 올라가는데, 마이크로 프론트엔드에선 이러한 코드공유를 하지 않음

 

아키텍처의 확장이 유연해진다.

나눠져 있기 때문에 새로운 확장이 편해진다.

 

마이크로프론트엔드 단점

페이로드 크기

공통 컴포넌트를 공유하는게 아닌 각 앱이 독립적이므로 각 앱의 자바스크립트 번들은 서로 같은 내용을 중복적으로 포함하게 된다.  그럼 중복된 만큼 전체 페이로드의 크기도 커지게 된다. 

 

복잡성과 일관성

여러 앱으로 분산되기 때문에 모든 앱들을 통합하거나 빌드 배포할때 복잡해지고 고려해야할 사항들이 많아진다. 또한 각 앱마다 각기 다른 팀들이 배정된다면 서로의 일관성을 유지하기가 어려워질 수 있다.  

 

 

앱을 분할하는 방법

 

기능별

각 앱을 기능별로 나눠서 필요에 따라 DOM에서 마운트 / 언마운트 시킨다.

 

섹션별

 각 앱을 해당 페이지의 구역별로 나눠서 개발을 진행한다.

 

페이지별

각 앱을 페이지 별로 나눠서 개발을 진행한다.

 

도메인별

각 앱을 도메인 기반으로 나눠서 개발을 진행한다. 가장 일반적으로 사용하는 분할방식이다.

 

구현방법

런타임 통합방식

Webpack Module Federation(웹팩 모듈 연합) 런타임 통합

웹팩이란? 여러 자바스크립트 파일들을 하나로 만들어서 압축시켜주므로 서버와의 접속횟수를 줄이고 성능을 최적화 시켜줌.

웹팩모듈연합은 이러한 웹팩의 최신버전인 웹팩5의 추가기능이다. react-create-app은 웹팩4를 지원하므로 커스텀설정해야한다.

이걸 사용하면 해당 앱이 다른앱의 코드를 동적으로 로드할 수 있다.   

https://medium.com/bb-tutorials-and-thoughts/7-different-ways-to-implement-micro-frontends-with-react-907b5e262230

위그림의 로컬모듈은 최종 빌드할 모듈이 된다. 각 리모트모듈은 런타임에 동적으로 로딩시킨다.

 

 

iframe 런타임 통합

iframe은 html안에 다른 문서나 컨텐츠들을 넣을 수 있는 태그이다.  다른 도메인과 포트의 앱 문서도 넣을 수도 있다.

<html>
  <body>
    <iframe id="micro-frontend-container"></iframe>

    <script type="text/javascript">
      const microFrontendsByRoute = {
        '/': 'https://browse.example.com/index.html',
        '/order-food': 'https://order.example.com/index.html',
        '/user-profile': 'https://profile.example.com/index.html',
      };

      const iframe = document.getElementById('micro-frontend-container');
      iframe.src = microFrontendsByRoute[window.location.pathname];
    </script>
  </body>
</html>

분기할 페이지의 도메인들을 객체로 정의해 놓고 해당 window의 location에 따라 iframe의 src를 바꿔주고 있다. iframe이 쉽고 간단하지만 iframe이 웹을 크롤링할때 문제를 끼칠수 있고 보안적 문제들이 존재하는 단점이 있다.

 

자바스크립트 런타임 통합

각 앱페이지를 <script>태그를 사용하여 로드시키고 분기처리 해준다.

<html>
  <body>
    <script src="https://browse.example.com/bundle.js"></script>
    <script src="https://order.example.com/bundle.js"></script>
    <script src="https://profile.example.com/bundle.js"></script>

    <div id="micro-frontend-root"></div>

    <script type="text/javascript">
      const microFrontendsByRoute = {
      <!--각 페이지의 진입점(렌더시킬 함수)로 분기됨-->
        '/': window.renderBrowseRestaurants,
        '/order-food': window.renderOrderFood,
        '/user-profile': window.renderUserProfile,
      };
      const renderFunction = microFrontendsByRoute[window.location.pathname];
	 <!--각 페이지를 위의 div아래 붙여 렌더시켜줌-->
      renderFunction('micro-frontend-root');
    </script>
  </body>
</html>

iframe과 달리 꼭 해당 페이지를 보여주지 않고 단지 로드만 시킬수 있고 진입점 함수를 이용해 해당 페이지와 통신도 가능하다. 

 

서버측 통합방식

nginx 서버 통합

nginx는 기존 Apach에 비해 경량화된 서버이다. 

브라우저에서 라우팅한 경로를 Nginx서버에서 라우팅해준다. 즉 서버에서 분기에 따라 html파일을 결정해 통합 후 뿌려준다.

 

빌드타임 통합

각 앱들이 별도로 빌드 후 컨테이너앱에서 한꺼번에 통합하여 빌드하는 방식으로, 먼저 각 앱들을 패키지로 개시한다.

{
   "name" : "@feed-me/container" ,
   "version" : "1.0.0" ,
   "description" : "음식 배달 웹 앱" ,
   "dependencies" : {
     "@feed-me/browse-restaurants " : "^1.2.3" ,
     "@feed-me/order-food" : "^4.5.6" ,
     "@feed-me/user-profile" : "^7.8.9"
  }
}

위의 코드는 앱의 의존성 정보를 기록해주는 설정파일이다. 이렇게 최종 컨테이너 앱을 빌드할때 나눠진 앱들의 패키지들을 의존성으로 추가함으로써 실행했을때 어떻게 작동할지 모두 알고 있으므로 런타임 통합에 비해 안정적인 통합 방식이다.

 

스타일링

css의 경우 마이크로 프론트엔드시에 여러 앱들 사이에 중복될 수 있다. 이러한 경우엔 BEM 같은 엄격한 이름규칙을 사용하거나 Sass를 사용해 네임스페이스 형식으로 해결하거나 Shadow DOM을 사용해 스타일을 격리 시킬 수 있다. 

 

각 앱들사이에 어떻게 통신 시킬 수 있을까?

이벤트 사용

<!--foo.html-->

<!DOCTYPE html>
<html lang="en">
<script>
  function sendMSG(){
    const bar = document.getElementById("bar")
    bar.contentWindow.postMessage({hello:"world"},'*')
  }

</script>
<body>
  <input type="button" onclick="sendMSG()" value="asd">
    <iframe id="bar" src="bar.html">
      
    </iframe>
</body>
</html>

예시는 iframe으로 foo안에 bar를 넣었고 버튼을 누르면 bar로 메시지를 보내고자 한다. postMessage를 이용한다.

<!--bar.html-->
<!DOCTYPE html>
<html>
    <script>
        window.addEventListener("message",(event)=>{
            console.log(event)
        })
        </script>
<body>
</body>
</html>

foo에서 보낸 이벤트는 bar로 들어오고, 미리 정의된 EventListener로 전달받는다.

 

그외엔 리액트처럼 데이터를 아래 컴포넌트에 전달하는 방식이나 라우팅을 통해 매개변수를 전달하는 방식 등이 있다.

 

백엔드와 통신은 어떻게 할까?

BFF(Backend for Frontend)패턴

각 앱들은 전용 백엔드 인터페이스를 갖는다. 예시로,  모바일 프론트앱은 거기에 필요한 모바일 백엔드 서비스들을 연동시킨 전용 모바일 BFF와 통신하고, 웹 전용 프론트앱은 거기에 필요한 웹 백엔드 서비스들을 연동시킨 전용 웹 BFF와 통신하는 식이다.

이렇게 함으로써 더효과적으로 각 프론트엔드 앱 환경에 맞는 서비스를 제공할 수 있다. 

 

React와 single-spa를 사용한 마이크로 프론트엔드 프로젝트 만들기

single-spa란?

여러 자바스크립트앱들을 통합하기 위한 프레임워크이다. single-spa를 사용하면 React,Angular, 등 여러 프레임워크와 같이 사용할 수 있고, 런타임에 지연로드를 지원하여 초기 로딩시간을 개선할 수 있다.

 

적용방식

 

해당 프레임워크를 설치해 준다.

yarn global add create-single-spa

 

root-config디렉토리를 만들고 그 안에서 create-single-spa 해준다.

mkdir single-spa-demo-root-config
cd single-spa-demo-root-config
npx create-single-spa

 

그 후에 이렇게 옵션을 선택할 수 있는데 여기서 프로젝트의 디렉토리, 생성옵션, 패키지매니저, 타입스크립트 여부 등을 체크 하면

 

이렇게 Single-spa프로젝트가 생성된다.

douzonebf-root-config.ts : 는 모든 리모트앱들을 등록하는 곳이다.

index.ejs : 컴파일 후 index.html로 쓰여질 파일로써 다른 앱에서 쓰일 공통파일들을 가지고 있다.

 

yarn start 후 localhost:9000 접속시 이런 초기화면이 나온다.

 

 

이번엔 2개의 리모트앱을 만들어 보겠다. 

아까와 똑같이 폴더를 만들고 npx create-single-spa해준다.

아까와 다른것은 생성타입이 root config가 아닌 single-spa application / parcel로 해준다.

해당 앱은 react를 사용할 것이므로 react를 선택하고 주의할점은 Organization name을 아까와 같이 douzonebf로 해준다.

 

root-config파일 내부에 공통파일을 놓는곳인 index.ejs안에 systemjs-importmap에 다음과 같이 react와 react-dom의 cdn 주소를 추가해준다.

 

그 후 root-config의 src안의 douzonebf-root-config.ts의 resisterApplication에 spa-1의 package.json파일에 써진 name 프로퍼티를 복사해 등록한다. 

이 registerApplication 함수에 등록됨으로써 spa-1앱이 마이크로프론트로 관리되는 것이다.

 

후에 이렇게 index.ejs에 추가적으로 설정을 더해준다.

 

spa-2에서도 똑같이 진행을 해주고 각 앱의 root 컴포넌트를 이렇게 바꾸면 

두 앱이 app-config에 통합된걸 확인할 수 있다.