Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/domain-book-builder/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "domain-book-builder",
"description": "기술 독립적 Domain Book 생성 - 완벽한 도메인 설계서 집필",
"version": "1.0.0",
"version": "0.1.0",
"author": {
"name": "URECA Team"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ Skill(skill="python-fastapi-programmer:fastapi-architecture")
Skill(skill="python-fastapi-programmer:fastapi-security")
```

```python
# 3. DDD Class Diagram 생성 (모델 코드 작성 전)
Skill(skill="python-fastapi-programmer:ddd-class-diagram")
```

**선택적 스킬** (위치 정보 구현 필요 시):

```python
Expand Down
19 changes: 19 additions & 0 deletions plugins/python-fastapi-programmer/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "F=$(cat | jq -r '.tool_input.file_path // empty'); [[ ! \"$F\" =~ \\.py$ ]] && exit 0; [[ \"$F\" =~ migrations/versions/ ]] && exit 0; [[ ! -f \"$F\" ]] && exit 0; uv run ruff format -q \"$F\" 2>/dev/null; OUT=$(uv run ruff check \"$F\" 2>&1); [ $? -ne 0 ] && echo \"$OUT\" >&2 && exit 2; exit 0",
"timeout": 15,
"statusMessage": "Linting Python file..."
},
{
"type": "prompt",
"prompt": "Check ONLY the Python file just modified for these architecture anti-patterns. If found, fix immediately:\n- `from src.modules.{A} import` in `src/modules/{B}/` → cross-domain import forbidden\n- `session.execute(text(\"...\"))` → no raw SQL, use SQLModel ORM\n- `os.getenv(` → use `settings.XXX` from pydantic-settings\n- `password` or `secret` in log/print → no sensitive data in logs\n- `async def` endpoint without return type annotation → must specify return type\n- `from fastapi import Request` in use case layer → Request only in router layer"
}
]
}
]
}
32 changes: 32 additions & 0 deletions plugins/python-fastapi-programmer/hooks/ruff-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/bash
# PostToolUse hook: Write/Edit 후 Python 파일 ruff check 자동 실행
# stdin으로 JSON input을 받아 file_path 추출 → ruff check 실행

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Python 파일이 아니면 스킵
if [[ ! "$FILE_PATH" =~ \.py$ ]]; then
exit 0
fi

# 마이그레이션 파일은 스킵 (autogenerate된 코드)
if [[ "$FILE_PATH" =~ migrations/versions/ ]]; then
exit 0
fi

# 파일이 존재하지 않으면 스킵 (삭제된 경우)
if [[ ! -f "$FILE_PATH" ]]; then
exit 0
fi

# ruff check 실행
OUTPUT=$(uv run ruff check "$FILE_PATH" 2>&1)
EXIT_CODE=$?

if [[ $EXIT_CODE -ne 0 ]]; then
echo "$OUTPUT" >&2
exit 2
fi

exit 0
36 changes: 36 additions & 0 deletions plugins/python-fastapi-programmer/hooks/ruff-format-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash
# PostToolUse hook: Write/Edit 후 Python 파일 ruff format 검사
# 포맷이 맞지 않으면 자동 수정 후 AI에게 알림

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Python 파일이 아니면 스킵
if [[ ! "$FILE_PATH" =~ \.py$ ]]; then
exit 0
fi

# 마이그레이션 파일은 스킵
if [[ "$FILE_PATH" =~ migrations/versions/ ]]; then
exit 0
fi

# 파일이 존재하지 않으면 스킵
if [[ ! -f "$FILE_PATH" ]]; then
exit 0
fi

# ruff format --check (포맷 검사만)
OUTPUT=$(uv run ruff format --check "$FILE_PATH" 2>&1)
EXIT_CODE=$?

if [[ $EXIT_CODE -ne 0 ]]; then
# 자동 포맷 적용
uv run ruff format "$FILE_PATH" 2>&1
echo "Auto-formatted: $FILE_PATH" >&2
echo "$OUTPUT" >&2
# exit 0: 자동 수정했으므로 AI에게 에러로 보내지 않음
exit 0
fi

exit 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
name: python-fastapi-programmer:ddd-class-diagram
description: DDD Class Diagram 생성. Domain Book의 domain-model.md를 읽고 Mermaid erDiagram으로 DDD_CLASS_DIAGRAM.md를 생성. PK/FK, Enum, Cascade, Index 전략 포함. Phase 4에서 _models.py 생성 전에 사용.
---

# DDD Class Diagram 생성

Domain Book의 도메인 모델 정의를 Mermaid erDiagram으로 변환합니다.

## 실행 흐름

1. **입력 읽기**: `ai-context/domain-books/{domain}/domain-model.md` 파일 읽기
2. **다이어그램 생성**: Mermaid erDiagram 문법으로 변환
3. **출력 저장**: `docs/DDD_CLASS_DIAGRAM.md`에 저장

## 필수 포함 항목

### Entity 정의
- **PK**: Primary Key (UUID 기본)
- **FK**: Foreign Key (관계 명시)
- **NOT NULL**: 필수 필드 표시
- **UNIQUE**: 유니크 제약 조건
- **DEFAULT**: 기본값
- **INDEX**: 인덱스 전략

### Enum 정의
- 모든 Enum 값 나열
- 각 값의 의미 주석 포함

### 관계 (Relationships)
- 1:1, 1:N, N:M 관계 명시
- FK 위치 명확히 표시

### Cascade 규칙
- ON DELETE (CASCADE, SET NULL, RESTRICT)
- ON UPDATE 정책

### 기타
- Fetch 전략 (EAGER/LAZY) — 주석으로 표시
- Orphan Removal 정책
- 복합 인덱스 전략

## 출력 형식

```markdown
# DDD Class Diagram

## Entity Relationship Diagram

\```mermaid
erDiagram
USER {
uuid id PK "NOT NULL"
string email "NOT NULL, UNIQUE, INDEX"
string password_hash "NOT NULL"
string nickname "NOT NULL"
boolean is_active "DEFAULT true"
datetime created_at "NOT NULL"
datetime updated_at "NOT NULL"
datetime deleted_at "NULLABLE"
}

ORDER {
uuid id PK "NOT NULL"
uuid user_id FK "NOT NULL, INDEX"
string status "NOT NULL, DEFAULT 'PENDING'"
}

USER ||--o{ ORDER : "places"
\```

## Enum Definitions

| Enum | Values | Description |
|------|--------|-------------|
| OrderStatus | PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED | 주문 상태 |

## Cascade Rules

| Parent | Child | ON DELETE | ON UPDATE |
|--------|-------|-----------|-----------|
| USER | ORDER | CASCADE | CASCADE |

## Index Strategy

| Table | Columns | Type | Purpose |
|-------|---------|------|---------|
| users | email | UNIQUE | 로그인 조회 |
| orders | user_id, created_at | COMPOSITE | 사용자별 주문 목록 |
```

## 참고

- [diagram-template.md](references/diagram-template.md) — 전체 예시 템플릿
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# DDD Class Diagram Template

## Mermaid erDiagram 문법 참조

### 기본 Entity 정의

```mermaid
erDiagram
ENTITY_NAME {
type field_name constraint "annotations"
}
```

### 타입 매핑 (Python → Mermaid)

| Python Type | Mermaid Type | SQLModel Field |
|-------------|-------------|----------------|
| `UUID` | `uuid` | `Field(primary_key=True)` |
| `str` | `string` | `Field(max_length=N)` |
| `int` | `int` | `Field(default=0)` |
| `float` | `float` | `Field()` |
| `bool` | `boolean` | `Field(default=True)` |
| `datetime` | `datetime` | `Field(default_factory=utcnow)` |
| `Enum` | `string` | `Field()` — DB에는 string 저장 |

### 제약 조건 표기

| 표기 | 의미 |
|------|------|
| `PK` | Primary Key |
| `FK` | Foreign Key |
| `"NOT NULL"` | 필수 필드 |
| `"NULLABLE"` | 선택 필드 |
| `"UNIQUE"` | 유니크 제약 |
| `"INDEX"` | 단일 인덱스 |
| `"DEFAULT value"` | 기본값 |

### 관계 표기

| 기호 | 의미 |
|------|------|
| `\|\|--o{` | 1:N (one-to-many) |
| `\|\|--\|\|` | 1:1 (one-to-one) |
| `}o--o{` | N:M (many-to-many) |

### 완전한 예시

```mermaid
erDiagram
USER {
uuid id PK "NOT NULL"
string email "NOT NULL, UNIQUE, INDEX"
string password_hash "NOT NULL"
string nickname "NOT NULL, max_length=50"
string profile_image_url "NULLABLE, max_length=500"
boolean is_active "NOT NULL, DEFAULT true"
int login_count "NOT NULL, DEFAULT 0"
datetime last_login_at "NULLABLE"
datetime created_at "NOT NULL"
datetime updated_at "NOT NULL"
datetime deleted_at "NULLABLE"
}

ORDER {
uuid id PK "NOT NULL"
uuid user_id FK "NOT NULL, INDEX"
string status "NOT NULL, DEFAULT 'PENDING'"
int total_amount "NOT NULL"
datetime created_at "NOT NULL"
datetime updated_at "NOT NULL"
datetime deleted_at "NULLABLE"
}

ORDER_ITEM {
uuid id PK "NOT NULL"
uuid order_id FK "NOT NULL, INDEX"
uuid product_id FK "NOT NULL"
int quantity "NOT NULL, DEFAULT 1"
int unit_price "NOT NULL"
}

PRODUCT {
uuid id PK "NOT NULL"
string name "NOT NULL, INDEX"
int price "NOT NULL"
boolean is_available "NOT NULL, DEFAULT true"
}

USER ||--o{ ORDER : "places"
ORDER ||--o{ ORDER_ITEM : "contains"
PRODUCT ||--o{ ORDER_ITEM : "included_in"
```

### Fetch 전략 주석

Mermaid에서는 직접 지원하지 않으므로 별도 테이블로 문서화:

| Parent | Child | Strategy | Reason |
|--------|-------|----------|--------|
| ORDER | ORDER_ITEM | EAGER | 주문 조회 시 항상 아이템 필요 |
| USER | ORDER | LAZY | 사용자 조회 시 주문 불필요 |

### Orphan Removal

| Parent | Child | Orphan Removal | 설명 |
|--------|-------|----------------|------|
| ORDER | ORDER_ITEM | YES | 주문 삭제 시 아이템도 삭제 |
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ src/
│ ├── utils.py # EnvironmentHelper
│ └── database.py # DB 연결
├── modules/{domain}/ # 도메인별 Vertical Slice
│ ├── __init__.py # Router 조합 (APIRouter + include_router)
│ ├── _models.py # Entities (BaseModel 상속)
│ ├── register.py # Use Case
│ ├── dtos.py # DTOs
│ └── router.py # Interface Adapter
│ ├── _repository.py # 공유 DB 접근 (선택)
│ ├── register.py # Use Case (DTO + Service + Controller 통합)
│ └── login.py # Use Case (DTO + Service + Controller 통합)
└── app/
└── main.py # FastAPI 엔트리포인트
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,29 @@ Vertical Slice Architecture는 기능별로 코드를 수직으로 분리하는

```
src/modules/{domain}/
├── __init__.py
├── _models.py # Entities (BaseModel 상속 → id, timestamps, soft_delete 포함)
├── register.py # Use Case (회원가입 비즈니스 로직)
├── login.py # Use Case (로그인 비즈니스 로직)
├── get_profile.py # Use Case (프로필 조회 비즈니스 로직)
└── router.py # Interface Adapter (API 엔드포인트만)
├── __init__.py # Router 조합 (APIRouter + include_router)
├── _models.py # Entities (BaseModel 상속 → id, timestamps, soft_delete 포함)
├── _repository.py # 공유 DB 접근 (선택)
├── register.py # Use Case (DTO + Service + Controller 통합)
├── login.py # Use Case (DTO + Service + Controller 통합)
└── get_profile.py # Use Case (DTO + Service + Controller 통합)
```

### __init__.py (Router 조합)

```python
"""users 도메인"""

from fastapi import APIRouter

from .register import router as register_router
from .login import router as login_router
from .get_profile import router as get_profile_router

router = APIRouter(prefix="/users", tags=["users"])
router.include_router(register_router)
router.include_router(login_router)
router.include_router(get_profile_router)
```

### Entities 규칙
Expand All @@ -39,8 +56,8 @@ class User(AppBaseModel, table=True):

## 핵심 정리

1. **기능별 파일 분리**: 각 Use Case는 독립 파일
1. **기능별 파일 분리**: 각 Use Case는 독립 파일 (DTO + Service + Controller 통합)
2. **Entities는 _models.py에만**: Domain 모델 중앙화
3. **Use Case = 비즈니스 로직**: 모든 로직은 Use Case 파일에
4. **Router = 엔드포인트만**: 단순 Use Case 호출
5. **DTO = Request/Response만**: 데이터 변환 역할
3. **Use Case = 완전한 Vertical Slice**: DTO, 비즈니스 로직, 엔드포인트가 한 파일에
4. **__init__.py = Router 조합**: 각 feature의 router를 include_router로 조합
5. **DTO = 인라인**: 각 feature 파일 내에 Request/Response 정의
Loading