1. 문제 정의: 바이브 코딩의 한계
"바이브 코딩의 한계를 넘어, 재사용 가능한 제품 빌더를 만들고 싶었습니다."
Claude Code와 대화하며 코드를 만드는 바이브 코딩을 하다 보면 몇 가지 벽에 부딪힙니다:
1.1 핵심 문제: 멱등성 없음
같은 프롬프트로 같은 요청을 해도 매번 다 른 코드가 생성됩니다.
❌ LLM 바이브 코딩의 멱등성 문제
─────────────────────────────────
Day 1: "피드백 CRUD 만들어줘" → FeedbackService.create()
Day 2: "피드백 CRUD 만들어줘" → FeedbackHandler.save() ← 다른 결과!
Day 3: "피드백 CRUD 만들어줘" → feedbackRepository.add() ← 또 다른 결과!
1.2 기타 문제들
문제
설명
컨텍스트 휘발
긴 대화 끝에 만든 코드의 "왜 이렇게 만들었는지"가 사라짐
일회성 결과물
같은 패턴의 앱을 또 만들려면 처음부터 다시 대화해야 함
플랫폼별 불일치
Backend, Web, Mobile을 각각 따로 만들면 로직/데이터 구조가 안 맞음
수동 수정의 덫
AI가 만든 코드를 직접 고치면, 재생성 시 덮어씌워짐
연동하기 귀찮음
Supabase, Firebase, Vercel 등 연동하기 귀찮음
1.3 해결책: DSL 기반 코드 생성
"DSL(Domain Specific Language)을 SSOT(Single Source of Truth)로 두고, 코드는 100% 자동 생성하는 시스템"을 구축했습니다.
✅ ITDALIFE의 멱등성 보장
─────────────────────────
같은 DSL → 항상 같은 코드 (100% 재현 가능)
Day 1: feedback.core.dsl → FeedbackService.create()
Day 2: feedback.core.dsl → FeedbackService.create() ← 항상 동일!
Day 3: feedback.core.dsl → FeedbackService.create() ← 항상 동일!
+ 3개 플랫폼 동시 생성 (Backend/Web/Mobile)
목표: PRD 문서 하나로 Backend(Spring Boot) + Web(Next.js) + Mobile(Flutter) 3개 플랫폼 동시 생성
2. 아키텍처 설계
2.1 3-Layer 구조: Templates + Modules + Generators
일반 바이브 코딩은 매번 boilerplate를 처음부터 생성합니 다. ITDALIFE는 사전 구축된 Templates와 Modules를 재사용합니다.
1️⃣ Templates (Boilerplate) - 새 제품 스캐폴딩 시 복사
디렉토리
내용
templates/backend/
Spring Boot 기본 구조, build.gradle, config
templates/frontend/
Next.js 기본 구조, package.json, i18n, layout
templates/mobile/
Flutter 기본 구조, pubspec.yaml, theme
2️⃣ Modules (재사용 라이브러리) - 생성된 코드에서 import하여 사용
Backend (27개 itda- 모듈)*
모듈
기능
itda-core
Base Entity, Response 래퍼
itda-security
JWT, OAuth2, 인증/인가, RLS
itda-graphql
GraphQL Federation, DataLoader
itda-r2dbc
R2DBC 설정, 트랜잭션
itda-files
S3/Local 파일 업로드
itda-cache
Redis 캐시, 분산 락
itda-events
이벤트 발행/구독, Outbox 패턴
itda-notification
이메일, SMS, 푸시 알림
itda-audit
감사 로그, 변경 이력
itda-i18n
다국어 지원
itda-batch
배치 처리, 스케줄링
itda-gateway
API Gateway, Rate Limit
기타
itda-multi-tenancy, itda-acl, itda-report 등
Frontend (17개 @itdalife/ NPM 패키지)*
패키지
기능
@itdalife/ui
shadcn 기반 공통 UI 컴포넌트
@itdalife/table
데이터 테이블 (정렬, 필터, 페이징)
@itdalife/crud-hooks
useList, useCreate, useUpdate, useDelete
@itdalife/form
React Hook Form 래퍼, 유효성 검증
@itdalife/auth
OAuth2, 세션 관리
@itdalife/files
파일 업로드/다운로드, 미리보기
@itdalife/charts
차트 컴포넌트 (Line, Bar, Pie)
@itdalife/detail-view
상세 페이지 레이아웃
기타
@itdalife/api-client, search-filter, enums 등
Mobile (18개 itdalife_ Flutter 패키지)*
패키지
기능
itdalife_ui
Material Wrapper 위젯
itdalife_form
폼 빌더, 유효성 검증
itdalife_auth
OAuth2, 생체인증, 세션
itdalife_graphql_crud_providers
Riverpod + GraphQL
itdalife_files
파일 업로드, 카메라/갤러리
itdalife_push
FCM 푸시 알림
itdalife_charts
차트 위젯
기타
itdalife_api, itdalife_list, itdalife_state 등
3️⃣ Generators (코드 생성기) - DSL → 코드 변환
생성기
기능
generators/code-generator/
Kotlin DSL 파서 + Backend 생성
generators/frontend-generator/
TypeScript Next.js 생성
generators/mobile-generator/
Dart Flutter 생성
2.2 DSL이란?
DSL (Domain Specific Language) = 특정 목적에 맞게 설계된 간결한 언어
일반 프로그래밍 언어(Java, Python)와 달리, "무엇을 만들 것인가"만 선언하면 됩니다.
Java로 Entity 작성 (50줄+)
@Entity
@Table(name = "feedbacks")
public class Feedback {
@Id @GeneratedValue
private UUID id;
@Column(nullable = false)
private String title;
@Enumerated(EnumType.STRING)
private FeedbackStatus status;
private String content;
// getter, setter, builder...
}
DSL로 선언 (10줄)
aggregate Feedback {
title : string @required
status : FeedbackStatus
content: text
}
↓ Generator가 자동 생성: Entity, DTO, Repository, Service, Controller, React 컴포넌트/훅, Flutter 화면/Provider
장점
설명
간결함
보일러플레이트 없이 핵심만 선언
가독성
비개발자도 이해 가능 (PM, 디자이너)
멱등성
같은 DSL → 항상 같은 결과
SSOT
모든 플랫폼의 단일 진실 원천
2.3 전체 워크플로우 비교
❌ 일반 바이브 코딩
컨텍스트 휘발 (왜 이렇게 만들었는지 기억 없음)
수동 수정이 재생성 시 사라짐
플랫폼별로 따로 대화해야 함
✅ ITDALIFE 제품 빌더
DSL이 SSOT - 모든 설계 의도가 코드로 남음
재생성해도 수동 수정 안 필요 (생성기를 고치면 됨)
한 번에 3개 플랫폼 생성
2.4 사용한 도구
도구
역할
Claude Code
PRD 작성, DSL 생성, 생성기 개발
Kotlin DSL Parser
DSL 파싱 → AST → IR 변환
JavaPoet
Backend Kotlin 코드 생성
ts-morph
Frontend TypeScript 코드 생성
code_builder (Dart)
Mobile Flutter 코드 생성
3. 개발 워크플로우 (SW 개발 순서)
Step 1: 요구사항 정의 (PRD 작성)
PRD는 "왜, 무엇을 만드는가"를 정의합니다.
PRD 생성 프롬프트
당신은 ITDALIFE 프로젝트의 PRD 전문가입니다.
PRD 작성 시 EARS 5개 패턴을 모두 정의해야 합니다:
| EARS 패턴 | PRD 섹션 | DSL 블록 |
|-----------|----------|----------|
| E (Event) | 사용자 스토리 US-NNN | usecase {} |
| S (State) | 워크플로우 (상태 전이) | stateMachine {} |
| U (Ubiquitous) | 인수 조건 (항상 만족) | invariants {} |
| O (Optional) | 비기능 요구사항 | capabilities {} |
| W (Unwanted) | 에러 시나리오 | api.errors {} |
명명 규칙:
- 제품명: kebab-case (feedback, hotel-booking)
- 역할: UPPER_SNAKE_CASE (USER, ADMIN)
- 엔티티: PascalCase 단수형 (Feedback, Category)
PRD 예시
# Feedback Manager PRD
## 1. 개요
- 목적: 사용자 피드백을 수집, 분류, 관리하고 답변을 제공
- 대상 사용자: 서비스 운영팀, 일반 사용자, 관리자
## 2. 사용자 스토리
**US-001**: 피드백 제출
> 사용자로서, 서비스에 대한 피드백을 제출하고 싶다.
**US-100**: 피드백 검토
> 관리자로서, 접수된 피드백을 검토하고 승인/거절하고 싶다.
## 3. 워크플로우
| 전이 ID | 전이 | 권한 |
|---------|------|------|
| S-001 | PENDING → APPROVED | ADMIN |
| S-002 | PENDING → REJECTED | ADMIN |
## 4. 에러 시나리오
| 에러 ID | 상황 | HTTP |
|---------|------|------|
| W-001 | 피드백 없음 | 404 |
| W-002 | 잘못된 상태 전이 | 400 |
Step 2: 도메인 설계 (Core DSL) - What!!
Core DSL은 플랫폼에 독립적인 도메인 명세입니다.
Core DSL 생성 프롬프트
당신은 ITDALIFE 프로젝트의 Core DSL 전문가입니다.
DSL 구조 (블록 순서 필수):
1. domain {} - 제품 메타데이터
2. enum {} - 열거형
3. aggregate {} - 엔티티 + stateMachine
4. usecase {} - 명령/쿼리
5. api {} - 엔드포인트 + 에러
6. security {} - 역할/권한
@ears() 어노테이션 필수 (PRD 추적성):
- stateMachine { @ears("S-001") transition ... }
- api.errors { @ears("W-001") ... }
Core DSL 예시
domain "feedback" {
description = "사용자 피드백 관리 서비스"
features {
oauth { enabled = true, providers = ["google"] }
audit = true
}
}
enum FeedbackStatus {
PENDING @label("대기") @color("yellow")
APPROVED @label("승인") @color("green")
REJECTED @label("거절") @color("red")
}
aggregate Feedback {
id : uuid @pk @auto
title : string @required @maxLength(200)
content : text @required
status : FeedbackStatus @default("PENDING")
stateMachine {
initial = PENDING
@ears("S-001")
transition PENDING -> APPROVED { action = "approve" roles = ["ADMIN"] }
@ears("S-002")
transition PENDING -> REJECTED { action = "reject" roles = ["ADMIN"] }
}
}
api {
errors {
@ears("W-001") FEEDBACK_NOT_FOUND { code = 404 }
@ears("W-002") INVALID_STATUS_TRANSITION { code = 400 }
}
}
Step 3: 플랫폼별 설계 (Extension DSL) - How!!
Extension DSL은 플랫폼별 구현 세부사항을 정의합니다.
Extension DSL
정의하는 내용
Backend DSL
itda-* 모듈, DB 인덱스, 캐시 정책
Frontend DSL
테이블 컬럼, 폼 그룹/필드, 색상/아이콘
Mobile DSL
화면 레이아웃, 앱바/FAB 설정, 상태 관리
Frontend Extension DSL 예시
extend "feedback.core.dsl"
frontend {
template = "shadcn"
port = 3001
}
extend aggregate Feedback {
ui {
menu { label = "피드백", icon = "message-square", order = 1 }
list {
display = ["title", "status", "category", "createdAt"]
columns {
status { render = "badge", width = "120px" }
}
}
create {
groups {
basic { label = "기본 정보", fields = ["title", "category"] }
content { label = "내용", fields = ["content"] }
}
}
colorMaps {
status { PENDING = "yellow", APPROVED = "green", REJECTED = "red" }
}
}
}
Step 4: 코드 생성 (자동화)
한 줄 명령으로 3개 플랫폼 코드 생성:
./scripts/itdalife-workflow.sh feedback
# 출력:
# [STEP] Building code-generator...
# [STEP] Validating DSL...
# [STEP] Generating backend... (24 files)
# [STEP] Generating frontend... (18 files)
# [STEP] Generating mobile... (15 files)
# [STEP] Verifying build...
# [INFO] ✅ All targets built successfully!
생성된 결과물
products/feedback/
├── backend/ # Spring Boot
│ └── src/main/kotlin/
│ ├── domain/FeedbackEntity.kt
│ ├── service/FeedbackService.kt
│ └── controller/FeedbackController.kt
│
├── frontend/ # Next.js + shadcn/ui
│ └── src/
│ ├── app/[locale]/(dashboard)/feedback/page.tsx
│ └── hooks/useFeedback.ts
│
└── mobile/ # Flutter + Riverpod
└── lib/features/feedback/
├── presentation/screens/feedback_list_screen.dart
└── providers/feedback_provider.dart
핵심 규칙: 생성된 코드 직접 수정 금지
❌ 잘못된 방법
products/feedback/backend/FeedbackService.kt 직접 수정
→ 다음 재생성 시 덮어씌워짐
✅ 올바른 방법
1. DSL 수정 → 재생성
2. 생성기 수정 → 재생성 → 모든 제품에 일관되게 적용
수동 수정 보존 (Manual Section Preservation)
"그래도 커스텀 로직을 추가해야 할 때가 있잖아요?" → @manual-start 마커로 보존 가능합니다.
예시: useFeedback.ts
// Code generated by ITDALIFE Code Generator. DO NOT EDIT.
// @generated-start crud-hooks
export function useFeedbackList() { ... }
export function useFeedbackCreate() { ... }
// @generated-end crud-hooks
// @manual-start custom-hooks ← 개발자가 추가한 영역
export function useFeedbackStatistics() {
// 커스텀 통계 로직
}
// @manual-end
재생성 시 동작:
영역
동작
@generated 영역
최신 DSL 기준으로 덮어쓰기
@manual 영역
그대로 보존 ✅
@generated 마커 없는 파일
덮어쓰기 안 함 (사용자 파일 보호)
Step 5: 스키마 관리 (Flyway)
DSL에서 Flyway 마이그레이션 파일도 자동 생성됩니다.
1️⃣ 최초 생성: DSL → Flyway Migration
2️⃣ 스키마 변경: DSL 수정 → 새 Migration 추가
DSL에 필드 추가 시:
aggregate {
title: string
status: enum
+ priority: int # ← 필드 추가
}
생성되는 마이그레이션:
파일
상태
V1_20260204_001__create_feedbacks.sql
기존 - 수정 안 함
V1_20260205_001__alter_feedbacks.sql
새로 추가 ✨
-- V1_20260205_001__alter_feedbacks.sql
ALTER TABLE feedbacks ADD COLUMN priority INTEGER;
🔒 규칙: 적용된 마이그레이션 수정 금지! 기존 파일 건드리지 않고, 새 버전 파일만 추가
자동 DB 반영: Backend 서버 기동 시
Flyway 마이그레이션 파일은 Backend 서버가 시작될 때 자동으로 실제 DB에 반영됩니다.
개발자가 할 일: 서버 시작만 하면 끝. SQL 직접 실행 필요 없음!
Step 5.5: 통합 테스트 환경 (Docker)
테스트 환경의 일관성도 중요한 문제였습니다. 개발자마다 로컬 환경이 달라 "내 컴퓨터에서는 되는데..."가 빈번했습니다.
문제: 분산된 Docker 설정
파일
문제
products/feedback/docker-compose.yml
PostgreSQL:5432
products/hotel/docker-compose.yml
PostgreSQL:5432 - 충돌!
templates/docker-compose.yml
또 다른 설정
→ 제품마다 Docker 설정이 달라 충돌 발생, 포트 충돌로 동시 실행 불가
해결책: 단일 통합 Docker Compose
구성
내용
파일
xtra/docker/docker-compose.yml (단일 통합 파일)
PostgreSQL
5433 (비기본 포트 - 기존 Docker와 충돌 방지)
Redis
6380 (비기본 포트)
컨테이너
itdalife-dev-* (명확한 네이밍)
→ 한 번 시작 → 모든 제품에서 공유, 테스트마다 재시작 불필요
통합 시작 스크립트
# 개발 환경 시작 (한 줄)
./scripts/dev-env.sh start
# 출력:
# [CHECK] Checking port 5433... available
# [CHECK] Checking port 6380... available
# [START] Starting Docker containers...
# [WAIT] Waiting for PostgreSQL health check...
# [WAIT] Waiting for Redis health check...
# [INFO] ✅ Development environment ready!
# 상태 확인
./scripts/dev-env.sh status
# 종료
./scripts/dev-env.sh stop
핵심 기능
기능
설명
포트 충돌 자동 감지
5433, 6380 포트 사용 중이면 자 동 Kill
Health Check 대기
PostgreSQL/Redis 준비될 때까지 대기
멱등성 보장
init-db.sh에서 WHERE NOT EXISTS 패턴 사용
다중 제품 지원
단일 PostgreSQL에서 제품별 DB 분리
멀티 제품 DB 구조
PostgreSQL (5433)
사용처
feedback_db
products/feedback/backend
hotel_db
products/hotel/backend
digital_asset_db
products/digital-asset/backend
→ 여러 제품 동시 개발/테스트 가능, 제품 간 DB 격리 보장
Step 6: 테스트 (E2E 자동화)
DSL에서 E2E 테스트도 자동 생성됩니다.
플랫폼
도구
생성되는 테스트
Next.js
Playwright
feedback.spec.ts (CRUD + Auth)
Flutter
integration_test
feedback_test.dart (iOS/Android)
Backend
MockK
FeedbackServiceTest.kt
전체 테스트 자동화 명령:
/itda-product-test-all feedback
이 명령 하나로:
DSL에서 코드 재생성
Backend/Next.js/Flutter 빌드
Next.js E2E 테스트 (claude-in-chrome)
Flutter iOS/Android 테스트 (integration_test)
결과 리포트 생성
4. 자동화 도구: Claude Skills & Commands
ITDALIFE 프로젝트에서는 반복 작업을 자동화하기 위해 Claude Code의 Skills와 Commands를 활용합니다.
Claude Skills (자동 활성화)
Skill
트리거
역할
itda-speckit-generator
"DSL 만들어줘"
PRD → DSL 변환 가이드
itda-dev-environment
"개발 환경 시작"
Docker + Backend + Next.js 자동 시작
itda-webapp-testing
"E2E 테스트"
claude-in-chrome 브라우저 테스트
itda-flutter-testing
"Flutter 테스트"
integration_test 실행
Slash Commands (수동 호출)
Command
용도
/itda-product-test-all {product}
전 체 재생성 + 3플랫폼 E2E 테스트
/itda-dsl-feature {name}
DSL에 새 feature 추가 (Parser/AST/IR 일괄 수정)
/itda-contract
Component Contract 자동 생성
/itda-ears-report
EARS 추적성 리포트 (PRD ↔ DSL ↔ Code)
5. 결과와 배운 점
정량적 성과
단계
바이브 코딩
ITDALIFE
1. PRD 작성
1-2일
1-2일 (동일)
2. DSL 작성
-
2-4시간 (추가)
3. 코드 생성
- Backend
4-8시간 (대화)
30초 (자동)
- Frontend
4-8시간 (대화)
30초 (자동)
- Mobile
4-8시간 (대화)
30초 (자동)
소계
12-24시간
~2분 ⚡
4. 수정/재생성
다시 대화 (컨텍스트 휘발, 불확실)
DSL 수정 → 재생성 (~2분, 100% 동일)
총 소요
2-3일+
1-2일
💡 핵심 차이: "코드 생성" 단계에서 12-24시간 → 2분으로 단축 + 재생성 시 멱등성 보장 (항상 동일한 결과)
항목
바이브 코딩
ITDALIFE
코드 생성 (3개 플랫폼)
12-24시간 (LLM 대화)
~2분 (자동)
3개 플랫폼 일관성
수동 검증 필요
100% 보장
필드 추가 (예: priority)
Entity, DTO, 타입, 컴포넌트, 모델, 화면 등 6곳+ 수정
DSL 1줄 추가 → 전체 자동 반영
재생성 결과
매번 다름
항상 동일 (멱등성)
보일러플레이트
직접 작성
0% (100% 생성)
로그인 화면
Mac의 피드백 페이지 스크린샷
대시보드 화면
Feedback 목록 화면 - 일반 사용자 등록 권한
일반 사용자 Feedback 상세 조회 화면 - 첨부 파일만 추가할 수 있는 권한.
Admin등 권한 있는 사용자 Feedback 상세 조회 화면
한국어와 중국어 텍스트가 포함된 웹사이트 스크린샷
배운 점 & 꿀팁
1. DSL을 SSOT(Single Source of Truth)로 삼아라
바이브 코딩의 가장 큰 문제는 "왜 이렇게 만들었는지" 컨텍스트가 사라진다는 것입니다.
바이브 코딩
DSL 기반
결과물
3시간 대화 끝에 완성된 FeedbackService.kt
feedback.core.dsl (Git에 영구 보존)
다음 날
"이 코드 왜 이렇게 짰지?" 기억 안 남
DSL 자체가 설계 문서 역할
수정 시
다시 대화해야 함
주석으로 "왜"를 남기고, 언제든 재생성 가능
2. 생성된 코드는 절대 직접 수 정하지 마라
처음엔 "이거 한 줄만 고치면 되는데..." 하고 직접 수정했습니다. 그런데:
Day
작업
Day 1
FeedbackService.kt 직접 수정 (버그 픽스)
Day 2
DSL 수정 후 재생성
Day 3
"어? 내가 고친 버그 픽스가 사라졌네?" 😱
올바른 방법: 버그를 발견하면 → 생성기 코드를 수정 → 재생성
이렇게 하면 feedback뿐 아니라 hotel, booking 등 모든 제품에 동일하게 적용됩니다.
3. Extension DSL로 플랫폼별 차이 처리
처음엔 Core DSL 하나에 모든 걸 넣었습니다. 그런데:
웹에서만 테이블 컬럼 순서를 바꾸고 싶은데, 모바일까지 재생성됨
모바일에서만 FAB 버튼 추가하고 싶은데, 웹에 영향 줌
해결: Core DSL (공통 도메인) + Extension DSL (플랫폼별 UI)로 분리
feedback.core.dsl→ 엔티티, 비즈니스 규칙 (공통)feedback.frontend.dsl→ 테이블 컬럼, 폼 레이아웃 (웹 전용)feedback.mobile.dsl→ 화면 구성, FAB 설정 (모바일 전용)
4. Claude Code + MCP로 워크플로우 자동화
코드 생성이 오래 걸릴 때 (빌드 포함 5-10분), 터미널 앞에서 기다리기 지루합니다.
해결: Telegram 알림 연동
생성 완료 → Telegram으로 "✅ 완료" 알림
에러 발생 → Telegram으로 "❌ 실패: [에러 내용]" 알림
다른 작업 하다가 알림 오면 확인
시행착오
1. 처음엔 OpenAPI + Mustache 템플릿으로 코드 생성 시도 → 실패
방식
문제점
OpenAPI
"API 계약"이지 "도메인 명세"가 아님 (상태머신, 권한 등 표현 불가)
Mustache
로직 없는 템플릿 → 조건문 하나 추가하려면 템플릿 자체를 수정
결과
템플릿이 코드 수준으로 복잡해짐 (5,000줄+ 템플릿 파일)
현재 해결책: Kotlin DSL + IR(Intermediate Representation: 중간표현)/AST(Abstract Syntax Tree: 추상구문트리) 방식
DSL로 도메인 전체 표현 (엔티티, 상태머신, 권한, UI 힌트)
프로그래밍 방식 코드 생성 → if/for 자유롭게 사용
복잡한 로직도 Kotlin 코드로 명확하게 표현
2. 검증하지 않고, 개발만 코드 생성만
→ 결국은 꼬여서 다 버리고, 새로 구현함.
3. 생성된 코드 직접 수정 → 재생성 시 날아감
이 실수를 3번 반복한 후 CLAUDE.md에 필수 규칙으로 추가
아무리 막아도 안 됨. compact 한 뒤에 빈번하게 발생.
4. Plan 모드 계획 세우고 작업할 때
너무 큰 계획은 세우지 말자.
작업 완료 되었다고 하면, 거짓보고 사절. 검증했어? 규칙 다 지켰어? 영향도 분석 했어? 사이드 이펙트 없어? 라고 다시 한번 검증 필요.
작업 중간에 새로운 요청을 할 때는 todo에 넣어서 관리해줘 라고 말하지 않으면, 하고 있던 작업을 중지하고, 새로운 작업만 진행.
앞으로 의 계획
더 많은 제품 구현 - LMS, E-commerce 등
모듈 추가 - 결제 등
Micro한 검증 및 테스트 - templates, modules, code-generator, dsl 깊게 들여다 보기
UI 개선 - 이것도 AI의 도움을 받아서.
수동 수정 보존 강화 - 객체 클래스, 오버라이딩, 믹스인, 화면 커스터마이징 등 지원 강화
Micro Service Architecture - 언젠가는 필요할 듯.