이전 포스팅에 적어둔 것처럼 회사에서는 일정 주기마다 이벤트를 연다. 각각의 도메인 별 서비스에서 전부 대응을 해야 하기 때문에, 매번 이벤트를 앞두고 각 팀별로 대비를 해야 한다. 이전 포스팅에서는 이벤트를 대응하는 우리 팀의 업무를 조금 더 편하게 관리하게 되었던 과정을 담은 포스팅이었다면, 이번에는 조금 더 확장해서 회사의 서비스 차원에서 전반적으로 업무 효율성을 높여보았던 과정을 정리하려고 한다.
간단한 회사의 서비스 상태
메인 페이지
: 앱으로 열었을 경우 가장 처음 노출되는 페이지이자, 웹 페이지에서도 '홈'의 역할을 하는 페이지이자 도메인 상관 없이 모든 정보가 들어있음.A 도메인 페이지
: A와 연관된 상품 조회, 예약, 결제가 가능한 A 도메인에 집중된 페이지.B 도메인 페이지
: B와 연관된 상품 조회, 예약, 결제가 가능한 B 도메인에 집중된 페이지.C 도메인 페이지
: C와 연관된 상품 조회, 예약, 결제가 가능한 C 도메인에 집중된 페이지.현재 재직 중인 회사의 서비스의 구조를 간단하게 그리고, 밑에 이해하기 쉽게 불릿으로 정리해 보았다. 겉보기에는 각자의 역할에 충실하면서 분리가 잘 되어있지만, 각 도메인 별로 저장소를 따로 관리하고 있기 때문에 상당히 비효율 적이다. 도대체 어떤 점에서 비효율 적일까?
외부에 있는 검은색 박스를 Repository
그리고 빨간색 네모와 파란색 동그라미를 각각 동일한 모양과 기능을 담당하는 컴포넌트
라고 생각해 보자.
각각의 관심사에 따른 도메인의 저장소를 각각 팀에서 관리하기 때문에 내부에 들어있는 컴포넌트
들은 각 저장소에서 각자 개발하여 사용하고 있다. 즉 빨간색 네모와 파란색 동그라미는 외부에서 보는 모양과 기능은 동일하겠지만, 각각의 팀마다 내부에 구현되어 있는 방법은 다 다르다는 이야기다.
예시) 빨간색 네모, 파란색 동그라미 =
GNB
,햄버거 메뉴
,Footer
등..
만약 빨간색 네모를 보라색 네모로 수정해야 한다면, 각 팀에게 각각 자체 개발했던 컴포넌트
에 대한 수정을 요청해야 한다. 즉, 유저가 보는 화면에서는 전체 서비스에서 빨간색 네모가 그저 보라색 네모로 수정된 것뿐인데, 유저에게 그렇게 일관된 UI를 제공하기 위해서는 4개의 저장소가 개발서버로 배포되고, QA를 거치고, 운영 서버에 배포가 되어야 한다.
지금도 충분히 번거로운 작업의 연속일 것 같은데... 여기서 도메인이 더 늘어나버리면 ???....🤯
근데 각 팀에서 원래 하던 일이고... 그대로 해도 되는 거 아닌가..?
사실 맞다. 여태껏 각 팀에서 그렇게 관리하면서 서로 불편한 것을 못 느끼고 있었다. 하지만 우리 팀은 메인 저장소
와 도메인 A 저장소
를 관리하고 있기 때문에 일단 내가 귀찮았다. 메인 저장소
와 도메인 A 저장소
는 pnpm
을 통한 모노레포
를 구축해두었지만, 단순히 모노레포
위에 저장소 두 개를 올려놓았던 것뿐, 모노레포
를 사용하는 가장 큰 장점인 반복되는 코드나 유틸 함수들을 분리하지는 못한 상태여서 제대로 활용하지 못하고 있었다. '이렇게 할 거면 모노레포를 왜...?'
라는 생각이 계속 들고 있던 상태였고, 시간적 여유가 되면 항상 분리해야겠다고 마음먹고 있었다.
마침 이런 문제가 생겨서 새로운 워크 스페이스
를 만들어서 반복되거나 두 저장소의 관심사와는 관계없이 공통으로 사용할 수 있는 코드를 정리하고 싶었지만 다른 도메인도 신경 쓰이기 시작했고, 그제야 여러 문제점들이 보였다. 단순하게 나만의 귀찮음의 문제가 아니고 프론트엔드 각 팀 끼리나 타 부서와의 커뮤니케이션의 비용이 너무나도 높았다. 각 도메인 별 PM 분들도 혼선이 생겨 옆 팀의 작업을 나에게 묻기도 하고, 그 반대의 일도 많이 생겼다.
MFA(Micro Frontends Architecture)
를 통해 문제를 해결해보자 !
해결책으로 '여러 저장소에 흩어져있는 같은 기능을 담당하고, 같은 UI를 가진 공통의 컴포넌트들을 외부 저장소에서 관리하여 주입시키는 것은 어떨까 ?'라는 생각이 들었고 조금 찾아보니 MFA(Micro Frontends Architecture)라는 것이 있었고, 비슷한 문제를 해결하기 위해 도입된 아키텍처였다. 이미 개발 문화를 선도하는 여러 빅 테크들에서 실무에 적용하는 사례들도 심심치 않게 찾아볼 수 있었다. 해당 주제로 컨퍼런스에서 발표했던 여러 영상들도 쉽게 찾아볼 수 있었다.
MFA
를 소개하는 Martin Fowler의 블로그 포스트를 읽어보면MFA
를 구현하는 5가지의 방법을 소개하고 있는데, 그 핵심은 다음과 같다.
👉🏻 각 저장소에 흩어져 있는 컴포넌트들을 한곳에서 따로 관리하고 싶어 !
이해를 돕기 위해 발로 그린 그림을 첨부했다. 기존 각 저장소에서 관리되던 빨간색 네모와 파란색 동그라미를 외부 저장소
로 분리하여 따로 관리하고, 해당 저장소에서 각 팀의 도메인 별 저장소
로 연결해 주기만 하면 된다. 앞으로 모든 빨간색 네모와 파란색 동그라미의 관리는 외부 저장소
에서만 하면 되는 것이다.
분리하고 싶은 컴포넌트를 따로 빼서 npm 라이브러리로 만들어서 배포할까도 생각했었는데, 버전 관리가 필요하고 분리된 컴포넌트의 버전이 바뀜에 따라서 사용하는 각 팀에서도 대응하고 재배포를 해야 한다는 불편함이 있을 거 같았다. 또한 각 팀마다 사용하는 프레임워크
가 달랐기에 React
를 기반으로 라이브러리를 만들려고 했었던 계획은 무산되었고, 모든 프레임워크
를 수용할 수 있는 다른 방법을 찾아야 했다.
근데 이제.. 익숙한
React
의JSX
와hook
을 곁들인.. (feat.Preact
)
React
의 기능을 얼추 비슷하게 사용할 수 있는 Preact
에서 간편하게 Web Component
를 정의할 수 있다는 것을 알게 되었고, Preact
는 3KB의 가벼운 용량을 자랑하고 있었기 때문에 빌드 결과물의 용량에 대한 압박도 없었다. 무엇보다 내가 익숙한 React
의 hook
과 같은 기능들을 그대로 사용할 수 있다는 것이 큰 장점으로 다가와서 Web Component
를 통해 MFA
를 구현하기로 결정했다. 또한 Web Component
는 Web Api
기능 중에 하나로, 프레임워크
와 무관하게 모든 곳에서 사용할 수 있다는 것이 큰 장점이었다.
Preact
를 사용해서custom-element
에 등록하기
Preact
내부에서 제공하는 register
함수를 사용하면 손쉽게 내가 만든 컴포넌트를 custom-element
에 등록할 수 있다. 간단한 예시는 다음과 같다.
import register from 'preact-custom-element';
import { HamburgerMenu } from '@/HamburgerMenu';
const HAMBURGER_MENU_WIDGET = 'hamburger-menu-widget';
if (!customElements.get(HAMBURGER_MENU_WIDGET)) {
register(
// 등록할 컴포넌트
HamburgerMenu,
// 등록고 싶은 custom-element 이름
HAMBURGER_MENU_WIDGET,
// attribute (속성)들의 이름을 작성
['env', 'open', 'login', 'name'],
{
// shadowDom 사용 여부
shadow: true,
},
);
}
custom-element
의 이름에는 하이픈('-')
이 반드시 포함되어야 한다. 언제 내가 정의한 태그가 웹 표준이 될지 모르기 때문에, 하이픈('-')
을 포함함으로써 이 요소는 custom-element
라는 것을 보장하기 위한 일종의 규칙이다. 또한 attr
들의 이름은 카멜 케이스로 작성했을 경우에, 자동으로 스네이크 케이스로 변환된다. 따라서 isOpen
같은 이름을 사용하지 않고 간편하게 작성했다. shadow-dom
과 같은 다른 여러 특성들도 있지만, 이 글에서는 Web Component
의 특성에 대해서는 자세하게 다루지는 않을 예정이다.
Preact
를 통해서 구현한 컴포넌트를 이미 custom-element
에 등록이 되어있는지를 체크하고 내가 원하는 hambuger-menu-widget
이라는 요소로 custom-element
에 등록해 준다. 이런 식으로 custom-element
에 등록된 컴포넌트는 빌드 후 사용하고 싶은 곳에서 script
태그를 통해 해당 custom-element
를 사용할 수 있게 빌드 결과물인 js 파일을 연결해 주고 나면, div
태그처럼 사용할 수 있게 된다.
현재 Next JS
에서 Web Component
를 사용하는 부분의 코드는 다음과 같다.
'use client';
import { FC } from 'react';
import { useWebComponent } from '../../hook';
interface IHamburgerMenuProps {
isHamburgerMenuOpen: boolean;
isLoggedIn: boolean;
userName: string;
}
/**
* @prop {boolean} isHamburgerMenuOpen 햄버거 메뉴 열림 닫힘 여부
* @prop {boolean} isLoggedIn 로그인 여부
* @prop {string} userName 유저 이름
*/
export const HamburgerMenu: FC<IHamburgerMenuProps> = ({
isHamburgerMenuOpen,
isLoggedIn,
userName,
}) => {
const { isLoaded } = useWebComponent('hamburger-menu-widget');
if (!isLoaded) {
return <></>;
}
return (
// div 태그 처럼 custom-element 사용
<hamburger-menu-widget
env={process.env.NEXT_PUBLIC_APP_ENV}
open={isHamburgerMenuOpen}
login={isLoggedIn}
name={userName}
/>
);
};
export default HamburgerMenu;
다음 포스트에서는 실제 Preact
를 통해 custom-element
를 만들고, 사용처(다른 저장소들)와의 상태를 동기화하면서 겪었던 문제점들에 대해서 작성할 예정이다.