Proxy
Proxy는 실제 객체 앞에 대리 객체를 두어 접근을 제어하는 구조 패턴입니다. Client는 실제 객체와 같은 인터페이스를 통해 Proxy에 요청하고, Proxy가 접근 제어, 캐싱, 지연 로딩 같은 부가 로직을 수행한 뒤 필요하면 실제 객체에 위임합니다. 비서와 비슷합니다. 외부에서 임원에게 직접 연락하는 대신 비서를 거칩니다. 비서가 일정을 확인하고, 긴급도를 판단하고, 때로는 비서 선에서 처리합니다. 요청하는 쪽은 비서든 임원이든 같은 방식으로 연락하기 때문에 차이를 느끼지 못합니다.
▶아키텍처 다이어그램
🔍 구조 다이어그램점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다
객체에 직접 접근하면 간단하지만, 현실에서는 그대로 열어 두기 어려운 상황이 많습니다. 데이터베이스 커넥션처럼 생성 비용이 큰 객체를 매번 새로 만들면 성능이 떨어집니다. 민감한 데이터를 다루는 객체에 아무나 접근하면 보안 문제가 생깁니다. 원격 서버에 있는 객체를 로컬 객체처럼 쓰고 싶지만 네트워크 호출이 필요합니다. 이런 문제를 해결하려고 클라이언트 코드에 조건문을 넣기 시작하면, 비즈니스 로직과 인프라 로직이 뒤섞입니다. 객체를 사용하는 곳마다 접근 제어 코드가 퍼지고, 정책이 바뀔 때마다 여러 곳을 수정해야 합니다.
GoF 디자인 패턴 중 구조 패턴에 속합니다. 분산 시스템이 보편화되면서 원격 객체를 로컬처럼 다루는 Remote Proxy가 먼저 주목받았습니다. Java RMI, CORBA 같은 분산 기술이 Proxy 패턴을 핵심 메커니즘으로 사용했습니다. 오늘날에는 Proxy의 쓰임이 더 넓어졌습니다. JavaScript의 Proxy 객체, Spring의 AOP 프록시, ORM의 Lazy Loading, API Gateway, 리버스 프록시 서버 모두 같은 원리로 동작합니다. '실제 대상 앞에 대리자를 두고 부가 로직을 처리한다'는 구조는 프로그래밍 언어 수준부터 인프라 수준까지 반복적으로 나타납니다.
Proxy의 핵심은 실제 객체와 동일한 인터페이스를 유지하는 것입니다. Client는 Subject 인터페이스에만 의존하고, 그 뒤에 Proxy가 있는지 Real Subject가 있는지 모릅니다. 동작 순서는 이렇습니다. 1. Client가 Subject 인터페이스의 `request()`를 호출합니다. 2. Proxy의 `request()` 안에서 사전 처리를 합니다. 접근 권한 확인, 캐시 조회, 로그 기록 등입니다. 3. 사전 처리를 통과하면 Real Subject의 `request()`에 위임합니다. 4. Real Subject의 결과를 받아 사후 처리(캐시 저장, 메트릭 기록)를 거쳐 Client에 반환합니다. 목적에 따라 변형이 달라집니다. Virtual Proxy는 무거운 객체의 생성을 첫 호출 시점까지 미룹니다. Protection Proxy는 호출자의 권한을 확인합니다. Caching Proxy는 동일한 요청의 결과를 저장해 실제 호출을 줄입니다. 구조는 같고, 사전/사후 처리 로직만 다릅니다.
Proxy와 Decorator는 구조가 거의 같습니다. 둘 다 대상 객체를 감싸고, 같은 인터페이스를 구현하며, 내부에서 대상에 위임합니다. 차이는 의도에 있습니다. Proxy는 접근 자체를 제어하거나 관리하는 것이 목적이고, Decorator는 기능을 동적으로 추가하는 것이 목적입니다. Proxy는 대상 객체의 생명주기를 관리하는 경우가 많지만, Decorator는 대상을 외부에서 주입받습니다. Proxy와 Adapter도 비교됩니다. Proxy는 같은 인터페이스를 유지하면서 접근을 제어하고, Adapter는 인터페이스 자체를 바꿉니다. 인터페이스가 변하는가, 유지되는가가 둘을 구분하는 기준입니다. '이 감싸기가 접근을 제어하려는 것인가, 기능을 추가하려는 것인가, 인터페이스를 바꾸려는 것인가'를 따지면 Proxy, Decorator, Adapter 중 어느 것이 맞는지 판단할 수 있습니다.
자주 비교하는 개념
Decorator
객체에 기능을 동적으로 덧붙이는 구조 패턴
Proxy는 접근을 제어하고, Decorator는 같은 인터페이스에 기능을 추가합니다. 구조는 같지만 의도와 생명주기 관리가 다릅니다.
Adapter
호환되지 않는 인터페이스를 연결하는 구조 패턴
Proxy는 같은 인터페이스를 유지하면서 접근을 제어하고, Adapter는 인터페이스 자체를 다른 형태로 변환합니다.
Facade
복잡한 서브시스템에 단순한 진입점을 만드는 구조 패턴
Proxy는 하나의 객체에 대한 접근을 제어하고, Facade는 여러 서브시스템을 하나의 단순한 인터페이스로 묶습니다.
Proxy는 인프라 관심사를 비즈니스 로직에서 분리할 때 자주 등장합니다. Spring에서 `@Transactional`을 붙이면 프레임워크가 해당 클래스의 Proxy를 만들어 트랜잭션 시작과 커밋을 자동으로 감싸 줍니다. 개발자는 비즈니스 로직만 작성하면 됩니다. ORM의 Lazy Loading도 Proxy입니다. `user.getOrders()`를 호출할 때 실제로 DB 쿼리가 발생하지만, user 객체를 로드할 때는 orders 필드에 Proxy만 넣어 두고 실제 조회를 미룹니다. 필요한 시점에만 비용이 발생하도록 제어하는 것입니다. 다만 Proxy가 과도하면 디버깅이 어려워집니다. 호출 스택에 Proxy가 여러 겹 쌓이면 실제 로직까지 도달하는 경로를 추적하기 힘들고, 예상치 못한 시점에 부가 로직이 끼어들 수 있습니다. Proxy는 한 겹이면 충분한 경우가 대부분입니다.