14 KiB
14 KiB
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 캡슐화
utils/
{기능}_extractor.py: 데이터 추출 (json_extractor.py){기능}_helper.py: 도우미 함수- 명확하고 구체적인 이름 사용
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. 코드 작성 원칙
중복 코드 제거 우선
- 동일한 로직이 2개 이상의 위치에 반복되면
utils/에 유틸리티 함수로 추출 - 한 곳만 수정하여 전체 반영 가능하도록 설계
- 범용적이고 재사용 가능한 함수로 구현
참고: DOCS/book/300_architecture/guidelines/utility_functions.md
LangGraph 워크플로우
- 복잡한 다단계 처리: LangGraph 적극 활용
- 의도 분류, 엔티티 추출, 스킬 선택 등 다단계 워크플로우는 LangGraph로 구현
- 상태 관리, 조건부 분기, 재시도 로직 등 복잡한 제어 흐름에 적합
- 프로덕션 핵심 워크플로우: PostgresSaver로 체크포인트 구현 (권장)
- 장기 실행 워크플로우는 상태 영속성 필수
- 중단 시 복구 가능하도록 체크포인트 저장
- 실험/경량 플로우: stateless LangGraph 허용
- 단순한 워크플로우는 stateless로 시작, 필요 시 체크포인트 추가
계층별 원칙
- router: 서비스 호출만, DB/비즈니스 로직 금지
- services: 비즈니스 로직 구현, state를 통한 DB 접근
- state: DB CRUD만, 비즈니스 로직 금지
6. DB 접근 규칙
환경변수 사용
DATABASE_URL: 메인 DBMETRICS_DATABASE_URL: 메트릭 전용 DBTEST_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 코드를 동시에 수정해야 함
필수 동기화 항목
- ORM 모델 (
state/{도메인}_repository.py또는models/{도메인}_model.py)- 컬럼 타입, nullable 여부, 기본값 등
- DDL (
_ensure_tables()또는 마이그레이션 스크립트)- CREATE TABLE, ALTER TABLE 문
- 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줄 이하로 유지할 수 있는가?
- 중복 코드는 utils/로 추출할 수 있는가?
- DB 스키마 변경 시 ORM/DDL/Repository 동시 수정 확인
- LLM 호출 횟수 계산 및 최적화 검토
- 애매한 케이스는 LLM 우선 접근 원칙 적용 확인
- 복잡한 워크플로우는 LangGraph 활용 검토
- 원칙 문서 확인 완료 (
311_FastAPI_구조_원칙.md,312_문서_작성_원칙.md)
10. 예외 상황
허용되는 예외
- 긴급 핫픽스: 임시로 계층 건너뛰기 가능 (문서화 필수)
- 레거시 코드: 점진적 리팩토링
- 성능 최적화: 충분한 근거 필요
예외 처리 시
- 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 우선 접근 원칙
핵심 원칙: 애매한 경우에는 하드코딩된 규칙(heuristic) 대신 LLM을 적극 활용
LLM 우선 적용 대상
- 의도 분류가 애매한 경우 (확신도 < 0.7)
- 규칙 기반 패턴 매칭 실패 시 LLM으로 재분류
- 짧은 질문("어디서?", "결과는?")은 LLM이 맥락을 해석하여 확장
- 맥락 의존 질문 ("그거 어떻게 됐어?", "취소해줘")
- LLM이 직전 대화 맥락을 참고하여 선행사 해석 및 질문 확장
- 모호한 표현 처리 ("어떻게 생각해?", "괜찮아?")
- LLM이 컨텍스트를 이해하여 적절한 응답 생성
- 엔티티 추출 및 해석
- 규칙 기반 추출 실패 시 LLM으로 재시도
- 대명사, 지시어 해소는 LLM 활용
규칙 기반은 보조적으로만 사용
- 명확한 케이스: 인사("안녕"), 명령어("/news") 등 패턴이 명확한 경우만 규칙 사용
- 성능 최적화: FastPath로 명확한 케이스를 빠르게 처리, 애매한 경우만 LLM 호출
- 비용 최적화: 확신도 높은 케이스는 규칙으로 처리, 애매한 경우만 LLM 사용
금지 사항
- ❌ 애매한 케이스를 규칙/패턴 매칭으로 강제 처리
- ❌ LLM 호출을 피하기 위해 복잡한 규칙 체인 구축
- ❌ 새로운 패턴마다 규칙 추가하는 방식 (유지보수 부담 증가)
장점
- 유연성: 새로운 패턴에 대한 규칙 추가 없이 LLM이 자연스럽게 처리
- 정확도: 맥락 이해 능력으로 규칙 기반보다 정확한 해석
- 유지보수: 규칙 관리 부담 감소, LLM이 자동으로 패턴 학습
14. LLM 호출 최적화 원칙
핵심 원칙: 호출 횟수 계산 및 최적화 사전 검토 필수
필수 검토 사항
- 호출 횟수 계산: 페이지/문서당 LLM 호출 횟수 사전 계산
- API 할당량 확인: 사용하는 LLM API의 할당량 제한 확인 (RPM, RPD 등)
- 통합 가능 여부: 단일 프롬프트로 통합 가능한 작업은 반드시 통합
최적화 방법
- 단일 호출 통합: 여러 개별 호출을 하나의 프롬프트로 통합
- 배치 처리: 가능한 경우 여러 항목을 한 번에 처리
- 캐싱: 동일한 입력에 대한 결과 캐싱
체크리스트
- LLM 호출 횟수 계산 완료
- API 할당량 제한 확인 완료
- 통합 가능한 호출 통합 완료
- 테스트로 호출 횟수 검증 완료
교훈
- 호출 횟수 미검토 시 API 할당량 초과(429 에러) 발생 가능
- 단일 프롬프트로 통합 가능한 작업은 반드시 통합하여 호출 횟수 최소화
15. 장기 작업(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) 블록 모두에 동일한 타임아웃 설정 적용
- 프록시 타임아웃은 백엔드 작업 시간보다 충분히 길게 설정
16. 정적 파일 서빙 원칙
필수 원칙
- 백엔드 금지: FastAPI 백엔드는 정적 파일(HTML/CSS/JS)을 서빙하지 않음
- nginx 직접 서빙: nginx가 정적 파일을 직접 서빙하는 업계 표준 방식 사용
- 역할 분리: 백엔드는 API 처리에 집중, 웹서버(nginx)는 정적 파일 서빙 담당
- FileResponse 제거: 백엔드에서 FileResponse로 정적 파일 서빙하는 코드 작성 금지
배경
- nginx가 정적 파일을 직접 서빙하면 FastAPI 프로세스를 거치지 않아 성능 최적화
- 프로덕션 환경에서 표준적인 배포 방식
- 백엔드와 웹서버의 명확한 역할 분리
17. Slack API 호출 원칙
- skill-slack API 사용 필수: rb8001에서 Slack API 호출 시 WebClient 직접 사용 금지, skill-slack HTTP API 사용
- thread_ts 처리: None/빈 문자열일 때는 payload에 포함하지 않음 (조건부 포함 필수)
- 메시지 업데이트: 인터랙티브 버튼 응답은 원본 메시지 업데이트(
/api/v1/update) 사용
18. 테스트 원칙
- 실제 테스트 필수: 코드 수정 후 추측하지 말고 실제로 테스트 (curl, Slack 직접 사용, DB 조회)
- Git 커밋 확인: 각 프로젝트 폴더에서 개별 확인 (루트에서 확인 금지)
19. 모범 사례 참고
본 문서는 FastAPI 커뮤니티의 다음 모범 사례를 반영하였습니다:
- models/schemas 분리: DB 스키마와 API 스펙 독립 관리
- Repository 패턴: state/repositories에서 CRUD 캡슐화
- DB 세션 중앙화: db/database.py에서 의존성 주입
- API 버전 관리: router/v1/, router/v2/ 구조
- 관심사 분리: 요청/비즈니스/데이터 계층 명확한 역할 분담