가장 먼저 시도한 방법은 React의 기본적인 props를 사용하는 것이었습니다.
function App() {
const [selectedAccount, setSelectedAccount] = useState(null);
const handleAccountSelect = (account) => {
setSelectedAccount(account);
};
return (
<div>
<BankApp onAccountSelect={handleAccountSelect} />
<GoalsApp selectedAccount={selectedAccount} />
</div>
);
}
이 방식은 간단했지만, 몇 가지 문제점이 있었습니다:
다음으로는 브라우저의 로컬 스토리지를 사용해 앱 간 데이터를 공유했습니다.
// Bank 애플리케이션에서 데이터 저장
const selectAccount = (account) => {
localStorage.setItem('selectedAccount', JSON.stringify(account));
// 변경 이벤트 발생
window.dispatchEvent(new Event('storage'));
};
// Goals 애플리케이션에서 데이터 사용
useEffect(() => {
const handleStorageChange = () => {
const accountData = JSON.parse(localStorage.getItem('selectedAccount'));
setAccountData(accountData);
};
// 초기 로드
handleStorageChange();
// 변경 이벤트 리스너
window.addEventListener('storage', handleStorageChange);
return () => {
window.removeEventListener('storage', handleStorageChange);
};
}, []);
이 방식으로 props drilling 문제는 해결했지만, 새로운 문제들이 생겼습니다:
마지막으로 도입한 방식은 중앙집중식 이벤트 관리 시스템인 MessageEventBus입니다.
// Bank 서비스에서 이벤트 발행
const { publish } = useMessageEventBus();
const handleAccountSelect = (account) => {
publish("ACCOUNT_SELECTED", account);
};
// Goals 서비스에서 이벤트 구독
const { useSubscription } = useMessageEventBus();
const [accountData, setAccountData] = useState(null);
useSubscription("ACCOUNT_SELECTED", (data) => {
setAccountData(data);
});
이 방식은 이전 접근법들의 문제점을 해결하고 여러 장점을 제공했습니다:
MessageEventBus를 사용하면서 중요한 개선점 중 하나는 "마지막 이벤트 기억" 기능이었습니다. 이 기능을 도입한 이유는 다음과 같았습니다:
public subscribe<T = any>(
eventType: string,
callback: EventCallbak<EventMessage<T>>
): () => void {
if (!this.subscribers.has(eventType)) {
this.subscribers.set(eventType, new Set());
}
this.subscribers.get(eventType)!.add(callback);
// 구독 시 마지막 이벤트가 있으면 즉시 발행
const lastEvent = this.lastEvents.get(eventType);
if (lastEvent) {
setTimeout(() => callback(lastEvent), 0);
}
return () => this.unsubscribe(eventType, callback);
}
public unsubscribe(eventType: string, callback: EventCallbak): void {
if (this.subscribers.has(eventType)) {
this.subscribers.get(eventType)!.delete(callback);
}
}
public publish<T = any>(eventType: string, payload: T): void {
const message: EventMessage<T> = {
type: eventType,
payload,
timestamp: Date.now(),
};
// 마지막 이벤트 저장
this.lastEvents.set(eventType, message);
if (this.subscribers.has(eventType)) {
this.subscribers.get(eventType)!.forEach((callback) => {
callback(message);
});
}
}
// 마지막 이벤트 조회 메서드
public getLastEvent<T = any>(eventType: string): EventMessage<T> | undefined {
return this.lastEvents.get(eventType) as EventMessage<T> | undefined;
}
이 기능 덕분에:
마이크로프론트엔드에서 효율적인 앱 간 통신은 전체 시스템의 성능과 유지보수성에 큰 영향을 미칩니다. 저희는 기본적인 props 전달에서 시작해 로컬 스토리지를 거쳐 최종적으로 MessageEventBus 패턴을 도입함으로써, 성능, 보안, 그리고 개발 경험을 크게 향상시켰습니다.
특히 마지막 이벤트 기억 기능의 도입으로 페이지 이동 시 데이터가 사라지는 문제를 해결하여 사용자 경험을 크게 개선했습니다. 이 접근법은 현재 저희의 마이크로프론트엔드 아키텍처에서 핵심적인 역할을 하고 있으며, 새로운 기능과 서비스를 추가할 때도 일관된 통신 패턴을 제공하고 있습니다.