1. SDD란 무엇인가
정의
SDD(Spec-Driven Development, 스펙 주도 개발)는 명세(Spec)를 완벽하게 작성하면, AI가 구현(Implementation)을 담당하는 개발 방법론.
- 핵심 원리: 자연어 명세(Markdown)와 테스트 시나리오를 먼저 작성 → AI에게 구현 위임
- 비유: PM(인간)과 시니어 개발자(AI)의 협업
- 인간: 요구사항 정의서(PRD)와 인터페이스 설계 담당
- AI: 명세 기반 코드 생성 담당
개발자 비유: Interface 우선 설계
Java에서 Interface를 먼저 설계하고 Implementation을 나중에 하는 것과 동일.
1
2
3
4
5
6
7
8
9
10
| // Step 1: Interface 정의 (명세)
public interface UserService {
Long createUser(String name, String email, UserRole role);
User getUser(Long userId);
}
// Step 2: Implementation (구현) - AI가 담당
public class UserServiceImpl implements UserService {
// AI가 명세 기반으로 자동 생성
}
|
SDD 구조:
- Interface =
spec.md (명세 문서) - Implementation = AI가 생성하는 코드
- Javadoc = 상세한 요구사항
2. Why SDD? (기존 TDD와의 관계)
TDD vs SDD 비교
| 구분 | TDD | SDD |
|---|
| 순서 | 테스트를 먼저 짜고 구현 | 자연어 명세와 테스트 시나리오를 먼저 짜고, AI에게 구현 맡김 |
| 초점 | 테스트 코드 작성 | 비즈니스 로직과 아키텍처 설계 |
| 장점 | 구현 디테일(Syntax)보다 비즈니스 로직과 아키텍처에 집중 가능 | |
TDD의 한계
TDD는 “무엇을 테스트할지”는 알려주지만, “어떤 구조로 짜야 할지”는 알려주지 않음.
1
2
3
4
5
6
7
8
9
10
11
12
| // TDD: 테스트만 작성
@Test
void 사용자_생성_테스트() {
UserCreateRequest request = new UserCreateRequest("김개발", "dev@email.com");
Long userId = userService.createUser(request);
assertThat(userId).isNotNull();
}
// ❌ AI가 봤을 때:
// "UserCreateRequest가 뭐지?"
// "userService는 어디 있지?"
// "어떤 필드들이 필요하지?"
|
SDD의 해결책
명세 문서로 구조를 먼저 정의 → AI가 정확히 이해하고 구현.
1
2
3
4
5
6
7
8
9
10
11
12
| # spec.md
## Data Model: UserCreateRequest
- name: String (2-50자)
- email: String (이메일 형식)
- role: UserRole (Enum)
## Service Layer: UserService
- createUser(UserCreateRequest): Long
- 유효성 검증
- 이메일 중복 체크
- DB 저장
- ID 반환
|
✅ AI가 봤을 때:
- “아, UserCreateRequest에는 name, email, role이 있구나”
- “userService는 유효성 검증 → 중복 체크 → 저장 순서로 동작하는구나”
- “바로 코드 짤 수 있어!”
3. SDD Workflow
1. Spec 작성
Markdown으로 기능 명세, 데이터 구조, 예외 처리 정의.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| # User Management API Specification
## 1. Data Models
### User Entity
- id: Long (PK, Auto Increment)
- name: String (2-50자, NOT NULL)
- email: String (이메일 형식, UNIQUE, NOT NULL)
- role: Enum ["USER", "ADMIN"] (기본값: USER)
- createdAt: LocalDateTime (자동 생성)
### UserCreateRequest DTO
- name: String (2-50자, 필수)
- email: String (이메일 형식, 필수)
- role: String (선택, 기본값: "USER")
## 2. API Endpoints
### POST /api/users - 사용자 생성
- Request Body: UserCreateRequest
- Response: 201 Created (UserResponse)
- Error Cases:
- 400: 유효성 검증 실패
- 409: 이메일 중복
## 3. Business Logic
1. Request Body 유효성 검증
2. 이메일 중복 확인
3. User Entity 생성 및 저장
4. UserResponse로 변환하여 반환
## 4. Exception Handling
- DuplicateEmailException: 이메일 중복 시
- UserNotFoundException: 사용자 없을 시
- ValidationException: 유효성 검증 실패 시
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| # User Management API Specification
## 1. Data Models
### User Entity
- id: Long (PK, Auto Increment)
- name: String (2-50자, NOT NULL)
- email: String (이메일 형식, UNIQUE, NOT NULL)
- role: Enum ["USER", "ADMIN"] (기본값: USER)
- createdAt: LocalDateTime (자동 생성)
- updatedAt: LocalDateTime (자동 업데이트)
### UserCreateRequest DTO
- name: String (2-50자, 필수)
- email: String (이메일 형식, 필수)
- role: String (선택, 기본값: "USER")
### UserResponse DTO
- userId: Long
- name: String
- email: String
- role: String
- createdAt: String (ISO8601)
---
## 2. API Endpoints
### POST /api/users - 사용자 생성
**Request:**
```json
{
"name": "김개발",
"email": "dev@example.com",
"role": "USER"
}
|
Response: 201 Created
1
2
3
4
5
6
7
| {
"userId": 1,
"name": "김개발",
"email": "dev@example.com",
"role": "USER",
"createdAt": "2025-12-13T19:30:00+09:00"
}
|
Error Responses:
- 400 Bad Request: 유효성 검증 실패
1
2
3
4
5
| {
"error": "VALIDATION_ERROR",
"message": "이메일 형식이 올바르지 않습니다",
"field": "email"
}
|
- 409 Conflict: 이메일 중복
1
2
3
4
| {
"error": "DUPLICATE_EMAIL",
"message": "이미 존재하는 이메일입니다"
}
|
GET /api/users/{userId} - 사용자 조회
Response: 200 OK
1
2
3
4
5
6
7
| {
"userId": 1,
"name": "김개발",
"email": "dev@example.com",
"role": "USER",
"createdAt": "2025-12-13T19:30:00+09:00"
}
|
Error Responses:
- 404 Not Found: 사용자 없음
1
2
3
4
| {
"error": "USER_NOT_FOUND",
"message": "사용자를 찾을 수 없습니다"
}
|
3. Business Logic
사용자 생성 로직
- Request Body 유효성 검증
- name: 2-50자 체크
- email: 이메일 형식 체크
- role: Enum 값 체크
- 이메일 중복 확인
- User Entity 생성 및 저장
- UserResponse로 변환하여 반환
사용자 조회 로직
- userId로 DB 조회
- 없으면 404 에러 반환
- UserResponse로 변환하여 반환
4. Exception Handling
Custom Exceptions
DuplicateEmailException: 이메일 중복 시UserNotFoundException: 사용자 없을 시ValidationException: 유효성 검증 실패 시
Global Exception Handler
- 모든 예외를 JSON 형식으로 일관되게 반환
- 500 에러는 상세 정보 숨김
5. Technology Stack
- Spring Boot 3.2
- Spring Data JPA
- Hibernate Validator
- H2 Database (개발용) ```
2. Prompting
AI에게 Spec을 주입하여 초안 코드 생성.
1
2
3
4
5
6
7
8
| # Cursor나 ChatGPT에서
"@spec.md 파일을 읽고, 명세대로 Spring Boot User API를 구현해줘.
요구사항:
1. 모든 DTO에 validation 어노테이션 추가
2. Controller에 Swagger 문서화 어노테이션 추가
3. 예외 처리를 GlobalExceptionHandler로 통일
4. 테스트 코드도 함께 작성"
|
AI 생성 결과:
User.java (Entity)UserCreateRequest.java (DTO)UserController.java (Controller)UserService.java (Service)UserRepository.java (Repository)GlobalExceptionHandler.java (예외 처리)UserControllerTest.java (테스트)
3. Review & Refine
생성된 코드 리뷰 및 테스트. (인간의 역할 = Code Reviewer)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| @SpringBootTest
class UserServiceTest {
@Test
void 사용자_생성_성공() {
UserCreateRequest request = new UserCreateRequest(
"김개발", "dev@example.com", "USER"
);
UserResponse response = userService.createUser(request);
assertThat(response.getUserId()).isNotNull();
assertThat(response.getName()).isEqualTo("김개발");
}
@Test
void 이메일_중복_시_예외_발생() {
userService.createUser(
new UserCreateRequest("김개발", "dev@example.com", "USER")
);
assertThatThrownBy(() ->
userService.createUser(
new UserCreateRequest("박개발", "dev@example.com", "USER")
)
).isInstanceOf(DuplicateEmailException.class);
}
}
|
4. Iterate
버그 발생 시 코드가 아닌 Spec(프롬프트)을 수정하여 재생성.
1
2
3
4
5
6
7
| # 문제 발생 시
1. 코드를 직접 수정하지 않음
2. spec.md에서 누락된 요구사항 추가
3. AI에게 재생성 요청
4. 테스트로 검증
→ Spec이 Single Source of Truth 역할
|
4. SDD vs TDD 비교
| 구분 | TDD | SDD |
|---|
| 초점 | 테스트 코드 | 명세/스펙 문서 |
| 문서 | 테스트 코드 | spec.md |
| AI 활용 | 어려움 | 최적화 |
| 구조 정의 | ❌ | ✅ |
| 비즈니스 로직 집중 | 보통 | 높음 |
함께 사용: SDD + TDD
1
2
3
4
5
6
7
| // 조합: SDD + TDD
1. spec.md 작성 (전체 구조 정의)
2. AI로 코드 생성
3. TDD로 테스트 작성 및 검증
4. 리팩토링
→ "명세 → 구현 → 검증" 완벽한 흐름
|
5. SDD 실전 활용
Use Case 1: 팀 협업
1
2
3
4
5
6
7
8
9
10
| # Before SDD
PM: "사용자 관리 기능 만들어주세요"
개발자: "알겠습니다" (막연함)
→ 2주 후 "이거 아닌데..." (요구사항 불일치)
# After SDD
PM: [spec.md 작성]
개발자: [spec.md 검토 후 승인]
개발자: AI에게 spec.md 전달 → 코드 생성
→ 1일 후 "완벽하게 맞아요!" (정확한 구현)
|
Use Case 2: 레거시 리팩토링
1
2
3
4
5
6
7
| // SDD 프로세스
1. 레거시 코드 분석 → spec.md 작성 (역공학)
2. spec.md 검토 및 개선
3. AI에게 spec.md 전달 → 깔끔한 코드 생성
4. TDD로 동일한 동작 검증
→ 안전한 리팩토링
|
Use Case 3: API 버전 관리
1
2
3
4
5
6
7
8
9
10
11
12
| # v1/spec.md
## POST /api/v1/users
- name: String
- email: String
# v2/spec.md
## POST /api/v2/users
- name: String
- email: String
- phone: String (추가됨)
→ 스펙 문서로 버전별 차이 명확히 관리
|
6. SDD 실전 팁
Tip 1: 스펙 문서는 ‘살아있는 문서’로 관리
1
2
3
4
5
6
7
8
| # Git으로 스펙 버전 관리
git add spec.md
git commit -m "feat: User API 스펙 추가"
# 코드와 함께 리뷰
- spec.md: 명세 검토
- implementation: 코드 검토
→ 명세와 코드가 일치하는지 확인
|
Tip 2: OpenAPI/Swagger 형식 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # swagger.yaml (더 구조화된 스펙)
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/api/users:
post:
summary: 사용자 생성
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserCreateRequest'
|
AI는 구조화된 형식을 더 잘 이해함.
Tip 3: 도메인별로 스펙 파일 분리
1
2
3
4
5
| specs/
├── user-spec.md # 사용자 도메인
├── product-spec.md # 상품 도메인
├── order-spec.md # 주문 도메인
└── common-spec.md # 공통 사양 (에러 응답, 페이징 등)
|
Tip 4: 예제를 풍부하게
1
2
3
4
5
6
7
8
9
| ## 좋은 스펙 예시
### POST /api/users
- Request 예시:
```json
{
"name": "김개발",
"email": "dev@example.com"
}
|
- Response 예시:
1
2
3
4
| {
"userId": 1,
"createdAt": "2025-12-13T19:30:00+09:00"
}
|
→ AI는 예시를 보고 패턴을 파악함 (Few-Shot Learning)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
------
### 7. SDD의 한계와 주의사항
#### 한계 1: 스펙 작성이 어려울 수 있음
```java
// 해결책: 점진적 스펙 작성
1. 초안 작성 (30% 완성도)
2. AI에게 "이 스펙을 개선해줘" 요청
3. 검토 후 확정
4. 코드 생성
→ AI가 스펙 작성도 도와줄 수 있음
|
한계 2: 복잡한 비즈니스 로직은 스펙으로 표현하기 어려움
1
2
3
4
5
6
7
8
9
10
11
12
13
| # Bad: 너무 복잡
"사용자의 최근 3개월 주문 내역을 분석하여,
구매 패턴이 유사한 다른 사용자들의 관심 상품 중,
현재 사용자가 보지 않은 상품을 추천 점수 순으로 정렬하되,
재고가 있는 상품만 최대 10개까지..."
# Good: 단계별로 분리
## 1단계: 사용자 주문 내역 조회
## 2단계: 유사 사용자 찾기 (별도 스펙)
## 3단계: 추천 상품 계산 (별도 스펙)
## 4단계: 정렬 및 필터링
→ 복잡한 로직은 작은 단위로 분해
|
주의사항: 스펙과 코드의 불일치
1
2
3
4
5
6
7
8
9
| // 문제 상황
1. spec.md 작성
2. AI로 코드 생성
3. 코드 수정 (스펙 업데이트 안 함)
4. 시간이 지나면... 스펙과 코드가 달라짐
// 해결책: Spec을 Single Source of Truth로 유지
- 코드 수정 시 반드시 spec.md도 함께 업데이트
- Git으로 스펙과 코드를 함께 관리
|
8. 핵심 정리
SDD의 핵심 철학:
- “기획서가 곧 코드다” - 명확한 스펙은 그 자체로 구현 가능
- “AI는 실행자, 개발자는 설계자” - 역할의 변화
- “스펙 작성이 곧 개발 역량” - 무엇을 만들지 정의하는 능력
- “생산성 향상” - 단순 작업은 AI에게, 창의적 설계는 개발자에게
생산성 비교:
| 기존 방식 | SDD + AI |
|---|
| 코드 작성에 60분 | 스펙 작성 10분 + AI 생성 5분 |
| 요구사항 불일치 자주 발생 | 스펙 검토로 사전 방지 |
| AI와 10번 왕복 대화 | 한 번에 정확한 코드 생성 |
| 레거시 코드 파악 어려움 | 스펙 문서로 명확히 이해 |
결론: “코딩을 잘하는 개발자”보다 “스펙을 잘 쓰는 개발자”가 더 생산적인 시대.
참고 자료
- OpenAPI Specification: https://swagger.io/specification/
- REST API Design Best Practices: https://restfulapi.net/
- Specification by Example (Book): https://www.manning.com/books/specification-by-example