본문 바로가기
디자인 패턴

프록시 패턴

by oncerun 2022. 1. 20.
반응형

기존 클라이언트가 서버에게 요청하는 클라이언트 서버 구조에서는 일반적으로 클라이언트가 서버를

직접 호출하고 처리 결과를 직접 받는다. 

그런데 클라이언트가 요청한 결과를 서버에 직접 요청하는 것이 아닌 대리자를 통해 대신 간접적으로 서버에 요청할 수 있다. 여기서 대리자를 영어로 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

댓글