
MFE가 서로 통신하거나 AI 에이전트를 실행하기 위해 백엔드가 필요하지 않은 이유
요약
Nirnam은 마이크로 프론트엔드(MFE)와 브라우저 워커 간의 통신을 위해 백엔드 없이 작동하는 브라우저 네이티브 메시지 버스 및 에이전트 프레임워크입니다. 기존의 복잡한 postMessage 방식이나 전역 변수 공유의 한계를 극복하고, 브라우저 내에서 컴포넌트 간의 원활한 통신과 AI 에이전트 실행을 지원합니다.
핵심 포인트
- 백엔드 없이 MFE 및 브라우저 워커 간 통신 가능
- 복잡한 postMessage 및 직렬화 보일러플레이트 제거
- 브라우저 네이티브 환경에서 AI 에이전트 실행 지원
- 확장 가능한 메시지 버스를 통한 워커 간 직접 통신 구조
Nirnam을 소개합니다 — 마이크로 프론트엔드(Micro-frontends, MFEs)를 위한 의존성 없는 메시지 버스(message bus)이자 브라우저 네이티브 에이전트 프레임워크입니다. 백엔드는 필요하지 않습니다 — MFE 간의 통신을 위해서도, AI 에이전트를 실행하기 위해서도 말이죠.
"인간의 정신은 연상 작용을 통해 작동합니다. 하나의 항목을 파악하면, 뇌세포가 운반하는 복잡한 경로의 망에 따라 사고의 연상에 의해 제안되는 다음 항목으로 즉시 넘어갑니다."
— Vannevar Bush, As We May Think (1945)
Bush는 지식을 저장하고 인간의 정신이 그러하듯 지식을 탐색할 수 있는 기계를 상상하며 이 문장을 썼습니다. 그는 그것을 Memex라고 불렀습니다. 그가 상상하지 못했던 것은 80년 후, 브라우저 탭 하나가 바로 그것을 구축하는 데 필요한 모든 것 — 즉, 서로 통신하고, 기억하며, 추론하는 연결된 컴포넌트의 웹 — 을 갖추게 될 것이라는 점입니다.
Nirnam은 하나의 솔루션에서 시작되어 부모님의 이니셜을 따서 이름이 지어졌으며, 동일한 오리진(origin) 내에서 MFE, 브라우저 워커(Browser Workers), 그리고 AI 에이전트를 위한 브라우저 네이티브 통신 계층 프레임워크로 발전했습니다.
섹션 1: 문제점
마이크로 프론트엔드 통신 난제
여러분의 이커머스 웹사이트가 무한히(그리고 그 너머로) 성장했다고 가정해 봅시다. 기술 팀이 커짐에 따라, 기능이 출시될 때마다 개발 사이클은 점점 더 엉망이 되었습니다. 그래서 여러분은 개발을 구획별로 관리하기로 결정했습니다. 프론트엔드에서는 모놀리스(monolith)를 마이크로 프론트엔드(micro-frontends)로 분할했습니다. 장바구니 MFE는 상품 MFE가 "장바구니 담기" 이벤트를 발생시킬 때를 알아야 합니다. 추천 MFE는 사용자 프로필 MFE에 선호도를 물어봐야 합니다. 그래서 여러분은 모두가 하는 방식을 따릅니다. window.__sharedBus 전역 변수를 연결하거나, postMessage를 사용하여 직렬화(serialization) 보일러플레이트(boilerplate) 코드를 작성하기 시작합니다. 6개월 후, 여러분은 처음에 시작했던 것과는 또 다른 혼란에 직면하게 됩니다.
워커크래프트(Workercraft)의 세계
여러분은 브라우저 게임을 만들고 있습니다. 물리 시뮬레이션 (Physics simulation)은 전용 워커 (Worker)에서 실행됩니다. 그렇지 않으면 충돌 감지 (Collision detection)가 메인 스레드 (Main thread)를 느리게 만들고 프레임 레이트 (Frame rate)가 급락하기 때문입니다. 적의 경로 탐색 (Pathfinding)도 같은 이유로 두 번째 워커에서 실행됩니다. 세 번째 워커는 게임 서버와의 WebSocket 연결을 처리합니다.
이제 AI 워커는 물리 워커가 감지한 충돌에 반응해야 합니다. 네트워크 워커는 들어오는 월드 상태 (World-state) 업데이트를 두 워커 모두에게 브로드캐스트 (Broadcast)해야 합니다. 메인 스레드는 렌더 루프 (Render loop)를 구동하기 위해 물리 워커로부터 위치 데이터가 필요합니다.
그래서 여러분은 postMessage를 연결하기 시작합니다. 통신이 필요한 워커 쌍마다 각각의 채널을 할당합니다. 메인 스레드는 중계기 (Relay) 역할을 하게 됩니다. 전용 워커끼리는 서로 직접 메시지를 보낼 수 없기 때문에, 메인 스레드가 물리 이벤트를 AI 워커로 전달합니다. 여러분은 직렬화 (Serialization) 보일러플레이트 (Boilerplate) 코드를 작성합니다. 메시지를 보내기 전에 어떤 워커가 초기화되었는지 추적합니다. 네 번째 워커인 오디오 (Audio)를 추가하면 모든 것을 다시 연결해야 합니다. 6개월 후, 여러분은 게임 코드보다 메시지 배관 (Message-plumbing) 코드가 더 많아지게 되며, 새로운 워커가 추가될 때마다 관리해야 할 영역이 배로 늘어납니다.
광기의 멀티 에이전트 (Multi-agents of Madness)
AI 에이전트 (AI agents)는 어디에나 있지만, 모든 프레임워크는 루프를 실행하는 Node.js 서버가 있다고 가정합니다. 만약 여러분의 앱이 로컬 퍼스트 (Local-first) SPA라면 어떨까요? 브라우저 확장 프로그램이라면요? 사용자의 데이터가 기기를 절대 떠나지 않는 마이크로 프론트엔드 (Micro-frontend) 쉘이라면 어떨까요? 설상가상으로, 이렇게 로컬 클라이언트에서 제어되는 에이전트들이 클라이언트 기기 내에서 서로 협업하기를 원한다면 어떻게 해야 할까요?
섹션 2: 오케스트라 — 3계층 버스 (3 Layered Bus)
Tab A Tab B Tab C
[MFE Shell] [Product MFE] [Cart MFE] [Agent Host]
| | | |
...
NirnamBus는 마이크로 프론트엔드 (Micro-frontends), 다중 에이전트 (Multiple agents), 그리고 브라우저 워커 (Browser workers) 사이의 통신 버스 (Communication bus) 역할을 합니다. 브라우저 네이티브 API (Browser native API)와 출처 제한 (Origin restrictions)의 특정 문제들을 해결하기 위해 세 개의 계층이 구축되었습니다.
- Layer 1 — BroadcastChannel: 브라우저 네이티브 API (browser native API)입니다. 메시지를 팬아웃 (fan out)하며, 별도의 설정 없이도 탭 간에 매우 잘 작동합니다! 하지만 한계가 있습니다. 오직 브로드캐스트 (broadcast) 용도로만 사용되며, 요청-응답 (request-reply)이나 라우팅 (routing)은 처리하지 못합니다.
- Layer 2 — SharedWorker (blob URL): 요청-응답 (request-reply)과 라우팅 (routing)을 처리하기 위한 다음 계층입니다. SharedWorker의 소스는 라이브러리에 번들링되어 있으며, 런타임에
URL.createObjectURL(new Blob([...]))를 통해 생성됩니다. 정적 파일, CDN, 빌드 설정 변경 없이도 SharedWorker의 의미론 (semantics)을 즉시 사용할 수 있습니다. 이를 통해 팀들은 내부 자산 파이프라인 (asset pipeline)을 제어하지 않고도 통신 라인을 구축할 수 있습니다. 하지만 앱이 마운트/언마운트될 때마다 동적인 blob URL이 변경되므로, 정적 URL 없이는 탭 간 통신 (cross-tab communication)이 불가능합니다. - Layer 3 — SharedWorker (static URL): 동일한 오리진 (origin) 내에서 진정한 탭 간 통신 (cross-tab communication)을 가능하게 하려면 정적 워커 URL (static worker URL)을 위한 빌드 플러그인 (build plugin)이 필요합니다. 한 줄의 코드로 선택적 빌드 플러그인을 구성할 수 있습니다. Vite, Webpack, RsBuild용 플러그인을 사용할 수 있습니다.
섹션 3: 전송 계층 (Transport Layer)
두 MFE 간의 Pub/Sub
Git: https://github.com/shaurcasm/nirnam/tree/main/Examples/react-mfe
// Bus
// host/src/App.tsx line 9
// 모듈 수준의 버스 (Module-level bus) — MFE당 하나씩 존재하며, 이 앱의 모든 컴포넌트에서 공유됩니다.
...
호스트 (host)와 리모트 (remote)라는 완전히 독립적인 두 개의 React 앱은 버스 (bus)와 토픽 이름 문자열 (topic name string) 외에는 아무것도 공유하지 않습니다. 유일한 계약 (contract)은 토픽 문자열과 페이로드 타입 (payload type)이며, MFE들은 서로 결합이 해제 (decoupled)되어 있습니다.
요청-응답 (Request-Reply)
Git: https://github.com/shaurcasm/nirnam/tree/main/Examples/transport
// 요청자 (Requester)
// src/components/HostPanel.tsx lines 27–39
const getTotal = useCallback(async () => {
...
[](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft5m5a95cfo3t2ht1l11y.gif)
페이로드(payload)를 위한 제네릭 타입 (generic types)이 엔드 투 엔드 (end-to-end)로 강제됩니다. 핸들러 (handler)는 요청 ID (request ID)나 상관관계 메커니즘 (correlation machinery)을 전혀 알 필요가 없으며, 그저 값만 반환합니다. 단순한 통신입니다. सरल सञ्चारः
### 스트리밍 (Streaming)
Git: [https://github.com/shaurcasm/nirnam/tree/main/Examples/transport](https://github.com/shaurcasm/nirnam/tree/main/Examples/transport)
// 스트림 요청자 (Stream requester)
// src/components/HostPanel.tsx lines 62–75
const streamPrices = useCallback(async () => {
...
[](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89w38kvgok6bn99h90t9.gif)
여기 장바구니 항목에 사용된 것과 동일한 AsyncIterable 스트림 패턴을 LLM 토큰 스트림 (token streams)에도 사용합니다. 이는 에이전트 (Agent) 프레임워크의 핵심적인 조각입니다.
## 섹션 4: 에이전트 프레임워크 (Agent Framework)
Nirnam은 NirnamBus를 기반 통신 계층으로 사용하는 브라우저 네이티브 에이전트 프레임워크 (browser-native agentic framework)를 갖추고 있습니다. 이를 통해 클라이언트 측에서 직접 에이전트를 구축하고 관리할 수 있으며, 클라이언트가 자신의 LLM을 구성하고 데이터를 자신의 쪽에 유지할 수 있도록 합니다. 이는 소비자가 자신의 데이터를 직접 제어할 수 있는 수많은 소비자 친화적인 애플리케이션의 가능성을 열어줍니다.
[](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwd9fpdoejwlktaafipfq.png)
`chat()`은 대화창 (conversation window)이고, `run()`은 백그라운드 작업 (background job)이며, `process()`는 웹훅 핸들러 (webhook handler)입니다.
### 기본 에이전트 (Basic Agent) (chat)
Git: [https://github.com/shaurcasm/nirnam/tree/main/Examples/agents](https://github.com/shaurcasm/nirnam/tree/main/Examples/agents)
// Agent + hooks
// src/components/ChatTab.tsx lines 52–62
const agent = useAgent({ llm, tools: TOOLS, autoCleanup: false }); // 에이전트 생성
...
[](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fubmvowp06qfmmnn07qkj.gif)
전체 에이전트 루프(agentic loop) — LLM 호출 → 도구 호출 결정 → 도구 실행 → 후속 LLM 호출 — 는 `send()` 내부에서 발생합니다. 컴포넌트는 메시지만을 처리합니다.
### 파일 시스템 접근이 가능한 에이전트 (Agent with Filesystem Access)
Git: [https://github.com/shaurcasm/nirnam/tree/main/Examples/agents](https://github.com/shaurcasm/nirnam/tree/main/Examples/agents)
// 에이전트 설정
const agent = useAgent(withPreset(presets.codeReview(), { llm, autoCleanup: false }));
/**
...
권한 부여 후 사용 가능한 도구들 — `read_file`, `list_directory`, `create_directory`, `write_file`, `delete_file`, `move_file` — 은 모두 부여된 폴더 루트에 한정되며, 경로 탐색(`..`)은 차단됩니다.
### 도구 인터셉터 (Tool Interceptors)
const agent = createAgent({
llm,
tools: TOOLS,
...
미들웨어 계층(Middleware layer)은 기업 팀을 위한 규정 준수 후크(compliance hook)입니다 — 비용 제한, 감사 로그(audit logs), LLM 호출 전 PII 제거 등이 포함됩니다.
### asTool() — 도구로서의 에이전트 (Agent-as-Tool)
const summariser = createAgent({ agentId: 'summariser', llm, systemPrompt: 'Summarise text concisely.' });
const coordinator = createAgent({
...
### 단위 테스트 및 영속성 (Unit Testing & Persistence)
테스트를 위해 Mock LLM이 제공되며 (`_isMock: true`) API 키 없이 단위 테스트가 가능합니다. 기록 관리(History management)는 `exportHistory()` / `importHistory()`를 통해 영속성 확보 및 세션 복원 기능을 수행합니다. `autoCleanup`은 에이전트가 `beforeunload` 시 스스로 파괴되어, 언마운트되는 MFE에서 메모리 누수(memory leaks)가 발생하지 않도록 합니다.
## 섹션 5: 크로스-탭 에이전트 (Cross-tab Agents)
Git: [https://github.com/shaurcasm/nirnam/tree/main/Examples/cross-tab-agent](https://github.com/shaurcasm/nirnam/tree/main/Examples/cross-tab-agent)
만약 하나의 AI 에이전트가 사용자가 열어둔 모든 탭에 걸쳐 전체 브라우저 세션 (browser session)을 지원하기를 원한다면 어떻게 될까요? `scope: 'page'` 설정을 통해 Nirnam은 이를 수행할 수 있습니다.
### 크로스-탭 에이전트 (Cross-tab Agent) — 호스트 측 (Host Side)
// src/App.tsx lines 269–291
const agent = createAgent({
agentId: AGENT_ID,
...
### 크로스-탭 에이전트 (Cross-tab Agent) — 클라이언트 측 (Client Side)
// src/App.tsx lines 375–423
// 마운트 시 호스트 에이전트에 실제로 도달 가능한지 확인
effect(() => {
...
[](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F53f4f9ej57reisdugi2w.gif)
[](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F36u0xjjj0v9sja0vq1m7.gif)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기