실무에서 자주 만드는 커스텀 데코레이터 패턴

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 wrapper

2. 특정 그룹 사용자만 접근 허용 데코레이터

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 decorator

3. 필수 쿼리 파라미터 검증 데코레이터

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 decorator

4. 간단한 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 decorator

5. 기능 플래그 데코레이터

새로운 기능을 점진적으로 공개하거나 긴급 상황 시 기능 비활성화

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