티스토리 뷰

객체지향, 디자인패턴 등을 공부하고나면 다형성의 이로움과 함께 실무 코드에 적용하고 싶다는 욕구가 생긴다.
하지만 막상 해보려고하면 어떻게 해야하는지 감이 잘 잡히지 않고, 꾸역꾸역 해내도 결과물이 썩 만족스럽지 않은 경우가 많다.
이 차이는 우리가 공부하는 객체지향 관련 자료들은 java 개발의 사실상 표준 환경이 되어버린 spring 을 염두하고있지 않기 때문이다.
간단한 예제와 함께 spring 환경에서 객체지향을 어떻게 구현할 수 있는지 살펴보자.
 

# 요구사항

- 트래픽 제어 객체인 TrafficGate 를 구현한다
- 정수로 이루어진 유저 id와 modulo 연산을 사용해 10% 단위로 제어하도록 구현한다
- 허용 비율은 외부 API를 호출하여 획득한다
- 외부 API 가 구현되기 전까지는 모든 유저를 통과시킨다
- 화이트리스트에 등록된 id는 항상 통과시킨다
- 사용사례는 아래와 같다

@RestController
class SampleController {
  private final TrafficGate trafficGate;

  // 생성자 생략

  @GetMapping
  public void request(UserId userId) {
    if(!trafficGate.isOpenable(userId)) {
      throw new TrafficGateClosedException(userId);
    }
  }
}

 

# 구현

거창하게 요구사항을 정리했지만 막상 구현하면 크게 어렵지 않은 내용이다.

@Component
class TrafficGate {
  private RestClient permitRateRestClient;
  
  public boolean isOpenable(UserId userId) {
    int permitRate = permitRateRestClient.get();
    return userId.value() % 10 < permitRate;
  }
}

외부 API를 호출하여 허용비율을 구하고 이를 사용해서 통과 여부를 결정한다. 완성했으니 배포하고 싶지만 요구사항에 나와있듯 허용비율 API가 아직 정상동작을 하지 않으니 모든 유저를 통과시켜야한다.

@Component
class TrafficGate {
  // private RestClient permitRateRestClient;
  
  public boolean isOpenable(UserId userId) {
    // int permitRate = permitRateRestClient.get();
    // return userId.value() % 10 < permitRate;
    return true;
  }
}

기존 코드를 주석 처리하여 배포하고, 리턴값을 하드코딩하면 문제없다. 이런식으로 구현해서 배포하거나, 아니면 허용비율 API 가 발목을 잡아 해당 API 가 제공되기 전까지 구현을 시작하지 않는 상황도 흔히 볼 수 있다.

@Component
class TrafficGate {
  private RestClient permitRateRestClient;
  private TrafficGateWhiteUsers trafficGateWhiteUsers;
  
  public boolean isOpenable(UserId userId) {
    if(trafficGateWhiteUsers.isIncluded(userId)) {
      return true;
    }
  
    int permitRate = permitRateRestClient.get();
    return userId.value() % 10 < permitRate;
  }
}

요구사항에 나와있는 화이트리스트 유저를 제어하는 로직도 추가됐다. TrafficGate 클래스는 요구사항에 따라 지속적으로 변화하고있다. 이 구현을 다형성을 이용해 구현한다면 어떻게 할 수 있을까?
 

# 다형성을 이용한 구현

interface TrafficGate {
  boolean isOpenable(UserId userId);
}

@Component
class AlwaysOpenTrafficGate implements TrafficGate {
  @Override
  public boolean isOpenable(UserId userId) {
    return true;
  }
}

TrafficGate 인터페이스를 정의하고, 외부 API가 완성되기까지 사용할 기본 구현체를 정의한다. 기본 구현체는 항상 허용하는 구현이므로 적절한 네이밍을 해주어 구현했다.

@Component
class PermitRateTrafficGate implements TrafficGate {
  private RestClient permitRateRestClient;

  @Override
  public boolean isOpenable(UserId userId) {
    int permitRate = permitRateRestClient.get();
    return userId.value() % 10 < permitRate;
  }
}

외부 API가 완성됐다면 새로운 구현을 추가해주면 된다. 기존 코드는 변경이 발생하지 않는다. 하지만 이렇게 구현해서 애플리케이션을 실행시키면 에러가 발생한다. TrafficGate 타입의 spring bean 이 2개이기 때문이다. 해결하는 방법은 몇가지 있겠지만 빈생성을 외부로 추출한다.

@Configuration
class TrafficGateConfiguration {
  @Bean
  public TrafficGate trafficGate(RestClient permitRateRestClient) {
    return new PermitRateTrafficGate(permitRateRestClient);
  }
}

설정을 외부로 분리하고, TrafficGate 구현체들에는 @Component 를 제거한다. 이제는 에러없이 구동된다.
이제 마지막 남은 화이트리스트 제어를 구현하자.

class TrafficGateWhiteUsersProxy implements TrafficGate {
  private TrafficGateWhiteUsers trafficGateWhiteUsers;
  private TrafficGate trafficGate;
  
  @Override
  public boolean isOpenable(UserId userId) {
    if(trafficGateWhiteUsers.isIncluded(userId)) {
      return true;
    }
    
    return trafficGate.isOpenable(userId);
  }
}

@Configuration
class TrafficGateConfiguration {
  @Bean
  public TrafficGate trafficGate(
    TrafficGateWhiteUsers trafficGateWhiteUsers, RestClient permitRateRestClient
  ) {
    return new TrafficGateWhiteUsersProxy(
      trafficGateWhiteUsers, new PermitRateTrafficGate(permitRateRestClient)
    );
  }
}

화이트리스트 제어 로직도 기존 구현을 전혀 변경하지 않고 프록시 객체로 구현했다. 위에서 빈 중복 등록 문제가 있었을때 해결하는 방법 중 하나는 @Primary 애노테이션을 이용하는 방법이 있었다. 만약 위에서 이를 이용해서 문제를 해결했다면 프록시를 구현했을때 또 새로운 문제를 맞이했을 것이다. 이런식으로 객체 구성을 다양하게 조립할때는 정적으로 의존관계를 맺어주는 컴포넌트 스캔보다는 런타임에 직접 조립해주는게 훨씬 낫다. 관련된 내용은 이전에 작성했던 글이 있다( https://multifrontgarden.tistory.com/311 ).

인터페이스와 구현체의 클래스 다이어그램을 그려보면 이런 모양이 된다. 요구사항별로 새로운 구현체를 추가하여 기존 코드에 변경없이 확장을 이어가는 OCP를 충족하는 구현이다. 이 외에도 추가적인 요구사항이 들어온다면 추가 구현체를 통해 문제를 해결할 수 있을 것이다.
그리고 객체 구성은 설정파일에서만 변경해주면 다른 코드는 변경없이 새로운 임무를 수행할 수 있다.
 
어떠한 구현이 더 낫다를 얘기하고 싶다기보다는 객체지향에 관심이 많고, 실무에서도 도입해보고 싶은데 익숙치 않아 좌절하는 모습들을 많이 봐왔다. 그러다보면 객체지향이나 디자인패턴은 이론으로나 가능하고 면접용 지식일뿐 실무에서는 적용하기 어렵다는 결론을 내는 사례들 심심찮게 보게된다. 거창하게 생각하지 않고 작은 부분에서부터 적용할 수 있다는 것을 예제로 설명해보고 싶었다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/02   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
글 보관함