Technology Stack¶
Umbra 의 기술 스택은 "학습 곡선을 낮추면서도 결제 SaaS 의 안정성을 타협하지 않는 선택" 이라는 기준으로 구성되었습니다. 이 문서는 각 기술의 역할과 선택 이유, 그리고 거부된 대안을 정리합니다.
Why this stack¶
Umbra 의 스택 선택을 관통하는 네 가지 기준입니다.
첫째, 페어 개발 가능성 입니다. Umbra 는 Pablo 의 설계와 진욱의 구현이 결합된 프로젝트입니다. 진욱이 빠르게 적응할 수 있는 언어와 생태계여야 하며, 이것이 Rust 기반 luxtra-bot 을 폐기하고 Go 로 전환한 결정적 이유입니다.
둘째, 결제 SaaS 의 안정성 입니다. 학습 곡선을 낮춘다고 장난감 수준의 도구를 쓸 수는 없습니다. 결제, 권한, 복구 같은 비즈니스 크리티컬 영역에는 검증된 라이브러리와 패턴을 사용합니다.
셋째, 언어 무관 도메인 모델 입니다. Umbra 는 진욱의 Rust 숙련도가 쌓인 후 Nabi 런타임 위에서 Rust 로 재작성될 예정입니다. 따라서 도메인 모델은 Go 특화 패턴(Go channel, errgroup)에 종속되지 않게 설계하며, 이는 어댑터 레이어에만 허용됩니다.
넷째, 한국 시장 적합성 입니다. 결제는 Toss Payments, 인프라는 한국 리전을 지원하는 Fly.io / Neon / Vercel 을 선택합니다.
Language & Runtime¶
| Layer | Choice | Version |
|---|---|---|
| Backend language | Go | 1.23 |
| Frontend language | TypeScript | 5.x |
| Frontend runtime | Bun | latest |
| Discord library | disgo | v0.19+ (Components V2) |
Go 는 concurrency 모델, 표준 라이브러리 완성도, 빠른 컴파일, 낮은 학습 곡선을 갖추었습니다. Rust 대비 추상화 비용을 포기하는 대신 페어 개발 속도를 얻습니다. 상세는 adr/0001-language-go.md 를 참조하세요.
Bun 은 Node 대비 빠른 설치와 실행으로 프론트엔드 DX 를 개선합니다. 패키지 매니저, 번들러, 테스트 러너 역할을 통합합니다.
disgo 는 Go 의 Discord 라이브러리 중 유일하게 Components V2 를 정식 지원합니다. discordgo 는 유지보수가 정체되어 있어 거부했습니다. 상세는 adr/0002-discord-library-disgo.md 를 참조하세요.
Web Framework¶
| Layer | Choice |
|---|---|
| HTTP framework | Echo v4 |
| Frontend framework | React 19 |
| Routing | TanStack Router (code-based) |
| UI components | shadcn/ui + Tailwind CSS |
| Icon | Lucide React |
Echo 는 CSRF, Rate Limit, Security Headers 같은 보안 미들웨어를 내장하여 결제 SaaS 의 안전 기본값을 빠르게 확보합니다. Gin 은 CSRF 공식 미들웨어 부재와 컨벤션 차이로 거부, chi 는 필요한 미들웨어 조합 부담으로 거부. 상세는 adr/0003-http-framework-echo.md 를 참조하세요.
React 19 는 생태계 성숙도와 shadcn/ui 통합을 고려한 선택입니다. 프로젝트 특성상 SSR 이 불필요하므로 Next.js 대신 Vite + React SPA 로 단순화합니다.
TanStack Router 는 타입 안전성이 가장 강력한 React 라우팅 라이브러리입니다. 코드 기반 모드로 사용하여 파일 기반 자동 생성 파일의 부담을 제거합니다. 상세는 adr/0028-tanstack-router-query.md 를 참조하세요.
shadcn/ui 는 Radix UI 기반의 접근성과 Tailwind 기반의 디자인 토큰 적용성을 함께 얻습니다. npm 패키지가 아니라 프로젝트에 직접 복사되는 방식이라 커스터마이징 자유도가 높습니다.
Data¶
| Layer | Choice |
|---|---|
| Database | Neon Serverless PostgreSQL |
| DB driver | pgx/v5 + pgxpool |
| DB layer | sqlc |
| Migration | Atlas |
| Cache / Session / Queue backend | Redis |
| PK type | UUID v7 |
Neon 은 serverless PostgreSQL 로 branching 기능을 제공합니다. PR 별 DB 브랜치 생성 → 마이그레이션 검증 → PR merge 시 main 브랜치 적용 흐름이 자연스럽습니다. 상세는 adr/0004-database-neon-postgres.md 를 참조하세요.
pgx 는 Go 의 PostgreSQL 드라이버 중 가장 빠르고 기능이 완전합니다. database/sql 인터페이스도 제공하지만 Umbra 는 네이티브 pgx 인터페이스를 사용합니다.
sqlc 는 SQL 에서 타입 안전한 Go 코드를 생성합니다. 쿼리 가시성 100%, ORM 의 비효율적 쿼리 생성 없음, Atlas 와의 책임 분리 명확성을 갖춥니다. GORM 은 결제 도메인의 트랜잭션 제어 위험으로 거부했습니다. 상세는 adr/0005-db-layer-sqlc.md 를 참조하세요.
Atlas 는 선언형 스키마 관리 도구입니다. db/schema/ 를 source of truth 로 두고 마이그레이션을 자동 생성합니다. golang-migrate 대비 schema drift detection 이 표준으로 내장됩니다. 상세는 adr/0006-migration-atlas.md 를 참조하세요.
Redis 는 세션, asynq 백엔드, idempotency 체크 세 용도로 사용됩니다. 상세는 adr/0007-cache-queue-redis.md 를 참조하세요.
PK 는 UUID v7 (RFC 9562) 을 채택합니다. 시간 정렬 가능성, PostgreSQL uuid 타입 네이티브 지원, 국제 표준 준수가 이유입니다. 상세는 adr/0021-pk-uuid-v7.md 를 참조하세요.
Workflow & Background Jobs¶
| Layer | Choice |
|---|---|
| Workflow engine | Temporal (Recovery 전용) |
| Job queue | asynq (Redis 기반) |
| Outbox poller | 자체 구현, worker 프로세스 내 고루틴 |
Temporal 은 Recovery 와 AntiNuke 워크플로우에만 사용됩니다. 장기 실행, 장애 복원, 시그널 기반 조정이 필요한 영역입니다. 상세는 adr/0014-recovery-temporal-workflow.md 를 참조하세요.
asynq 는 Redis 기반 Go 작업 큐로 짧은 비동기 작업(결제 cron, 재시도, Live Sync 배치, 알림)을 담당합니다. River 는 PostgreSQL 기반이라 Redis 를 이미 쓰는 환경에 불필요한 중복 인프라가 됩니다. 상세는 adr/0015-asynq-vs-temporal-split.md 를 참조하세요.
Outbox poller 는 PostgreSQL events.outbox 테이블을 2초 주기로 폴링하여 도메인 이벤트를 구독자에 전달합니다. platform/event/ 에 구현됩니다. 상세는 adr/0016-outbox-pattern.md 를 참조하세요.
External Services¶
| Layer | Choice |
|---|---|
| Payment | Toss Payments (Billing API) |
| Discord | Discord REST + Gateway API |
| 미정 (Phase 2) |
Toss Payments 는 한국 시장 표준이며 Billing API (정기결제 빌링키 기반) 를 제공합니다. 공식 Go SDK 가 없어 자체 HTTP 클라이언트를 engine/billing/adapter/toss/ 에 구현합니다. 빌링키는 AES-256-GCM 으로 암호화되어 DB 에 저장됩니다. 상세는 adr/0013-payment-toss-billing.md 를 참조하세요.
Observability¶
| Layer | Choice |
|---|---|
| Logging | slog (Go 표준) |
| Tracing | OpenTelemetry |
| Metrics | OpenTelemetry |
| Backend | Grafana Cloud |
slog 는 Go 1.21+ 표준 구조화 로그 라이브러리입니다. 외부 의존성 없이 JSON 구조 로그를 제공합니다.
OpenTelemetry 는 trace / metric / log 를 통합하는 벤더 중립 표준입니다. Grafana Cloud 는 무료 tier 에서 세 종류 데이터를 모두 수용하며 Fly.io 와 통합이 용이합니다.
Development Tooling¶
| Layer | Choice |
|---|---|
| Go linter | golangci-lint |
| Go formatter | gofumpt |
| Frontend linter + formatter | Biome |
| Dev reload | air (Go), vite dev (Frontend) |
| Tool version management | mise |
| Task orchestrator | just (justfile) |
| OpenAPI spec generation | swag |
| OpenAPI TS types | openapi-typescript |
| Config management | envconfig |
모든 개발·DB·코드 생성·린트·테스트 태스크는 루트 justfile 로 오케스트레이션합니다. make 는 사용하지 않습니다 — just 가 더 단순한 문법과 cross-platform 호환성을 제공합니다. package.json 은 Bun workspace 선언 전용이며 orchestration scripts 는 넣지 않습니다.
API 타입 동기화는 swag 로 Echo 핸들러에서 OpenAPI spec 을 생성한 뒤 openapi-typescript 로 프론트엔드 TS 타입을 자동 생성하는 흐름입니다. 자동 생성 파일은 commit 되어 CI 에서 drift detection 에 사용됩니다. 상세는 adr/0030-openapi-type-sync.md 를 참조하세요.
State Management (Frontend)¶
| Layer | Choice |
|---|---|
| Server state | TanStack Query |
| Client state | Zustand |
| Form | React Hook Form + Zod |
| HTTP client | Native fetch + custom wrapper |
TanStack Query 는 TanStack Router 와 같은 팀이 개발하여 통합이 자연스럽습니다. Server state 는 전적으로 이곳에서 관리하며 Zustand 에 넣지 않습니다.
Zustand 는 전역 UI 상태만 관리합니다. Provider 없이 hook 으로 접근 가능하며 Redux 대비 보일러플레이트가 적습니다.
React Hook Form + Zod 는 표준 조합으로, shadcn/ui Form 컴포넌트가 이 조합을 1급 지원합니다.
HTTP 클라이언트로 axios 는 사용하지 않습니다. fetch + 30줄 래퍼로 충분합니다. TanStack Query 가 retry, caching, error handling 을 모두 처리합니다.
Deployment¶
| Layer | Choice |
|---|---|
| Backend hosting | Fly.io |
| Frontend hosting | Vercel |
| DNS | Cloudflare |
| CI/CD | GitHub Actions |
| Container | Distroless multi-stage Docker |
Fly.io 는 Discord Gateway 같은 long-running 프로세스를 지원하며 asia-northeast1 리전을 제공합니다. Cloud Run 은 stateless 전용이라 Bot 프로세스에 부적합합니다. 상세는 adr/0010-deploy-fly-vercel.md 를 참조하세요.
Vercel 은 SPA 정적 호스팅과 PR preview 에 최적화되어 있습니다.
Monorepo¶
| Layer | Choice |
|---|---|
| Package manager | Bun workspace |
| Go module | 단일 go.mod (github.com/luxtradev/Umbra) |
| Repo structure | apps/*, engine/*, platform/*, db/, docs/ |
Bun workspace 와 단일 Go module 로 구성된 단일 레포 구조입니다. Bun 은 apps/web/ 의 TypeScript 영역을 관리하고, 단일 go.mod 가 Go 프로세스(bot/api/worker)와 도메인 패키지(engine/*, platform/*)를 모두 포함합니다. go.work 는 도입하지 않습니다. 상세는 adr/0009-monorepo-bun-workspace.md 를 참조하세요.
Summary table¶
전체 스택을 한눈에 보기 위한 요약입니다.
| Category | Tools |
|---|---|
| Language | Go 1.23, TypeScript 5.x |
| Runtime | Bun (frontend) |
| Discord | disgo |
| Web | Echo, React 19, TanStack Router, TanStack Query, Zustand, RHF + Zod, shadcn/ui, Tailwind, Lucide |
| Data | Neon PostgreSQL, pgx/v5, sqlc, Atlas, Redis, UUID v7 |
| Workflow | Temporal (Recovery only), asynq, Outbox |
| External | Toss Payments, Discord API |
| Observability | slog, OpenTelemetry, Grafana Cloud |
| Dev tools | golangci-lint, gofumpt, air, mise, swag, openapi-typescript, envconfig, Biome, just |
| Deploy | Fly.io, Vercel, Cloudflare, GitHub Actions, Distroless Docker |
| Monorepo | Bun workspace + 단일 go.mod |
Constraints¶
이 스택이 전제하는 조건입니다.
- 결제 SaaS 규모의 트래픽 — 초대형 트래픽(예: 수백만 TPS)에는 별도 아키텍처 필요
- 한국 리전 중심 — 글로벌 분산 필요 시 Fly.io 멀티 리전 + Neon read replica 추가 검토
- 소규모 팀 — Pablo + 진욱 + 향후 소수 합류. 10인 이상 팀은 조직 구조에 맞게 재검토
- Discord 전용 — Slack, Matrix 등 다른 플랫폼 추가 시 Bot 레이어 재설계
See also¶
architecture/overview.md— 시스템 아키텍처 전체architecture/deployment.md— 배포 토폴로지 상세adr/— 각 기술 선택의 상세 결정 기록guides/backend-conventions.md— Go 코딩 컨벤션guides/frontend-conventions.md— React 코딩 컨벤션