프로젝트 아키텍처
Multica의 클라이언트, Go 서비스, 실행 데몬, 공유 프런트엔드 패키지가 함께 작동하는 방식을 알아봅니다.
Multica는 Go backend, 여러 클라이언트, 실행 컴퓨터에서 동작하는 데몬으로 구성됩니다. PostgreSQL이 협업 데이터를 저장하고 데몬이 태스크를 가져가 로컬 AI 코딩 도구를 호출합니다.
Web / Desktop / Mobile / CLI
│
HTTP + WebSocket
│
Go API ───────── PostgreSQL
│
daemon WebSocket
│
로컬 데몬 ─── AI 코딩 도구제품 사용자 관점의 설명은 Multica의 작동 방식을 참고하세요. 이 페이지에서는 코드의 계층 구조를 설명합니다.
저장소 구조
| 디렉터리 | 역할 | 주요 기술 |
|---|---|---|
server/ | API, 인증, 태스크 스케줄링, 연동, CLI, 데몬 | Go, Chi, sqlc, gorilla/websocket |
apps/web/ | 브라우저 클라이언트와 landing page | Next.js App Router |
apps/desktop/ | 데스크톱 클라이언트와 로컬 프로세스 관리 | Electron, electron-vite |
apps/mobile/ | 독립적인 iOS 클라이언트 | Expo, React Native |
apps/docs/ | 다국어 문서 사이트 | Next.js, Fumadocs |
packages/core/ | API client, 유형, query, mutation, 플랫폼 독립 비즈니스 로직 | TanStack Query, Zustand |
packages/ui/ | 비즈니스 로직이 없는 기본 UI | shadcn, Base UI |
packages/views/ | Web과 Desktop이 공유하는 비즈니스 페이지 및 컴포넌트 | React |
packages/tsconfig/, packages/eslint-config/ | 공유 도구 설정 | TypeScript, ESLint |
공유 패키지는 .ts와 .tsx 소스 파일을 직접 export하며 사용하는 애플리케이션이 컴파일합니다. 의존 방향은 views → core + ui이고 core와 ui는 서로 의존하지 않습니다.
Web과 Desktop의 코드 공유
Web과 Desktop은 세 계층을 공유합니다.
packages/core가 API, cache, 권한, 플랫폼 독립 상태를 처리합니다.packages/ui가 기본 컴포넌트를 제공합니다.packages/views가 비즈니스 페이지를 구성합니다.
라우팅, cookie, Electron IPC 같은 플랫폼 기능은 애플리케이션 계층에 둡니다. 공유 페이지는 NavigationAdapter를 통해 이동하며 next/* 또는 react-router-dom을 직접 import하지 않습니다.
예를 들어 Web과 Desktop에 모두 필요한 이슈 기능은 일반적으로 다음 위치에 걸쳐 있습니다.
packages/core/issues/ query, mutation, cache 업데이트
packages/views/issues/ 페이지와 비즈니스 컴포넌트
apps/web/platform/ Next.js 라우팅 adapter
apps/desktop/.../platform/ Electron 라우팅 adapterMobile은 이 React 페이지를 재사용하지 않습니다. @multica/core의 유형과 순수 함수를 import할 수 있지만 UI, query key, 상태, 실시간 구독, 출시 절차는 별도로 소유합니다.
프런트엔드 상태
서버 데이터와 클라이언트 상태를 분리해 관리합니다.
- TanStack Query는 이슈, 에이전트, 멤버, 인박스 같은 서버 데이터를 보관합니다.
- Zustand는 필터, 초안, 팝업, 레이아웃 같은 클라이언트 상태를 보관합니다.
- 현재 워크스페이스는 route가 결정하며 요청, 영속화 namespace, 재연결에 필요한 범위만 플랫폼 계층에 반영합니다.
- React Context는 워크스페이스 ID와 navigation adapter 같은 플랫폼 plumbing만 전달합니다.
WebSocket 이벤트는 TanStack Query cache를 업데이트하거나 무효화해야 합니다. 서버 객체를 Zustand에 복사하면 안 됩니다. 워크스페이스 생성, 삭제, 나가기처럼 화면 이동을 동반하는 mutation은 서버 확인을 기다린 뒤 로컬 상태를 정리해야 합니다.
API 응답은 packages/core/api/ 경계에서 zod schema로 parsing합니다. 설치된 Desktop이 더 새로운 backend에 연결될 수 있으므로 network JSON을 TypeScript 유형으로 바로 강제 변환하면 안 됩니다.
Backend 계층
주요 entry point는 server/cmd/에 있습니다.
| entry point | 역할 |
|---|---|
server | HTTP API, WebSocket, scheduler, 연동 worker 시작 |
multica | CLI와 로컬 데몬 |
migrate | 데이터베이스 migration 실행 |
backfill_* | 특정 버전의 데이터 backfill 도구 |
요청은 일반적으로 다음 방향으로 흐릅니다.
router → middleware → handler → service → sqlc query → PostgreSQLinternal/middleware/는 인증, 워크스페이스, 요청 경계를 처리합니다.internal/handler/는 HTTP 입력을 해석하고 응답을 생성합니다.internal/service/는 여러 query에 걸친 비즈니스 흐름과 transaction을 담당합니다.pkg/db/queries/에는 직접 작성한 SQL이 있습니다.pkg/db/generated/는 sqlc가 생성하므로 직접 수정하면 안 됩니다.internal/integrations/는 GitHub, Slack, Feishu 같은 외부 이벤트를 처리합니다.internal/storage/는 로컬 또는 S3 첨부 파일을 처리합니다.
PostgreSQL은 비즈니스 데이터의 신뢰할 수 있는 원본입니다. Redis는 선택 컴포넌트이며 여러 인스턴스 간 실시간 이벤트, cache, 임시 조율에 사용합니다. 설정하지 않으면 단일 인스턴스 개발 환경이 프로세스 내부 구현을 사용합니다.
실시간 연결
Multica에는 서로 다른 두 WebSocket 경로가 있습니다.
internal/realtime/은 이슈, 댓글, 인박스 등의 변경을 사용자 클라이언트로 전달합니다.internal/daemonws/는 데몬을 연결해 런타임을 깨우고 daemon RPC를 실행합니다.
WebSocket은 지연을 줄이지만 최종 상태는 데이터베이스에 있습니다. 클라이언트는 재연결 후 query로 다시 동기화해야 합니다. 데몬도 polling 경로를 유지해 한 번의 연결 끊김으로 queued 태스크가 영구히 멈추지 않게 합니다.
한 번의 실행 코드 경로
- 사용자가 이슈를 할당하거나 에이전트를 멘션하거나 자동화가 트리거됩니다.
TaskService가 queued 태스크를 만들고 해당 런타임에 알립니다.- 데몬이 daemon API를 통해 태스크를 가져갑니다.
- 서버가 태스크와 에이전트에 연결된 임시 자격 증명을 발급합니다.
- 데몬이 로컬 디렉터리를 준비하고
pkg/agent에서 해당 provider backend를 호출합니다. - 도구가 로컬에서 실행되고 데몬이 진행 상황, 메시지, 최종 상태를 업로드합니다.
- 서버가 태스크와 이슈를 업데이트하고 실시간 이벤트로 클라이언트를 새로 고칩니다.
provider adapter 계층은 AI 코딩 도구별 시작, streaming 이벤트, 취소, 사용량 데이터를 통일하지만 로컬 디렉터리와 세션은 계속 데몬이 관리합니다.
여러 워크스페이스의 경계
비즈니스 query는 반드시 workspace_id로 제한해야 하며 요청이 워크스페이스 route에 들어가기 전에 멤버 자격을 확인합니다. X-Workspace-ID는 현재 워크스페이스를 선택하지만 권한 검사를 대체하지 않습니다.
이슈 담당자는 polymorphic 관계이며 멤버, 에이전트, 스쿼드 중 하나를 가리킬 수 있습니다. 새 query, cache key, 실시간 이벤트를 추가할 때 리소스 ID만으로 전역에서 고유한 컨텍스트라고 가정하지 말고 워크스페이스와 담당자 유형을 유지해야 합니다.