ADR-006: Tech Stack (기술 스택 선택)
Status: Accepted | Date: 2026-02-10
맥락
Fascia는 분리된 아키텍처를 가지고 있습니다: 설계 시 작업을 위한 **Control Plane (컨트롤 플레인, Fascia 호스팅)**과 런타임 실행을 위한 Data Plane (데이터 플레인, 고객 GCP 프로젝트). 기술 스택은 서로 다른 요구사항을 가진 두 환경을 지원해야 합니다:
- Control Plane -- JSON Schema 중심의 스펙 관리, Safety Agent를 위한 LLM API 통합, 배포 오케스트 레이션, 시각적 플로우 빌더 UI.
- Data Plane (Executor) -- 고객의 GCP 프로젝트에서 Cloud Run으로 실행. 콜드 스타트 시간이 고객 프로덕트 최종 사용자의 모든 API 호출에 직접 영향을 미치므로 매우 중요합니다.
- Frontend -- Flow Studio에서 플로우 그래프를 구축하기 위한 성숙한 시각적 그래프/캔버스 라이브러리가 필요합니다.
결정
| 레이어 | 선택 | 목표 버전 |
|---|---|---|
| Frontend | React + TypeScript + ReactFlow (xyflow) | React 19+, ReactFlow 12+ |
| Platform Backend | Node.js + TypeScript + Fastify | Node 22 LTS, Fastify 5+ |
| Executor (고객 Cloud Run) | Go | Go 1.23+ |
| Platform Database | PostgreSQL | Postgres 16+ |
| LLM 전략 | 멀티 모델 크로스체크 (Claude + GPT-4 + Gemini 폴백) | 프로바이더 추상화 레이어 |
Fastify를 선택한 이유
Fastify는 NestJS와 Hono보다 주로 Ajv를 통한 네이티브 JSON Schema 검증 때문에 선택되었습니다. Fascia의 전체 도메인 모델은 JSON Schema로 정의됩니다 -- Entity 스펙, Tool 입출력 스키마, Policy 조건 모두 JSON Schema 문서입니다. Fastify의 라우트 수준 스키마 선언을 통해 도메인 정의에서 API 검증, 자동 생성 Swagger 문서까지 스펙이 자연스럽게 흐릅니다.
- NestJS -- 스펙 관리 플랫폼에 비해 과도한 보일러플레이트 (의존성 주입, 데코레이터, 모듈). class-validator 접근은 Fascia의 도메인에서 JSON Schema보다 자연스럽지 않습니다.
- Hono -- 더 빠른 벤치마크와 깔끔한 API를 제공하지만, JSON Schema가 아닌 Zod 검증기를 사용합니다. 멀티 런타임 이점 (Bun, Deno, Cloudflare Workers)은 Control Plane이 Node.js로 GCP Cloud Run에서 실행되므로 무관합니다.
Go를 Executor에 선택한 이유
Executor는 경계가 명확하고 변경이 드문 컴포넌트입니다. 플로우 그래프 엔진과 노드 핸들러가 구현되면 변경이 빈번하지 않습니다. Go의 콜드 스타트 이점 (~50ms vs Node.js의 ~300-500ms)은 고객 프로덕트 최종 사용자의 모든 API 호출에 직접 영향을 미칩니다.
Go는 Cloud Run에서의 컨테이너 배포에 이상적인 작고 정적 링크된 바이너리를 생성하며, 내장 동시성 프리미티브 (goroutine, channel)는 플로우 그래프 내 병렬 노드 실행에 적합합니다.
React + ReactFlow를 선택한 이유
ReactFlow (xyflow)는 브라우저에서 시각적 그래프 에디터를 구축하기 위한 사실상의 표준입니다. 사용자가 노드를 연결하여 Tool 실행 로직을 설계하는 Flow Studio는 핵심 프로덕트 표면입니다. ReactFlow는 팬, 줌, 노드 드래그, 엣지 라우팅, 미니맵 기능을 프로덕션 수준으로 제공하여 처음부터 구축하면 몇 달이 걸릴 개발 시간을 절약합니다.
검토한 대안
Frontend
| 옵션 | 장점 | 단점 |
|---|---|---|
| React + TypeScript | 가장 큰 에코시스템, ReactFlow가 플로우 빌더의 표준 | 번들 크기, 에코시스템 복잡성 |
| SvelteKit | 더 작은 번들, 더 단순한 상태 관리 | 그래프/플로우 라이브러리 부족; Flow Studio가 크리티컬 패스 |
| Vue + TypeScript | 단순성과 기능의 좋은 균형 | React 에코시스템보다 적은 플로우 에디터 옵션 |
Platform Backend
| 옵션 | 장점 | 단점 |
|---|---|---|
| Fastify (Node.js + TS) | Ajv 네이티브 JSON Schema, 빠름, 프론트엔드와 타입 공유 | 싱글 스레드 (클러스터링으로 완화 가능) |
| Hono (Node.js + TS) | 초경량, 현대적 API | Zod 기반 검증, 작은 플러그인 에코시스템 |
| NestJS (Node.js + TS) | 견고한 구조 (DI, 모듈) | 과도한 보일러플레이트, class-validator가 JSON Schema와 덜 정렬 |
| Go | 높은 성능, 좋은 동시성 | 프론트엔드와 타입 공유 불가, JSON 중심 스펙 작업에 덜 인간공학적 |
| Python (FastAPI) | 좋은 LLM 라이브러리 에코시스템 | 프론트엔드와 다른 언어, GIL 한계, LLM 호출은 어차피 HTTP |
Executor
| 옵션 | 장점 | 단점 |
|---|---|---|
| Go | ~50ms 콜드 스타트, 작은 바이너리, 우수한 동시성, 낮은 메모리 | 이중 언어 코드베이스 |
| Node.js + TypeScript | 플랫폼과 동일한 언어, 타입 공유 | ~300-500ms 콜드 스타트, 높은 메모리 사용량 |
| Rust | 가장 빠른 콜드 스타트, 가장 작은 바이너리 | 가파른 학습 곡선, 느린 개발 속도 |
Database
| 옵션 | 장점 | 단점 |
|---|---|---|
| PostgreSQL | 스펙 저장을 위한 JSONB, 풍부한 쿼리 기능, 메타데이터를 위한 관계형 무결성 | 관리 필요 (Cloud SQL로 완화) |
| MongoDB | 유연한 스키마 | 약한 트랜잭션 지원, 스펙은 관계형 모델이 유리한 알려진 구조 |
결과
긍정적
- JSON Schema 정합성 -- 스키마가 스펙 정의에서 Fastify 라우트 검증, 자동 생성 Swagger 문서까지 매끄럽게 흐릅니다. 별도의 변환 레이어가 필요하지 않습니다.
- TypeScript 타입 공유 -- 프론트엔드와 플랫폼 백엔드가 스펙 인터페이스에 대한 TypeScript 타입 정의를 공유하여, Control Plane의 단일 진실의 원천을 형성합니다.
- 100ms 미만 콜드 스타트 -- Go Executor가 Cloud Run에서 빠른 콜드 스타트를 제공하여 고객 프로덕트의 우수한 최종 사용자 경험을 보장합니다.
- 프로덕션 수준 플로우 캔버스 -- ReactFlow가 Flow Studio를 위한 성숙한 시각적 에디터를 제공하여 몇 달의 커스텀 개발을 절약합니다.
- 효율적인 스펙 저장 -- PostgreSQL JSONB가 워크스페이스 및 버전 메타데이터에 대한 관계형 무결성을 유지하면서 스펙 문서를 효율적으로 저장합니다.
부정적
- 이중 언어 코드베이스 -- Control Plane의 TypeScript와 Executor의 Go는 양쪽 언어에 대한 숙련도를 요구합니다. 개발자 온보딩 시간이 길어집니다.
- 타입 동기화 -- Go Executor는 TypeScript 타입 정의를 임포트할 수 없습니다. 스펙 타입은 JSON Schema를 공유 계약으로 사용하여 동기화하며, 수동 중복을 줄이기 위해 코드 생성을 활용합니다.
- 작은 커뮤니티 -- Fastify는 Express나 NestJS보다 커뮤니티가 작습니다. 일부 패턴은 기성 플러그인 대신 커스텀 구현이 필요합니다.
리스크
- TypeScript와 Go 사이의 타입 드리프트 -- JSON Schema를 공유 계약으로 사용하고 동일한 스키마에서 Go 구조체를 생성하여 완화합니다.
- Go Executor 유지보수 부담 -- Executor의 범위를 최소한으로 유지하여 완화합니다. Executor는 컴파일된 스펙 번들만 실행하며, 비즈니스 로직은 Executor 코드가 아닌 스펙에 존재합니다.
- Fastify 플러그인 공백 -- 더 넓은 Node.js 에코시스템을 활용하여 완화합니다. 대부분의 Express 미들웨어는
@fastify/middie를 통해 호환됩니다.