mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-09 10:53:34 +08:00
docs(ko-KR): add Korean translation for examples
Translate 6 CLAUDE.md examples (project, user, SaaS Next.js, Django API, Go microservice, Rust API) and copy statusline.json config.
This commit is contained in:
308
docs/ko-KR/examples/django-api-CLAUDE.md
Normal file
308
docs/ko-KR/examples/django-api-CLAUDE.md
Normal file
@@ -0,0 +1,308 @@
|
||||
# Django REST API — 프로젝트 CLAUDE.md
|
||||
|
||||
> PostgreSQL과 Celery를 사용하는 Django REST Framework API의 실전 예시입니다.
|
||||
> 프로젝트 루트에 복사하여 서비스에 맞게 커스터마이즈하세요.
|
||||
|
||||
## 프로젝트 개요
|
||||
|
||||
**기술 스택:** Python 3.12+, Django 5.x, Django REST Framework, PostgreSQL, Celery + Redis, pytest, Docker Compose
|
||||
|
||||
**아키텍처:** 비즈니스 도메인별 앱으로 구성된 도메인 주도 설계. API 레이어에 DRF, 비동기 작업에 Celery, 테스트에 pytest 사용. 모든 엔드포인트는 JSON을 반환하며 템플릿 렌더링은 없음.
|
||||
|
||||
## 필수 규칙
|
||||
|
||||
### Python 규칙
|
||||
|
||||
- 모든 함수 시그니처에 type hints 사용 — `from __future__ import annotations` 사용
|
||||
- `print()` 문 사용 금지 — `logging.getLogger(__name__)` 사용
|
||||
- 문자열 포매팅은 f-strings 사용, `%`나 `.format()`은 사용 금지
|
||||
- 파일 작업에 `os.path` 대신 `pathlib.Path` 사용
|
||||
- isort로 import 정렬: stdlib, third-party, local 순서 (ruff에 의해 강제)
|
||||
|
||||
### 데이터베이스
|
||||
|
||||
- 모든 쿼리는 Django ORM 사용 — raw SQL은 `.raw()`와 parameterized 쿼리로만 사용
|
||||
- 마이그레이션은 git에 커밋 — 프로덕션에서 `--fake` 사용 금지
|
||||
- N+1 쿼리 방지를 위해 `select_related()`와 `prefetch_related()` 사용
|
||||
- 모든 모델에 `created_at`과 `updated_at` 자동 필드 필수
|
||||
- `filter()`, `order_by()`, 또는 `WHERE` 절에 사용되는 모든 필드에 인덱스 추가
|
||||
|
||||
```python
|
||||
# 나쁜 예: N+1 쿼리
|
||||
orders = Order.objects.all()
|
||||
for order in orders:
|
||||
print(order.customer.name) # 각 주문마다 DB를 조회함
|
||||
|
||||
# 좋은 예: join을 사용한 단일 쿼리
|
||||
orders = Order.objects.select_related("customer").all()
|
||||
```
|
||||
|
||||
### 인증
|
||||
|
||||
- `djangorestframework-simplejwt`를 통한 JWT — access token (15분) + refresh token (7일)
|
||||
- 모든 뷰에 permission 클래스 지정 — 기본값에 의존하지 않기
|
||||
- `IsAuthenticated`를 기본으로, 객체 수준 접근에는 커스텀 permission 추가
|
||||
- 로그아웃을 위한 token blacklisting 활성화
|
||||
|
||||
### Serializers
|
||||
|
||||
- 간단한 CRUD에는 `ModelSerializer`, 복잡한 유효성 검증에는 `Serializer` 사용
|
||||
- 입력/출력 형태가 다를 때는 읽기와 쓰기 serializer를 분리
|
||||
- 유효성 검증은 serializer 레벨에서 — 뷰는 얇게 유지
|
||||
|
||||
```python
|
||||
class CreateOrderSerializer(serializers.Serializer):
|
||||
product_id = serializers.UUIDField()
|
||||
quantity = serializers.IntegerField(min_value=1, max_value=100)
|
||||
|
||||
def validate_product_id(self, value):
|
||||
if not Product.objects.filter(id=value, active=True).exists():
|
||||
raise serializers.ValidationError("Product not found or inactive")
|
||||
return value
|
||||
|
||||
class OrderDetailSerializer(serializers.ModelSerializer):
|
||||
customer = CustomerSerializer(read_only=True)
|
||||
product = ProductSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = ["id", "customer", "product", "quantity", "total", "status", "created_at"]
|
||||
```
|
||||
|
||||
### 오류 처리
|
||||
|
||||
- 일관된 오류 응답을 위해 DRF exception handler 사용
|
||||
- 비즈니스 로직용 커스텀 예외는 `core/exceptions.py`에 정의
|
||||
- 클라이언트에 내부 오류 세부 정보를 노출하지 않기
|
||||
|
||||
```python
|
||||
# core/exceptions.py
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
class InsufficientStockError(APIException):
|
||||
status_code = 409
|
||||
default_detail = "Insufficient stock for this order"
|
||||
default_code = "insufficient_stock"
|
||||
```
|
||||
|
||||
### 코드 스타일
|
||||
|
||||
- 코드나 주석에 이모지 사용 금지
|
||||
- 최대 줄 길이: 120자 (ruff에 의해 강제)
|
||||
- 클래스: PascalCase, 함수/변수: snake_case, 상수: UPPER_SNAKE_CASE
|
||||
- 뷰는 얇게 유지 — 비즈니스 로직은 서비스 함수나 모델 메서드에 배치
|
||||
|
||||
## 파일 구조
|
||||
|
||||
```
|
||||
config/
|
||||
settings/
|
||||
base.py # 공유 설정
|
||||
local.py # 개발 환경 오버라이드 (DEBUG=True)
|
||||
production.py # 프로덕션 설정
|
||||
urls.py # 루트 URL 설정
|
||||
celery.py # Celery 앱 설정
|
||||
apps/
|
||||
accounts/ # 사용자 인증, 회원가입, 프로필
|
||||
models.py
|
||||
serializers.py
|
||||
views.py
|
||||
services.py # 비즈니스 로직
|
||||
tests/
|
||||
test_views.py
|
||||
test_services.py
|
||||
factories.py # Factory Boy 팩토리
|
||||
orders/ # 주문 관리
|
||||
models.py
|
||||
serializers.py
|
||||
views.py
|
||||
services.py
|
||||
tasks.py # Celery 작업
|
||||
tests/
|
||||
products/ # 상품 카탈로그
|
||||
models.py
|
||||
serializers.py
|
||||
views.py
|
||||
tests/
|
||||
core/
|
||||
exceptions.py # 커스텀 API 예외
|
||||
permissions.py # 공유 permission 클래스
|
||||
pagination.py # 커스텀 페이지네이션
|
||||
middleware.py # 요청 로깅, 타이밍
|
||||
tests/
|
||||
```
|
||||
|
||||
## 주요 패턴
|
||||
|
||||
### Service 레이어
|
||||
|
||||
```python
|
||||
# apps/orders/services.py
|
||||
from django.db import transaction
|
||||
|
||||
def create_order(*, customer, product_id: uuid.UUID, quantity: int) -> Order:
|
||||
"""재고 검증과 결제 보류를 포함한 주문 생성."""
|
||||
product = Product.objects.select_for_update().get(id=product_id)
|
||||
|
||||
if product.stock < quantity:
|
||||
raise InsufficientStockError()
|
||||
|
||||
with transaction.atomic():
|
||||
order = Order.objects.create(
|
||||
customer=customer,
|
||||
product=product,
|
||||
quantity=quantity,
|
||||
total=product.price * quantity,
|
||||
)
|
||||
product.stock -= quantity
|
||||
product.save(update_fields=["stock", "updated_at"])
|
||||
|
||||
# 비동기: 주문 확인 이메일 발송
|
||||
send_order_confirmation.delay(order.id)
|
||||
return order
|
||||
```
|
||||
|
||||
### View 패턴
|
||||
|
||||
```python
|
||||
# apps/orders/views.py
|
||||
class OrderViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
pagination_class = StandardPagination
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == "create":
|
||||
return CreateOrderSerializer
|
||||
return OrderDetailSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
Order.objects
|
||||
.filter(customer=self.request.user)
|
||||
.select_related("product", "customer")
|
||||
.order_by("-created_at")
|
||||
)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
order = create_order(
|
||||
customer=self.request.user,
|
||||
product_id=serializer.validated_data["product_id"],
|
||||
quantity=serializer.validated_data["quantity"],
|
||||
)
|
||||
serializer.instance = order
|
||||
```
|
||||
|
||||
### 테스트 패턴 (pytest + Factory Boy)
|
||||
|
||||
```python
|
||||
# apps/orders/tests/factories.py
|
||||
import factory
|
||||
from apps.accounts.tests.factories import UserFactory
|
||||
from apps.products.tests.factories import ProductFactory
|
||||
|
||||
class OrderFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = "orders.Order"
|
||||
|
||||
customer = factory.SubFactory(UserFactory)
|
||||
product = factory.SubFactory(ProductFactory, stock=100)
|
||||
quantity = 1
|
||||
total = factory.LazyAttribute(lambda o: o.product.price * o.quantity)
|
||||
|
||||
# apps/orders/tests/test_views.py
|
||||
import pytest
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestCreateOrder:
|
||||
def setup_method(self):
|
||||
self.client = APIClient()
|
||||
self.user = UserFactory()
|
||||
self.client.force_authenticate(self.user)
|
||||
|
||||
def test_create_order_success(self):
|
||||
product = ProductFactory(price=29_99, stock=10)
|
||||
response = self.client.post("/api/orders/", {
|
||||
"product_id": str(product.id),
|
||||
"quantity": 2,
|
||||
})
|
||||
assert response.status_code == 201
|
||||
assert response.data["total"] == 59_98
|
||||
|
||||
def test_create_order_insufficient_stock(self):
|
||||
product = ProductFactory(stock=0)
|
||||
response = self.client.post("/api/orders/", {
|
||||
"product_id": str(product.id),
|
||||
"quantity": 1,
|
||||
})
|
||||
assert response.status_code == 409
|
||||
|
||||
def test_create_order_unauthenticated(self):
|
||||
self.client.force_authenticate(None)
|
||||
response = self.client.post("/api/orders/", {})
|
||||
assert response.status_code == 401
|
||||
```
|
||||
|
||||
## 환경 변수
|
||||
|
||||
```bash
|
||||
# Django
|
||||
SECRET_KEY=
|
||||
DEBUG=False
|
||||
ALLOWED_HOSTS=api.example.com
|
||||
|
||||
# 데이터베이스
|
||||
DATABASE_URL=postgres://user:pass@localhost:5432/myapp
|
||||
|
||||
# Redis (Celery broker + 캐시)
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
|
||||
# JWT
|
||||
JWT_ACCESS_TOKEN_LIFETIME=15 # 분
|
||||
JWT_REFRESH_TOKEN_LIFETIME=10080 # 분 (7일)
|
||||
|
||||
# 이메일
|
||||
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
|
||||
EMAIL_HOST=smtp.example.com
|
||||
```
|
||||
|
||||
## 테스트 전략
|
||||
|
||||
```bash
|
||||
# 전체 테스트 실행
|
||||
pytest --cov=apps --cov-report=term-missing
|
||||
|
||||
# 특정 앱 테스트 실행
|
||||
pytest apps/orders/tests/ -v
|
||||
|
||||
# 병렬 실행
|
||||
pytest -n auto
|
||||
|
||||
# 마지막 실행에서 실패한 테스트만 실행
|
||||
pytest --lf
|
||||
```
|
||||
|
||||
## ECC 워크플로우
|
||||
|
||||
```bash
|
||||
# 계획 수립
|
||||
/plan "Add order refund system with Stripe integration"
|
||||
|
||||
# TDD로 개발
|
||||
/tdd # pytest 기반 TDD 워크플로우
|
||||
|
||||
# 리뷰
|
||||
/python-review # Python 전용 코드 리뷰
|
||||
/security-scan # Django 보안 감사
|
||||
/code-review # 일반 품질 검사
|
||||
|
||||
# 검증
|
||||
/verify # 빌드, 린트, 테스트, 보안 스캔
|
||||
```
|
||||
|
||||
## Git 워크플로우
|
||||
|
||||
- `feat:` 새 기능, `fix:` 버그 수정, `refactor:` 코드 변경
|
||||
- `main`에서 feature 브랜치 생성, PR 필수
|
||||
- CI: ruff (린트 + 포맷), mypy (타입), pytest (테스트), safety (의존성 검사)
|
||||
- 배포: Docker 이미지, Kubernetes 또는 Railway로 관리
|
||||
Reference in New Issue
Block a user