AgentSkillsCN

model-domain

编写无处不在的语言规范——以叙述式领域模型(替代ERD)

SKILL.md
--- frontmatter
name: model-domain
description: 유비쿼터스 언어 명세 작성 - 서술형 도메인 모델 (ERD 대체)
user-invocable: false

Skill: Model Domain

목적

도메인의 유비쿼터스 언어를 정의하고 서술형 명세로 작성

철학: "ERD 그리기 말고, 말로 설명하기"


입력

  • 도메인 이름 (예: "users")
  • Phase 2 결과 (SESSION.md의 완전한 도메인 정의)

출력

  • ai-context/domain-books/{domain}/domain-model.md
  • 용어 정의 + 관계 규칙 + 제약 조건 + 생명주기

유비쿼터스 언어란?

해당 도메인 전용이면서 개발자가 쉽게 이해할 수 있는 전용 언어

❌ 잘못된 접근 (ERD)

code
┌─────────┐        ┌──────────────┐
│  User   │───1:N──│ Translation  │
└─────────┘        └──────────────┘

✅ 올바른 접근 (유비쿼터스 언어)

code
"사용자는 여러 개의 번역 기록을 가질 수 있다."
"번역 기록은 반드시 한 명의 사용자에게 속한다."

사용 방법

1. 용어 추출

python
from skills.model_domain import extract_terms

terms = extract_terms(
    session_data=read(".claude/SESSION.md"),
    domain="users"
)

# 결과:
# [
#     {"term": "사용자 (User)", "definition": "앱을 사용하는 개인", "example": "홍길동"},
#     {"term": "식별자 (ID)", "definition": "사용자를 고유하게 구분하는 값", "example": "u_123abc"},
#     {"term": "이메일 (Email)", "definition": "로그인 및 연락용 이메일 주소", "example": "user@example.com"},
#     ...
# ]

2. 관계 규칙 생성

python
from skills.model_domain import generate_relationship_rules

rules = generate_relationship_rules(
    domain="users",
    session_data=read(".claude/SESSION.md")
)

# 결과:
# [
#     {
#         "rule_number": 1,
#         "statement": "사용자는 여러 개의 번역 기록을 가질 수 있다.",
#         "details": "한 사용자가 0개 이상의 번역을 요청할 수 있음",
#         "example": "홍길동 사용자는 10개의 번역 기록을 가지고 있다"
#     },
#     ...
# ]

3. 제약 조건 추출

python
from skills.model_domain import extract_constraints

constraints = extract_constraints(
    domain="users",
    session_data=read(".claude/SESSION.md")
)

# 결과:
# [
#     {
#         "number": 1,
#         "constraint": "식별자 (ID)는 시스템이 자동으로 생성한다.",
#         "reason": "사용자가 직접 설정할 수 없음"
#     },
#     {
#         "number": 2,
#         "constraint": "이메일 (Email)은 중복될 수 없다.",
#         "reason": "이미 존재하는 이메일로는 가입 불가",
#         "example": "user@example.com으로 이미 가입되어 있으면 다시 가입 불가"
#     },
#     ...
# ]

4. 생명주기 정의

python
from skills.model_domain import define_lifecycle

lifecycle = define_lifecycle(
    domain="users",
    session_data=read(".claude/SESSION.md")
)

# 결과:
# {
#     "create": {
#         "steps": ["요청 접수", "중복 확인", "필수 정보 검증", "엔티티 생성", "자동 값 설정"],
#         "rules": ["선호 언어 미제공 시 기본값은 '영어'"]
#     },
#     "update": {
#         "steps": ["수정 요청", "권한 검사", "변경 가능 필드만 수정"],
#         "editable_fields": ["표시 이름", "프로필 사진", "선호 언어"],
#         "readonly_fields": ["식별자", "이메일", "가입 시각"],
#         "rules": ["본인만 자신의 프로필을 수정할 수 있다"]
#     },
#     "delete": {
#         "steps": ["탈퇴 요청", "본인 확인", "연결 데이터 확인", "모든 데이터 삭제", "엔티티 삭제"],
#         "rules": ["탈퇴는 즉시 실행되며, 복구 불가능하다"]
#     }
# }

domain-model.md 구조

markdown
# {domain} 도메인 모델

> 생성일: {오늘 날짜}
> Phase: 3 (Domain Modeler)
> 상태: ✅ 완료

---

## 📖 유비쿼터스 언어 (용어 정의)

> 이 도메인에서 사용하는 전용 용어들

### 핵심 용어

| 용어 | 정의 | 예시 |
|------|------|------|
| 사용자 (User) | 앱을 사용하는 개인 | "홍길동", "john@example.com" |
| 식별자 (ID) | 사용자를 고유하게 구분하는 값 | "u_123abc" |
| ... | ... | ... |

---

## 📐 관계 규칙

> 이 도메인의 엔티티들이 어떻게 연결되는지 서술형으로 정의

### 사용자와 번역 기록

**규칙 1**: 사용자는 여러 개의 번역 기록을 가질 수 있다.
- 한 사용자가 0개 이상의 번역을 요청할 수 있음
- 예: "홍길동 사용자는 10개의 번역 기록을 가지고 있다"

**규칙 2**: 번역 기록은 반드시 한 명의 사용자에게 속한다.
- 모든 번역은 누가 요청했는지 기록됨
- 예: "이 번역 기록은 john@example.com 사용자의 것이다"

---

## ✅ 제약 조건

> 엔티티가 지켜야 하는 규칙

### 필수 규칙

1. **식별자 (ID)**는 시스템이 자동으로 생성한다.
   - 사용자가 직접 설정할 수 없음

2. **이메일 (Email)**은 중복될 수 없다.
   - 이미 존재하는 이메일로는 가입 불가
   - 예: "user@example.com"으로 이미 가입되어 있으면 다시 가입 불가

---

## 🔄 생명주기

> 엔티티가 어떻게 생성, 수정, 삭제되는지

### 생성 (가입)

\`\`\`
사용자가 가입을 요청한다
    ↓
이메일이 중복되지 않는지 확인한다
    ↓
모든 필수 정보가 제공되었는지 확인한다
    ↓
사용자 엔티티를 생성한다
    ↓
식별자(ID)와 가입 시각(CreatedAt)을 자동으로 설정한다
\`\`\`

**규칙**: 가입 시 선호 언어가 제공되지 않으면 기본값은 '영어'로 설정된다.

### 수정 (프로필 편집)

...

### 삭제 (탈퇴)

...

작성 원칙

✅ 좋은 명세 (서술형)

  • "사용자는 여러 개의 번역 기록을 가질 수 있다."
  • "번역 기록은 반드시 한 명의 사용자에게 속한다."
  • "이메일은 중복될 수 없다."

❌ 나쁜 명세 (기술적)

  • "User 테이블의 id 컬럼은 UUID 타입"
  • "Translation.user_id는 User.id를 참조하는 Foreign Key"
  • "email 컬럼에 UNIQUE 제약"

말로 설명하기! 테이블/컬럼 언급 금지.


관계 표현 패턴

1:N 관계

code
"A는 여러 개의 B를 가질 수 있다."
"B는 반드시 하나의 A에 속한다."

예시:

  • "사용자는 여러 개의 번역 기록을 가질 수 있다."
  • "번역 기록은 반드시 한 명의 사용자에게 속한다."

1:1 관계

code
"A는 하나의 B를 가진다."
"B는 반드시 하나의 A에 속한다."

예시:

  • "사용자는 하나의 프로필을 가진다."
  • "프로필은 반드시 한 명의 사용자에게 속한다."

N:M 관계

code
"A는 여러 개의 B와 연결될 수 있다."
"B는 여러 개의 A와 연결될 수 있다."

예시:

  • "사용자는 여러 개의 관심 태그를 가질 수 있다."
  • "태그는 여러 명의 사용자와 연결될 수 있다."

Cascade 관계

code
"A가 삭제되면 모든 B도 함께 삭제된다."

예시:

  • "사용자가 삭제되면 모든 번역 기록도 함께 삭제된다."

제약 조건 패턴

필수/선택

code
"X는 반드시 제공되어야 한다."
"Y는 선택 사항이다."

유일성

code
"X는 중복될 수 없다."

형식 검증

code
"X는 반드시 Y 형식이어야 한다."

예시:

  • "이메일은 반드시 유효한 형식이어야 한다. (@ 기호 포함)"

값 범위

code
"X는 A, B, C 중 하나여야 한다."
"Y는 최소 N 이상이어야 한다."

예시:

  • "선호 언어는 '한국어' 또는 '영어'만 가능하다."
  • "표시 이름은 최소 1자 이상이어야 한다."

자동 생성

code
"X는 시스템이 자동으로 생성한다."

생명주기 작성 가이드

생성 (Create)

code
요청자가 생성을 요청한다
    ↓
[검증 단계 1]
    ↓
[검증 단계 2]
    ↓
엔티티를 생성한다
    ↓
[자동 설정 단계]

수정 (Update)

code
요청자가 수정을 요청한다
    ↓
권한을 확인한다
    ↓
[검증 단계]
    ↓
변경 가능한 필드만 수정한다

수정 가능/불가능 필드 명시 필수

삭제 (Delete)

code
요청자가 삭제를 요청한다
    ↓
권한을 확인한다
    ↓
연결된 데이터를 확인한다
    ↓
[Cascade 처리]
    ↓
엔티티를 삭제한다

검증 체크리스트

domain-model.md 작성 완료 후:

  • 용어 테이블에 모든 핵심 용어 포함
  • 각 용어에 정의와 예시 있음
  • 관계 규칙이 서술형으로 작성됨
  • 모든 관계에 예시 포함
  • 제약 조건에 이유 설명
  • 생명주기 3단계 (생성/수정/삭제) 모두 포함
  • 기술 용어 (테이블, 컬럼, FK, UUID 등) 0개
  • 한글 용어 사용 (영어 병기는 괄호 안에)

기술 용어 필터링

금지 키워드:

python
TECH_KEYWORDS = [
    # 데이터베이스
    "테이블", "컬럼", "Foreign Key", "Primary Key", "Index",
    "UUID", "VARCHAR", "INT", "TIMESTAMP",

    # 프레임워크
    "FastAPI", "SQLAlchemy", "Alembic", "Pydantic",

    # 프로토콜
    "HTTP", "REST", "GraphQL", "WebSocket",

    # 기술 스택
    "PostgreSQL", "MySQL", "Redis", "S3"
]

검증:

python
def validate_no_tech_terms(content: str) -> bool:
    """기술 용어가 없는지 확인"""
    lower_content = content.lower()
    for keyword in TECH_KEYWORDS:
        if keyword.lower() in lower_content:
            return False
    return True