# 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 계층 ```python # ✅ 올바름 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 계층 ```python # ✅ 올바름 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 계층 ```python # ✅ 올바름 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 ### 연결 방식 ```python # 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 규칙 ### 금지 ```python from app.state.database import * # ❌ wildcard from ..router.slack_handler import x # ❌ 순환 가능성 ``` ### 권장 ```python 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. **성능 최적화**: 충분한 근거 필요 ### 예외 처리 시 ```python # TODO: 계층 위반 - 리팩토링 필요 (issue #123) # 긴급 수정: 2025-10-02, 사유: DB 장애 복구 ```