본문 바로가기
Spring

[스프링 핵심 원리 - 고급편] 6. 프록시 패턴으로 데이터 캐싱하기

by doogfoot 2024. 1. 18.

[스프링 핵심 원리 - 고급편] 6. 프록시 패턴

 

프록시 패턴

 

 

프록시 패턴 - 정의

 

프록시 패턴에 대해 정리하기 전에 프록시(Proxy)라는 개념부터 설명드리겠습니다.

 

일반적으로 컴퓨터공학, IT, 프로그래밍 분야에서 프록시라는 개념은 대리자 또는 대리인이라는 뜻으로 누군가에게 어떤 일을 시킬 때 중간에 프록시를 통해 대신시키는 것을 말합니다.

 

 

위와 같이 일반적으로 Client(요청하는 곳)에서 Server(요청을 받아 처리하는 곳)를 직접 호출하고 처리 결과를 받습니다.

 

 

 

여기에서 클라이언트가 요청한 결과를 서버에 직접 호출하는 것이 아니라 어떤 대리자를 통해 간접적으로 호출하는 경우가 있습니다. 이 경우 대리자 역학을 하는 곳을 프록시라고 합니다. 

 

위의 개념을 Java의 객체에 도입하면 클라이언트는 요청하는 객체이고, 서버는 요청을 처리하는 객체가 됩니다.

 

이렇게 프록시를 사용하는 이유는 다음과 같은 기능을 할 수 있기 때문입니다.

 

  • 접근 제어
    • 권한에 따른 접근 차단, 보안
    • 캐싱 
    • 지연 로딩
  • 부가 기능 추가
    • 기존 서버의 기능에 추가적인 기능을 수행
    • 기존 서버 기능을 수정할 수 없을 때 기능을 추가하기 위해

 

추가적으로 데코레이터 패턴이라는 것이 있는데 프록시 패턴과 비슷하게 프록시를 사용하지만 GOF 디자인 패턴에서는 이 두 패턴을 의도와 상황에 따라 구분하고 있습니다.

 

  • 프록시 패턴 : 접근 제어가 목적
  • 데코레이터 패턴 : 새로운 기능 추가가 목적

결론적으로 프록시 패턴이란 원본이 되는 객체를 대신하여 처리함으로써 로직의 흐름과 접근을 제어하는 패턴이라고 정의할 수 있습니다.

 

객체에서 프록시로 동작하려면 서버와 프록시는 같은 인터페이스를 사용해야 합니다. 

그리고 서버 객체 대신에 프록시 객체로 변경하여 대신하더라도 클라이언트 코드를 변경하지 않아야 합니다.

 

 

다이어그램으로 프록시 패턴을 표현하면 위와 같습니다. 서버와 동일하게 ServerInteface를 구현해 사용하고 있습니다. 

 

클라이언트는 ServerInterface 만 바라보고 호출하기 때문에 클라이언트 입장에서는 내부적으로 프록시에게 요청하는지  서버에게 요청하는지 조차 몰라야합니다. (클라이언트 내부가 아닌 클라이언트를 사용하는쪽에서 결정)

 

 

프록시 패턴 - 예제

 

프록시 패턴을 이용해 데이터를 캐싱하는 예제 코드를 설명드리겠습니다.

public interface Subject {
    String operation();
}

public class RealSubject implements Subject {
    public String operation() {
        System.out.println("실제 객체");
        return "real"; 
    }
}

public class ProxySubject implements Subject {
    private Subject subject;
    private String cacheValue;
    
    public ProxySubject(Subject subject) {
    	this.subject = subject;
    }
    
    public String operation() {
        System.out.println("프록시 객체");
        if(cacheValue == null) {
            cacheValue = subject.operation();
        }        
        return cacheValue;
    }
}

 

Subject라는 인터페이스를 정의하고 원본 객체(RealSubject)를 구현했습니다.

클라이언트 코드에서 원본 객체의 operation메서드를 호출한다고 했을 때 고정적으로 "real"이라는 문자열이 리턴되도록 되어있습니다.

 

프록시 객체를 거쳐서 원본 객체를 호출하기 위한 ProxySubject라는 프록시 객체를 만들고 똑같이 Subject 라는 인터페이스를 구현하고 있습니다.

 

operation 메서드에서는 캐싱한 결과값이 있으면 원본 객체를 호출하지 않고 저장된 값을 리턴하도록 operation 메서드를 구현했습니다.

 

public class ProxyClient {
    private Subject subject;
    
    public ProxyClient(Subject subject) {
        this.subject = subject;
    }
    
    public void execute() {
        subject.operation();
    }
}

 

위 코드는 원본 객체를 호출하거나 프록시 객체를 호출할 클라이언트 코드입니다.

생성자의 입력 파라미터로 Subject 인터페이스를 받을 수 있어서 원본 객체, 프록시 객체 중 둘다 받을 수 있으며 입력 받은 구현체에 따라 execute 메서드의 동작이 달라지게 됩니다.

 

프록시 패턴 정의에서도 설명드렸지만 client 코드만 봐서는 런타임에서 원본객체가 호출될지, 프록시 객체가 호출될지 알수 없습니다. 때문에 Subject 로 어떤 객체가 들어오는지에 따라 코드 변경없이 유연하게 동작을 변경할 수 있습니다.

 

RealSubject realSubject = new RealSubject();
ProxySubject proxySubject = new ProxySubject(realSubject);

// 원본 객체 호출
ProxyClient realClient = new ProxyClient(realSubject);
realClient.execute();

// 프록시 객체 호출
ProxyClient proxyClient = new ProxyClient(proxySubject);
proxyClient.execute();

 

위의 코드는 클라이언트를 통해 원본 객체를 호출하거나 프록시 객체를 호출하는 예제입니다.

 

클라이언트의 생성자로 어떤 구현체를 넣는지에 따라 동작 및 흐름이 결정됩니다.

 

 

프록시 패턴 - 장점

  • 부가 기능 추가
    • 기존 코드를 변경하지 않고 부가 기능 추가 가능(로깅, 캐싱, 소요시간 등)
  • 보안성 향상
    • 원본 객체에 직접 접근을 막거나 권한을 제한할 수 있어서 보안성 향상
    • 접근 로그를 남기는 등 보안적인 추가 기능 적용 가능
  • 유연성 향상
    • 원본 객체에 직접 접근하지 않기 때문에 객체 간에 결합도를 낮춰 유연성 향상
  • 성능 향상
    • 원본 객체가 실제 필요할 때 생성함으로 성능을 향상 시킬 수 있음
    • 사이즈가 큰 객체(영상, 이미지, 파일 등)가 로딩되기 전에 프록시를 통해 참조 가능

 

프록시 패턴 - 단점

  • 복잡성 증가
    • 중간 과정인 프록시 객체가 추가되어 코드 복잡성 증가 및 가독성 떨어짐
  • 성능 저하
    • 프록시 객체 생성이 빈번한 경우 성능이 저하 될수도 있음

 

프록시 패턴 - 정리

 

프록시 패턴은 원본 객체를 대신하는 프록시 객체를 호출함으로써 로직의 흐름을 변경하거나 부가적인 기능을 추가하는 패턴이라고 정리할 수 있습니다.

 

개발을 하다 보면 기존 객체를 변경하지 않고 부가적인 기능을 추가하거나 데이터를 캐싱해야 되는 경우가 빈번하게 발생합니다.

 

이때 프록시 패턴을 기억하고 있다면 훨씬 유연한 코드를 작성할 수 있을 것 같습니다.

 

디자인 패턴을 공부하면서 인터페이스 및 다형성 개념이 얼마나 중요한지 다시 한번 느끼고 있습니다.

 

댓글