마이크로 프론트엔드와 모노레포 & 제로빌드

dalbodre·2022년 2월 2일
21

study

목록 보기
2/6
post-thumbnail

이것은 마치 까먹지 않기 위해 정리하는 메모장이랄까.. toss slash21 보고나서 언젠가 정리해야지 미루고 미뤘는데,, 곳곳의 세미나에서 마이크로 프론트엔드 도입기를 자랑하기 시작했다,,,,,,

모노리스 아키텍쳐에서부터 마이크로 프론트엔드까지

모노리스 아키텍쳐

기존의 개발 방식으로, 각각의 기능들을 개발한 후 하나의 앱으로 패키징하여 배포하는 것을 말한다. 일반적으로 하나의 레포지토리 내에 하나의 큰 앱만 존재하여, 코드 공유가 쉽고 형식 통일과 배포 관리에 용이하다는 장점이 있다.

여러 팀으로 구성되는 대규모 프로젝트에서 문제가 될 수 있다. 예를 들어 계정/상품 확인/장바고니/배송/결제 기능이 포함된 인터넷 쇼핑몰에서, 결제 기능을 개발하기 위해 로그인 개발이 먼저 완료되어야 하는 등 개발 병목현상이 발생한다. 결제 시스템에서 문제가 생기더라도 상품 확인 및 장바구니를 포함한 모든 기능을 사용하지 못하게 되는 에러 확산이 발생하기도 한다.

전체 서비스가 하나의 프레임워크와 언어에 제한되는 경우도 생긴다. (사실 이 예시는 어떻게든 연결하는 레이어를 담으면 되는게 아닌가 싶기는 하지만.. https://wooaoe.tistory.com/57 요기 블로그 왈) 블록체인 모듈은 nodeJS를 일반적으로 사용하는데, 서버를 스프링을 통해 서비스를 시작하는 경우 이를 통해 연동해야 한다. 또한 수정하지 않은 다른 기능들을 포함한 전체 서비스 빌드가 필요하고, 작은 변경에도 높은 테스트 비용이 발생한다.

SOA : service oriented architecture, MSA : micro service architecture

이런 문제를 해결하기 위해서 프론트와 백단이 나뉘기도 하고, 공통모듈을 기반으로 개별 모듈들의 의존성을 줄인 SOA와 아예 모듈들을 분리하여 API로 통신하는 마이크로 서비스 아키텍쳐가 제안되기도 했다. (참고 : https://www.redhat.com/ko/topics/cloud-native-apps/what-is-service-oriented-architecture)

마이크로 서비스에서, 앞서 말한 인터넷 쇼핑몰의 경우 로그인/결제/리뷰/장바구니 등등의 각각의 서비스는 개별적으로 모듈화되어 독립적으로 테스트 및 배포가 이루어진다. 서비스 구현 기술과는 상관없이 API를 사용하여 통신하기 때문에 서비스별로 기술스택이 달라도 상관이 없다. 특히 대규모이면서 레거시 코드가 대부분인 프로젝트에서, 이와 구분하여 새로운 기술스택을 사용한 기능 개발에 제한이 없다는 장점이 있다.

백엔드에서 MSA가 마구마구 사용되던 것에 반해, 프론트엔드는 모노리스 상태를 최근까지 유지해오던 경향이 있었다. 최근(ㅇ..ㅓ..최근의 약간 이전..?) 프론트 엔드 추세는 사용자 인터랙션이 일어나면 최소한의 리렌더링을 통해 기능하는 Single Page Application를 구성하는 것이었기 때문이다. 백엔드에서 겪었던 문제와 비슷하게, 시간이 지남에 따라 규모가 커지고 여러 팀이 함께 하나의 서비스를 개발하게 되면서 점차 유지관리가 어려워짐에 따라 대규모프로젝트에서 마이크로 프론트엔드가 도입되기 시작했다. (물론 내가 참여하는 대부분의 프로젝트는 대규모가 아니다 ㅎㅁㅎ,,)

마이크로 프론트엔드

마이크로 프론트엔드란 결국 프론트엔드에서 개별 팀이 담당하는 비즈니스 영역을 모듈화하여 완전히 구분하는 것을 말한다. 한 앱의 각각 일부 앱만 담당하는 것이 아니라, 각 팀 내에서 데이터베이스에서부터 사용자 인터페이스에 이르기까지 end-to-end를 완료하는 것이다.

마이크로 프론트엔드를 구성하는 방법에는 서버 템플릿 통합, 빌드타임 통합, iframe를 통한 런타임 통합, JS를 통한 런타임 통합, Web Components를 통한 런타임 통합 방식이 있다. 이 중에서 토스에서도 사용하고 있고, lerna를 통해 쉽게 사용할 수 있어 보이는(?) 빌드타임 통합 방식에 대해 조금 더 이야기해보려 한다.

토스의 마이크로 프론트엔드 도입기 : 의존성 지옥과 긴 빌드타임

토스는 8~9명으로 구성된 사일로가 하나의 기능 개발을 담당한다고 한다. 각 사일로마다 개별적인 서비스를 구현하기 때문에 21년 세미나 당시 25개 이상의 리액트 서비스가 존재했다. 새로운 프로젝트는 자연스럽게 기존에 존재하던 프로젝트에 추가되는 형태로, 각 서비스는 웹팩의 엔트리포인트로 구분되기는 하지만, 하나의 패키지에서 하나의 웹팩설정으로 한번에 빌드되는 구조였다.

이로 인해 의존성 지옥과 너무 긴 빌드타임이라는 문제점이 발생했다.
서로 코드를 공유하지 않는 A와 B 서비스, 그리고 두 서비스가 공유하는 X라는 의존성 패키지가 존재한다고 하자. A는 이미 개발이 완료되어 정상적으로 서비스되던 중 B를 개발하다 보니 (이미 A에서 사용하던) X 패키지에서 버그 발견하게 되었다. 이때 B의 개발을 위해 X의 버전을 올리면 A에서 에러가 발생하거나 작동이 달라지는 경우가 종종 발생했다.

또한 (앞에서 말했던 모노리스의 문제점..) A 서비스에서만 변경이 있다고 하더라도 변경이 없는 B부터 Z 서비스를 모두 새로 빌드해야 했으며, 사진에서와 같이 개발 도중에도 20분에서 40분간 빌드를 돌리고 멍하니 기다려야 하는 병목현상이 두드러지기 시작했다.

토스는 이를 해결하기 위해 기존의 거대한 소스코드를 독립적인 패키지로 분리하고 각각을 빌드하기로 결정하였는데, 레포지토리를 구분해서 사용하자니,,, 공통 코드의 공유 어려움, 사용 라이브러리의 파편화, 복잡한 서비스 관리 등의 문제가 발생하여 결국 모노레포를 도입하게 되었다. (아래 그림ㅋㅋㅋㅋㅋ 미쳤나봨ㅋㅋㅋㅋ 너무 웃곀ㅋㅋㅋ)

모노레포 : 의존성 지옥의 구원자

모노레포는 무엇이고, 어떤 좋은 점을 갖고 있길래 토스 뿐만 아니라 Google, Facebook 및 Twitter와 같은 대기업이 사용할까?

일반적으로 자주 사용했던 멀티레포 방식은 여러 레포지토리에 패키지들을 분산시켜서 사용한다.
장점 :

  • Repository 별 Owner를 지정 : 수월한 패키지 관리
  • 각 레포지토리별 빠른 CI Build : 하나의 Repository는 하나의 Continuous Integration 구성
  • 패키지의 명확한 분리로 인한 유연성 향상 : Repository 상 서로 연계 관계가 없기 때문에 추가, 수정, 유지 관리 편리

단점 :

  • 중복된 설정 및 반복된 설치 : 모든 공통된 설정과 모듈들을 반복적으로 설정/설치해야 함
  • 이슈의 분산 : 각 다른 레포지토리가 연관되어있는 이슈 트래킹 관리 어려움
  • Dependency Hell : 여러 패키지들이 사용하는 같은 모듈에서 (의도됐든 의도되지 않았든) 버전 차이 발생 및 충돌 발생 가능
  • 중복 코드의 가능성 : 중복된 설정 및 반복된 설치와 비슷하게 레포지토리가 분리되어 공통된 코드가 중복될 가능성이 커짐

반면, 모노레포란 하나의 저장소에서 여러 프로젝트를 관리한다. (기존의 멀티레포의 단점을 장점으로, 장점을 단점으로 가진다)
장점 :

  • 공통 항목 단일화 : eslint, Build, Unit Test 등 공통된 설정 및 필요한 node module을 한 번의 설치와 한 번의 설정으로 모든 패키지가 사용할 수 있다.
  • 쉬운 Package 공유
  • 단일 이슈 트래킹 : 모노레포 내 연관된 패키지들에 관한 (분산될 필요 없는) 이슈 트래킹
  • 효율적인 의존성 관리

단점 :

  • Repository의 거대화 : 분산되어 있던 모든 리소스를 하나의 레포로 합치면서,,
  • 느린 CI Build : CI가 하나로 구성된다는 장점 == 규모가 커짐에 따라 분산된 CI 빌드보다 속도가 느릴 수밖에 없음
  • 무분별한 의존성 : Package 간 의존성 관리가 쉽지만, 오히려 과도한 의존성 관계 발생 가능

제로빌드 : 긴 빌드시간을 줄이자!

토스에서는 빌드시간을 줄이기 위해서 yarn berry의 zero-install이 작동하는 방식을 차용했다.

(yarn berry와 node modules도 한번 정리해야 하긴 하는데,, 간략하게 정리하면 yarn berry는 기존의 yarn과는 다르게 의존성을 압축 파일로 관리하고 숫자가 적어 Git으로도 의존성을 충분히 관리할 수 있다. 이렇게 의존성을 아예 버전 관리에 포함하는 것을 Zero-Install이라고 한다. 새로 저장소를 복제하거나 브랜치를 바꾸었다고 해서 yarn install을 실행하지 않아도 되고, 이로 인해 CI/CD도 쉬워진다는 이점이 있다. 참고 : https://toss.tech/article/node-modules-and-yarn-berry)

빌드 시간을 줄이기위해서 변경사항이 없는 패키지는 빌드를 하지 않도록 변경했다! 모노리스 방식에서는 이런 방식을 사용하기 어려웠지만, 마이크로 프론트엔드 아키텍쳐에서는 소스코드 변경이 있는 패키지만 새로 빌드하면 됐기 때문에 쉽게 도입할 수 있었다. 기존 빌드의 결과물을 아예 git에 저장하여 언제든 pull만 받으면 최신 빌드 결과를 사용할 수 있도록 하였다.

무조건 모노레포를 사용해야 할까?

모노레포가 좋다는건 알겠는데,, 그럼 무조건 마이크로서비스/모노레포를 선택해야 할까?

정답은 너무 당연하게, NO!
이들은 어느 정도 복잡성을 가진 (각 서비스/패키지로 구별되는) 시스템에서 유용하다. 실제로는 여러 개의 서비스를 나누고 관리하는 것 또한 비용이기 때문에 오히려 생산성에 방해가 될 수도 있다. 특히 프로젝트 초반에는 실제로 사용자들에게 유용한 기능을 빠르고 정확하게 만드는 것이 중요할 때가 많고, 비교적 규모가 적은 프로젝트에서는 서비스 간에 경계를 명확하게 나누는 일이 어렵기도 하다.

결론 : 소규모 프로젝트 내 빠른 개발을 위해서는 모노리스/멀티레포 구성이 더 수월한 경우가 있음!

Lerna : 모노레포를 쉽게 만들어보자!

lerna는 다중의 패키지가 있는 모노레포 자바스크립트 프로젝트의 test, build, release 같은 작업을 최적화 시켜주는 툴이다. 여러 패키지의 의존성을 묶어주는 역할과 각 패키지별 버전 업데이트를 도와준다.

lerna로 init한 프로젝트의 최상위에는 packages 폴더와 package.json, lerna.json이 존재한다. packages에는 각각의 서브 패키지들이 들어가며, package.json는 기존과 같이 script를 통해 (개별적으로 혹은 동시에) 패키지의 스크립트를 실행할 수 있도록 한다. lerna.json에서는 lerna관련 옵션값들이 저징되어 있는데, package의 폴더 지정이나 npm/yarn 지정 및 bootstrap 등을 지정할 수 있다.

Lerna의 여러 커맨드

  • lerna init : 새로운 lerna 저장소 생성
  • lerna bootstrap : 각 패키지별로 겹치는 dependency를 연결하면서 모든 패키지들의 의존성 설치
    ex) @foo/server와 @foo/client에서 같은 lodash 라이브러리를 사용한다면,
    겹치는 의존성은 연결되어서 루트 디렉토리의 node_modules에 설치되고,
    겹치지 않는 의존성은 각각의 패키지 프로젝트의 node_modules에 설치
  • lerna import : 에 해당하는 패키지를 커밋 히스토리와 함께 packages/으로 import해준다. 손쉽게 모노레포 완성!
  • lerna publish : 업데이트된 패키지의 새로운 배포 생성 (버전 업데이트, git 및 npm에서 모든 패키지 업데이트)
  • lerna chanced : 지난 배포 이후 어떤 패키지에 변화가 있었는지 확인
  • lerna run : 각 패키지들의 package.json에 정의된 script들을 실행

현재 토이프로젝트로 만들고 있는 보드게임에서는 react native와 react native web을 통해 앱과 웹을 한번에 만들고 있는데, 모노레포 내에서 여러 의존성 충돌이 발생하여 해결하는데 애를 먹었었다. 어떤 것들이 문제였는지, 어떻게 해결했는지 미래의 내가 다시 기록해주길 바라면서,, (똑같은 문제 다시 보면 적어놔야지,,, mmazzarolo님 사랑합니다 https://github.com/mmazzarolo/react-native-universal-monorepo)

오늘은 여기까지!

참고)
https://www.youtube.com/watch?v=FQoNY2W0s4E
https://toss.im/slash-21/sessions/2-5
https://github.com/Pyrolistical/microapps.git
https://soobing.github.io/micro-frontends/
https://martinfowler.com/articles/micro-frontends.html#TheExampleInDetail

profile
휘뚜루마뚜루

1개의 댓글

comment-user-thumbnail
2022년 8월 7일

보기 좋게 정리해주셔서 잘 읽었습니당 :-)

답글 달기