- 7-8월 초기 구축 문서 12개를 _archive/troubleshooting/2025_07-08_initial_setup/로 이동 - book/300_architecture/390_human_in_the_loop_intent_learning.md를 journey/research/intent_classification/로 이동 (개발 여정 문서) - 빈 폴더 제거 (journey/assets/*)
6.4 KiB
6.4 KiB
FastAPI 프로젝트 구조 원칙
작성일: 2025-09-17 수정일: 2025-10-02 (모범 사례 반영)
1. 계층 분리 원칙
필수 계층
요청 계층 (router/)
↓
비즈니스 계층 (services/, llm/, brain/)
↓
데이터 계층 (state/, repositories/)
계층별 책임
| 계층 | 역할 | 금지 사항 |
|---|---|---|
| router/ | HTTP 요청/응답 처리만 | DB 직접 접근, 비즈니스 로직 |
| services/llm/ | 비즈니스 로직 구현 | 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 캡슐화 (예: user_repository.py는 User 모델 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 계층
# ✅ 올바름
async def handle_request(data: dict):
result = await some_service.process(data) # 서비스 호출
return {"result": result}
# ❌ 금지
async def handle_request(data: dict):
conn = await asyncpg.connect(...) # DB 직접 접근
result = complex_logic(data) # 비즈니스 로직
services 계층
# ✅ 올바름
async def process_data(data: dict):
validated = validate(data) # 비즈니스 로직
await save_to_db(validated) # state 호출
return validated
# ❌ 금지
async def process_data(data: dict):
conn = await asyncpg.connect(...) # 직접 DB 연결
state 계층
# ✅ 올바름
async def save_emotion(data: dict):
conn = await asyncpg.connect(METRICS_DB_URL)
await conn.execute("INSERT INTO ...") # DB만
await conn.close()
# ❌ 금지
async def save_emotion(data: dict):
if data['emotion'] == 'anger': # 비즈니스 로직
data = transform(data)
await conn.execute(...)
6. DB 접근 규칙
환경변수 사용
DATABASE_URL: 메인 DBMETRICS_DATABASE_URL: 메트릭 전용 DBTEST_DATABASE_URL: 테스트 DB
연결 방식
# db/database.py: DB 세션 중앙 관리 (권장)
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
async def get_session() -> AsyncSession:
# 의존성 주입으로 세션 제공
pass
# state/database.py: 직접 연결 (간단한 경우)
async def get_connection():
return await asyncpg.connect(os.getenv("DATABASE_URL"))
금지 사항
- ❌ router/services에서 직접 asyncpg.connect()
- ❌ 하드코딩된 DB URL
- ❌ JSONB 저장 시 dict 직접 전달 (json.dumps() 필수)
7. 파일 크기 제한
- 한 파일 최대 300줄
- 초과 시 기능별 분리
- 예:
services/email_integration.py(500줄) →email_send.py+email_fetch.py
8. Import 규칙
금지
from app.state.database import * # ❌ wildcard
from ..router.slack_handler import x # ❌ 순환 가능성
권장
from app.state.database import save_emotion_reading # ✅ 명시적
from app.services import coldmail_filter # ✅ 모듈 import
9. 체크리스트
코드 작성 전:
- 이 코드는 어느 계층인가?
- DB 접근은 state를 통하는가?
- 비즈니스 로직이 router에 있지 않은가?
- 순환 import 가능성은 없는가?
- 파일 크기가 300줄 이하인가?
10. 예외 상황
허용되는 예외
- 긴급 핫픽스: 임시로 계층 건너뛰기 가능 (문서화 필수)
- 레거시 코드: 점진적 리팩토링
- 성능 최적화: 충분한 근거 필요
예외 처리 시
# TODO: 계층 위반 - 리팩토링 필요 (issue #123)
# 긴급 수정: 2025-10-02, 사유: DB 장애 복구
11. 모범 사례 참고
본 문서는 FastAPI 커뮤니티의 다음 모범 사례를 반영하였습니다:
- models/schemas 분리: DB 스키마와 API 스펙 독립 관리
- Repository 패턴: state/repositories에서 CRUD 캡슐화
- DB 세션 중앙화: db/database.py에서 의존성 주입
- API 버전 관리: router/v1/, router/v2/ 구조
- 관심사 분리: 요청/비즈니스/데이터 계층 명확한 역할 분담