Python 프로젝트 아키텍처 분석 및 설계 검토 가이드
Python 프로젝트의 아키텍처 분석, 설계 패턴 검토, 구조 개선을 위한 가이드
의존성 트리 시각화
uv add --dev pipdeptree
pipdeptree
pipdeptree --graph-output png > dependencies.png
# 또는 uvx로 직접 실행
uvx pipdeptree
UML 다이어그램 생성
uv add --dev pylint
pyreverse -o png backend/src
# 생성된 파일: classes.png, packages.png
# 또는 uvx로 직접 실행
uvx pylint pyreverse -o png backend/src
코드 복잡도 측정
uv add --dev radon
# Cyclomatic Complexity (CC)
radon cc backend/src -a -s
# Maintainability Index (MI)
radon mi backend/src -s
# Raw metrics (LOC, LLOC, SLOC)
radon raw backend/src -s
복잡도 등급:
종합 코드 품질 분석
uv add --dev prospector
prospector backend/src
# 또는 uvx로 직접 실행
uvx prospector backend/src
PlantUML 다이어그램 생성
uv add --dev py2puml
py2puml backend/src backend.models > models.puml
# 또는 uvx로 직접 실행
uvx py2puml backend/src backend.models > models.puml
모듈 의존성 그래프
uv add --dev pydeps
pydeps backend/src --max-bacon 2 -o dependencies.svg
# 또는 uvx로 직접 실행
uvx pydeps backend/src --max-bacon 2 -o dependencies.svg
backend/src/
├── main.py # Entry point
├── api/ # Presentation Layer
│ ├── routes.py
│ └── middleware.py
├── workflows/ # Application Layer
│ └── group_chat.py
├── agents/ # Domain Layer
│ ├── planning_agent.py
│ ├── research_agent.py
│ └── content_agent.py
├── services/ # Infrastructure Layer
│ ├── azure_openai_service.py
│ └── bing_grounding_search.py
└── models/ # Domain Models
└── query.py
체크리스트:
from abc import ABC, abstractmethod
from typing import Generic, TypeVar, Optional
T = TypeVar('T')
class Repository(ABC, Generic[T]):
"""Base repository interface."""
@abstractmethod
async def get(self, id: str) -> Optional[T]:
"""Get entity by ID."""
pass
@abstractmethod
async def save(self, entity: T) -> T:
"""Save entity."""
pass
@abstractmethod
async def delete(self, id: str) -> bool:
"""Delete entity."""
pass
class ResearchService:
"""
Service layer for research operations.
Orchestrates multiple agents and repositories.
"""
def __init__(
self,
planning_agent: PlanningAgent,
research_agent: ResearchAgent,
search_service: SearchService
):
self.planning_agent = planning_agent
self.research_agent = research_agent
self.search_service = search_service
async def conduct_research(self, query: str) -> ResearchResult:
"""Coordinate research workflow."""
plan = await self.planning_agent.create_plan(query)
results = await self.research_agent.execute(plan)
return results
# Using FastAPI's Depends
from fastapi import Depends
def get_azure_service() -> AzureOpenAIService:
"""Dependency factory."""
return AzureOpenAIService()
@app.post("/research")
async def research(
query: Query,
azure_service: AzureOpenAIService = Depends(get_azure_service)
):
"""Endpoint with DI."""
return await azure_service.process(query)
# ❌ Bad: Multiple responsibilities
class UserManager:
def save_user(self, user): pass
def send_email(self, user): pass
def generate_report(self, user): pass
# ✅ Good: Single responsibility
class UserRepository:
def save(self, user): pass
class EmailService:
def send(self, user): pass
class ReportGenerator:
def generate(self, user): pass
# ✅ Good: Open for extension, closed for modification
from abc import ABC, abstractmethod
class SearchProvider(ABC):
@abstractmethod
async def search(self, query: str) -> list:
pass
class BingSearch(SearchProvider):
async def search(self, query: str) -> list:
# Bing implementation
pass
class GoogleSearch(SearchProvider):
async def search(self, query: str) -> list:
# Google implementation
pass
# ✅ Good: Depend on abstractions
class ResearchWorkflow:
def __init__(self, search_provider: SearchProvider):
self.search = search_provider # Abstract interface
# Check module cohesion
radon raw backend/src -s
radon cc backend/src -a -s
Guidelines:
# Check for circular imports
pydeps backend/src --max-bacon 2 --show-cycles
# ✅ Good: Loose coupling via interfaces
class Agent:
def __init__(self, llm_service: LLMService):
self.llm = llm_service
# ✅ Good: Related functionality grouped
class SearchService:
def search(self): pass
def filter_results(self): pass
def rank_results(self): pass
# ADR-001: Use FastAPI for API Layer
## Status
Accepted
## Context
Need a modern Python web framework for async API endpoints.
## Decision
Use FastAPI for REST and WebSocket endpoints.
## Consequences
- Automatic OpenAPI documentation
- Native async/await support
- Type validation with Pydantic
"""
System: Deep Research Agent
Users: Researchers, Data Analysts
External Systems: Azure OpenAI, Bing Search API
"""
[Frontend (React)] ---> [Backend API (FastAPI)] ---> [Azure OpenAI]
---> [Bing Search]
API Layer (routes, middleware)
↓
Workflow Layer (group_chat)
↓
Agent Layer (planning, research, content)
↓
Service Layer (azure_openai, search)
radon mi backend/src -s
Interpretation:
# Using prospector
prospector backend/src --strictness veryhigh
# Using SonarQube (if available)
sonar-scanner \
-Dsonar.projectKey=deep-research-maf \
-Dsonar.sources=backend/src
# Before
def process_query(query):
# 50 lines of code
pass
# After
def process_query(query):
validated_query = validate_query(query)
plan = create_plan(validated_query)
results = execute_plan(plan)
return format_results(results)
def validate_query(query): pass
def create_plan(query): pass
def execute_plan(plan): pass
def format_results(results): pass
# Before
def search(provider, query):
if provider == "bing":
return bing_search(query)
elif provider == "google":
return google_search(query)
# After
class SearchProvider(ABC):
@abstractmethod
def search(self, query): pass
class BingProvider(SearchProvider):
def search(self, query): pass
class GoogleProvider(SearchProvider):
def search(self, query): pass
# Architecture analysis
pydeps backend/src --max-bacon 2 -o arch.svg
pyreverse -o png backend/src
# Code metrics
radon cc backend/src -a -s
radon mi backend/src -s
# Dependency tree
pipdeptree
# Comprehensive analysis
prospector backend/src