기존 클라이언트가 서버에게 요청하는 클라이언트 서버 구조에서는 일반적으로 클라이언트가 서버를
직접 호출하고 처리 결과를 직접 받는다.
그런데 클라이언트가 요청한 결과를 서버에 직접 요청하는 것이 아닌 대리자를 통해 대신 간접적으로 서버에 요청할 수 있다. 여기서 대리자를 영어로 Proxy라고 한다.
이러한 대리자를 통해 호출하는 방식에는 다양한 이점이 존재한다.
- 권한에 따른 접근제어
- 캐싱
- 부가기능 추가
- 프락시 체인
- 지연 로딩
객체의 관점에서 프락시를 생각해보면 요청하는 객체는 클라이언트, 응답하는 객체는 서버라는 관점에서 어떠한 객체가 프락시가 될 수 있을까?

객체가 프락시가 되기 위해선, 클라이언트가 요청을 한 대상이 프록시인지, 실제 서버인지 몰라야한다. 즉 서버와 프록시는 같은 인터페이스를 사용해야하며, 클라이언트가 요청하는 서버객체를 프록시 객체로 변경하여도 클라이언트에 영향이 없어야 한다.
GOF 디자인 패턴에서 프락시 패턴과, 데코레이터 패턴은 둘다 프록시를 사용하는 방법이지만
GOF 디자인 패턴에서는 이 둘을 의도(intent)에 따라서 프록시 패턴과 데코레이터 패턴으로 구분한다.
- 프록시 패턴: 접근 제어가 목적
- 데코레이터 패턴: 새로운 기능 추가가 목적
기존 클라이언트와 서버가 직접 호출을 하고 있다고 가정하고 해당 테스트 코드를 만들어 보자.
직접 호출로 구조를 잡자. Client와 Server는 직접 호출을 진행한다.
@Slf4j
public class Client{
private final ServerInterface server;
public Client(ServerInterface server) {
this.server = server;
}
public void execute(){
String result = server.operation();
log.info("result {}", result);
}
}
@Slf4j
public class Server implements ServerInterface{
@Override
public String operation() {
try {
log.info("서버 호출");
Thread.sleep(1000);
return "data";
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
ServerInterface에 의존하는 Client는 해당 타입으로 의존성을 주입받아 1초 후 데이터가 반환되는 작업을 요청한다고 예시를 만들었다. 이후 해당 요청을 3번 진행하면 그 결과는 3초가 걸려서 데이터를 3번 반환한다.
@Test
void directRequest(){
Server server = new Server();
Client client = new Client(server);
client.execute();
client.execute();
client.execute();
}

프락시 패턴의 의도인 접근제어에 관한 내용에 캐싱이 존재한다. 만약 해당 데이터가 변하지 않는 데이터라면 어딘가에 보관 후 조회한 데이터를 사용하는 것이 성능상 좋다. 캐시도 접근 자체를 제어하는 기능 중 하나이다.
직접 호출에서 클라이언트와 서버의 코드를 고치지 않고 간접 호출로 변경해보자.
프락시 객체는 실제 서버를 알아야 클라이언트의 호출에 응답할 수 있다. 이 경우 프록시에서 서버를 target이라고 보통 명명한다. 또한 프록시 객체는 서버의 타입과 동일한 타입이어야 한다.
@Slf4j
public class CacheProxyTest implements ServerInterface{
private Server target;
private String cache;
public CacheProxyTest(Server target) {
this.target = target;
}
@Override
public String operation() {
log.info("캐시 프록시 호출");
if (cache == null) {
this.cache = target.operation();
}
return cache;
}
}
@Test
void indirectRequest(){
Server server = new Server();
CacheProxyTest cacheProxy = new CacheProxyTest(server);
Client client = new Client(cacheProxy);
client.execute();
client.execute();
client.execute();
}

로그를 보면 첫 요청에 대해서 실제 서버의 행동을 응답하고, 이후부터는 캐시 된 데이터를 통해 접근제어의 역할을 하는 프락시 객체가 동작한다.
런타임 의존관계
client | cacheProxy | server |
프락시 패턴은 Client와 Server의 코드를 변경하지 않고 프록시를 도입해 접근 제어를 했다는 점이다. 그리고 클라이언트 코드의 변경 없이 자유롭게 프록시를 넣고 뺄 수 있으며, 프록시 체인을 통해 다양한 확장도 가능하다.
실제로 클라이언트 입장에서는 프록시 객체가 주입되었는지, 서버의 객체가 주입되었는지는 알지 못한다.
'디자인 패턴' 카테고리의 다른 글
데코레이터 패턴 + [F] (0) | 2022.02.19 |
---|---|
빌더 패턴 + [F] (0) | 2022.02.19 |
데코레이터 패턴 (0) | 2022.01.21 |
전략 패턴 (0) | 2022.01.17 |
템플릿 메서드 패턴 (0) | 2022.01.15 |
댓글