[Design Pattern] Proxy 패턴
by Cory
Proxy Pattern 이란 말 그대로 객체를 ‘대리자(proxy)’ 객체를 통해 접근하는 패턴입니다.
즉, Real Subject 는 실제 기능을 수행하는 객체가 되며, 이는 Subject 인터페이스를 상속받아서 구현됩니다. 여기서 Proxy 객체 또한 Subject 인터페이스를 상속받음으로써 RealSubject 와 상호작용할 수 있게 됩니다. 그 때, Proxy 객체는 Real Subject 를 그냥 ‘by pass’ 로 사용하여 대신 사용하는 것처럼 구현하게 됩니다.
예를 들면 아래와 같이 코딩할 수 있을 것입니다.
// Subject
public interface SampleSubject {
void request();
}
// Real Subject
public class RealSampleSubject implements SampleSubject {
@Override
public void request() {
System.out.println("Request 발생!!!");
}
}
// Proxy
public class ProxySubject implements SampleSubject {
SampleSubject sampleSubject;
public ProxySubject(SampleSubject sampleSubject) {
this.sampleSubject = sampleSubject;
}
@Override
public void request() {
// proxy
sampleSubject.request();
}
}
// Client
public class Client {
SampleSubject sampleSubjectProxy;
public Client() {
SampleSubject sampleSubject = new RealSampleSubject();
sampleSubjectProxy = new ProxySubject(sampleSubject);
}
public void callRequest() {
sampleSubjectProxy.request();
}
}
즉, RealSubject 를 Proxy 에서 가지고 있으면서 단순히 by pass 로 대리로 실행시켜 주는 것을 볼 수 있습니다.
사용하는 이유
보통 프록시를 쓰는 이유는 보통 두 가지 이유로 사용된니다.
- lazy instantiation, 필요할 때 객체를 추가시키고 싶을 때 (Virtual Proxy)
- 보안 문제로 인하여 특정 메서드의 접근을 제어하고 싶지 않을 때 (Protection Proxy)
먼저 lazy instantiation 에 대해서 말씀드리겠습니다. lazy instantitation 의 가장 좋은 예는 아마 데이터베이스에서 query 를 날릴때 일것 같습니다.
아래와 같이 User, Board entity 가 있다고 생각해 보겠습니다. (JPA 를 사용했습니다.)
// User
@Data
@Entity
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Email(message = "Username needs to be an email")
@NotBlank(message = "username is required")
@Column(unique = true)
private String username;
@NotBlank(message = "Password field is required")
private String password;
@OneToMany
private List<Boards> boards = new ArrayList<>();
}
// Board
@Data
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
@ManyToOne
private User user;
}
이 때 아래와 같은 프로필을 불러 올때, Board 테이블과 join 해서 Board 정보를 가져올 필요가 있을까요??
아마 없을 것입니다. 그리고 이 사람의 게시글을 보고 싶을때, 그 때 불러오면 될 것입니다.
그럼 여기서 User 클래스의 Board 객체를 아래와 같이 변경하면 Board 가 필요한 곳에서 가져오게 됩니다.
@OneToMany(fetch = FetchType.LAZY) // 사실 default 값이 lazy 입니다.
private List<Boards> boards = new ArrayList<>();
JPA 에서 이렇게 지연로딩을 실행할 때 Proxy 를 사용하고 있습니다.
※ JPA 에서 fetch 를 'FetchType.LAZY' 로 하면 lazy 로 가져오고, 'FetchType.Eager' 로 하면 한번에 가져오게 됩니다.
그럼 다음으로 Protection Proxy(보호 프록시) 에 대해서 알아보겠습니다. 아래는 ‘React’ 로 작성된 Router 관련 코드입니다.
이를 보면 유효한 인증이면 그대로 원래대로 Proxy 를 만들어내며, 유효하지 않은 경우 login 페이지로 넘겨버리는 코드입니다.
import React from "react";
import {Redirect, Route, RouteProps} from "react-router-dom";
import {RootState} from "../reducers";
const SecuredRoute = ({component: Component, isValid, ...otherProps}) => (
<Route
{...otherProps}
render={props =>
isValid ? (
<Component {...props}/>
) : (
<Redirect to="/login"/>
)
}
/>
);
즉, 특정한 상황에서 외부에 공개하고 싶지 않을 때, Proxy 를 사용해서 방어할 수 있게 됩니다.
장점과 단점
그럼 Proxy 패턴의 장단점을 알아보겠습니다.
장점
- lazy instantiation 을 이용하여 성능을 최적화 시킬 수 있습니다.
- 기존 클래스의 변경없이 유연하게 기능을 확장할 수 있습니다. (ocp 원칙)
단점
- 이미 객체가 생성되었을 때, Proxy 를 통한 접근은 오히려 오베헤드가 될 가능성이 있으므로 주의해서 개발해야 합니다.
- 너무 많은 proxy 들은 코드 가독성을 오히려 망칠 수 있습니다.
Decorator 패턴과의 차이점??
Decorator 패턴과 Proxy 패턴은 큰 차이점은 존재하지 않습니다. 다만, 사용하는 목적이 달라질 뿐입니다.
Decorator 패턴의 경우 주요 기능에 무언가를 추가 하고 싶을때, 사용됩니다. 이전에서 예를 들었듯이, Log 를 찍고 싶다던가, 함수가 실행되는 시간을 계산하고 싶다던가 할때 사용합니다.
반면 Proxy 패턴의 목적은 주요기능을 어떻게 컨트롤 할 것인가에 있습니다. 같은 기능을 수행하지만, 특정 유저는 막고싶은 경우(Protection), 똑같이 호출하지만 필요할때, 나중에 호출하고 싶은 경우(Virtual) 등입니다.
AOP 의 경우를 생각해볼까요?? 만약, 바로 전 예처럼 (데코레이터 패턴) 시간을 측정하고 싶다? 하면 데코레이터 패턴을 사용하게 된 것입니다.
하지만 lazy 하게 Transaction 을 발생시킬거다? 하면 프록시 패턴을 사용한 것입니다.
애매하긴 하지만, 같은 형식이라도 쓰이는 목적에 따라 달라질 수 있습니다.
이렇게 해서 Proxy 패턴에 대해서 알아봤습니다.
이번에 공부하면서 느낀점은.. 디자인 패턴은 코딩을 잘 하기 위해서 만들어진 건데, 이로 인해서 코딩을 하기 더 힘들어진다? 하면 너무 목매여서 하지 않아도 될 것 같다는 생각이 들었습니다. 디자인 패턴은 말 그대로 패턴일 뿐, 자신의 서비스에 맞는 패턴 혹은 팀원끼리 함께 이해할 수 있는 패턴이 있다면 그걸 사용하는게 더 낫지 않을까 합니다.
Subscribe via RSS