프록시 패턴 심층 가이드: 접근 제어 구축의 예술
Source: Dev.to
프록시 패턴 개요
핵심 역할
| 역할 | 설명 |
|---|---|
| Subject(추상 주제) | RealSubject와 Proxy의 공통 인터페이스를 정의 |
| RealSubject(실제 주제) | 실제 비즈니스 로직을 수행하는 객체 |
| Proxy(프록시) | RealSubject에 대한 참조를 보유하고, 필요할 때 실제 객체를 생성하거나 접근 |
| Client(클라이언트) | Proxy를 통해 실제 객체와 상호 작용하며, 실제 객체 존재를 인식하지 못함 |
전형적인 호출 체인은 다음과 같습니다:
클라이언트 → 로컬 프록시 → 네트워크 → 원격 서비스
프록시 패턴의 전형적인 적용
지연 로드(가상 프록시)
비용이 많이 드는 리소스(예: 원격 이미지)에 접근해야 할 때, 실제로 필요할 때만 실제 객체를 생성합니다.
class VirtualProxyImage:
def __init__(self, url):
self.url = url
self._real_image = None
def display(self):
# 최초 호출 시에만 실제 이미지를 로드
if not self._real_image:
self._real_image = RealImage(self.url)
self._real_image.display()
접근 제어
프록시는 호출 전에 인증이나 권한 검사를 수행할 수 있으며, 이는 회사 출입 통제 시스템과 유사합니다.
캐시 및 지연 로드 예시
아래 예시는 프록시를 사용해 이미지의 지연 로드와 캐시를 구현하여 디스크 읽기를 중복하지 않도록 하는 방법을 보여줍니다.
from abc import ABC, abstractmethod
import time
# 추상 주제
class Image(ABC):
@abstractmethod
def display(self):
pass
# 실제 주제
class RealImage(Image):
def __init__(self, filename):
self.filename = filename
self._load_from_disk()
def _load_from_disk(self):
print(f"이미지를 로드 중: {self.filename}")
time.sleep(1) # 로드 시간 시뮬레이션
print("로드 완료!")
def display(self):
print(f"이미지 표시: {self.filename}")
# 가상 프록시
class ProxyImage(Image):
def __init__(self, filename):
self.filename = filename
self._real_image = None
def display(self):
if self._real_image is None:
self._real_image = RealImage(self.filename)
self._real_image.display()
# 사용 예시
print("=== 첫 번째 표시 ===")
img = ProxyImage("photo.jpg")
img.display()
print("\n=== 두 번째 표시 ===")
img.display()
실행 결과
=== 첫 번째 표시 ===
이미지를 로드 중: photo.jpg
로드 완료!
이미지 표시: photo.jpg
=== 두 번째 표시 ===
이미지 표시: photo.jpg
프록시 패턴 특성 비교
| 특성 | 프록시 패턴 | 데코레이터 패턴 |
|---|---|---|
| 목적 | 접근 제어, 지연 로드, 권한 검증 | 동적 기능 추가, 행동 강화 |
| 관계 | 실제 객체를 대체 | 실제 객체를 감싸는 형태 |
| 생성 시점 | 사전 또는 런타임에 생성 | 런타임에 감싸서 생성 |
| 클라이언트 인식 | 무감지(투명 프록시) | 추가 데코레이션 레이어를 인식 |
실제 사례
연결 풀(리소스 프록시)
연결 풀은 본질적으로 데이터베이스/네트워크 연결에 대한 프록시입니다. 미리 일정 수의 연결을 생성해 두고, 클라이언트는 프록시를 통해 연결을 얻고 사용 후 반환합니다. 매번 새로 만들 필요가 없습니다.
API 제한 프록시
고성능 시스템에서 프록시는 요청 속도 제한을 구현해 백엔드 서비스가 과부하되지 않도록 보호합니다.
import time
class RateLimitedProxy:
def __init__(self, real_service, max_requests, time_window):
self.real_service = real_service
self.max_requests = max_requests
self.time_window = time_window
self.requests = [] # 요청 타임스탬프 기록
def call(self, *args, **kwargs):
now = time.time()
# 만료된 요청 기록 정리
self.requests = [t for t in self.requests if now - t < self.time_window]
if len(self.requests) >= self.max_requests:
raise Exception("요청이 너무 빈번합니다. 잠시 후 다시 시도하세요")
self.requests.append(now)
return self.real_service.execute(*args, **kwargs)
온라인 시험 시스템에서의 프록시
- 화면 전환 횟수 감지
- 복사·붙여넣기 제한
- 비정상적인 행동(예: 이상 네트워크 요청) 기록
프록시를 활용하면 비즈니스 로직을 수정하지 않고도 위와 같은 모니터링·제한 기능을 구현할 수 있습니다.
프록시 패턴의 가치
- 디커플링: 접근 로직과 비즈니스 로직을 분리
- 강화: 로그, 캐시, 제한 등 기능을 무감지하게 추가
- 제어: 접근 제어·권한 검증 구현
- 최적화: 지연 로드와 캐시를 통해 성능 향상
프록시 패턴을 마스터하면 시스템 설계 시 강력한 무기를 얻게 됩니다.
다음 예고
전략 패턴(Strategy Pattern) — 알고리즘을 교환 가능하게 만드는 예술.
맺음말
프록시 패턴을 적절히 사용하면 시스템의 성능과 보안을 크게 향상시킬 수 있지만, 과도한 설계는 피해야 합니다. 실제로 접근 제어나 횡단 관심사를 추가해야 할 때만 프록시 도입을 고려하세요. 디자인 패턴 학습 여정에서 풍성한 수확을 거두시길 바랍니다!