기능 명세가 필요할 때 /product-specs-writer가 전체 PRD를 작성해 바로 개발 검토로 넘어가게 합니다. — Claude Skill
Claude Code용 Claude 스킬 · 제공: Nicklrs · 실행: /product-specs-writer (Claude 내)·업데이트: 2026년 6월 14일
간단한 브리프에서 PRD, 사용자 스토리, 수용 기준, 기술 명세를 작성합니다.
- 목표, 비목표, 마일스톤, 성공 지표가 포함된 PRD를 생성합니다
- 기능을 Gherkin 수용 기준이 포함된 사용자 스토리로 나눕니다
- 오류 상태와 경계 조건을 포괄하는 예외 상황 매트릭스를 만듭니다
- 요청/응답 스키마가 포함된 API 엔드포인트 명세를 작성합니다
- 비율 단계와 중지 스위치가 포함된 기능 플래그 출시 계획을 만듭니다
대상
기능
/product-specs-writer에 한 문단짜리 기능 아이디어를 주면 문제 진술, 제안 해결책, 마일스톤, 지표, 열린 질문이 포함된 구조화된 PRD를 받습니다.
PRD를 /product-specs-writer에 붙여 넣어 에픽별로 묶인 8-15개 사용자 스토리와 Given/When/Then 형식의 수용 기준을 생성합니다.
기능 명세에 /product-specs-writer를 실행해 null 입력, 속도 제한, 동시 편집, 권한 경계를 포함한 20개 이상의 예외 상황 매트릭스를 만듭니다.
엔드포인트를 /product-specs-writer에 설명하면 요청 스키마, 응답 예시, 오류 코드, 인증 요건이 포함된 OpenAPI 스타일 명세를 받습니다.
출시 맥락을 /product-specs-writer에 입력해 내부 사용, 5% 카나리, 25% 베타, 100% 정식 출시 단계와 각 단계의 롤백 조건을 만듭니다.
작동 방식
명세화가 필요한 기능 브리프, 기존 문서, 제품 영역을 제공합니다.
이 스킬은 PRD, 스토리, 수용 기준, API 문서, 플래그 계획 중 필요한 산출물을 식별합니다.
각 산출물을 일관된 상호 참조로 생성해 스토리는 PRD 목표에, 수용 기준은 예외 상황에 연결합니다.
검토 후 제약이나 맥락을 추가하고 다시 실행해 개발팀이 바로 볼 수 있는 명세로 다듬습니다.
예시
이메일 작성기에 '예약 발송' 기능을 추가합니다. 사용자가 날짜/시간을 선택하면 이메일이 대기열에 들어가 그 시간에 발송됩니다. 시간대 선택과 발송 전 취소를 지원해야 합니다.
분산된 팀의 사용자는 수신자의 업무 시간 밖에 이메일을 작성합니다. 설문 응답자의 34%가 최적의 시간에 받은 편지함에 도착하도록 지연 발송을 요청했습니다.
US-1: 작성자로서 미래 날짜와 시간을 선택해 수신자가 활동 중일 때 이메일이 도착하게 하고 싶습니다. AC: 유효한 미래 일시를 선택하고 '예약'을 클릭하면 이메일이 상태 '대기 중'으로 예약 대기열로 이동합니다. US-2: 작성자로서 예약 이메일이 발송되기 전 취소해 수정하거나 폐기하고 싶습니다. AC: 상태가 '대기 중'인 예약 이메일에서 '취소'를 클릭하면 초안으로 돌아가고 대기열 작업이 삭제됩니다.
1. 사용자가 과거 시간을 선택함 → 인라인 오류를 표시하고 모달을 유지합니다. 2. 예약과 발송 사이 시간대가 바뀜 → 내부적으로 UTC를 사용하고 현재 사용자 시간대로 표시합니다. 3. 예약 시간에 서버 장애 발생 → 15분 동안 3회 재시도 후 사용자에게 알립니다.
지원 도구
제품 명세 작성기을(를) 사용해 보시겠어요?
시작 방법을 선택하세요.
이 스킬을 컴퓨터에 로컬로 설치하고 실행합니다.
컴퓨터에서 터미널을 열고 이 명령을 붙여넣으세요:
이 명령은 스킬과 모든 파일을 컴퓨터에 다운로드합니다:
모든 프로젝트에서 사용하려면 끝에 -g를 추가하세요.
Claude Code를 시작한 다음 명령을 입력하세요:
제품 명세 작성기
포괄적인 제품 문서 전문 지식입니다. 전략적 PRD부터 개발팀이 실제로 구현할 수 있는 구현 준비 명세까지 다룹니다.
철학
훌륭한 제품 명세는 비전과 실행 사이의 간극을 잇습니다. 관료적 문서가 아니라 팀을 정렬하고 비싼 오해를 막는 커뮤니케이션 도구입니다.
최고의 제품 명세는 다음 원칙을 따릅니다.
- 왜에서 시작합니다 — 요구사항보다 맥락이 먼저입니다
- 테스트 가능합니다 — 모든 요구사항에는 명확한 수용 기준이 있습니다
- 질문을 예상합니다 — 예외 상황, 오류, 제약을 미리 문서화합니다
- 제품과 함께 진화합니다 — 정적인 산출물이 아니라 살아 있는 문서입니다
- 독자를 존중합니다 — 개발자, 디자이너, 이해관계자가 모두 이해할 수 있어야 합니다
이 스킬의 작동 방식
호출되면 rules/에 정리된 다음 지침을 적용합니다.
prd-*— 제품 요구사항 문서, 비전, 범위stories-*— 사용자 스토리, 페르소나, jobs-to-be-donecriteria-*— 수용 기준, 완료의 정의technical-*— 기술 명세, 아키텍처 결정api-*— API 명세, 계약, 버전 관리edge-*— 예외 상황, 오류 처리, 실패 모드design-*— 디자인 인계, 컴포넌트 명세, 상호작용rollout-*— 기능 플래그, 출시 계획, 실험metrics-*— 성공 지표, KPI, 측정 계획maintenance-*— 문서 생애주기, 버전 관리, 지원 중단
핵심 프레임워크
명세 계층
┌─────────────────────────────────────────┐
│ 비전 │ ← 왜 이것을 만드는가?
│ (문제와 기회) │
├─────────────────────────────────────────┤
│ PRD │ ← 무엇을 만드는가?
│ (요구사항과 제약) │
├─────────────────────────────────────────┤
│ 사용자 스토리 │ ← 누가 어떻게 이익을 얻는가?
│ (페르소나와 여정) │
├─────────────────────────────────────────┤
│ 수용 기준 │ ← 완료를 어떻게 아는가?
│ (테스트 가능한 조건) │
├─────────────────────────────────────────┤
│ 기술 명세 │ ← 어떻게 만드는가?
│ (아키텍처와 구현) │
└─────────────────────────────────────────┘
대상별 문서 유형
| 문서 | 주요 대상 | 목적 | 업데이트 빈도 |
|---|---|---|---|
| PRD | 리더십, PM, 디자인 | 무엇과 왜에 대한 정렬 | 마일스톤별 |
| 사용자 스토리 | 개발, QA | 범위와 가치 정의 | 스프린트별 |
| 수용 기준 | QA, 개발 | 완료 정의 | 스토리별 |
| 기술 명세 | 개발 | 구현 방식 정의 | 기능별 |
| API 명세 | 프론트엔드, 외부 개발자 | 계약 정의 | 버전별 |
| 디자인 인계 | 개발 | UI/UX 정의 | 컴포넌트별 |
| 출시 계획 | 개발, 운영 | 배포 정의 | 릴리스별 |
| 성공 지표 | 리더십, 데이터 | 성공 정의 | 분기별 |
INVEST 기준(사용자 스토리)
| 기준 | 질문 | 예시 |
|---|---|---|
| Independent | 단독으로 만들 수 있는가? | 미완성 스토리에 의존하지 않음 |
| Negotiable | 범위가 유연한가? | 세부사항을 개발팀과 다듬을 수 있음 |
| Valuable | 사용자가 이익을 얻는가? | 명확한 가치 제안이 적혀 있음 |
| Estimable | 규모를 추정할 수 있는가? | 노력 추정에 충분한 세부사항 |
| Small | 스프린트 안에 맞는가? | 1-5일 안에 완료 가능 |
| Testable | 검증할 수 있는가? | 명확한 수용 기준이 있음 |
명세 완성도 체크리스트
PRD 완성도:
├── 문제 진술 □ 사용자 고통이 명확히 정의됨
├── 성공 지표 □ 측정 가능한 성과가 정의됨
├── 사용자 스토리 □ 모든 페르소나가 포함됨
├── 범위 □ 포함/제외 범위가 명확함
├── 제약 □ 기술 및 비즈니스 한계가 명시됨
├── 의존성 □ 외부 의존성이 식별됨
├── 위험 □ 알려진 위험과 완화책이 있음
├── 일정 □ 마일스톤과 기한이 설정됨
└── 열린 질문 □ 모르는 것이 명시적으로 나열됨
기술 명세 완성도:
├── 아키텍처 □ 시스템 설계가 문서화됨
├── 데이터 모델 □ 스키마와 관계가 정의됨
├── API 계약 □ 엔드포인트와 페이로드가 명시됨
├── 예외 상황 □ 실패 모드가 문서화됨
├── 보안 □ 인증, 암호화, 컴플라이언스 포함
├── 성능 □ SLA와 기준값이 정의됨
├── 모니터링 □ 관측 전략이 명확함
└── 롤백 계획 □ 복구 절차가 문서화됨
오류 처리 분류
| 오류 유형 | 예시 | 필요한 문서화 |
|---|---|---|
| 검증 | 잘못된 이메일 형식 | 오류 메시지, 필드 강조 |
| 권한 | 사용자 권한 부족 | 오류 상태, 에스컬레이션 경로 |
| 리소스 | 항목을 찾을 수 없음 | 빈 상태, 복구 조치 |
| 시스템 | 데이터베이스 시간 초과 | 재시도 전략, 사용자 피드백 |
| 비즈니스 로직 | 잔액 부족 | 오류 설명, 다음 단계 |
| 외부 | 제3자 API 중단 | 대체 동작, 제한 모드 |
명세 템플릿
최소 PRD 구조
# 기능: [이름]
## 문제
우리가 해결하는 사용자 문제는 무엇인가?
## 해결책
상위 접근 방식(1-2문단)
## 성공 지표
- 주요: [지표]를 X에서 Y로
- 보조: [지표]를 X에서 Y로
## 사용자 스토리
- [사용자]로서 [목표]를 원합니다. 그래서 [이점]을 얻습니다
## 범위
**범위 안:** [목록]
**범위 밖:** [목록]
## 열린 질문
- [ ] 질문 1
- [ ] 질문 2
사용자 스토리 템플릿
**As a** [persona/user type]
**I want** [capability/action]
**So that** [benefit/value]
**Acceptance Criteria:**
- Given [context], when [action], then [result]
- Given [context], when [action], then [result]
**Edge Cases:**
- What if [edge case]? Then [behavior]
**Out of Scope:**
- [Explicit exclusion]
안티패턴
- 위원회식 명세 — 과도한 협업이 모호한 문서를 만듭니다
- 성급한 최적화 — 구현 세부사항을 너무 일찍 명세화합니다
- 왜의 누락 — 결정을 위한 맥락 없는 요구사항
- 주방 싱크 범위 — 한 번의 출시로 모든 것을 해결하려 함
- 일방향 문서화 — 학습이 생겨도 업데이트되지 않는 명세
- 가정 맹목 — 암묵적 가정을 문서화하지 않음
- 디자이너/개발자 전화놀이 — 직접 소통 없이 문서만 오감
- 성공 연극 — 의미 있어서가 아니라 쉬워서 선택한 지표
- 계약서 같은 명세 — 명세를 바꿀 수 없는 법적 문서처럼 다룸
- 문서 부채 — 오래된 명세는 명세가 없는 것보다 나쁩니다
참조 문서
title: 섹션 구성
1. 제품 요구사항 문서 작성 (prd)
영향도: 매우 높음 설명: 비전, 범위, 성공 기준에 대해 이해관계자를 정렬하는 제품 요구사항 문서(PRD)입니다. 모든 후속 명세의 기반입니다.
2. 사용자 스토리 (stories)
영향도: 매우 높음 설명: 누가 이익을 얻고, 무엇이 필요하며, 왜 중요한지를 담는 사용자 중심 요구사항입니다. 비즈니스 목표와 엔지니어링 작업을 연결합니다.
3. 수용 기준 (criteria)
영향도: 매우 높음 설명: 스토리가 완료되었음을 정의하는 테스트 가능한 조건입니다. 제품 관리자, 엔지니어링, QA 사이의 계약입니다.
4. 기술 명세 (technical)
영향도: 높음 설명: 아키텍처 결정, 시스템 설계, 구현 지침입니다. 솔루션을 어떻게 구축할지 설명합니다.
5. API 명세 (api)
영향도: 높음 설명: API 계약, 요청/응답 형식, 버전 관리, 문서화입니다. 시스템과 팀 사이의 인터페이스입니다.
6. 예외 사례 및 오류 처리 (edge)
영향도: 높음 설명: 실패 모드, 오류 상태, 예외 처리입니다. 일이 잘못될 때 무엇이 일어나는지 정의합니다.
7. 디자인 인수인계 (design)
영향도: 중간-높음 설명: 컴포넌트 명세, 상호작용 상태, 반응형 요구사항입니다. 디자인을 구현 가능한 형태로 번역합니다.
8. 기능 플래그 및 단계적 배포 (rollout)
영향도: 중간-높음 설명: 점진적 배포 전략, 기능 플래그, 실험, 긴급 차단 스위치입니다. 안전한 배포 패턴입니다.
9. 성공 지표 (metrics)
영향도: 높음 설명: KPI, 측정 계획, 성공 기준입니다. 올바른 것을 만들었는지 판단하는 방법입니다.
10. 문서 유지관리 (maintenance)
영향도: 중간 설명: 문서 생명주기, 버전 관리, 지원 중단, 업데이트 흐름입니다. 명세를 시간이 지나도 정확하게 유지합니다.
title: API 명세 impact: HIGH tags: api, rest, openapi, documentation, contracts
API 명세
영향도: 높음
API 명세는 서비스, 팀, 외부 개발자 사이의 계약입니다. 명확한 명세는 통합 버그를 막고, 반복적인 확인을 줄이며, 프론트엔드와 백엔드의 병렬 개발을 가능하게 합니다.
API 설계 원칙
- 일관성 - 모든 엔드포인트에서 같은 패턴
- 예측 가능성 - 개발자가 동작을 예상할 수 있음
- 발견 가능성 - 응답이 스스로 문서 역할을 함
- 진화 가능성 - 클라이언트를 깨뜨리지 않고 변경 가능
- 디버깅 가능성 - 오류가 개발자의 수정에 도움을 줌
API 명세 구성요소
| 구성요소 | 목적 | 필수 |
|---|---|---|
| 엔드포인트 | URL 경로 및 HTTP 메서드 | 예 |
| 설명 | 이 엔드포인트가 하는 일 | 예 |
| 인증 | 인증 요구사항 | 예 |
| 요청 | 헤더, 매개변수, 본문 | 예 |
| 응답 | 성공 및 오류 형식 | 예 |
| 예시 | 실제 요청/응답 샘플 | 예 |
| 속도 제한 | 제한 규칙 | 해당 시 |
| 버전 관리 | API 버전 정보 | 예 |
좋은 API 명세 예시(OpenAPI 스타일)
# POST /workspaces/{workspace_id}/invitations
# 새 워크스페이스 초대 생성
summary: 사용자를 워크스페이스에 초대
description: |
지정한 주소로 초대 이메일을 보냅니다. 수신자는 초대를 수락해
지정된 권한 수준으로 워크스페이스에 참여할 수 있습니다.
초대는 7일 후 만료됩니다.
워크스페이스의 소유자 권한이 필요합니다.
tags:
- 워크스페이스 공유
security:
- BearerAuth: []
parameters:
- name: workspace_id
in: path
required: true
description: 워크스페이스의 고유 식별자
schema:
type: string
format: uuid
example: "550e8400-e29b-41d4-a716-446655440000"
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- email
- permission_level
properties:
email:
type: string
format: email
description: 초대할 사람의 이메일 주소
example: "[email protected]"
permission_level:
type: string
enum: [viewer, editor, owner]
description: 부여할 권한 수준
example: "editor"
message:
type: string
maxLength: 500
description: 이메일에 포함할 선택 개인 메시지
example: "팀에 오신 것을 환영합니다! Q4 계획 워크스페이스를 확인해 보세요."
responses:
201:
description: 초대가 성공적으로 생성됨
content:
application/json:
schema:
type: object
properties:
id:
type: string
format: uuid
workspace_id:
type: string
format: uuid
email:
type: string
permission_level:
type: string
invited_by:
type: string
format: uuid
expires_at:
type: string
format: date-time
created_at:
type: string
format: date-time
example:
id: "d290f1ee-6c54-4b01-90e6-d701748f0851"
workspace_id: "550e8400-e29b-41d4-a716-446655440000"
email: "[email protected]"
permission_level: "editor"
invited_by: "7c9e6679-7425-40de-944b-e07fc1f90ae7"
expires_at: "2024-01-22T10:30:00Z"
created_at: "2024-01-15T10:30:00Z"
400:
description: 잘못된 요청
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
invalid_email:
summary: 잘못된 이메일 형식
value:
error:
code: "INVALID_EMAIL"
message: "이메일 주소 형식이 잘못되었습니다"
field: "email"
workspace_full:
summary: 워크스페이스가 구성원 한도에 도달
value:
error:
code: "WORKSPACE_FULL"
message: "이 워크스페이스가 구성원 한도(50명)에 도달했습니다"
limit: 50
current: 50
401:
description: 인증 필요
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: "UNAUTHORIZED"
message: "인증 토큰이 없거나 유효하지 않습니다"
403:
description: 권한 거부
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: "FORBIDDEN"
message: "워크스페이스 소유자만 초대를 보낼 수 있습니다"
required_permission: "owner"
your_permission: "editor"
404:
description: 워크스페이스를 찾을 수 없음
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: "WORKSPACE_NOT_FOUND"
message: "ID가 '550e8400-...'인 워크스페이스가 존재하지 않습니다"
409:
description: 충돌 - 사용자가 이미 초대되었거나 구성원임
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
already_member:
summary: 사용자가 이미 구성원임
value:
error:
code: "ALREADY_MEMBER"
message: "이 사용자는 이미 워크스페이스 구성원입니다"
existing_permission: "editor"
pending_invitation:
summary: 초대가 이미 대기 중
value:
error:
code: "INVITATION_PENDING"
message: "이 이메일에는 이미 대기 중인 초대가 있습니다"
invitation_id: "d290f1ee-6c54-4b01-90e6-d701748f0851"
expires_at: "2024-01-22T10:30:00Z"
429:
description: 속도 제한 초과
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: "RATE_LIMITED"
message: "초대 한도를 초과했습니다. 나중에 다시 시도하세요."
retry_after: 3600
headers:
Retry-After:
description: 속도 제한이 초기화될 때까지의 초
schema:
type: integer
example: 3600
x-rate-limit:
requests: 10
window: 3600
scope: workspace
x-changelog:
- version: "2024-01-15"
changes:
- 선택 메시지 필드 추가
- version: "2023-11-01"
changes:
- 최초 릴리스
나쁜 API 명세 예시
# POST /invite
# 사용자 초대
request:
email: string
level: string
response:
success: boolean
실패하는 이유:
- 엔드포인트 설명 없음
- 인증 문서화 없음
- 매개변수 세부정보나 제약 누락
- 오류 응답 누락
- 예시 없음
- 버전 정보 없음
- 모호한 응답 스키마
오류 응답 표준
일관된 오류 스키마:
{
"error": {
"code": "MACHINE_READABLE_CODE",
"message": "사람이 읽을 수 있는 설명",
"details": {
"field": "problematic_field",
"reason": "구체적인 검증 실패"
},
"request_id": "req_abc123",
"documentation_url": "https://docs.api.com/errors/CODE"
}
}
표준 오류 코드:
| HTTP 상태 | 코드 | 사용 사례 |
|---|---|---|
| 400 | VALIDATION_ERROR | 잘못된 입력 |
| 400 | INVALID_FORMAT | 잘못된 JSON, 잘못된 콘텐츠 유형 |
| 401 | UNAUTHORIZED | 인증 누락 또는 유효하지 않음 |
| 401 | TOKEN_EXPIRED | 인증 토큰 만료 |
| 403 | FORBIDDEN | 유효한 인증이지만 권한 부족 |
| 404 | NOT_FOUND | 리소스가 존재하지 않음 |
| 409 | CONFLICT | 리소스 상태 충돌 |
| 422 | UNPROCESSABLE | 형식은 유효하지만 의미가 잘못됨 |
| 429 | RATE_LIMITED | 요청이 너무 많음 |
| 500 | INTERNAL_ERROR | 서버 측 실패 |
| 503 | SERVICE_UNAVAILABLE | 유지보수 또는 과부하 |
API 버전 관리 전략
| 전략 | 예시 | 장점 | 단점 |
|---|---|---|---|
| URL 경로 | /v1/users | 명확함, 캐시 가능 | URL 오염 |
| 헤더 | API-Version: 2024-01 | 깔끔한 URL | 덜 보임 |
| 쿼리 매개변수 | /users?version=1 | 테스트 쉬움 | 캐시 이슈 |
| 콘텐츠 유형 | Accept: application/vnd.api.v1+json | RESTful | 복잡함 |
권장: 주요 버전은 URL 경로 버전 관리, 작은 변경은 헤더 기반 버전 관리.
요청/응답 예시
목록 엔드포인트 패턴:
// GET /workspaces/{id}/members?limit=20&cursor=abc123
// 응답
{
"data": [
{
"id": "user-1",
"email": "[email protected]",
"permission_level": "editor",
"joined_at": "2024-01-10T10:00:00Z"
},
{
"id": "user-2",
"email": "[email protected]",
"permission_level": "owner",
"joined_at": "2024-01-05T08:00:00Z"
}
],
"pagination": {
"cursor": "def456",
"has_more": true,
"total_count": 47
}
}
단일 리소스 패턴:
// GET /workspaces/{id}
// 응답
{
"data": {
"id": "workspace-123",
"name": "Q4 계획",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-15T10:30:00Z",
"owner": {
"id": "user-1",
"email": "[email protected]"
},
"member_count": 12,
"settings": {
"visibility": "private",
"allow_guest_access": false
}
},
"_links": {
"self": "/workspaces/workspace-123",
"members": "/workspaces/workspace-123/members",
"invitations": "/workspaces/workspace-123/invitations"
}
}
페이지네이션 표준
| 방식 | 적합한 경우 | 예시 |
|---|---|---|
| 커서 | 큰 데이터셋, 실시간 데이터 | ?cursor=eyJpZCI6MTIzfQ |
| 오프셋 | 안정적인 데이터셋, 페이지 이동 | ?offset=40&limit=20 |
| 키셋 | 정렬된 결과, 효율적인 DB | ?after_id=123&limit=20 |
항상 포함할 것:
- 현재 페이지 표시
- 더 있음 표시
- 전체 개수(성능상 가능하면)
API 문서 체크리스트
엔드포인트 문서
├── □ HTTP 메서드와 경로
├── □ 요약(한 줄)
├── □ 설명(상세)
├── □ 인증 요구사항
├── □ 경로 매개변수(타입, 제약 포함)
├── □ 쿼리 매개변수(기본값 포함)
├── □ 요청 본문 스키마
├── □ 응답 스키마(모든 상태 코드)
├── □ 실제 예시(복사해 붙여넣을 수 있음)
└── □ 오류 코드와 의미
API 전체 문서
├── □ 인증 가이드
├── □ 속도 제한 정책
├── □ 버전 관리 정책
├── □ 페이지네이션 가이드
├── □ 오류 처리 가이드
├── □ 변경 로그
└── □ SDK/클라이언트 라이브러리 링크
안티패턴
- 문서화되지 않은 엔드포인트 - "코드 보면 명확합니다"
- 예시 없는 명세 - 구체적 예시 없는 스키마
- 오류 기억상실 - 200 응답만 문서화
- 버전 방치 - 호환성 깨는 변경이 생길 때까지 버전 전략 없음
- 일관성 없는 이름 - 한 엔드포인트는
user_id, 다른 엔드포인트는userId - 잡동사니 응답 - 필요한 것 대신 모든 것을 반환
- 호출이 많은 API - 하나의 논리 작업에 여러 호출이 필요
- 문서화되지 않은 제한 - 속도 제한, 페이지네이션, 최대 크기 미명시
- 오래된 명세 - 문서가 구현과 맞지 않음
- 내부 노출 - 오류 메시지에 구현 세부사항 유출
title: 수용 기준 impact: CRITICAL tags: acceptance-criteria, testing, qa, definition-of-done, gherkin
수용 기준
영향도: 매우 높음
수용 기준은 제품 관리자, 엔지니어링, QA 사이의 계약을 정의합니다. "이 스토리가 완료되었음을 어떻게 아는가?"에 답합니다. 명확한 기준이 없으면 끝없는 논쟁, 누락된 요구사항, 재작업이 생깁니다.
수용 기준의 목적
- 공유된 이해 - 모두가 "완료"의 의미에 동의
- 테스트 가능한 조건 - QA가 각 기준을 검증 가능
- 범위 경계 - 개발 중 기능 범위 증가 방지
- 추정 입력 - 엔지니어가 복잡도를 미리 이해
Given-When-Then 형식(Gherkin)
테스트 가능한 수용 기준의 표준 형식입니다.
Given [사전 조건/맥락]
When [행동/트리거]
Then [기대 결과]
여러 조건:
Given [사전 조건]
And [추가 사전 조건]
When [행동]
Then [결과]
And [추가 결과]
But [예외/제외]
수용 기준 패턴
| 패턴 | 사용 시점 | 예시 |
|---|---|---|
| 정상 경로 | 정상 흐름 설명 | 사용자가 성공적으로 로그인 |
| 오류 상태 | 잘못된 입력/실패 | 잘못된 비밀번호가 오류 표시 |
| 예외 사례 | 경계 조건 | 항목 999개가 있는 장바구니 |
| 권한 | 접근 제어 | 관리자 전용 작업이 일반 사용자에게 차단 |
| 상태 변경 | 전/후 조건 | 초안 → 게시됨 |
| 성능 | 속도 요구사항 | 페이지가 2초 미만에 로드 |
좋은 수용 기준 예시
기능: 비밀번호 재설정
Scenario: 성공적인 비밀번호 재설정 요청
Given 로그인 페이지에 있음
And "[email protected]" 이메일로 등록된 계정이 있음
When "비밀번호를 잊으셨나요"를 클릭
And 이메일 주소를 입력
And "재설정 링크 보내기"를 클릭
Then "재설정 지침을 이메일에서 확인하세요" 확인 메시지가 표시됨
And 60초 안에 이메일을 받음
And 이메일에는 24시간 동안 유효한 고유 재설정 링크가 포함됨
Scenario: 등록되지 않은 이메일로 비밀번호 재설정
Given 비밀번호 찾기 페이지에 있음
When 우리 시스템에 없는 이메일을 입력
And "재설정 링크 보내기"를 클릭
Then 동일한 확인 메시지가 표시됨(보안: 이메일 존재 여부를 드러내지 않음)
And 이메일은 발송되지 않음
Scenario: 비밀번호 재설정 링크 만료
Given 24시간보다 오래전에 비밀번호 재설정을 요청함
When 이메일의 재설정 링크를 클릭
Then "이 링크는 만료되었습니다"가 표시됨
And 새 재설정 링크 요청 옵션이 표시됨
Scenario: 새 비밀번호 검증
Given 유효한 재설정 링크를 클릭함
When 새 비밀번호를 입력
Then 비밀번호는 최소 8자여야 함
And 대문자 하나 이상 포함
And 숫자 하나 이상 포함
And 강도 표시기 표시(약함/보통/강함)
Scenario: 성공적인 비밀번호 변경
Given 유효한 새 비밀번호를 입력함
When "비밀번호 재설정"을 클릭
Then 비밀번호가 업데이트됨
And 자동으로 로그인됨
And 확인 이메일을 받음
And 다른 모든 세션이 무효화됨
기능: 장바구니
Scenario: 빈 장바구니에 항목 추가
Given 장바구니가 비어 있음
When 가격이 $25.00인 상품을 추가
Then 장바구니에 항목 1개가 표시됨
And 소계가 $25.00로 표시됨
And 장바구니 아이콘에 "1" 배지가 표시됨
Scenario: 같은 항목 여러 번 추가
Given 장바구니에 "파란 위젯" 2개가 있음
When "파란 위젯"을 하나 더 추가
Then 장바구니에 "파란 위젯" 3개가 표시됨
And 수량이 합산됨(별도 줄 항목이 아님)
Scenario: 재고를 초과해 항목 추가
Given 상품 재고가 5개 있음
And 이미 장바구니에 5개가 있음
When 하나 더 추가하려고 함
Then "최대 구매 가능 수량에 도달했습니다"가 표시됨
And 장바구니 수량은 5로 유지됨
Scenario: 세션 간 장바구니 유지
Given 로그인 상태이며 장바구니에 항목이 있음
When 로그아웃한 뒤 다시 로그인
Then 장바구니 항목이 보존됨
And 수량이 이전과 일치함
Scenario: 장바구니 추가 후 가격 변경
Given 가격이 $50.00인 항목을 추가함
And 이후 가격이 $45.00로 변경됨
When 장바구니를 봄
Then 현재 가격 $45.00가 표시됨
And "$50.00에서 가격이 내려갔습니다" 알림이 표시됨
나쁜 수용 기준 예시
너무 모호함:
- 사용자가 비밀번호를 재설정할 수 있음
- 안전해야 함
- 좋은 사용자 경험
실패하는 이유: 테스트할 수 없습니다. "안전"은 무엇을 뜻하나요? "좋음"은 무엇인가요?
구현에 너무 구체적임:
- 유효한 이메일로 POST /api/auth/reset이 호출되면
200을 반환하고 password_reset_tokens 테이블에
SHA-256 해시 토큰이 있는 행을 삽입
실패하는 이유: 구현 세부사항을 지시합니다. 코드가 아니라 동작에 집중하세요.
예외 사례 누락:
Given 이메일을 입력
When 재설정을 클릭
Then 이메일을 받음
실패하는 이유: 이메일이 잘못되면? 등록되지 않았으면? 속도 제한에 걸리면?
불완전함:
- 사용자가 장바구니에 항목을 추가할 수 있음
실패하는 이유: 몇 개까지 추가하나요? 재고 제한은요? 유지되나요? 어떤 피드백을 보나요?
수용 기준 체크리스트
각 사용자 스토리마다 기준이 다음을 포함하는지 확인합니다.
기능 요구사항
├── □ 정상 경로(정상 성공 흐름)
├── □ 검증(입력 요구사항)
├── □ 오류 상태(잘못될 수 있는 것)
├── □ 예외 사례(경계, 제한)
├── □ 권한(가능/불가능한 사람)
└── □ 상태 전환(전/후)
비기능 요구사항
├── □ 성능(속도, 제한)
├── □ 접근성(스크린 리더, 키보드)
├── □ 보안(인증, 데이터 보호)
└── □ 호환성(브라우저, 기기)
사용자 경험
├── □ 피드백(성공/오류 메시지)
├── □ 로딩 상태(스피너, 스켈레톤)
└── □ 빈 상태(데이터 없음 시나리오)
수용 기준 표 형식
간단한 기능에는 표가 효과적일 수 있습니다.
로그인 검증:
| 입력 | 기대 결과 |
|---|---|
| 유효한 이메일 + 올바른 비밀번호 | 로그인 성공, 대시보드로 이동 |
| 유효한 이메일 + 잘못된 비밀번호 | "잘못된 자격 증명" 오류, 로그인 화면 유지 |
| 잘못된 이메일 형식 | "유효한 이메일을 입력하세요" 인라인 오류 |
| 빈 이메일 | "이메일은 필수입니다" 인라인 오류 |
| 빈 비밀번호 | "비밀번호는 필수입니다" 인라인 오류 |
| 계정 잠김(5회 이상 실패) | "계정이 잠겼습니다. 비밀번호를 재설정하거나 30분 기다리세요" |
| 인증되지 않은 이메일 계정 | "먼저 이메일을 인증하세요" + 재발송 옵션 |
완료 정의 vs 수용 기준
| 개념 | 범위 | 예시 |
|---|---|---|
| 수용 기준 | 스토리별 | "Given X, When Y, Then Z" |
| 완료 정의 | 모든 스토리 | 코드 리뷰 완료, 테스트 통과, 문서 업데이트 |
완료 정의(팀 전체):
□ 모든 수용 기준 통과
□ 코드 리뷰 및 승인 완료
□ 단위 테스트 작성(커버리지 >80%)
□ 통합 테스트 통과
□ 심각/높음 보안 이슈 없음
□ 접근성 감사 통과
□ 문서 업데이트
□ 스테이징 배포
□ 제품 관리자 승인
스토리 유형별 기준 작성
UI/프론트엔드 스토리:
- 시각 상태 포함(로딩, 오류, 성공, 빈 상태)
- 반응형 동작 명시
- 애니메이션/전환 기대치 기록
- 디자인 명세 참조
API/백엔드 스토리:
- 요청/응답 형식 포함
- 오류 코드와 메시지 명시
- 속도 제한과 할당량 정의
- 캐싱 동작 기록
통합 스토리:
- 인수인계 지점 정의
- 재시도/대체 동작 명시
- 시간 초과 기대치 문서화
- 데이터 형식 변환 명확화
안티패턴
- 요구사항이 된 기준 - 스토리 대신 수용 기준에 새 요구사항 작성
- 과도한 장식 - 스토리와 관련 없는 기준 추가
- 가정 숨기기 - 공유 지식을 가정하는 기준("일반적인 경우 처리")
- 테스트 케이스 가장 - 동작 대신 낮은 수준의 테스트 단계 작성
- 스프린트 중 변경 - 개발 시작 후 기준 추가
- 체크박스 증후군 - 항상 "충족"되는 너무 넓은 기준("작동함")
- 부정 사례 누락 - 정상 경로만 있고 오류 시나리오 없음
- 복붙 템플릿 - 모든 스토리에 같은 상투적 기준 사용
title: 디자인 인수인계 문서 impact: MEDIUM-HIGH tags: design, handoff, ui, ux, components, specifications
디자인 인수인계 문서
영향도: 중간-높음
디자인 인수인계 문서는 디자인 파일과 작동하는 코드 사이의 간극을 메웁니다. 시각 디자인을 구현 가능한 명세로 번역하여 "제가 의도한 게 아니에요"라는 재작업 사이클을 막습니다.
디자인 인수인계 구성요소
| 구성요소 | 목적 | 작성자 |
|---|---|---|
| 컴포넌트 명세 | 개별 UI 요소 세부정보 | 디자이너 |
| 상호작용 명세 | 동작, 전환, 애니메이션 | 디자이너 |
| 반응형 명세 | 브레이크포인트, 레이아웃 변경 | 디자이너 |
| 콘텐츠 명세 | 문구, 마이크로카피, 문자 제한 | 제품/디자인 |
| 접근성 명세 | WCAG 요구사항, 스크린 리더 | 디자이너 |
| 자산 인수인계 | 아이콘, 이미지, 글꼴 | 디자이너 |
| 예외 상태 명세 | 로딩, 빈 상태, 오류 상태 | 제품/디자인 |
컴포넌트 명세 템플릿
## 컴포넌트: [이름]
### 시각 참고
[스크린샷 또는 Figma 링크]
### 속성
| 속성 | 유형 | 기본값 | 옵션 |
|----------|------|---------|---------|
| variant | string | "primary" | primary, secondary, ghost |
| size | string | "medium" | small, medium, large |
| disabled | boolean | false | - |
| loading | boolean | false | - |
| icon | string | null | 아이콘 이름 또는 null |
| fullWidth | boolean | false | - |
### 구조
[컴포넌트 부분을 보여주는 다이어그램]
| 부분 | 토큰/값 | 메모 |
|------|-------------|-------|
| 컨테이너 | padding-x: 16px, padding-y: 8px | |
| 텍스트 | font-body-medium | 말줄임표로 자름 |
| 아이콘 | 20x20px | 왼쪽 정렬, 8px 간격 |
| 테두리 | 1px solid gray-300 | secondary에만 적용 |
### 상태
| 상태 | 시각 변화 | 토큰 |
|-------|---------------|-------|
| 기본 | - | bg-primary |
| 호버 | 10% 어둡게 | bg-primary-hover |
| 활성 | 20% 어둡게 | bg-primary-active |
| 포커스 | 2px 링 | ring-focus |
| 비활성 | 50% 불투명도 | opacity-50 |
| 로딩 | 텍스트를 스피너로 대체 | - |
### 반응형 동작
| 브레이크포인트 | 동작 |
|------------|----------|
| 모바일(<640px) | 전체 너비, 아이콘 세로 배치 |
| 태블릿(640-1024px) | 자동 너비, 아이콘 인라인 |
| 데스크톱(>1024px) | 고정 너비 옵션 사용 가능 |
### 접근성
- 역할: button
- 키보드: Enter/Space로 활성화
- 포커스: 보이는 포커스 링
- 스크린 리더: 로딩 상태 알림
- 색상 대비: 최소 4.5:1
### 사용 가이드라인
**해야 할 것:**
- 주요 행동에는 primary 사용
- 섹션당 primary 버튼 하나만 사용
- 비동기 작업에는 로딩 상태 사용
**하지 말 것:**
- primary 버튼을 여러 개 함께 사용하지 않기
- 이유 설명 없이 비활성화하지 않기
- 중요한 행동에 ghost를 사용하지 않기
좋은 디자인 인수인계 예시
# 디자인 인수인계: 워크스페이스 초대 모달
## 개요
워크스페이스 소유자가 새 팀원을 초대하는 모달입니다. 워크스페이스
헤더의 "초대" 버튼을 클릭하면 표시됩니다.
**Figma:** [디자인 링크]
**프로토타입:** [프로토타입 링크]
## 모달 컨테이너
| 속성 | 값 | 토큰 |
|----------|-------|-------|
| 너비 | 480px | - |
| 최대 높이 | 80vh | - |
| 패딩 | 24px | spacing-6 |
| 테두리 반경 | 12px | radius-lg |
| 배경 | 흰색 | bg-surface |
| 그림자 | 큼 | shadow-lg |
| 배경 오버레이 | 검정 50% | overlay-50 |
### 애니메이션
| 행동 | 애니메이션 |
|--------|-----------|
| 열기 | 페이드 인(200ms) + 95%에서 확대 |
| 닫기 | 페이드 아웃(150ms) |
| 배경 클릭 | 모달 닫기 |
| Escape 키 | 모달 닫기 |
## 헤더 섹션
┌─────────────────────────────────────────┐ │ [아이콘] 워크스페이스에 초대 [X] │ │ "Q4 계획"에 팀원 추가 │ └─────────────────────────────────────────┘
| 요소 | 명세 |
|---------|------|
| 제목 | text-lg, font-semibold, gray-900 |
| 부제목 | text-sm, gray-600 |
| 닫기 버튼 | 24x24, gray-500, hover: gray-700 |
| 아이콘 | 20x20, primary color |
| 제목/부제목 사이 간격 | 4px |
## 폼 섹션
### 이메일 입력
| 속성 | 값 |
|----------|-------|
| 레이블 | "이메일 주소" |
| 자리표시자 | "[email protected]" |
| 유형 | email |
| 검증 | 실시간 이메일 형식 |
| 오류 | 인라인, 입력 아래 |
**상태:**
| 상태 | 테두리 | 배경 | 텍스트 |
|-------|--------|------------|------|
| 기본 | gray-300 | white | gray-900 |
| 포커스 | primary-500 | white | gray-900 |
| 오류 | red-500 | red-50 | gray-900 |
| 비활성 | gray-200 | gray-50 | gray-500 |
### 권한 드롭다운
| 속성 | 값 |
|----------|-------|
| 레이블 | "권한 수준" |
| 기본값 | "편집자" |
| 옵션 | 뷰어, 편집자, 소유자 |
| 너비 | 전체 너비 |
**옵션 표시:**
┌─────────────────────────────────┐ │ 편집자 ▼ │ ├─────────────────────────────────┤ │ 뷰어 │ │ 콘텐츠를 볼 수 있음 │ ├─────────────────────────────────┤ │ 편집자 ✓ │ │ 콘텐츠를 보고 편집할 수 있음 │ ├─────────────────────────────────┤ │ 소유자 │ │ 설정을 포함한 전체 접근 │ └─────────────────────────────────┘
### 선택 메시지(기본 접힘)
| 속성 | 값 |
|----------|-------|
| 토글 | "개인 메시지 추가" 링크 |
| 텍스트 영역 | 기본 3행, 자동 확장 |
| 문자 제한 | 500 |
| 카운터 | 오른쪽 정렬 "0/500" |
## 푸터 섹션
┌─────────────────────────────────────────┐ │ [취소] [초대 보내기]│ └─────────────────────────────────────────┘
| 버튼 | 변형 | 동작 |
|--------|---------|----------|
| 취소 | Ghost | 모달 닫기, 작업 없음 |
| 초대 보내기 | Primary | 검증, 제출, 닫기 |
**버튼 상태:**
| 상태 | 초대 보내기 |
|-------|-------------|
| 기본 | 활성 |
| 잘못된 폼 | 비활성, 툴팁: "유효한 이메일을 입력하세요" |
| 제출 중 | 로딩 스피너, 텍스트: "보내는 중..." |
| 성공 | 모달 닫기, 토스트 표시 |
## 예외 상태
### 빈 상태
해당 없음(모달에는 항상 폼이 있음)
### 로딩 상태
이메일이 이미 구성원인지 확인하는 동안:
- 이메일 입력에 스피너 표시
- 제출 버튼 비활성화
### 오류 상태
| 오류 | 표시 |
|-------|---------|
| 잘못된 이메일 | 인라인 오류: "유효한 이메일을 입력하세요" |
| 이미 구성원 | 인라인 오류: "이 사람은 이미 구성원입니다" |
| 초대 대기 중 | 인라인 경고: "초대가 대기 중입니다. 다시 보내시겠습니까?" |
| 워크스페이스 가득 참 | 상단 배너: "구성원 한도에 도달했습니다(50/50)" |
| 네트워크 오류 | 토스트: "보낼 수 없습니다. 연결을 확인하세요." |
### 성공 상태
- 모달 닫힘
- 토스트: "[email protected]으로 초대를 보냈습니다"
- 구성원 목록 업데이트(표시 중인 경우)
## 반응형 동작
| 브레이크포인트 | 변경 |
|------------|---------|
| 데스크톱(>768px) | 중앙 모달, 너비 480px |
| 태블릿(640-768px) | 중앙 모달, 너비 90% |
| 모바일(<640px) | 아래에서 올라오는 전체 화면 드로어 |
### 모바일 드로어 세부사항
- 아래에서 위로 슬라이드
- 전체 너비, 상단 모서리 둥글게
- 최대 높이 90vh
- 드래그 닫기를 위한 상단 핸들 바
## 접근성 체크리스트
- [ ] 열려 있을 때 포커스가 모달 안에 갇힘
- [ ] 첫 포커스 가능 요소(이메일 입력)에 자동 포커스
- [ ] Escape 키로 모달 닫기
- [ ] aria-labelledby가 제목을 가리킴
- [ ] aria-describedby가 부제목을 가리킴
- [ ] 닫기 버튼에 aria-label="닫기"
- [ ] 폼 입력에 연결된 레이블 있음
- [ ] 오류 메시지가 aria-describedby로 연결됨
- [ ] 제출 버튼이 로딩 상태를 알림
## 콘텐츠 명세
| 요소 | 문구 | 문자 제한 |
|---------|------|-----------------|
| 모달 제목 | "워크스페이스에 초대" | 30 |
| 부제목 | "[워크스페이스 이름]에 팀원 추가" | 동적 |
| 이메일 레이블 | "이메일 주소" | - |
| 이메일 자리표시자 | "[email protected]" | - |
| 권한 레이블 | "권한 수준" | - |
| 메시지 토글 | "개인 메시지 추가" | - |
| 취소 버튼 | "취소" | - |
| 제출 버튼 | "초대 보내기" | - |
| 제출 로딩 | "보내는 중..." | - |
| 성공 토스트 | "[email]으로 초대를 보냈습니다" | 동적 |
## 필요한 자산
| 자산 | 형식 | 크기 |
|-------|--------|-------|
| 초대 아이콘 | SVG | 20x20 |
| 닫기 아이콘 | SVG | 24x24 |
| 드롭다운 화살표 | SVG | 12x12 |
| 체크 표시 | SVG | 16x16 |
| 스피너 | SVG/CSS | 16x16 |
## 구현 메모
- 디자인 시스템의 기존 Modal 컴포넌트 사용
- 이메일 검증은 백엔드 정규식과 일치해야 함
- API 호출을 줄이기 위해 이메일 확인 디바운스(500ms)
- 연락처 목록에서 초대하는 경우 이메일 미리 채우기
나쁜 디자인 인수인계 예시
# 초대 모달
디자인은 Figma를 보세요.
버튼은 작동해야 합니다.
모바일에서 보기 좋게 만드세요.
실패하는 이유:
- 명세 없이 참조만 있음
- 상태나 상호작용이 문서화되지 않음
- 반응형 동작 세부사항 없음
- 접근성 요구사항 없음
- 콘텐츠 명세 없음
디자인 토큰 참고
일관성을 위해 토큰 매핑을 포함합니다.
## 디자인 토큰 참고
### 간격
| 토큰 | 값 | 사용 |
|-------|-------|-----|
| spacing-1 | 4px | 촘촘한 그룹 |
| spacing-2 | 8px | 관련 요소 |
| spacing-4 | 16px | 섹션 패딩 |
| spacing-6 | 24px | 큰 간격 |
### 타이포그래피
| 토큰 | 값 | 사용 |
|-------|-------|-----|
| text-xs | 12px/16px | 캡션 |
| text-sm | 14px/20px | 보조 텍스트 |
| text-base | 16px/24px | 본문 |
| text-lg | 18px/28px | 소제목 |
| text-xl | 20px/28px | 제목 |
### 색상
| 토큰 | 값 | 사용 |
|-------|-------|-----|
| gray-50 | #F9FAFB | 배경 |
| gray-500 | #6B7280 | 보조 텍스트 |
| gray-900 | #111827 | 주요 텍스트 |
| primary-500 | #3B82F6 | 주요 행동 |
| red-500 | #EF4444 | 오류 |
| green-500 | #22C55E | 성공 |
인수인계 체크리스트
시각 명세
├── □ 모든 상태 문서화(기본, 호버, 활성, 포커스, 비활성)
├── □ 간격과 치수 명시
├── □ 타이포그래피 토큰 참조
├── □ 색상 토큰 참조
├── □ 테두리 반경과 그림자 기록
상호작용 명세
├── □ 애니메이션/전환 정의
├── □ 키보드 상호작용 기록
├── □ 터치 상호작용 기록
├── □ 로딩 상태 설계
├── □ 오류 상태 설계
├── □ 빈 상태 설계
반응형 명세
├── □ 브레이크포인트 정의
├── □ 브레이크포인트별 레이아웃 변경
├── □ 터치 대상 크기 검증(최소 44px)
접근성 명세
├── □ 색상 대비 검증(텍스트 4.5:1, UI 3:1)
├── □ 포커스 상태가 보임
├── □ 스크린 리더 콘텐츠 명시
├── □ 키보드 탐색 흐름 정의
콘텐츠 명세
├── □ 모든 문구 제공
├── □ 문자 제한 정의
├── □ 오류 메시지 작성
├── □ 성공 메시지 작성
자산
├── □ 아이콘 내보내기(SVG)
├── □ 이미지 내보내기(적절한 형식/크기)
├── □ 글꼴 문서화
안티패턴
- Figma만 있는 인수인계 - "모든 것이 Figma에 있습니다" (엔지니어링이 해석하게 만들면 안 됨)
- 상태 누락 - 정상 경로만 디자인하고 로딩/오류/빈 상태 없음
- 픽셀 완벽주의 폭정 - 플랫폼 관례를 무시할 정도로 경직된 명세
- 반응형 명세 없음 - 데스크톱 디자인만 주고 "모바일에서 작동하게"
- 접근성 사후 고려 - 감사가 실패할 때까지 a11y 명세 없음
- 문서화되지 않은 애니메이션 - 타이밍 없이 "부드럽게 느껴져야 함"
- 콘텐츠 누락 - 프로덕션에 Lorem ipsum
- 예외 사례 없음 - 100자는 어떻게 되나요? 1000자는요?
- 오래된 인수인계 - 디자인은 바뀌었는데 명세는 업데이트되지 않음
title: 예외 사례 및 오류 처리 impact: HIGH tags: edge-cases, errors, error-handling, failure-modes, resilience
예외 사례 및 오류 처리
영향도: 높음
예외 사례와 오류 처리는 다듬어진 제품과 답답한 제품을 가릅니다. 실패 모드를 미리 문서화하면 프로덕션에서의 놀라운 문제를 줄이고 디버깅 시간을 단축합니다.
예외 사례를 문서화하는 이유
- 프로덕션의 예상치 못한 문제 방지 - 알고 있는 미지수가 모르는 미지수보다 낫습니다
- QA 커버리지 가능 - 테스터는 무엇을 테스트해야 하는지 알아야 합니다
- 오류 메시지 안내 - 사용자는 도움이 되는 피드백이 필요합니다
- 모니터링 입력 - 무엇에 알림을 걸지 압니다
- 기술 부채 감소 - 처음부터 사례를 제대로 처리합니다
예외 사례 카테고리
| 카테고리 | 설명 | 예시 |
|---|---|---|
| 경계 | 최소/최대 제한 | 항목 0개, 최대 문자 수 |
| 타이밍 | 경쟁 조건, 만료 | 동시 편집, 만료된 세션 |
| 상태 | 잘못된 전환 | 결제된 주문 취소, 삭제된 항목 편집 |
| 데이터 | 누락, 손상, 예상 밖 | Null 값, 유니코드, 큰 파일 |
| 외부 | 제3자 장애 | API 중단, 시간 초과, 속도 제한 |
| 권한 | 접근 예외 사례 | 삭제된 사용자, 이전된 소유권 |
| 네트워크 | 연결 이슈 | 오프라인, 느린 연결, 시간 초과 |
| 동시성 | 동시 작업 | 이중 제출, 병렬 업데이트 |
예외 사례 문서 템플릿
## 예외 사례: [이름]
**카테고리:** [경계/타이밍/상태/데이터/외부/권한/네트워크/동시성]
**가능성:** [흔함/가끔/드묾]
**영향:** [매우 심각/높음/중간/낮음]
**시나리오:**
[구체적인 상황 설명]
**현재 동작:**
[현재 발생하는 일, 있다면]
**원하는 동작:**
[발생해야 하는 일]
**사용자 메시지:**
[표시할 정확한 오류 메시지]
**복구 조치:**
[사용자가 해결하기 위해 할 수 있는 일]
**기술 메모:**
[구현 고려사항]
포괄적인 예외 사례 예시
기능: 파일 업로드
## 예외 사례: 파일 업로드
### 경계 사례
| 사례 | 제한 | 동작 | 사용자 메시지 |
|------|-------|----------|--------------|
| 파일이 너무 큼 | 100MB 초과 | 업로드 전 거부 | "파일이 100MB 제한을 초과합니다. 압축하거나 분할해 보세요." |
| 파일 이름이 너무 김 | 255자 초과 | 해시와 함께 잘라냄 | 해당 없음(조용히 잘라냄) |
| 0바이트 파일 | 0바이트 | 허용(의도적일 수 있음) | 해당 없음 |
| 특수 문자가 있는 파일명 | 유니코드, 공백 | 백엔드에서 정리 | 해당 없음(그대로 수락, 저장 이름만 정리) |
### 타이밍 사례
| 사례 | 트리거 | 동작 | 사용자 메시지 |
|------|---------|----------|--------------|
| 업로드 시간 초과 | 5분 초과 업로드 | 이어서 재시도 | "업로드가 중단되었습니다. 이어서 진행합니다..." |
| 업로드 중 세션 만료 | 인증 토큰 만료 | 업로드 완료, 저장 시 실패 | "세션이 만료되었습니다. 로그인한 뒤 다시 시도하세요." |
| 사용자가 떠남 | 브라우저 탭 닫힘 | 업로드 취소 | 확인: "업로드가 진행 중입니다. 그래도 나가시겠습니까?" |
### 상태 사례
| 사례 | 트리거 | 동작 | 사용자 메시지 |
|------|---------|----------|--------------|
| 업로드 중 폴더 삭제 | 대상 삭제 | 폴더 생성 또는 거부 | "대상 폴더가 더 이상 존재하지 않습니다. 새 위치를 선택하세요." |
| 저장 공간 할당량 초과 | 요금제 제한 | 업로드 거부 | "저장 공간 한도에 도달했습니다. 요금제를 업그레이드하거나 파일을 삭제하세요." |
| 중복 파일명 | 같은 이름 존재 | 해결 방법 묻기 | "파일이 이미 있습니다. 바꾸기, 이름 변경, 취소 중 선택하세요." |
### 데이터 사례
| 사례 | 트리거 | 동작 | 사용자 메시지 |
|------|---------|----------|--------------|
| 손상된 파일 | 잘못된 헤더 | 업로드 시도, 보기 시 경고 | "파일이 손상되었을 수 있습니다. 그래도 업로드하시겠습니까?" |
| 지원되지 않는 형식 | 차단된 확장자 | 거부 | ".exe 파일 형식은 허용되지 않습니다. 지원 형식: pdf, doc, jpg..." |
| 악성코드 감지 | 바이러스 검사 양성 | 거부, 보안 이벤트 기록 | "잠재적으로 위험한 파일로 표시되었습니다. 지원팀에 문의하세요." |
| EXIF 데이터가 있는 이미지 | 개인정보 우려 | 업로드 시 EXIF 제거 | 해당 없음(조용히 제거) |
### 외부 의존성 사례
| 사례 | 트리거 | 동작 | 사용자 메시지 |
|------|---------|----------|--------------|
| S3 사용 불가 | AWS 장애 | 재시도 대기열 등록 | "업로드를 처리 중입니다. 완료되면 알려드리겠습니다." |
| 바이러스 스캐너 시간 초과 | 검사 서비스 느림 | 비동기 검사로 허용 | "파일이 업로드되었습니다. 보안 검사가 진행 중입니다." |
| CDN 전파 지연 | 새 파일 사용 불가 | 원본에서 표시 | 해당 없음(투명한 대체) |
### 네트워크 사례
| 사례 | 트리거 | 동작 | 사용자 메시지 |
|------|---------|----------|--------------|
| 연결 끊김 | 네트워크 끊김 | 일시 중지, 자동 재시도 | "연결이 끊겼습니다. 온라인이 되면 이어서 진행합니다." |
| 느린 업로드(<100kb/s) | 나쁜 연결 | 경고 표시, 계속 진행 | "느린 연결이 감지되었습니다. 업로드 시간이 더 걸릴 수 있습니다." |
| 패킷 손실 | 불안정한 네트워크 | 청크 재시도 | 해당 없음(투명한 재시도) |
### 동시성 사례
| 사례 | 트리거 | 동작 | 사용자 메시지 |
|------|---------|----------|--------------|
| 업로드 이중 클릭 | 사용자 조급함 | 디바운스, 단일 업로드 | 해당 없음(중복 클릭 무시) |
| 두 탭에서 같은 파일 | 여러 세션 | 둘 다 성공, 중복 감지 | "이 파일은 다른 탭에서 업로드되었습니다." |
| 다운로드 중 파일 교체 | 동시 접근 | 이전 파일 버전 제공 | 해당 없음(일관된 버전 제공) |
오류 분류
복구 가능성에 따라 오류를 구성합니다.
| 유형 | 사용자가 고칠 수 있나? | 응답 전략 |
|---|---|---|
| 검증 | 예 | 인라인 오류, 명확한 지침 |
| 인증 | 예 | 로그인으로 이동 |
| 인가 | 때때로 | 권한 설명, 접근 요청 링크 |
| 찾을 수 없음 | 때때로 | 검색 제안, "이것을 찾으셨나요" |
| 충돌 | 예 | 현재 상태와 해결 옵션 표시 |
| 속도 제한 | 기다림 | 재시도 가능 시점 표시 |
| 서버 오류 | 아니오 | 사과, 재시도 제안, 상태 페이지 링크 |
| 유지보수 | 아니오 | 예상 복구 시간 |
오류 메시지 가이드라인
구조:
[발생한 일] + [왜 중요한지] + [다음에 할 일]
좋은 오류 메시지:
| 상황 | 좋은 메시지 |
|---|---|
| 잘못된 이메일 | "유효한 이메일 주소를 입력하세요(예: [email protected])" |
| 결제 실패 | "은행에서 결제를 거절했습니다. 다른 카드를 사용하거나 은행에 문의하세요." |
| 권한 거부 | "이 문서를 수정하려면 편집자 접근 권한이 필요합니다. Sarah M.에게 접근을 요청하세요." |
| 속도 제한 | "요청이 너무 많습니다. 다시 시도하기 전에 5분 기다려 주세요." |
| 서버 오류 | "저희 쪽에서 문제가 발생했습니다. 알림을 받았으며, 몇 분 뒤 다시 시도해 주세요." |
나쁜 오류 메시지:
| 나쁨 | 이유 | 더 나은 메시지 |
|---|---|---|
| "오류" | 정보 없음 | "변경사항을 저장할 수 없습니다" |
| "오류 403" | 기술 전문용어 | "권한이 없습니다" |
| "잘못된 입력" | 모호함 | "사용자 이름은 3-20자여야 합니다" |
| "요청 실패" | 다음 단계 없음 | "요청이 실패했습니다. 연결을 확인하고 다시 시도하세요." |
| "null reference exception" | 내부 구현 노출 | "문제가 발생했습니다. 다시 시도하세요." |
오류 처리 매트릭스
| 오류 유형 | 로그 수준 | 알림? | 사용자 피드백 | 자동 재시도? |
|---|---|---|---|---|
| 검증 | Debug | 아니오 | 인라인 오류 | 아니오 |
| 인증 실패 | Info | 아니오 | 모달/이동 | 아니오 |
| 찾을 수 없음 | Warn | 패턴이면 | 빈 상태 | 아니오 |
| 속도 제한 | Warn | 과도하면 | 토스트 | 예(지연) |
| 시간 초과 | Warn | SLA 위반이면 | 토스트 | 예(3회) |
| DB 오류 | Error | 예 | 일반 오류 | 예(2회) |
| 심각 | Fatal | 예(PagerDuty) | 유지보수 페이지 | 아니오 |
실패 모드 문서화
각 통합/의존성마다:
## 의존성: Stripe Payment API
### 실패 모드
| 실패 | 감지 | 영향 | 완화책 |
|---------|-----------|--------|------------|
| API 시간 초과(10초 초과) | HTTP 시간 초과 | 결제 멈춤 | 3회 재시도 후 대기열 등록 |
| 잘못된 API 키 | 401 응답 | 모든 결제 실패 | 알림, 수동 수정 |
| 카드 거절 | 거절 코드 | 단일 결제 실패 | 사용자 메시지 표시 |
| 속도 제한 | 429 응답 | 느려짐 | 지수 백오프 |
| 웹훅 누락 | 이벤트 수신 없음 | 데이터 동기화 불일치 | 폴링 조정 |
| API 지원 중단 | 경고 헤더 | 향후 장애 | 기록, 업그레이드 계획 |
### 대체 동작
- 기본: Stripe API 직접 호출
- 재시도: 지수 백오프로 3회 시도(1초, 2초, 4초)
- 대기열: 모든 재시도 실패 시 백그라운드 처리 대기열에 등록
- 알림: 대기열 깊이 100 초과 또는 대기 시간 1시간 초과 시
- 수동: 관리자가 강제 재시도 또는 환불 가능
### 복구 절차
1. Stripe 상태 페이지 확인
2. API 키 유효성 확인
3. 속도 제한 헤더 확인
4. 최근 코드 변경 검토
5. 필요 시 Stripe 지원팀에 에스컬레이션
예외 사례 발견 체크리스트
기능을 명세할 때 다음을 묻습니다.
입력
├── □ 입력이 비어 있거나 null이면?
├── □ 입력이 최솟값이면?
├── □ 입력이 최댓값이면?
├── □ 입력이 최대를 초과하면?
├── □ 입력에 예상 밖 문자가 있으면?
├── □ 입력에 악성 콘텐츠가 있으면?
└── □ 입력 형식이 잘못되면?
타이밍
├── □ 작업이 매우 느리면?
├── □ 작업이 시간 초과되면?
├── □ 작업이 중단되면?
├── □ 작업 중 세션이 만료되면?
└── □ 두 사용자가 동시에 작업하면?
상태
├── □ 리소스가 존재하지 않으면?
├── □ 리소스가 방금 삭제되었으면?
├── □ 리소스가 잠겼거나 사용 중이면?
├── □ 작업 중 사용자가 권한을 잃으면?
└── □ 관련 리소스가 바뀌면?
외부
├── □ 제3자 서비스가 중단되면?
├── □ 제3자가 예상 밖 데이터를 반환하면?
├── □ 네트워크를 사용할 수 없으면?
└── □ 응답이 느리면?
안티패턴
- 정상 경로만 문서화 - 성공 시나리오만 문서화
- 일반 오류 - 모든 상황에 "문제가 발생했습니다" 사용
- 조용한 실패 - 피드백 없이 오류를 삼킴
- 기술 메시지 - 사용자에게 스택 추적 표시
- 처리되지 않은 상태 - 영원히 로딩, 빈 상태 없음
- 일관성 없는 처리 - 같은 오류를 다르게 처리
- 복구 경로 없음 - 해결 안내 없는 오류
- 과도한 알림 - 모든 오류가 온콜을 호출
- 부족한 로깅 - 문제가 생겼을 때 감사 추적 없음
- 사용자 탓 - 무엇이 유효한지 설명하지 않고 "잘못된 입력"만 표시
title: 문서 유지관리 impact: MEDIUM tags: maintenance, versioning, deprecation, lifecycle, documentation
문서 유지관리
영향도: 중간
유지관리되지 않는 문서는 문서가 없는 것보다 나쁩니다. 적극적으로 오해를 만듭니다. 명세를 최신으로 유지하고, 변경을 버전 관리하며, 오래된 콘텐츠를 지원 중단하는 명확한 프로세스를 세우세요.
문서 생명주기
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 초안 │───▶│ 리뷰 │───▶│ 활성 │───▶│ 오래됨 │───▶│ 보관 │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │ │
작성 중 피드백 및 사용 중 오래되어 과거 참고용
승인 참고 자료 업데이트 필요 으로만 사용
문서 상태 시스템
모든 명세 문서에는 상태가 있어야 합니다.
| 상태 | 배지 | 의미 | 필요한 조치 |
|---|---|---|---|
| 초안 | [DRAFT] | 작업 중 | 의존하지 말 것 |
| 리뷰 | [REVIEW] | 피드백 준비 완료 | 기한까지 피드백 제공 |
| 활성 | 없음 | 현재 정확함 | 참고 자료로 사용 |
| 업데이트 필요 | [NEEDS UPDATE] | 오래된 것으로 알려짐 | 사용 전 업데이트 |
| 지원 중단 | [DEPRECATED] | 단계적으로 폐기 중 | 대체 문서 참고 |
| 보관됨 | [ARCHIVED] | 과거 참고용 | 사용하지 말 것 |
문서 헤더 템플릿
---
title: [문서 제목]
status: active
version: 1.2.0
last_updated: 2024-01-15
owner: @username
reviewers: @reviewer1, @reviewer2
related_docs:
- [PRD: 기능 이름](link)
- [기술 명세](link)
---
# 문서 제목
> **상태:** 활성
> **버전:** 1.2.0
> **마지막 업데이트:** 2024년 1월 15일
> **담당:** 제품 팀(@username)
## 변경 로그
| 버전 | 날짜 | 작성자 | 변경사항 |
|---------|------|--------|---------|
| 1.2.0 | 2024-01-15 | @username | 모바일 명세 추가 |
| 1.1.0 | 2024-01-10 | @username | 오류 처리 업데이트 |
| 1.0.0 | 2024-01-05 | @username | 최초 릴리스 |
버전 관리 전략
문서용 의미적 버전 관리:
| 버전 | 올릴 때 | 변경 예시 |
|---|---|---|
| 주 버전 (X.0.0) | 호환성을 깨는 변경, 대규모 재구조화 | 완전히 새로운 접근 방식 |
| 부 버전 (x.Y.0) | 새 섹션, 의미 있는 추가 | API 섹션 추가 |
| 패치 (x.y.Z) | 수정, 명확화 | 오타 수정, 날짜 업데이트 |
새 버전 작성 vs. 업데이트 기준:
- 같은 문서: 작은 명확화, 수정
- 새 버전: 접근 방식이나 범위의 의미 있는 변경
- 새 문서: 완전히 다른 기능 또는 요구사항
리뷰 일정
| 문서 유형 | 리뷰 빈도 | 트리거 이벤트 |
|---|---|---|
| PRD | 마일스톤별 | 범위 변경, 방향 전환 |
| 기술 명세 | 릴리스별 | 아키텍처 변경 |
| API 명세 | 버전별 | 호환성을 깨는 변경 |
| 사용자 스토리 | 스프린트별 | 정제 변경 |
| 런북 | 분기별 | 인시던트 학습 |
| 스타일 가이드 | 반기별 | 디자인 시스템 업데이트 |
문서 리뷰 체크리스트
## 분기 문서 리뷰: [문서 이름]
**리뷰어:** @username
**리뷰 날짜:** 2024-01-15
**문서 버전:** 1.2.0
### 정확성 확인
- [ ] 모든 링크가 작동함(404 없음)
- [ ] 코드 예시가 여전히 작동함
- [ ] 스크린샷이 현재 UI와 일치함
- [ ] 참조된 기능이 여전히 존재함
- [ ] 지표/숫자가 최신임
### 완전성 확인
- [ ] 현재 범위를 다룸(큰 누락 없음)
- [ ] 예외 사례가 여전히 관련 있음
- [ ] 오류 처리가 여전히 정확함
- [ ] 통합 지점이 최신임
### 관련성 확인
- [ ] 여전히 목적을 수행함
- [ ] 대상 독자가 여전히 필요로 함
- [ ] 다른 문서로 대체되지 않음
### 조치 항목
- [ ] [필요한 구체적 업데이트 목록]
- [ ] [담당자와 날짜 배정]
### 판정
- [ ] **활성:** 변경 필요 없음
- [ ] **업데이트 필요:** 구체적 업데이트 식별
- [ ] **지원 중단:** 대체되었거나 더 이상 필요 없음
지원 중단 프로세스
문서를 폐기할 때:
# [DEPRECATED] 원본 문서 제목
> **지원 중단:** 이 문서는 더 이상 유지관리되지 않습니다.
> **이유:** [새 문서 제목](link)으로 대체됨
> **지원 중단일:** 2024년 1월 15일
> **제거일:** 2024년 4월 15일
---
## 이 문서가 지원 중단된 이유
[이 문서가 단계적으로 폐기되는 이유에 대한 짧은 설명]
## 마이그레이션 가이드
| 이전 개념 | 새 위치 | 메모 |
|-------------|--------------|-------|
| 섹션 A | [새 문서, 섹션 X](link) | 대부분 변경 없음 |
| 섹션 B | 제거됨 | 더 이상 적용되지 않음 |
| 섹션 C | [다른 문서](link) | 크게 업데이트됨 |
## 아래 원본 콘텐츠
[전환 기간 동안 참고할 수 있도록 원본 콘텐츠 유지]
문서 부채 지표
문서가 기술 부채가 되고 있다는 신호:
| 지표 | 위험 신호 | 조치 |
|---|---|---|
| 나이 | 6개월 이상 업데이트 없음 | 리뷰 일정 잡기 |
| 정확성 | 알려진 부정확성이 수정되지 않음 | 수정 우선순위화 |
| 완전성 | 주요 섹션이 "추후 결정"으로 표시 | 완료 또는 제거 |
| 관련성 | 기능이 지원 중단됨 | 문서 보관 |
| 소유권 | 명확한 담당자 없음 | 담당자 배정 |
| 사용량 | 3개월 동안 조회 없음 | 보관 검토 |
| 피드백 | 같은 주제에 대한 반복 질문 | 명확성 개선 |
문서 유지관리 자동화
자동 확인:
| 확인 | 빈도 | 알림 |
|---|---|---|
| 깨진 링크 | 매주 | Slack 알림 |
| 오래된 문서(X일 동안 업데이트 없음) | 매월 | 담당자에게 이메일 |
| 고아 문서(연결된 링크 없음) | 분기별 | 리뷰 대기열 |
| 담당자 누락 | 변경 시 | 병합 차단 |
| 만료된 리뷰 날짜 | 매일 | 알림 통지 |
권장 도구:
- 링크 검사기: 대부분의 문서 플랫폼에 내장
- 최신성 추적: 사용자 정의 메타데이터 + 스크립트
- 분석: 문서 플랫폼 분석 또는 사용자 정의
- 소유권: 문서용 CODEOWNERS 스타일 파일
좋은 문서 유지관리 예시
# 문서 유지관리 로그: 워크스페이스 공유
## 활성 문서
| 문서 | 버전 | 마지막 업데이트 | 다음 리뷰 | 담당 |
|----------|---------|--------------|-------------|-------|
| PRD | 2.1.0 | 2024-01-15 | 2024-04-15 | @pm |
| 기술 명세 | 1.3.0 | 2024-01-10 | 2024-02-10 | @eng |
| API 명세 | 1.0.0 | 2024-01-05 | 2024-04-05 | @eng |
| 런북 | 1.1.0 | 2024-01-12 | 2024-04-12 | @sre |
## 최근 변경
### 2024년 1월
**PRD v2.1.0 (1월 15일)**
- 모바일 공유 흐름 요구사항 추가
- 베타 학습을 바탕으로 성공 지표 업데이트
- 지원 중단된 "공개 링크" 참조 제거
**기술 명세 v1.3.0 (1월 10일)**
- 캐싱 계층이 포함된 아키텍처 다이어그램 업데이트
- 성능 최적화 섹션 추가
- 잘못된 데이터베이스 스키마 수정
**런북 v1.1.0 (1월 12일)**
- 초대 이메일 지연 문제 해결 추가
- 에스컬레이션 연락처 업데이트
## 예정 리뷰
- [ ] 기술 명세 리뷰 기한 2월 10일(@eng)
- [ ] 분기 PRD 리뷰 기한 4월 15일(@pm)
## 보관된 문서
| 문서 | 보관일 | 이유 | 대체 |
|----------|---------------|--------|-------------|
| 공유 RFC | 2024-01-01 | 명세로 변환 | 기술 명세 v1.0 |
| 디자인 브리프 | 2024-01-05 | PRD에 통합 | PRD 섹션 5 |
## 열린 문서 부채
| 항목 | 우선순위 | 담당 | 기한 |
|------|----------|-------|-----|
| API 명세에 모바일 예시 추가 | 중간 | @eng | 1월 31일 |
| 새 UI에 맞춰 스크린샷 업데이트 | 낮음 | @design | 2월 15일 |
나쁜 문서 유지관리 예시
# 공유 기능 문서
마지막 업데이트: 아마 작년 언젠가
참고:
- sharing-spec.doc (누군가의 컴퓨터에 있음)
- 위키 페이지(어느 페이지인지는 확실하지 않음)
- Jake에게 물어보세요, 그가 압니다
TODO: 이것 업데이트
실패하는 이유:
- 버전 관리 없음
- 명확한 위치 없음
- 소유권 없음
- 리뷰 프로세스 없음
- 정확성 불확실
문서 정리 의식
매월:
- "업데이트 필요" 태그 문서 리뷰
- 깨진 링크 확인
- 오래된 스크린샷 업데이트
분기별:
- 활성 문서 전체 리뷰
- 완료된 프로젝트 문서 보관
- 문서 색인 업데이트
릴리스마다:
- 버전 번호 업데이트
- 변경 로그 항목 추가
- 모든 참조 문서 리뷰
프로젝트 완료마다:
- 프로젝트별 문서 보관
- 학습 내용을 영구 문서로 추출
- 패턴이 생긴 경우 스타일 가이드 업데이트
안티패턴
- 한 번 쓰고 다시 안 읽음 - 문서를 만들고 잊음
- 신성한 문서 - "공식" 문서 업데이트를 두려워함
- 부족 지식 - 문서화 대신 "모두가 알아요"
- 문서 확산 - 같은 정보가 여러 곳에 있음
- 완벽주의 마비 - 완벽해질 때까지 게시하지 않음
- 담당자 없는 문서 - 유지관리자가 배정되지 않은 문서
- 가짜 최신성 - 실제 리뷰 없이 날짜만 업데이트
- 보관 회피 - 오래된 문서를 "혹시 몰라" 남김
- 리뷰 연극 - 실제로 읽지 않고 리뷰 완료 표시
- 위키 황야 - 구조가 없어 검색만이 유일한 탐색 수단
title: 성공 지표 정의 impact: HIGH tags: metrics, kpis, measurement, analytics, success-criteria
성공 지표 정의
영향도: 높음
성공 지표는 가장 중요한 질문에 답합니다. "우리가 올바른 것을 만들었는가?" 명확한 지표가 없으면 기능을 허공에 출시하게 되고, 효과적으로 학습하거나 반복할 수 없습니다.
지표를 미리 정의해야 하는 이유
- 정렬 - 모두가 성공의 모습에 동의
- 우선순위화 - 지표를 움직이는 것에 집중
- 학습 - 무엇이 작동하고 무엇이 작동하지 않는지 이해
- 책임 - 성과에 대한 명확한 소유권
- 자원 배분 - 데이터 기반 투자 결정
지표 유형
| 유형 | 목적 | 예시 | 기간 |
|---|---|---|---|
| 주요 | 핵심 성공 측정 | 전환율 | 기능별 |
| 보조 | 보조 지표 | 첫 행동까지 시간 | 기능별 |
| 가드레일 | 회귀 방지 | 페이지 로드 시간 | 항상 |
| 입력 | 선행 지표 | 가입 시도 | 단기 |
| 출력 | 후행 결과 | 매출 | 장기 |
지표 계층
┌─────────────────────┐
│ 북극성 지표 │
│ (회사 성공) │
└──────────┬──────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐
│ 축 1 │ │ 축 2 │ │ 축 3 │
│ 획득 │ │ 활성화 │ │ 유지 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐
│ 기능 │ │ 기능 │ │ 기능 │
│ 지표 │ │ 지표 │ │ 지표 │
└─────────────┘ └─────────────┘ └─────────────┘
SMART 지표 프레임워크
모든 지표는 다음을 충족해야 합니다.
| 기준 | 질문 | 나쁜 예 | 좋은 예 |
|---|---|---|---|
| S 구체적 | 정확히 무엇을 측정하는가? | "참여" | "DAU/MAU 비율" |
| M 측정 가능 | 실제로 추적할 수 있는가? | "사용자 행복" | "NPS 점수" |
| A 달성 가능 | 목표가 현실적인가? | "100% 유지" | "30일 유지율 85%" |
| R 관련성 있음 | 이 기능에 중요한가? | "전체 사용자 수"(틈새 기능에 대해) | "기능 도입률" |
| T 기한 있음 | 언제 측정하는가? | "언젠가 개선" | "Q2까지 10% 증가" |
지표 정의 템플릿
## 지표: [이름]
### 정의
| 속성 | 값 |
|----------|-------|
| 이름 | 활성 팀 워크스페이스 |
| 정의 | 최근 7일 내 활동이 있고 구성원이 2명 이상인 워크스페이스 |
| 공식 | COUNT(workspaces WHERE member_count >= 2 AND last_activity > now() - 7d) |
| 유형 | 주요 |
| 담당 | 제품 팀 |
### 맥락
**이 지표가 중요한 이유:**
팀이 공유 기능에서 가치를 얻고 있는지 측정합니다.
팀 요금제 유지와 직접 연결됩니다.
**비즈니스 목표와의 연결:**
북극성 지표: 주간 활성 팀
축: 팀 활성화
이 지표: 기능 수준 성공
### 측정
**데이터 출처:** 분석 웨어하우스(Snowflake)
**계산 빈도:** 매일
**대시보드:** [대시보드 링크]
**알림 임계값:** 3일 연속 목표의 90% 미만
### 목표
| 기간 | 기준선 | 목표 | 도전 목표 |
|--------|----------|--------|---------|
| 출시(1주 차) | 0 | 100 | 200 |
| 1개월 | 100 | 500 | 750 |
| 3개월 | 500 | 2,000 | 3,000 |
| 6개월 | 2,000 | 5,000 | 7,500 |
### 세그먼트
이 지표를 다음 기준으로 추적합니다.
- 요금제 유형(무료, Teams, Enterprise)
- 회사 규모(1-10, 11-50, 51-200, 200+)
- 획득 코호트(가입 월)
- 지역(US, EU, APAC, 기타)
### 주의사항
- 테스트/데모 계정 제외
- "활동" = 생성/편집/보기 이벤트 중 하나
- 조작 위험: 낮음(활동 정의가 넓음)
좋은 지표 명세 예시
# 성공 지표: 워크스페이스 공유 기능
## 개요
| 지표 유형 | 지표 | 목표 | 측정 |
|-------------|--------|--------|-------------|
| 주요 | 팀 활성화율 | 30% | 공유를 사용하는 Teams 계정 비율 |
| 보조 | 첫 초대까지 시간 | 24시간 미만 | 워크스페이스 생성 후 중앙값 |
| 가드레일 | 워크스페이스 로드 시간 | p95 2초 미만 | 공유 추가로 인한 회귀 없음 |
| 입력 | 발송된 초대 | 주 1,000건 | 도입의 선행 지표 |
## 주요 지표: 팀 활성화율
### 정의
가입 후 30일 안에 하나 이상의 워크스페이스에 구성원을 한 명 이상
초대한 Teams 요금제 계정의 비율입니다.
### 공식
team_activation_rate = COUNT(teams_accounts WHERE invited_member = true AND account_age <= 30d) / COUNT(teams_accounts WHERE account_age <= 30d)
### 이 지표를 쓰는 이유
- 팀이 가치를 얻고 있는지 직접 측정
- 유지율 예측 가능(상관 0.7)
- 실행 가능(초대 흐름을 개선할 수 있음)
- 조작하기 어려움(실제 행동 필요)
### 현재 상태
- 기준선: 해당 없음(새 기능)
- 비교 가능 지표: 이메일 초대율 = 15%
- 업계 벤치마크: 팀 기능 약 25-35%
### 목표
| 마일스톤 | 목표 | 근거 |
|-----------|--------|-----------|
| 1주 차 | 5% | 초기 도입자만 |
| 1개월 | 15% | 인지도와 함께 증가 |
| 3개월 | 25% | 학습 기반 최적화 |
| 6개월 | 35% | 성숙한 도입 |
### 세분화
- 요금제 등급별(Teams vs Enterprise)
- 회사 규모별
- 획득 채널별
- 산업군별
## 보조 지표: 첫 초대까지 시간
### 정의
결국 초대가 있는 워크스페이스에서, 워크스페이스 생성부터
첫 초대 발송까지 걸린 시간의 중앙값입니다.
### 공식
time_to_first_invite = MEDIAN( first_invitation_timestamp - workspace_created_timestamp WHERE workspace.invitation_count > 0 )
### 이 지표를 쓰는 이유
- 초대 흐름의 마찰 측정
- 빠를수록 더 나은 사용자 경험
- 특정 단계를 최적화할 수 있음
### 현재 상태
- 기준선: 해당 없음(새 기능)
- 가설: 24시간 미만이면 건강함
### 목표
| 마일스톤 | 목표 | 근거 |
|-----------|--------|-----------|
| 출시 | 48시간 미만 | 초기 발견 |
| 1개월 | 24시간 미만 | 개선된 온보딩 |
| 3개월 | 12시간 미만 | 간소화된 흐름 |
## 가드레일 지표: 워크스페이스 로드 시간
### 정의
워크스페이스 페이지 로드 시간의 95번째 백분위입니다.
### 이 지표를 쓰는 이유
공유 기능이 핵심 경험을 회귀시키지 않도록 보장합니다.
### 임계값
- 현재 기준선: p95 1.8초
- 최대 허용: p95 2.0초
- 알림 조건: 15분 동안 2.0초 초과
### 위반 시 조치
1. 공유 기능 롤백
2. 성능 조사
3. 다시 활성화하기 전에 최적화
## 입력 지표: 발송된 초대
### 정의
주당 발송된 워크스페이스 초대 총수입니다.
### 이 지표를 쓰는 이유
도입의 선행 지표입니다. 초대가 줄어들면 활성화율에 영향이 가기 전에 조사합니다.
### 목표
| 주 | 목표 | 메모 |
|------|--------|-------|
| 1 | 100 | 내부 + 알파 |
| 2 | 300 | 베타 확대 |
| 3 | 700 | 더 넓은 베타 |
| 4 | 1,500 | 정식 출시(GA) |
| 정상 상태 | 주 2,000건 이상 | 지속 |
## 지표 의존성
발송된 초대(입력) │ ▼ 수락된 초대(전환) │ ▼ 활성 팀 워크스페이스(참여) │ ▼ 팀 활성화율(주요) │ ▼ 팀 요금제 유지(비즈니스 성과)
## 대시보드 요구사항
### 실시간 보기
- 발송된 초대(최근 24시간)
- 오류율
- 로드 시간
### 일간 보기
- 활성화율 추세
- 첫 초대까지 시간 분포
- 퍼널 전환율
### 주간 보기
- 세그먼트별 분해
- 코호트 분석
- 기능 사용 패턴
## 분석 이벤트
| 이벤트 | 트리거 | 속성 |
|-------|---------|------------|
| `invitation_sent` | 초대 제출 | workspace_id, permission_level |
| `invitation_accepted` | 초대 클릭 및 수락 | workspace_id, days_pending |
| `invitation_expired` | 7일 만료 | workspace_id |
| `member_added` | 사용자가 워크스페이스에 참여 | workspace_id, source |
| `member_removed` | 사용자가 제거/탈퇴 | workspace_id, reason |
나쁜 지표 명세 예시
# 지표: 공유 기능
다음을 추적합니다.
- 사용자 수
- 참여
- 고객 만족도
성공 = 사람들이 사용함.
실패하는 이유:
- 구체적 정의 없음
- 목표나 기준선 없음
- "참여"와 "만족도"가 정의되지 않음
- 기간 없음
- 측정 계획 없음
기능 유형별 흔한 지표
| 기능 유형 | 주요 지표 | 보조 지표 |
|---|---|---|
| 온보딩 | 활성화율 | 첫 가치까지 시간, 이탈 지점 |
| 참여 | DAU/MAU 비율 | 세션 길이, 기능 사용량 |
| 전환 | 전환율 | 퍼널 단계별 비율, 전환까지 시간 |
| 유지 | D7/D30 유지율 | 이탈률, 재활성화율 |
| 매출 | ARPU, LTV | 업그레이드율, 확장 매출 |
| 성능 | 페이지 로드 시간 | 오류율, 가용성 |
| 지원 | 티켓량 | 해결 시간, CSAT |
피해야 할 지표 함정
| 함정 | 문제 | 해결책 |
|---|---|---|
| 허영 지표 | 중요하지 않은 큰 숫자 | 실행 가능한 지표에 집중 |
| 지표 조작 | 실제 진전 없이 지표만 개선 | 여러 지표 사용, 행동 모니터링 |
| 단기주의 | 지표 최적화가 장기 성과를 해침 | 가드레일 지표 추가 |
| 과도한 측정 | 모든 것을 추적하지만 아무것도 실행하지 않음 | 핵심 지표 3-5개로 제한 |
| 오래된 목표 | 학습에 따라 목표가 진화하지 않음 | 분기별 리뷰 |
지표 리뷰 주기
| 빈도 | 리뷰 | 참여자 |
|---|---|---|
| 매일 | 실시간 대시보드 | 엔지니어링 |
| 매주 | 지표 추세, 이상 징후 | 제품 + 엔지니어링 |
| 매월 | 목표 대비 진척 | 리더십 |
| 분기별 | 목표 재보정 | 임원 팀 |
안티패턴
- 지표 연극 - 지표를 추적하지만 행동하지 않음
- 골대 옮기기 - 목표를 놓치자 목표를 바꿈
- 생존자 편향 - 남아 있는 사용자만 측정
- 상관관계 혼동 - 상관관계에서 인과를 가정
- 국소 최적화 - 전체 경험을 해치면서 한 지표만 개선
- 지표 부채 - 아무도 쓰지 않는 측정을 쌓음
- 정의되지 않은 기준선 - 시작점을 모른 채 목표 설정
- 누락에 의한 성공 - 실패를 보여줄 수 있는 것을 측정하지 않음
- 대리 지표 집착 - 실제 성과 대신 대리 지표를 최적화
- 출시하고 잊기 - 출시 때 지표를 정하고 다시 보지 않음
title: 제품 요구사항 문서 작성 impact: CRITICAL tags: prd, requirements, product, vision, scope
제품 요구사항 문서 작성
영향도: 매우 높음
제품 요구사항 문서(PRD)는 무엇을 왜 만드는지에 대한 단일 진실 공급원입니다. 이해관계자를 정렬하고, 범위 증가를 막고, 엔지니어링이 좋은 결정을 내릴 수 있는 맥락을 제공합니다.
PRD의 목적
PRD는 다음 질문에 답합니다.
- 왜 이것을 만드는가? (문제, 기회)
- 무엇을 만드는가? (솔루션, 범위)
- 누구를 위해 만드는가? (사용자, 페르소나)
- 작동한다는 것을 어떻게 알 수 있는가? (지표, 성공 기준)
- 제약은 무엇인가? (일정, 자원, 의존성)
PRD 구조 프레임워크
| 섹션 | 목적 | 길이 |
|---|---|---|
| 임원 요약 | 빠르게 훑는 사람을 위한 개요 | 2-3문장 |
| 문제 설명 | 이것이 중요한 이유 | 1-2문단 |
| 목표 및 성공 지표 | 성공 측정 방식 | 글머리표 목록 |
| 사용자 페르소나 | 누구를 위해 만드는가 | 페르소나 1-2개 |
| 사용자 스토리 | 사용자가 할 수 있는 것 | 스토리 5-15개 |
| 범위 | 포함되는 것과 제외되는 것 | 목록 2개 |
| 요구사항 | 상세 필요사항 | 분류된 목록 |
| 제약 | 제한과 의존성 | 글머리표 목록 |
| 일정 | 핵심 마일스톤 | 표 또는 목록 |
| 열린 질문 | 미해결 결정 | 글머리표 목록 |
| 부록 | 보조 연구, 목업 | 필요 시 |
문제 설명 공식
템플릿:
[사용자 세그먼트]는 [목표]를 시도할 때 [문제]로 어려움을 겪습니다.
현재 이들은 [우회 방법]을 사용하며, 그 결과 [고통/비용]이 발생합니다.
[솔루션]을 만들면 [이점]을 제공할 수 있고, 이는 [비즈니스 영향]으로 이어집니다.
좋은 PRD 예시
# PRD: 팀 워크스페이스 공유
## 임원 요약
세분화된 권한으로 팀이 워크스페이스를 공유할 수 있게 하여,
현재의 워크스페이스 복제와 수동 접근 관리 마찰을 줄입니다.
## 문제 설명
구성원이 5명 이상인 팀은 워크스페이스 협업에 어려움을 겪습니다. 현재
사용자는 워크스페이스를 복제하거나 계정 자격 증명을 공유하며, 이로 인해:
- 접근 이슈 관련 지원 티켓이 40%
- 팀당 평균 중복 워크스페이스 3.2개
- 공유 자격 증명으로 인한 보안 위험
고객 인터뷰(n=23)에서 이 기능이 요청 1위로 확인되었습니다.
## 목표 및 성공 지표
**주요 목표:** 출시 후 90일 안에 워크스페이스 중복을 60% 줄입니다.
| 지표 | 현재 | 목표 | 측정 |
|--------|---------|--------|-------------|
| 팀당 중복 워크스페이스 | 3.2 | 1.3 | 분석 |
| 접근 관련 지원 티켓 | 40% | 15% | Zendesk |
| 팀 협업 NPS | 32 | 50 | 설문 |
**보조 목표:**
- 팀 요금제 업그레이드 25% 증가
- 새 팀원의 협업 시작 시간 50% 단축
## 사용자 페르소나
**주요: 팀 리드(Sarah)**
- 팀원 5-10명 관리
- 누가 무엇을 볼 수 있는지 제어해야 함
- 고통: 접근 권한을 수동으로 관리하는 데 주 2시간 이상 사용
**보조: 팀원(Alex)**
- 기존 팀에 합류
- 관련 워크스페이스에 빠르게 접근해야 함
- 고통: 접근 요청에 1-2일 대기
## 사용자 스토리
[상세 형식은 stories-* 규칙 참고]
1. 팀 리드로서, 중복을 만들지 않고 협업할 수 있도록
내 워크스페이스에 구성원을 초대하고 싶습니다
2. 팀 리드로서, 누가 편집할 수 있고 누가 볼 수만 있는지 제어할 수 있도록
권한 수준을 설정하고 싶습니다
3. 팀원으로서, 필요한 것을 빠르게 찾을 수 있도록
나와 공유된 모든 워크스페이스를 보고 싶습니다
## 범위
**범위 내(v1):**
- 이메일로 워크스페이스 초대
- 세 가지 권한 수준: 소유자, 편집자, 뷰어
- 초대 관리(철회, 수정)
- UI의 공유 워크스페이스 표시
**범위 밖(v1):**
- 공개/링크 공유
- 워크스페이스 이전(소유자 변경)
- 권한 상속(폴더)
- 게스트 접근(외부 사용자)
## 요구사항
### 기능 요구사항
- FR-1: 사용자는 워크스페이스당 최대 50명의 구성원을 초대할 수 있음
- FR-2: 초대는 수락되지 않으면 7일 후 만료됨
- FR-3: 소유자는 언제든 권한을 수정할 수 있음
- FR-4: 구성원은 초대 시 이메일 알림을 받음
### 비기능 요구사항
- NFR-1: 초대 흐름은 2초 미만에 완료됨
- NFR-2: 권한 변경은 30초 안에 적용됨
- NFR-3: 워크스페이스 구성원 1000명 동시 지원
## 제약
**기술:**
- 기존 인증 시스템(Auth0)과 통합해야 함
- 현재 워크스페이스 데이터 모델을 크게 변경할 수 없음
**비즈니스:**
- 엔터프라이즈 영업 추진을 위해 Q3 전에 출시해야 함
- 디자인 자원은 2주로 제한됨
**의존성:**
- 이메일 서비스 용량 증설(운영팀)
- 그룹 클레임을 위한 Auth0 요금제 업그레이드
## 일정
| 마일스톤 | 날짜 | 산출물 |
|-----------|------|-------------|
| 디자인 완료 | 6월 15일 | Figma 명세 승인 |
| API 완료 | 7월 1일 | 백엔드 통합 준비 완료 |
| 베타 출시 | 7월 15일 | Teams 요금제에 10% 단계적 배포 |
| 정식 출시(GA) | 8월 1일 | 100% 단계적 배포 |
## 열린 질문
- [ ] 무료 요금제에 공유가 있어야 하는가? (결정: 제품 리더십)
- [ ] 워크스페이스당 최대 구성원 수? (제안: 50명, 엔지니어링 리뷰 대기)
- [ ] 이메일 템플릿 소유권? (마케팅 또는 제품?)
## 부록
- [고객 인터뷰 요약](link)
- [경쟁 분석](link)
- [디자인 탐색](link)
나쁜 PRD 예시
# PRD: 공유 기능
## 개요
고객이 원하므로 워크스페이스에 공유를 추가해야 합니다.
## 요구사항
- 공유 추가
- 빠르게 만들기
- 안전해야 함
- 사용자가 좋아해야 함
## 일정
최대한 빨리 - 우선순위가 높음
## 메모
무엇이 가능한지 엔지니어링과 이야기하기.
실패하는 이유:
- 문제 설명이나 비즈니스 근거가 없음
- 성공 지표나 수용 기준이 없음
- 모호한 요구사항("빠르게", "안전하게", "좋아해야")
- 범위 경계가 없음. 모든 것이 암시됨
- 사용자 맥락이나 페르소나가 없음
- 마일스톤 없는 일정
PRD 리뷰 체크리스트
PRD를 공유하기 전에 확인합니다.
□ 문제가 데이터 또는 연구로 검증됨
□ 성공 지표가 구체적이고 측정 가능함
□ 범위가 포함 항목뿐 아니라 제외 항목도 명확히 설명함
□ 모든 사용자 페르소나가 스토리에 반영됨
□ 기술 제약이 문서화됨
□ 의존성이 담당자와 함께 식별됨
□ 일정에 구체적 마일스톤이 있음
□ 열린 질문이 명시적으로 나열됨
□ 승인할 이해관계자가 식별됨
PRD 버전 관리
| 버전 | 트리거 | 문서화 |
|---|---|---|
| 초안 | 최초 작성 | 제목에 "[초안]" 표시 |
| 리뷰 | 피드백 준비 완료 | 이해관계자와 공유 |
| 승인됨 | 이해관계자 승인 | 승인자와 날짜 기록 |
| 업데이트됨 | 범위 변경 | 변경 로그 섹션, 버전 증가 |
| 보관됨 | 기능 출시/취소 | "[보관됨]" 표시 |
안티패턴
- 소설 - 아무도 읽지 않는 30페이지 PRD. 훑어보기 쉽게 유지하세요.
- 소원 목록 - 고객이 한 번이라도 말한 모든 것. 냉정하게 우선순위를 정하세요.
- 솔루션 명세 - "무엇"과 "왜"를 정하기 전에 "어떻게"로 뛰어듦
- 고아 문서 - 명확한 소유자나 이해관계자가 없는 PRD
- 얼어붙은 문서 - 학습이 생겨도 업데이트되지 않는 PRD
- 가정 공장 - 검증 없이 "우리는 생각한다"만 있는 문장
- 기능 공장 - 비즈니스 성과 없는 요구사항
- Jira로 만든 명세 - 연결 서사가 없는 티켓 모음
title: 기능 플래그 및 단계적 배포 계획 impact: MEDIUM-HIGH tags: feature-flags, rollout, deployment, experiments, releases
기능 플래그 및 단계적 배포 계획
영향도: 중간-높음
기능 플래그와 단계적 배포 계획은 위험한 일괄 릴리스를 통제 가능하고 되돌릴 수 있는 배포로 바꿉니다. 실험, 점진적 노출, 즉시 롤백을 가능하게 합니다.
기능 플래그가 필요한 이유
- 위험 감소 - 버그의 영향 범위를 제한
- 빠른 반복 - 기능을 노출하지 않고 코드 배포
- 실험 - 실제 사용자로 A/B 테스트
- 운영 제어 - 배포 없이 기능 비활성화
- 고객 제어 - 특정 계정에만 활성화
기능 플래그 유형
| 유형 | 목적 | 수명 | 예시 |
|---|---|---|---|
| 릴리스 | 미출시 기능 게이트 | 짧음(몇 주) | sharing_v2_enabled |
| 실험 | A/B 테스트 | 짧음(며칠-몇 주) | checkout_flow_experiment |
| 운영 | 회로 차단기, 부하 차단 | 영구 | heavy_report_enabled |
| 권한 | 요금제/역할 기반 접근 | 영구 | enterprise_analytics |
| 긴급 차단 스위치 | 비상 비활성화 | 영구 | third_party_integration |
기능 플래그 이름 규칙
{scope}_{feature}_{purpose}
예시:
- release_workspace_sharing_enabled
- experiment_pricing_page_variant
- ops_batch_processing_enabled
- permission_custom_domains_access
- kill_switch_stripe_integration
기능 플래그 명세 템플릿
## 기능 플래그: [이름]
### 개요
| 속성 | 값 |
|----------|-------|
| 플래그 이름 | `release_workspace_sharing_enabled` |
| 유형 | 릴리스 |
| 담당 | 제품 팀 |
| 생성일 | 2024-01-15 |
| 예상 제거일 | 2024-03-01 |
### 설명
다음을 포함한 새 워크스페이스 공유 기능 접근을 제어합니다.
- 워크스페이스 헤더의 초대 모달
- 워크스페이스 설정의 구성원 탭
- 공유 관련 API 엔드포인트
### 타깃팅 규칙
| 단계 | 대상 | 비율 | 시작일 |
|-------|----------|------------|------------|
| 내부 | @company.com 이메일 | 100% | 1월 15일 |
| 알파 | 특정 계정(아래 목록) | 100% | 1월 22일 |
| 베타 | Teams 요금제 사용자 | 10% → 50% | 2월 1일 |
| 정식 출시(GA) | 모든 사용자 | 100% | 2월 15일 |
**알파 계정 목록:**
- Acme Corp (acct_123)
- Beta Inc (acct_456)
- Gamma LLC (acct_789)
### 대체 동작
플래그가 꺼져 있을 때:
- 워크스페이스 헤더의 "초대" 버튼 숨김
- 공유 API 엔드포인트가 404 반환
- 공유 페이지 직접 링크는 워크스페이스로 리디렉션
### 의존성
| 의존성 | 상태 |
|------------|--------|
| 공유 서비스 배포됨 | 필수 |
| 이메일 템플릿 준비됨 | 필수 |
| 분석 이벤트 추적 중 | 필수 |
### 롤백 기준
다음 경우 즉시 비활성화:
- 공유 엔드포인트 오류율 1% 초과
- 공유 관련 지원 티켓 50건 초과
- 데이터 무결성 이슈 감지
### 모니터링할 지표
- `sharing.invitations.sent` - 증가 예상
- `sharing.invitations.errors` - 0.1% 미만 유지
- `workspace.load_time` - 100ms 초과 증가 없어야 함
- 지원 티켓량 - 공유 이슈 모니터링
### 정리 계획
100% 단계적 배포 후 2주:
1. 코드에서 플래그 확인 제거
2. 구성에서 플래그 제거
3. 문서 업데이트
4. 이 명세 보관
단계적 배포 계획 템플릿
# 단계적 배포 계획: [기능 이름]
## 개요
| 속성 | 값 |
|----------|-------|
| 기능 | 워크스페이스 공유 |
| PRD 링크 | [링크] |
| 기술 명세 링크 | [링크] |
| 기능 플래그 | `release_workspace_sharing_enabled` |
| 목표 정식 출시(GA) 날짜 | 2024년 2월 15일 |
## 출시 전 체크리스트
### 엔지니어링 준비
- [ ] 코드가 main에 병합됨
- [ ] 기능 플래그 생성 및 테스트 완료
- [ ] 데이터베이스 마이그레이션 완료
- [ ] 성능 테스트 통과
- [ ] 보안 리뷰 승인
- [ ] 모니터링/알림 설정
### 제품 준비
- [ ] 사용자 문서 게시
- [ ] 도움말 센터 글 업데이트
- [ ] 앱 내 툴팁 설정
- [ ] 이메일 템플릿 승인
### 지원 준비
- [ ] 지원 팀 브리핑 완료
- [ ] FAQ 문서 작성
- [ ] 에스컬레이션 경로 정의
- [ ] 알려진 이슈 문서화
### 마케팅 준비
- [ ] 공지 블로그 글 초안 작성
- [ ] 소셜 미디어 게시물 예약
- [ ] 이메일 캠페인 준비(해당 시)
## 단계적 배포 단계
### 1단계: 내부 테스트(1월 15-21일)
**대상:** 내부 직원만
**목표:** 핵심 기능 검증, 명확한 버그 찾기
| 작업 | 담당 | 상태 |
|------|-------|--------|
| @company.com에 활성화 | 엔지니어링 | |
| 내부 테스트 수행 | QA | |
| 심각한 버그 수정 | 엔지니어링 | |
| 알려진 이슈 문서화 | 제품 관리자 | |
**진행/중단 기준:**
- [ ] 심각한 버그 없음
- [ ] 핵심 흐름이 처음부터 끝까지 작동
- [ ] 내부 팀 승인
### 2단계: 알파(1월 22-31일)
**대상:** 디자인 파트너 계정 10곳
**목표:** 실제 사용자로 검증, 피드백 수집
| 작업 | 담당 | 상태 |
|------|-------|--------|
| 알파 계정에 활성화 | 엔지니어링 | |
| 피드백 통화 예약 | 제품 관리자 | |
| 오류율 모니터링 | 엔지니어링 | |
| 피드백 기반 반복 | 팀 | |
**알파 계정:**
1. Acme Corp — Sarah(제품 관리자), [email protected]
2. Beta Inc — John(CTO), [email protected]
3. [...]
**진행/중단 기준:**
- [ ] 알파 사용자 NPS 30 초과
- [ ] 오류율 0.5% 미만
- [ ] 데이터 무결성 이슈 없음
- [ ] 알파 사용자 승인
### 3단계: 베타(2월 1-14일)
**대상:** Teams 요금제 사용자, 점진적 비율 배포
**목표:** 규모 검증, 스트레스 테스트
| 일자 | 비율 | 사용자 | 메모 |
|-----|------------|-------|-------|
| 2월 1일 | 5% | 약 500명 | 초기 베타 |
| 2월 5일 | 10% | 약 1,000명 | 이슈 없을 시 |
| 2월 8일 | 25% | 약 2,500명 | 확대 |
| 2월 12일 | 50% | 약 5,000명 | 최종 베타 |
**모니터링:**
- 매시간 확인: 오류율, 지연 시간
- 매일 확인: 지원 티켓, 사용자 피드백
- 매주: 분석 리뷰
**일시 중지/롤백 트리거:**
- 오류율이 30분 동안 1% 초과
- p99 지연 시간이 30분 동안 500ms 초과
- 기능 관련 지원 티켓이 시간당 10건 초과
- 데이터 손상 감지
### 4단계: 일반 제공(GA)(2월 15일)
**대상:** 모든 사용자
**목표:** 기능 완전 출시
| 작업 | 담당 | 상태 |
|------|-------|--------|
| 100%로 램프업 | 엔지니어링 | |
| 공지 게시 | 마케팅 | |
| 밀착 모니터링(48시간) | 엔지니어링 | |
| 플래그 정리 시작 | 엔지니어링 | |
## 롤백 계획
### 롤백 절차
1. **결정자:** 온콜 엔지니어 또는 제품 관리자
2. **조치:** LaunchDarkly에서 플래그 비활성화
3. **영향:** 모든 사용자에게 기능 즉시 숨김
4. **데이터:** 데이터 손실 없음, 기존 멤버십 보존
5. **커뮤니케이션:** 15분 안에 상태 페이지 업데이트
### 롤백 커뮤니케이션 템플릿
**상태 페이지(인시던트):**
이슈를 조사하는 동안 워크스페이스 공유를 일시적으로 비활성화합니다. 기존 워크스페이스 구성은 보존됩니다. [기간] 안에 기능을 다시 활성화할 예정입니다.
**고객 이메일(필요 시):**
제목: 워크스페이스 공유 일시 중지
발견된 이슈를 해결하는 동안 새 워크스페이스 공유 기능을 일시적으로 중지했습니다. 기존 워크스페이스 설정은 안전하며 기능을 다시 활성화하면 정상적으로 작동합니다.
[기간] 안에 해결할 예정입니다.
## 출시 후
### 성공 기준(정식 출시 후 2주)
- [ ] Teams 계정의 20%가 공유를 사용함
- [ ] 워크스페이스 중복 30% 감소
- [ ] 접근 관련 티켓 25% 감소
- [ ] 미해결 P0/P1 버그 없음
### 정리 작업
- [ ] 코드에서 기능 플래그 제거(3월 1일까지)
- [ ] 단계적 배포 문서 보관
- [ ] 회고 회의 예약
- [ ] 학습 내용을 팀과 공유
단계적 배포 전략
| 전략 | 사용 사례 | 위험 수준 |
|---|---|---|
| 일괄 배포 | 단순 변경, 낮은 위험 | 높음 |
| 비율 기반 | 점진적 노출 | 중간 |
| 링 기반 | 직원 → 베타 → 정식 출시(GA) | 낮음 |
| 지역 기반 | 단일 지역에서 먼저 테스트 | 낮음 |
| 계정 기반 | 특정 고객 | 매우 낮음 |
| 시간 기반 | 트래픽이 낮을 때 활성화 | 낮음 |
좋은 단계적 배포 예시
## 단계적 배포: 새 결제 흐름
### 전략: 링 기반 + 비율 기반
링 1(1주 차): 내부 직원
├── @company.com의 100%
├── 모든 결제 경로 검증
└── 성공: 결제 실패 없음
링 2(2주 차): 우호 고객
├── 옵트인한 계정 50곳
├── 정성 피드백 수집
└── 성공: NPS 40 초과, 심각한 버그 없음
링 3(3주 차): 카나리
├── 프로덕션 트래픽의 1%
├── 전환율을 대조군과 비교
├── 성공: 전환율이 기준선의 5% 이내
링 4(4주 차): 점진적 배포
├── 1% → 5% → 10% → 25% → 50% → 100%
├── 각 단계 사이 24시간
└── 성공: 전환율 개선 또는 중립
롤백: 즉시 플래그 비활성화, 기존 결제 흐름으로 대체
나쁜 단계적 배포 예시
## 단계적 배포: 새 결제 흐름
월요일에 배포하자. 문제가 있으면 고치면 된다.
실패하는 이유:
- 단계적 배포 없음
- 성공 기준 없음
- 모니터링 계획 없음
- 롤백 계획 없음
- 커뮤니케이션 계획 없음
기능 플래그 관리 위생
| 생명주기 단계 | 조치 |
|---|---|
| 생성 | 목적, 담당, 예상 제거일 문서화 |
| 활성 | 매월 리뷰, 타깃팅 규칙 업데이트 |
| 오래됨 | 100% 상태로 90일 후 정리 일정 잡기 |
| 제거 | 플래그 삭제, 코드 확인 제거, 문서 보관 |
기술 부채 경고 신호:
- 6개월보다 오래된 플래그
- 최근 평가가 없는 플래그
- 문서화된 담당자가 없는 플래그
- 같은 기능에 대해 여러 플래그를 확인하는 코드
안티패턴
- 영원한 플래그 - 플래그를 제거하지 않아 코드 유지보수성이 무너짐
- 플래그 폭증 - 플래그가 너무 많아 상태를 이해할 수 없음
- 롤백 테스트 없음 - 롤백 계획을 실제로 테스트하지 않음
- 플래그가 있어도 일괄 배포 - 플래그를 갖고도 0→100%로 바로 이동
- 보안처럼 쓰는 플래그 - 적절한 인증/인가 대신 플래그 사용
- 플래그 결합 - 플래그 A가 플래그 B를 필요로 해 의존성 생성
- 조용한 단계적 배포 - 점진적 배포 중 모니터링 없음
- 성급한 축하 - 정리 전에 성공 선언
- 담당자 없음 - 명확한 소유권 없는 플래그는 썩음
- 플래그 이름 혼란 - 검색할 수 없을 정도로 일관성 없는 이름
title: 사용자 스토리 작성 impact: CRITICAL tags: user-stories, agile, requirements, personas, jtbd
사용자 스토리 작성
영향도: 매우 높음
사용자 스토리는 비즈니스 요구사항을 엔지니어링 작업으로 번역합니다. 단순한 티켓 템플릿이 아니라, 누가 이익을 얻고 무엇이 필요하며 왜 중요한지를 담는 커뮤니케이션 도구입니다.
스토리 구조
고전 형식:
[페르소나/사용자 유형]으로서
[역량/행동]을 원합니다
그래서 [이점/가치]를 얻고 싶습니다
확장 형식:
[맥락/상황]에 있는 [페르소나]로서
[행동/역량]을 원합니다
그래서 [즉각적 이점]을 얻고 싶습니다
이를 통해 [비즈니스 성과]가 가능해집니다
INVEST 기준
모든 사용자 스토리는 다음을 충족해야 합니다.
| 기준 | 질문 | 위험 신호 |
|---|---|---|
| I 독립적 | 단독으로 출시할 수 있는가? | "스토리 #47에 막힘" |
| N 협상 가능 | 범위를 조정할 수 있는가? | 구현이 과도하게 명시됨 |
| V 가치 있음 | 사용자가 이익을 얻는가? | 스토리처럼 포장한 기술 작업 |
| E 추정 가능 | 크기를 산정할 수 있는가? | 너무 모호하거나 너무 큼 |
| S 작음 | 스프린트에 들어가는가? | 여러 주짜리 추정 |
| T 테스트 가능 | 완료를 검증할 수 있는가? | 수용 기준 없음 |
스토리 크기 가이드
| 크기 | 일수 | 특징 |
|---|---|---|
| XS | 0.5 | 설정 변경, 문구 업데이트 |
| S | 1-2 | 단일 컴포넌트, 명확한 경로 |
| M | 3-5 | 여러 컴포넌트, 일부 미지수 |
| L | 5-8 | 시스템 간 작업, 분해 필요 |
| XL | 8+ | 에픽 수준, 반드시 쪼개야 함 |
경험칙: 한 스프린트 안에 끝낼 수 없다면 스토리가 아니라 에픽입니다.
좋은 사용자 스토리 예시
전자상거래: 장바구니에 추가
**스토리:** 상품을 장바구니에 추가
**다음 사용자로서:** 상품을 둘러보는 쇼핑객
**원하는 것:** 상품 페이지를 떠나지 않고 항목을 장바구니에 추가
**이유:** 선택한 항목을 잃지 않고 계속 둘러볼 수 있도록
**수용 기준:**
- 재고가 있는 상품 페이지에 있을 때
"장바구니에 추가"를 클릭하면
항목이 추가되고 장바구니 수량이 500ms 안에 업데이트됨
- 사이즈 옵션이 있는 상품 페이지에 있을 때
사이즈를 선택하지 않고 "장바구니에 추가"를 클릭하면
사이즈 선택을 요청하는 인라인 오류가 표시됨
- 이미 장바구니에 이 항목이 10개 있을 때
하나 더 추가하려고 하면
수량 제한 메시지가 표시됨
**예외 사례:**
- 페이지 로드와 추가 사이에 상품 품절
- 사용자가 로그인하지 않음
- 추가 중 네트워크 실패
**범위 밖:**
- 위시리스트 기능
- 상품 추천
- 게스트 결제 흐름
SaaS: 팀 초대
**스토리:** 팀원을 워크스페이스에 초대
**다음 사용자로서:** 워크스페이스 소유자
**원하는 것:** 동료를 이메일로 초대
**이유:** 중복을 만들지 않고 프로젝트에서 협업할 수 있도록
**수용 기준:**
- 소유자 권한이 있을 때
유효한 이메일을 입력하고 "초대"를 클릭하면
60초 안에 초대 이메일이 발송됨
- 이메일이 이미 워크스페이스 구성원일 때
그 사람을 초대하려고 하면
"이 사람은 이미 구성원입니다" 메시지가 표시됨
- 초대를 보낸 뒤
대기 중인 초대를 보면
이메일, 발송일, 재발송/철회 옵션이 표시됨
**기술 메모:**
- 기존 이메일 서비스(SendGrid) 사용
- 초대는 7일 후 만료
- 워크스페이스당 최대 구성원 50명
**의존성:**
- 이메일 템플릿(디자인: 6월 10일 마감)
나쁜 사용자 스토리 예시
너무 모호함:
사용자로서
더 나은 검색을 원합니다
그래서 물건을 찾고 싶습니다
실패하는 이유: 사용자가 누구인가요? "더 나은"은 무엇을 뜻하나요? 어떤 물건인가요? 수용 기준이 없습니다.
너무 기술적임:
개발자로서
인증 모듈을 JWT 토큰을 쓰도록 리팩터링하고 싶습니다
그래서 코드가 더 깔끔해지도록
실패하는 이유: 이것은 사용자 스토리가 아니라 기술 작업입니다. 사용자 가치가 없습니다. "사용자로서, 여러 기기에서 로그인 상태를 유지하고 싶습니다..."처럼 다시 구성하세요.
너무 큼:
관리자로서
모든 사용자, 역할, 권한, 감사 로그를 관리하고 싶습니다
그래서 시스템 접근을 제어할 수 있도록
실패하는 이유: 이것은 에픽입니다. 사용자 관리, 역할 관리, 권한 관리, 감사 로그 보기로 쪼개세요.
맥락 누락:
사용자로서
데이터를 내보내고 싶습니다
그래서 다른 곳에서 사용할 수 있도록
실패하는 이유: 어떤 데이터를 내보내나요? 어떤 형식인가요? "다른 곳"은 어디인가요? 다음처럼 추가하세요. "마케팅 관리자로서, Excel에서 보고서를 만들 수 있도록 캠페인 분석을 CSV로 내보내고 싶습니다."
페르소나 기반 스토리 작성
스토리를 쓰기 전에 페르소나를 정의합니다.
| 페르소나 | 설명 | 목표 | 불만 |
|---|---|---|---|
| 관리자 Amy | IT 관리자, 5년 이상 | 보안 유지, 티켓 감소 | 수동 프로세스, 불명확한 감사 |
| 고급 사용자 Paul | 매일 많이 사용하는 사용자 | 속도, 키보드 단축키 | 반복 작업의 마찰 |
| 신규 사용자 Nina | 막 합류해 배우는 중 | 빠른 온보딩 | 혼란스러운 UI, 숨은 기능 |
| 임원 Eve | 가끔 쓰는 의사결정자 | 빠른 인사이트, 대시보드 | 너무 많은 세부정보, 느린 보고서 |
그 관점에서 스토리를 작성합니다.
- "관리자 Amy로서, 퇴사 처리가 효율적이도록 사용자를 일괄 비활성화하고 싶습니다"
- "고급 사용자 Paul로서, 더 빠르게 일할 수 있도록 흔한 작업에 키보드 단축키를 쓰고 싶습니다"
해야 할 일(JTBD) 통합
JTBD와 사용자 스토리를 결합합니다.
JTBD 형식:
[상황/트리거]일 때
[동기/해야 할 일]을 원합니다
그래서 [기대 결과]를 얻고 싶습니다
예시:
10분 뒤 고객 미팅을 준비할 때
지난 분기 지표를 빠르게 가져오고 싶습니다
그래서 대시보드를 헤매지 않고 자신 있게 질문에 답할 수 있도록
이것은 다음 스토리가 됩니다.
고객 미팅을 준비하는 영업 담당자로서
핵심 계정 지표의 1페이지 요약을 보고 싶습니다
그래서 통화 중 성과 데이터를 빠르게 참고할 수 있도록
스토리 매핑
스토리를 지도로 구성합니다.
┌─────────────────────────────────────────────────┐
사용자 │ 발견 │ 선택 │ 구매 │ 사용 │
여정 └─────────────────────────────────────────────────┘
│ │ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌────┴────┐ ┌────┴────┐
릴리스 1 │ 검색 │ │ 비교 │ │ 결제 │ │대시보드 │
(MVP) │ 탐색 │ │ 상세 │ │ 지불 │ │ 기본 │
└───────────┘ └───────────┘ └─────────┘ └─────────┘
│ │ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌────┴────┐ ┌────┴────┐
릴리스 2 │ 필터 │ │ 리뷰 │ │카드 저장│ │ 보고서 │
│ 정렬 │ │ 비교 │ │빠른 결제│ │ 내보내기│
└───────────┘ └───────────┘ └─────────┘ └─────────┘
스토리 분할 기법
스토리가 너무 크면 다음 기준으로 나눕니다.
| 기법 | 예시 |
|---|---|
| 워크플로 단계 | 등록 → 이메일 인증 → 비밀번호 설정 |
| 비즈니스 규칙 | 기본 검증 → 복잡한 검증 |
| 데이터 변형 | 단일 항목 → 일괄 항목 |
| 작업 유형 | 생성 → 읽기 → 업데이트 → 삭제 |
| 사용자 유형 | 관리자 → 사용자 → 게스트 |
| 플랫폼 | 웹 → 모바일 → API |
| 성능 | 작동함 → 빠르게 작동함 |
안티패턴
- 작업으로 쓰는 스토리 - "로그인 API 구현" (사용자 가치는 어디에 있나요?)
- 복합 스토리 - "사용자로서 X와 Y와 Z를 원합니다" (쪼개세요)
- 과도한 장식 - 솔루션을 제약하는 과도하게 상세한 수용 기준
- "그래서" 누락 - 명시된 가치가 없는 스토리는 목적 없는 기능
- 복붙 페르소나 - 구체적 페르소나 대신 모든 곳에 "사용자로서"
- 명세가 된 스토리 - 협상의 여지를 남기지 않는 500단어 스토리
- 고아 스토리 - 에픽이나 목표와 연결되지 않은 스토리
- 좀비 스토리 - 백로그에 남아 우선순위화되거나 제거되지 않는 오래된 스토리
title: 기술 명세 impact: HIGH tags: technical-spec, architecture, system-design, engineering
기술 명세
영향도: 높음
기술 명세는 제품 요구사항을 엔지니어링 청사진으로 번역합니다. 코드가 작성되기 전에 아키텍처 결정, 시스템 상호작용, 구현 접근 방식을 문서화합니다.
기술 명세를 작성해야 할 때
| 복잡도 | 명세 필요? | 접근 방식 |
|---|---|---|
| 작음 (1일 미만) | 아니오 | 인라인 주석, PR 설명 |
| 중간 (1-5일) | 가볍게 | 짧은 설계 문서 섹션 |
| 큼 (1-2주) | 예 | 전체 기술 명세 |
| 중요 (크기 무관) | 예 | 전체 명세 + 리뷰 |
다음 경우에는 항상 명세를 작성합니다.
- 여러 팀/시스템이 관련됨
- 기존 API에 호환성을 깨는 변경이 있음
- 새 인프라 또는 서비스가 필요함
- 보안에 민감한 기능
- 성능이 중요한 경로
기술 명세 구조
# 기술 명세: [기능 이름]
## 개요
[무엇과 왜에 대한 2-3문장 요약]
## 목표
[이 설계가 달성하는 것]
## 비목표
[이 설계가 명시적으로 다루지 않는 것]
## 배경
[문제를 이해하는 데 필요한 맥락]
## 상세 설계
[명세의 핵심: 아키텍처, 컴포넌트, 흐름]
## 데이터 모델
[스키마 변경, 새 모델, 관계]
## API 설계
[엔드포인트, 계약, 오류 처리]
## 보안 고려사항
[인증, 암호화, 취약점, 컴플라이언스]
## 관측 가능성
[로깅, 지표, 알림]
## 단계적 배포 계획
[배포 전략, 기능 플래그, 마이그레이션]
## 검토한 대안
[다른 접근 방식과 기각 이유]
## 열린 질문
[미해결 결정]
## 참고 자료
[관련 문서, RFC, PRD]
좋은 기술 명세 예시
# 기술 명세: 워크스페이스 공유 서비스
## 개요
워크스페이스 소유자가 세분화된 권한으로 팀원을 초대할 수 있는
공유 서비스를 구축합니다. 이는 팀 요금제 전환 25% 증가를 목표로 하는
"팀 협업" 이니셔티브를 지원합니다.
## 목표
- 소유자/편집자/뷰어 권한으로 워크스페이스 공유 가능
- 워크스페이스당 최대 구성원 50명 지원
- 워크스페이스 접근 권한 확인을 1초 미만으로 수행
- 기존 워크스페이스에 무중단 마이그레이션 제공
## 비목표
- 공개 링크 공유(v2 예정)
- 조직 간 공유
- 실시간 협업 기능(별도 이니셔티브)
## 배경
현재 사용자는 자격 증명을 공유하거나(보안 위험) 워크스페이스를
복제하여(데이터 동기화 이슈) 워크스페이스를 공유합니다. 접근 관리 관련
지원 티켓은 전체의 40%입니다.
참고: [PRD: 팀 워크스페이스 공유](link)
## 상세 설계
### 아키텍처 개요
┌─────────────────────────────────────────────────────────────┐
│ API 게이트웨이 │
└────────────────────────┬────────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 워크스페이스 │ │ 공유 │ │ 인증 │
│ 서비스 │ │ 서비스 │ │ 서비스 │
│ (기존) │ │ (신규) │ │ (기존) │
└──────┬──────┘ └──────┬──────┘ └─────────────┘
│ │
│ ┌──────┴──────┐
│ │ │
▼ ▼ ▼
┌─────────────────────┐ ┌─────────────┐
│ PostgreSQL │ │ Redis │
│ (권한) │ │ (캐시) │
└─────────────────────┘ └─────────────┘
### 컴포넌트 책임
| 컴포넌트 | 책임 |
|-----------|----------------|
| **공유 서비스** | 초대 CRUD, 권한 관리 |
| **워크스페이스 서비스** | 기존 엔드포인트에 권한 확인 추가 |
| **인증 서비스** | 워크스페이스 클레임이 포함된 토큰 발급 |
| **Redis** | 권한 조회 캐시 |
### 권한 모델
효율적인 저장을 위해 비트마스크를 사용하는 세 가지 권한 수준:
| 수준 | 값 | 가능 작업 |
|-------|-------|--------------|
| 뷰어 | 1 | 워크스페이스 읽기, 기록 보기 |
| 편집자 | 3 | 뷰어 + 콘텐츠 생성/편집/삭제 |
| 소유자 | 7 | 편집자 + 구성원 관리, 설정, 워크스페이스 삭제 |
권한 상속:
- 소유자 > 편집자 > 뷰어
- 각 수준은 더 낮은 권한을 모두 포함
### 초대 흐름
1. 소유자가 초대 요청 제출(이메일, 권한 수준)
2. 공유 서비스가 검증:
- 소유자 권한 있음
- 이메일 형식 유효
- 워크스페이스가 구성원 한도에 도달하지 않음
3. 초대 토큰 생성(암호학적으로 안전한 난수, 32바이트)
4. pending_invitations 테이블에 초대 저장
5. 기존 알림 서비스를 통해 이메일 대기열 등록
6. 초대받은 사용자가 링크 클릭 → 토큰 검증 → 멤버십 생성
7. 사용 또는 만료(7일) 후 토큰 무효화
### 권한 확인 흐름
모든 워크스페이스 요청에서:
1. 요청에서 workspace_id 추출
2. Redis 캐시 확인: `workspace:{id}:user:{user_id}`
3. 미스이면 PostgreSQL 조회 → 5분 동안 캐시
4. 권한 레코드가 없으면 거부
5. 작업을 권한 수준과 비교
6. 접근 시도 기록(성공/실패)
성능 목표: 캐시 적중 시 10ms 미만, 캐시 미스 시 50ms 미만
## 데이터 모델
### 새 테이블
-- 워크스페이스 멤버십
CREATE TABLE workspace_memberships (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
permission_level SMALLINT NOT NULL DEFAULT 1,
invited_by UUID REFERENCES users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(workspace_id, user_id)
);
CREATE INDEX idx_memberships_workspace ON workspace_memberships(workspace_id);
CREATE INDEX idx_memberships_user ON workspace_memberships(user_id);
-- 대기 중인 초대
CREATE TABLE workspace_invitations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
email VARCHAR(255) NOT NULL,
permission_level SMALLINT NOT NULL DEFAULT 1,
token VARCHAR(64) NOT NULL UNIQUE,
invited_by UUID NOT NULL REFERENCES users(id),
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
accepted_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX idx_invitations_token ON workspace_invitations(token);
CREATE INDEX idx_invitations_workspace ON workspace_invitations(workspace_id);
### 마이그레이션 전략
1. 새 테이블 생성(비차단)
2. 기존 워크스페이스 소유자를 memberships 테이블에 백필
3. 권한 확인을 shadow mode로 배포(기록만 하고 강제하지 않음)
4. 정상 접근이 차단되지 않는지 검증
5. 기능 플래그로 강제 적용 활성화
## API 설계
전체 OpenAPI 명세는 [api-specs.md]를 참고하세요.
핵심 엔드포인트:
- POST /workspaces/{id}/invitations - 초대 생성
- GET /workspaces/{id}/members - 구성원 목록
- PATCH /workspaces/{id}/members/{user_id} - 권한 업데이트
- DELETE /workspaces/{id}/members/{user_id} - 구성원 제거
## 보안 고려사항
### 인증
- 모든 엔드포인트는 유효한 JWT 필요
- 초대 토큰은 1회용이며 시간 제한 있음
### 인가
- 모든 워크스페이스 작업에서 권한 확인
- 소유자 전용 작업: 초대, 제거, 권한 변경, 워크스페이스 삭제
- 모든 권한 변경의 감사 로그
### 데이터 보호
- 이메일 주소는 저장 시 암호화
- 초대 토큰은 데이터베이스에 해시로 저장
- 속도 제한: 워크스페이스당 시간당 초대 10건
### 컴플라이언스
- GDPR: 내보내기/삭제에 멤버십 데이터 포함
- SOC2: 감사 추적 요구사항 충족
## 관측 가능성
### 지표
- `sharing.invitations.sent` - 카운터, 워크스페이스별
- `sharing.invitations.accepted` - 카운터
- `sharing.permission_checks` - 히스토그램, 결과별(허용/거부)
- `sharing.cache_hit_rate` - 게이지
### 로깅
- 모든 권한 변경은 행위자, 대상, 변경 전/후와 함께 기록
- 실패한 권한 확인은 요청 맥락과 함께 기록
### 알림
- 캐시 적중률이 5분 동안 80% 미만
- 권한 확인 지연 시간 p99가 100ms 초과
- 24시간 동안 초대 수락률 50% 미만
## 단계적 배포 계획
| 단계 | 대상 | 기간 | 성공 기준 |
|-------|----------|----------|------------------|
| 알파 | 내부 팀 | 1주 | 차단 요소 없음 |
| 베타 | Teams 요금제 5% | 2주 | 오류율 0.1% 미만 |
| 정식 출시(GA) | 100% | 1주 램프 | 지표 정상 |
기능 플래그: `workspace_sharing_enabled`
긴급 차단 스위치: 공유 서비스를 비활성화하고 소유자 전용 접근으로 대체
## 검토한 대안
### 1. 기존 인증 서비스 확장
**기각:** 인증 서비스는 중요 경로이므로 복잡도를 추가하고 싶지 않습니다.
별도 서비스는 독립적인 확장과 배포를 가능하게 합니다.
### 2. 실시간 권한 동기화(웹소켓)
**기각:** 명확한 사용자 이점 없이 복잡도만 추가합니다.
권한 전파에는 5분 캐시 TTL이 허용 가능합니다.
### 3. 문서 수준 권한
**기각:** v1 범위 밖입니다. 사용자 조사 기준으로는
워크스페이스 수준 세분성이 충분합니다.
## 열린 질문
- [ ] 워크스페이스당 초대 속도 제한? (제안: 50/일)
- [ ] 사용자가 워크스페이스를 떠날 수 있게 할 것인가? (예 쪽으로 기울어짐)
- [ ] 권한 변경 시 알림? (제품 관리자 입력 필요)
## 참고 자료
- [PRD: 팀 워크스페이스 공유](link)
- [RFC: 권한 시스템 진화](link)
- [인증 서비스 문서](link)
나쁜 기술 명세 예시
# 기술 명세: 공유
워크스페이스에 공유를 추가해야 합니다.
## 설계
권한을 위한 새 테이블을 추가합니다. 공유용 API 엔드포인트를 추가합니다.
캐싱에는 Redis를 사용합니다.
## 보안
안전하게 만듭니다.
## 단계적 배포
준비되면 배포합니다.
실패하는 이유:
- 맥락이나 목표가 없음
- 아키텍처 다이어그램 없음
- 데이터 모델 세부정보 누락
- API 계약 없음
- 모호한 보안 섹션
- 단계적 배포 전략 없음
- 대안 검토 없음
기술 명세 리뷰 체크리스트
아키텍처
├── □ 명확한 컴포넌트 다이어그램
├── □ 책임이 잘 정의됨
├── □ 통합 지점 식별
├── □ 의존성 문서화
└── □ 확장성 고려
데이터 모델
├── □ 타입과 함께 스키마 정의
├── □ 인덱스 명시
├── □ 마이그레이션 전략 명확
└── □ 하위 호환성 반영
API 설계
├── □ 엔드포인트 문서화
├── □ 요청/응답 형식
├── □ 오류 처리 정의
└── □ 버전 관리 전략
보안
├── □ 인증/인가 접근 방식
├── □ 데이터 보호
├── □ 위협 모델 고려
└── □ 컴플라이언스 요구사항
운영
├── □ 지표 정의
├── □ 로깅 전략
├── □ 알림 임계값
├── □ 런북 고려사항
└── □ 롤백 계획
프로세스
├── □ 이해관계자 리뷰 완료
├── □ 열린 질문 해결
├── □ 대안 문서화
└── □ 일정 현실적
안티패턴
- 위원회식 설계 - 모든 의견을 수용하려다 명세가 모호해짐
- 성급한 최적화 - 아직 없는 규모를 위해 설계
- 맥락 누락 - PRD 링크나 비즈니스 근거 없는 명세
- 구현 가장 - 설계 대신 명세에 코드를 작성
- 단독 작성 - 영향을 받는 팀의 리뷰 없음
- 범위 증가 - 명세가 "있으면 좋은 것"까지 포함하도록 확장
- 분석 마비 - 완벽하지만 출시되지 않는 명세
- 죽은 문서 - 구현이 달라져도 업데이트되지 않는 명세