# FastAPI 프로젝트 구조 원칙

**작성일**: 2025-09-17
**수정일**: 2025-12-02 (Nginx 타임아웃 설정 확인 필수 원칙 추가)

## 1. 계층 분리 원칙

### 필수 계층
```
요청 계층 (router/)
    ↓
비즈니스 계층 (services/, llm/, brain/)
    ↓
데이터 계층 (state/, repositories/)
```

### 계층별 책임

| 계층 | 역할 | 금지 사항 |
|------|------|----------|
| **router/** | HTTP 요청/응답 처리만 | DB 직접 접근, 비즈니스 로직 |
| **services/** | 비즈니스 로직 구현 | DB 직접 연결 (state를 통해서만) |
| **state/** | DB CRUD만 | 비즈니스 로직 포함 |

## 2. 폴더 구조 규칙

### 표준 구조
```
{service_name}/
├── main.py              # 앱 실행, 라우터 등록만
├── app/
│   ├── router/         # HTTP 엔드포인트
│   │   └── v1/         # API 버전 관리 (선택)
│   ├── services/       # 비즈니스 로직
│   ├── state/          # DB 접근 (Repository 패턴)
│   ├── models/         # ORM 모델 (DB 테이블 정의)
│   ├── schemas/        # Pydantic 모델 (API 요청/응답)
│   ├── core/           # 설정, 공통 기능
│   ├── db/             # DB 세션 관리 (선택)
│   └── utils/          # 유틸리티
└── tests/
```

### 폴더 명명 규칙
- `router/` 또는 `api/`: HTTP 처리
- `services/`: 비즈니스 로직
- `state/` 또는 `repositories/`: Repository 패턴으로 CRUD 캡슐화
- `models/`: SQLAlchemy 등 ORM 모델
- `schemas/`: Pydantic 요청/응답 스키마 (models와 분리)
- `db/`: DB 엔진/세션 중앙 관리 (선택)
- 복수형 사용 권장

## 3. 파일 명명 규칙

### router/
- `{기능}_handler.py`: 이벤트 처리 (slack_handler.py)
- `{기능}_endpoint.py`: REST API (emotion_endpoint.py)

### services/
- `{도메인}_{기능}.py`: coldmail_filter.py, ir_analyzer.py
- 한 파일 최대 300줄

### state/ (Repository 패턴)
- `database.py`: 통합 DB 접근
- `{도메인}_repository.py`: 도메인별 CRUD 캡슐화

### models/
- `{도메인}_model.py`: ORM 모델 (예: user_model.py, emotion_model.py)

### schemas/
- `{도메인}_schema.py`: API 입출력 스키마 (예: user_schema.py, emotion_schema.py)

## 4. 의존성 방향 규칙

### 단방향 흐름
```
router → services → state/repositories
  ↓         ↓         ↓
schemas   core     models
  ↓
utils
```

### 계층 간 데이터 흐름
- **router**: schemas로 요청/응답 검증
- **services**: schemas + models 사용 가능
- **state/repositories**: models만 사용 (DB 접근)

### 금지 사항
- ❌ 순환 참조: A imports B, B imports A
- ❌ 하위가 상위 호출: state가 services 호출
- ❌ 계층 건너뛰기: router가 직접 state 호출 (긴급 상황 제외)

## 5. 코드 작성 원칙

### LangGraph 워크플로우
- **복잡한 다단계 처리**: LangGraph 적극 활용
- **프로덕션 핵심 워크플로우**: PostgresSaver로 체크포인트 구현 (권장)
- **실험/경량 플로우**: stateless LangGraph 허용

### 계층별 원칙
- **router**: 서비스 호출만, DB/비즈니스 로직 금지
- **services**: 비즈니스 로직 구현, state를 통한 DB 접근
- **state**: DB CRUD만, 비즈니스 로직 금지

## 6. DB 접근 규칙

### 환경변수 사용
- `DATABASE_URL`: 메인 DB
- `METRICS_DATABASE_URL`: 메트릭 전용 DB
- `TEST_DATABASE_URL`: 테스트 DB

### 연결 방식
- **권장**: `db/database.py`에서 DB 세션 중앙 관리 (의존성 주입)
- **간단한 경우**: `state/database.py`에서 직접 연결

### 금지 사항
- ❌ 프로덕션 router/services에서 직접 asyncpg.connect()
- ❌ 하드코딩된 DB URL
- ❌ JSONB 저장 시 dict 직접 전달 (json.dumps() 필수)
- ❌ 프로덕션 요청 경로에서 직접 DB 연결 재사용

## 6-1. DB 스키마 변경 시 동기화 필수

**핵심 원칙**: ORM 모델, DDL, Repository 코드를 동시에 수정해야 함

### 필수 동기화 항목
1. **ORM 모델** (`state/{도메인}_repository.py` 또는 `models/{도메인}_model.py`)
   - 컬럼 타입, nullable 여부, 기본값 등
2. **DDL** (`_ensure_tables()` 또는 마이그레이션 스크립트)
   - CREATE TABLE, ALTER TABLE 문
3. **Repository 코드** (`state/{도메인}_repository.py`)
   - INSERT/UPDATE 시 필드 처리 로직

### 체크리스트
- [ ] ORM 모델 수정 완료
- [ ] DDL 수정 완료 (기존 DB 마이그레이션 스크립트 작성)
- [ ] Repository 코드 수정 완료 (`.get()` 사용 등)
- [ ] 테스트 작성 및 검증 완료

### 교훈
- 한 곳만 수정 시 런타임 에러(KeyError 등) 또는 스키마 불일치 발생
- 스키마 변경 시 3곳(ORM/DDL/Repository) 동시 점검 필수

## 7. 파일 크기 제한

- **한 파일 최대 300줄 권장**
- 초과 시 기능별 분리

## 8. Import 규칙

### 금지
- ❌ wildcard import (`from module import *`)
- ❌ 상대 import로 순환 참조 가능성 (`from ..router import x`)

### 권장
- ✅ 명시적 import (`from app.state.database import save_emotion_reading`)
- ✅ 모듈 import (`from app.services import coldmail_filter`)

## 9. 체크리스트

코드 작성 전:
- [ ] 이 코드는 어느 계층인가?
- [ ] DB 접근은 state를 통하는가?
- [ ] 비즈니스 로직이 router에 있지 않은가?
- [ ] 순환 import 가능성은 없는가?
- [ ] 핵심 파일은 300줄 이하로 유지할 수 있는가?
- [ ] DB 스키마 변경 시 ORM/DDL/Repository 동시 수정 확인
- [ ] LLM 호출 횟수 계산 및 최적화 검토
- [ ] 원칙 문서 확인 완료 (`311_FastAPI_구조_원칙.md`, `312_문서_작성_원칙.md`)

## 10. 예외 상황

### 허용되는 예외
1. **긴급 핫픽스**: 임시로 계층 건너뛰기 가능 (문서화 필수)
2. **레거시 코드**: 점진적 리팩토링
3. **성능 최적화**: 충분한 근거 필요

### 예외 처리 시
- TODO 주석으로 계층 위반 표시
- 긴급 수정 사유 명시

## 11. 로깅 원칙

**로그 레벨 사용 기준** (Python logging 공식 문서):
- **DEBUG**: 개발/디버깅용 상세 정보 (중간 과정, 내부 상태)
- **INFO**: 정상 동작 및 주요 이벤트 (시작/종료, 주요 단계)
- **WARNING**: 잠재적 문제 (예상치 못한 상황, 성능 저하 가능성)
- **ERROR**: 오류 발생 (기능 실패, 예외 처리)

**규칙**:
- 시작/종료는 반드시 INFO 레벨
- 중간 과정은 DEBUG 레벨
- 프로덕션에서는 INFO 기본, DEBUG는 필요 시에만 활성화

## 12. 환경변수 관리 원칙

**단일 소스 원칙**:
- **`.env`**: 모든 환경변수 값의 단일 소스 (실제 값만 저장)
- **`docker-compose.yml`**: `env_file: - .env`로 자동 로드, `environment:` 섹션은 선택사항
- **`config.py`**: Pydantic Settings로 `.env` 자동 로드, 타입 검증 및 기본값만 담당

**금지 사항**:
- ❌ `.env`, `docker-compose.yml`, `config.py`에 동일한 변수를 중복 정의
- ❌ 코드에서 `os.getenv()` 직접 호출 (Pydantic Settings 사용)
- ❌ `docker-compose.yml`의 `environment:` 섹션에 하드코딩된 값
- ❌ 코드에 API 키, 토큰, 비밀번호 등 민감 정보를 기본값/백업 값으로 직접 하드코딩

## 13. LLM 호출 최적화 원칙

**핵심 원칙**: 호출 횟수 계산 및 최적화 사전 검토 필수

### 필수 검토 사항
1. **호출 횟수 계산**: 페이지/문서당 LLM 호출 횟수 사전 계산
2. **API 할당량 확인**: 사용하는 LLM API의 할당량 제한 확인 (RPM, RPD 등)
3. **통합 가능 여부**: 단일 프롬프트로 통합 가능한 작업은 반드시 통합

### 최적화 방법
- **단일 호출 통합**: 여러 개별 호출을 하나의 프롬프트로 통합
- **배치 처리**: 가능한 경우 여러 항목을 한 번에 처리
- **캐싱**: 동일한 입력에 대한 결과 캐싱

### 체크리스트
- [ ] LLM 호출 횟수 계산 완료
- [ ] API 할당량 제한 확인 완료
- [ ] 통합 가능한 호출 통합 완료
- [ ] 테스트로 호출 횟수 검증 완료

### 교훈
- 호출 횟수 미검토 시 API 할당량 초과(429 에러) 발생 가능
- 단일 프롬프트로 통합 가능한 작업은 반드시 통합하여 호출 횟수 최소화

## 14. 장기 작업(LLM/RAG 등) 처리 원칙

**핵심 원칙**: HTTP 요청으로 노출되는 장기 작업(LLM, RAG, 대용량 PDF 처리 등)은 동기식으로 붙잡지 말고, 항상 "즉시 ID 반환 + 비동기 백그라운드 작업 + 후속 조회/웹훅" 패턴으로 설계한다.

### 권장 패턴
- `POST /api/.../start` → 백그라운드 작업 등록, 즉시 `job_id` 또는 `evaluation_id` 반환
- `GET /api/.../{id}` → 상태/결과 조회 (프론트는 폴링 또는 SSE/WebSocket 사용)
- 필요 시 웹훅/이벤트로 완료 알림 (Slack, 이메일 등)

### 금지 사항
- ❌ Nginx `proxy_read_timeout`에 의존해 60초 이상 동기식으로 응답을 붙잡는 설계
- ❌ LLM/RAG/외부 API 호출이 여러 단계로 이어지는 워크플로우를 단일 HTTP 요청에 모두 묶는 방식

### Nginx 타임아웃 설정 확인 필수

**핵심 원칙**: 장기 작업을 위한 프록시 엔드포인트는 HTTP/HTTPS 블록 모두에 충분한 타임아웃 설정 필요

**필수 확인 사항**:
- Nginx 설정 파일에서 프록시 경로의 `location` 블록 확인
- `proxy_read_timeout`, `proxy_connect_timeout`, `proxy_send_timeout` 설정 확인
- HTTP(port 80)와 HTTPS(port 443) 블록 모두에 동일한 타임아웃 설정 적용
- 프록시 타임아웃은 백엔드 작업 시간보다 충분히 길게 설정

## 15. 정적 파일 서빙 원칙

### 필수 원칙
- **백엔드 금지**: FastAPI 백엔드는 정적 파일(HTML/CSS/JS)을 서빙하지 않음
- **nginx 직접 서빙**: nginx가 정적 파일을 직접 서빙하는 업계 표준 방식 사용
- **역할 분리**: 백엔드는 API 처리에 집중, 웹서버(nginx)는 정적 파일 서빙 담당
- **FileResponse 제거**: 백엔드에서 FileResponse로 정적 파일 서빙하는 코드 작성 금지

### 배경
- nginx가 정적 파일을 직접 서빙하면 FastAPI 프로세스를 거치지 않아 성능 최적화
- 프로덕션 환경에서 표준적인 배포 방식
- 백엔드와 웹서버의 명확한 역할 분리

## 16. 모범 사례 참고

본 문서는 FastAPI 커뮤니티의 다음 모범 사례를 반영하였습니다:

1. **models/schemas 분리**: DB 스키마와 API 스펙 독립 관리
2. **Repository 패턴**: state/repositories에서 CRUD 캡슐화
3. **DB 세션 중앙화**: db/database.py에서 의존성 주입
4. **API 버전 관리**: router/v1/, router/v2/ 구조
5. **관심사 분리**: 요청/비즈니스/데이터 계층 명확한 역할 분담
