DOCS/300_architecture/311_FastAPI_구조_원칙.md
happybell80 c5d2f08800 docs: FastAPI 구조 원칙 문서 정리
- 311_FastAPI_구조_원칙.md: 현재 구조가 아닌 지켜야 할 원칙으로 재작성
- 계층 분리, 의존성 방향, DB 접근 규칙 명확화
- 체크리스트 및 예외 상황 추가
2025-10-02 15:15:40 +09:00

4.5 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 엔드포인트
│   ├── services/       # 비즈니스 로직
│   ├── state/          # DB 접근
│   ├── core/           # 설정, 공통 기능
│   └── utils/          # 유틸리티
└── tests/

폴더 명명 규칙

  • router/ 또는 api/: HTTP 처리
  • services/: 도메인 로직
  • state/ 또는 repositories/: 데이터 접근
  • 복수형 사용 권장

3. 파일 명명 규칙

router/

  • {기능}_handler.py: 이벤트 처리 (slack_handler.py)
  • {기능}_endpoint.py: REST API (emotion_endpoint.py)

services/

  • {도메인}_{기능}.py: coldmail_filter.py, ir_analyzer.py
  • 한 파일 최대 500줄

state/

  • database.py: 통합 DB 접근
  • {도메인}_repository.py: 도메인별 분리 시

4. 의존성 방향 규칙

단방향 흐름

router → services → state
  ↓         ↓         ↓
utils     core     models

금지 사항

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

5. 코드 작성 원칙

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: 메인 DB
  • METRICS_DATABASE_URL: 메트릭 전용 DB
  • TEST_DATABASE_URL: 테스트 DB

연결 방식

# state/database.py만 DB 연결 가능
async def get_connection():
    return await asyncpg.connect(os.getenv("DATABASE_URL"))

금지 사항

  • router/services에서 직접 asyncpg.connect()
  • 하드코딩된 DB URL
  • JSONB 저장 시 dict 직접 전달 (json.dumps() 필수)

7. 파일 크기 제한

  • 한 파일 최대 500줄
  • 초과 시 기능별 분리
  • 예: services/email_integration.py (800줄) → 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 가능성은 없는가?
  • 파일 크기가 500줄 이하인가?

10. 예외 상황

허용되는 예외

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

예외 처리 시

# TODO: 계층 위반 - 리팩토링 필요 (issue #123)
# 긴급 수정: 2025-10-02, 사유: DB 장애 복구