실무에서 자주 만드는 커스텀 데코레이터 패턴
DRF 프로젝트에서 특정 뷰나 함수에 간단하게 적용할 수 있는 커스텀 데코레이터 제작법을 통해 코드 중복을 줄이고 관심사를 분리하여 유지보수성을 향상
데코레이터 사용 이유
코드 중복 감소: 여러 곳에서 반복되는 로직을 하나의 데코레이터로 재사용
관심사 분리: 뷰의 핵심 비즈니스 로직과 부가 기능을 명확하게 분리
직관적인 사용법:
@decorator_name형태로 함수 위에 선언만 하면 됨
실무 활용 시점
특정 뷰에만 적용되는 간단한 권한 확인
API 요청/응답에 대한 상세 로깅이나 성능 측정
특정 API의 결과를 캐싱하여 성능 향상
외부 서비스와의 연동 시 특정 헤더나 파라미터 검증
기능 플래그를 적용하여 특정 사용자에게만 새로운 기능 노출
필수 규칙: functools.wraps 사용
데코레이터 작성 시 functools.wraps를 반드시 사용하여 원래 함수의 메타데이터 보존
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper실무 데코레이터 패턴 5가지
1. API 실행 시간 로깅 데코레이터
API의 성능 병목 지점을 찾거나 응답 시간 모니터링
def log_execution_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
view_name = args[0].__class__.__name__
logger.info(f"[{view_name}.{func.__name__}] Execution time: {execution_time:.4f} seconds")
return result
return wrapper2. 특정 그룹 사용자만 접근 허용 데코레이터
DRF의 Permission 클래스보다 간단하게 특정 권한 확인
def group_required(allowed_groups=[]):
def decorator(view_func):
@functools.wraps(view_func)
def wrapper(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return Response({"detail": "Authentication credentials were not provided."},
status=status.HTTP_401_UNAUTHORIZED)
user_groups = set(request.user.groups.values_list('name', flat=True))
if not set(allowed_groups).intersection(user_groups):
return Response({"detail": "You do not have permission to perform this action."},
status=status.HTTP_403_FORBIDDEN)
return view_func(self, request, *args, **kwargs)
return wrapper
return decorator3. 필수 쿼리 파라미터 검증 데코레이터
request.query_params에 특정 키 존재 여부 검사
def query_param_required(params: list):
def decorator(view_func):
@functools.wraps(view_func)
def wrapper(self, request, *args, **kwargs):
missing_params = [p for p in params if p not in request.query_params]
if missing_params:
return Response({"detail": f"Missing required query parameters: {', '.join(missing_params)}"},
status=status.HTTP_400_BAD_REQUEST)
return view_func(self, request, *args, **kwargs)
return wrapper
return decorator4. 간단한 API 응답 캐싱 데코레이터
자주 변경되지 않는 API의 응답을 캐시하여 DB 부하 감소
def cache_response(timeout: int, key_prefix: str):
def decorator(view_func):
@functools.wraps(view_func)
def wrapper(self, request, *args, **kwargs):
query_params = request.query_params.urlencode()
cache_key = f"{key_prefix}:{request.path}?{query_params}"
cached_response = cache.get(cache_key)
if cached_response:
return Response(cached_response)
response = view_func(self, request, *args, **kwargs)
if response.status_code >= 200 and response.status_code < 300 and response.data:
cache.set(cache_key, response.data, timeout=timeout)
return response
return wrapper
return decorator5. 기능 플래그 데코레이터
새로운 기능을 점진적으로 공개하거나 긴급 상황 시 기능 비활성화
def feature_flag(feature_name: str, enabled_by_default=False):
def decorator(view_func):
@functools.wraps(view_func)
def wrapper(self, request, *args, **kwargs):
is_enabled = getattr(settings, 'FEATURE_FLAGS', {}).get(feature_name, enabled_by_default)
if not is_enabled:
return Response({"detail": "This feature is currently disabled."},
status=status.HTTP_503_SERVICE_UNAVAILABLE)
return view_func(self, request, *args, **kwargs)
return wrapper
return decorator실무 조언
데코레이터는 만능이 아님: 복잡한 로직은 미들웨어나 DRF 기본 클래스 사용이 더 적합
데코레이터 순서 중요: 가장 위에 있는 데코레이터부터 실행되므로 순서 고려 필요
테스트 필수: 직접 만든 데코레이터는 반드시 단위 테스트 작성하여 검증
Last updated
