Skip to content

hasune/design-pattern-proxy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

프록시 패턴 (Proxy Pattern) 예제

Java Spring Boot Design Pattern H2 Database

🔍 프록시 패턴이란?

프록시 패턴은 객체 지향 디자인 패턴 중 하나로, 다른 객체에 대한 접근을 제어하는 대리인(프록시) 객체를 제공합니다. 프록시는 원래 객체와 동일한 인터페이스를 구현하며, 클라이언트의 요청을 원래 객체에 전달하기 전이나 후에 추가 기능을 수행할 수 있습니다.

💡 주요 구성 요소

  1. Subject (주체) 인터페이스:

    • RealSubject와 Proxy가 구현하는 공통 인터페이스
    • 클라이언트는 이 인터페이스만 알면 됨
  2. RealSubject (실제 주체):

    • 실제 비즈니스 로직을 처리하는 클래스
    • 주요 기능을 담당
  3. Proxy (프록시):

    • RealSubject와 동일한 인터페이스 구현
    • RealSubject에 대한 참조를 가지고 있음
    • 클라이언트의 요청을 RealSubject에 위임하기 전후에 추가 기능 수행

🌟 이 예제에서의 구현

1. Subject 인터페이스: UserService

public interface UserService {
    User getUserById(Long id);
    List<User> getAllUsers();
    User createUser(User user);
    User updateUser(User user);
    void deleteUser(Long id);
}

2. RealSubject: RealUserService

@Service
public class RealUserService implements UserService {
    // 실제 비즈니스 로직 구현
    // ...
}

3. Proxy: UserServiceProxy

@Service
@Primary
@RequiredArgsConstructor
@Slf4j
public class UserServiceProxy implements UserService {
    private final UserService userService; // RealUserService 주입
    
    @Override
    public User getUserById(Long id) {
        log.info("Proxying getUserById request for user ID: {}", id);
        
        long startTime = System.currentTimeMillis();
        try {
            // 실제 서비스 메서드 호출
            return userService.getUserById(id);
        } finally {
            long endTime = System.currentTimeMillis();
            log.info("getUserById completed in {} ms", (endTime - startTime));
        }
    }
    // 다른 메소드도 비슷한 패턴으로 구현
}

❓ 자주 묻는 질문

Q: UserServiceProxy는 어떻게 RealUserService를 주입받나요?

A: Spring의 의존성 주입 메커니즘을 통해 가능합니다. UserServiceProxy는 UserService 타입의 빈을 주입받을 때, 자기 자신이 아닌 다른 UserService 구현체(RealUserService)를 주입받습니다. Spring은 순환 참조를 방지하기 위해 이런 방식으로 동작합니다.

Q: 클라이언트(Controller)는 어떤 서비스를 사용하게 되나요?

A: @Primary 어노테이션으로 인해 UserServiceProxy가 우선적으로 주입됩니다. 따라서 컨트롤러는 UserServiceProxy를 사용하게 되고, 실제 비즈니스 로직은 내부적으로 RealUserService에 위임됩니다.

Q: 위임(Delegation)은 어떻게 이루어지나요?

A: UserServiceProxy의 각 메소드 내부에서 userService.메소드명(파라미터) 형태로 실제 서비스에 작업을 위임합니다. 이 전후로 로깅이나 시간 측정 같은 부가 기능을 수행할 수 있습니다.

🚀 프록시 패턴의 장점

  1. 관심사의 분리:

    • 실제 비즈니스 로직(RealUserService)과 부가 기능(UserServiceProxy)이 분리됨
  2. 코드 변경 없이 기능 추가:

    • 기존 코드를 수정하지 않고 프록시를 통해 새로운 기능을 추가할 수 있음
  3. 클라이언트 코드 영향 최소화:

    • 클라이언트는 인터페이스만 알면 되므로, 프록시 도입에 따른 변경이 필요 없음

💼 실무 활용 사례

  1. 로깅 및 감사:

    • 모든 메소드 호출에 대한 로그를 일관성 있게 남김
  2. 성능 모니터링:

    • 각 메소드의 실행 시간을 측정하여 성능 병목 식별
  3. 캐싱:

    • 자주 조회되는 데이터를 캐시에 저장하여 성능 향상
  4. 접근 제어/보안:

    • 메소드 호출 전에 사용자 권한 확인
  5. 지연 초기화:

    • 리소스가 많이 소모되는 객체의 생성을 실제로 필요할 때까지 지연

📊 이 예제의 프록시 패턴 설명

이 프로젝트에서는 프록시 패턴을 사용하여 다음 두 가지 핵심 기능을 구현했습니다:

1. 로깅 기능

프록시 클래스에서는 각 서비스 메소드 호출 시 로깅을 수행합니다:

log.info("Proxying getUserById request for user ID: {}", id);

이 로그를 통해 어떤 메소드가 어떤 파라미터와 함께 호출되었는지 추적할 수 있습니다. 실무에서 이러한 로깅은 다음과 같은 이점을 제공합니다:

  • 시스템 활동 감사 추적
  • 문제 발생 시 원인 분석 지원
  • 사용 패턴 분석을 위한 데이터 제공

2. 성능 측정 기능

각 메소드 호출의 시작과 종료 시간을 측정하여 실행 시간을 로깅합니다:

long startTime = System.currentTimeMillis();
try {
    return userService.getUserById(id);
} finally {
    long endTime = System.currentTimeMillis();
    log.info("getUserById completed in {} ms", (endTime - startTime));
}

이 기능은 다음과 같은 장점을 제공합니다:

  • 성능 병목 지점 파악
  • 느린 메소드 식별 및 최적화 대상 선정
  • 시간 경과에 따른 성능 변화 모니터링

실제 개발 환경에서는 이런 성능 측정 데이터를 수집하여 대시보드를 구성하거나, 임계값을 초과하는 경우 알림을 발송하는 등의 모니터링 시스템을 구축할 수 있습니다.

🔧 Spring AOP를 활용한 고급 구현

Spring에서는 AOP(Aspect-Oriented Programming)를 통해 더 선언적인 방식으로 프록시 패턴을 활용할 수 있습니다:

@Aspect
@Component
@Slf4j
public class PerformanceAspect {
    
    @Around("execution(* com.designpattern.sample.service.impl.*.*(..))")
    public Object logPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        // 부가 기능 및 위임 처리
        // ...
    }
}

이 방식을 사용하면 프록시 클래스를 직접 작성하지 않아도 Spring AOP가 자동으로 프록시를 생성합니다.

📝 프로젝트 포인트 정리

  1. 인터페이스(UserService)를 정의합니다.
  2. 실제 구현체(RealUserService)를 만듭니다.
  3. 프록시 구현체(UserServiceProxy)를 만들고, @Primary 어노테이션을 붙입니다.
  4. 프록시 구현체는 실제 구현체를 주입받고, 추가 기능 수행 후 실제 작업을 위임합니다.
  5. 클라이언트(Controller)는 인터페이스만 주입받으면, 자동으로 프록시를 통해 서비스를 이용하게 됩니다.

이 모든 과정이 Spring의 의존성 주입 메커니즘을 통해 우아하게 이루어집니다.

🧪 애플리케이션 테스트 방법

1. 애플리케이션 실행

./gradlew bootRun

또는 IntelliJ IDEA에서 SampleApplication.java 파일을 실행합니다.

2. Postman을 사용한 API 테스트

모든 사용자 조회

  • URL: GET http://localhost:8080/api/users
  • 응답: 모든 사용자 목록 JSON 배열

단일 사용자 조회

  • URL: GET http://localhost:8080/api/users/1
  • 응답: ID가 1인 사용자의 JSON 객체

사용자 생성

  • URL: POST http://localhost:8080/api/users
  • Headers: Content-Type: application/json
  • Body:
    {
      "username": "newuser",
      "email": "[email protected]",
      "role": "USER"
    }
  • 응답: 생성된 사용자 JSON 객체

사용자 업데이트

  • URL: PUT http://localhost:8080/api/users
  • Headers: Content-Type: application/json
  • Body:
    {
      "id": 1,
      "username": "updateduser",
      "email": "[email protected]",
      "role": "ADMIN"
    }
  • 응답: 업데이트된 사용자 JSON 객체

사용자 삭제

  • URL: DELETE http://localhost:8080/api/users/1
  • 응답: 204 No Content

3. 프록시 패턴 동작 확인

애플리케이션 로그를 확인하여 다음과 같은 로그가 출력되는지 확인합니다:

INFO com.designpattern.sample.service.proxy.UserServiceProxy - Proxying getUserById request for user ID: 1
INFO com.designpattern.sample.service.proxy.UserServiceProxy - getUserById completed in 23 ms

이러한 로그는 프록시 패턴이 제대로 작동하고 있음을 확인할 수 있는 증거입니다. 로그에 표시된 실행 시간을 통해 각 메소드의 성능을 모니터링할 수 있습니다.

4. H2 데이터베이스 콘솔 확인

  1. 브라우저에서 http://localhost:8080/h2-console 접속
  2. 다음 정보로 로그인:
    • JDBC URL: jdbc:h2:mem:userdb
    • Username: sa
    • Password: (비워두기)
  3. USERS 테이블을 조회하여 데이터 확인

About

스프링부트로 배우는 디자인 패턴 - Proxy 패턴

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages