728x90
목차
1. 들어가기 앞서
2. 변경 전 코드
3. 변경 후 코드
- 1차 리팩토링 - 클래스 변수
- 2차 리팩토링 - 유틸 클래스 분리
- 3차 리팩토링 - 서비스 계층 추가
✅ 들어가기 앞서
코드를 작성하다 보면 중복되는 로직이 존재한다‼️
그 중 RestTemplate 클래스를 사용하여 다른 서버 API를 호출하는 부분이 계속 중복되었다.
이렇게 중복되게 사용하는 코드 처리는 아래와 같은 방법 중 하나를 선택해서 처리한다.
- 클래스 변수로 빼서, 각 메서드에서 공용으로 사용한다.
- 로직 의미에 따라서 common 또는 util 이라는 패키지에 넣어서 처리한다.
이번 리팩토링은 1번과 2번의 과정을 모두 시도하면서 변화 되는 과정을 보여주려고 한다.
✅ 변경 전 코드
각 메서드마다 new RestTemplate()로 restTemplate 인스턴스를 생성하고 다른 서버를 호출하는 코드가 들어간다.
// Before Refactoring
public class Test {
public void testB() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(APPLICATION_JSON);
RestTemplate restTemplate = new RestTemplate.getForEntinty()
// 그외 코드...
}
}
✅ 변경 후 코드
1️⃣ 1차 리팩토링 - 클래스 변수
클래스 변수를 사용해서 각 메서드 마다 동일한 인스턴스를 사용하도록 변경했다.
장단점
장점
- 싱글톤 패턴 구현: RestTemplate 인스턴스를 static으로 선언하여, 모든 인스턴스가 동일한 RestTemplate 객체를 사용하게 됩니다. 불필요한 객체 생성을 줄이고 메모리 효율을 높일 수 있습니다.
- 불변성: final 키워드를 사용함으로써 RestTemplate 객체를 변경하지 않도록 보장합니다. 특히 다중 스레드 환경에서 안전한 사용이 가능합니다.
- 재사용성: 여러 메서드에서 이 RestTemplate 객체를 재사용할 수 있어 코드가 효율적이고 간결해집니다.
단점
- 여전히 여러 로직에서 사용되는 부분을 분리해내지 못했다.
클래스 변수 구현
// Forst Refactoring
public class Test {
private static final RestTemplate restTemplate = new RestTemplate();
public void testB() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(APPLICATION_JSON);
RestTemplate restTemplate = restTemplate.getForEntinty()
}
}
2️⃣ 2차 리팩토링 - 유틸 클래스 분리
유틸 클래스 분리 방법
Spring context에 new RestTemplate()을 빈으로 등록하여 싱글톤으로 관리되도록 함.
장단점
장점(나의 생각)
- RestTemplate를 util 패키지로 빼서 클래스로 작성하여 중복되는 부분을 제거할 수 있다.
문제점
- 싱글턴 빈으로 관리된다고 해도, new RestTemplate()을 직접 사용하면 내부적으로 SimpleClientHttpRequestFactory를 기본으로 사용하기 때문에 여전히 HTTP 연결을 새로 생성하는 방식으로 성능이 저하될 수 있음.
- 커넥션 풀을 활용하지 않으면 빈번한 연결 생성 및 해제로 인해 성능이 떨어지고, 리소스 낭비가 발생할 수 있음.
유틸클래스 구현
① 스프링 빈 등록
// 스프링 빈 등록
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
② 유틸 클래스 작성
@Slf4j
@Component
public class ApiClient {
private final RestTemplate restTemplate;
public ApiClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public <T> ResponseEntity<T> sendRequest(String url, HttpMethod method, HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType) {
try {
return restTemplate.exchange(url, method, requestEntity, responseType);
} catch (HttpStatusCodeException e) {
if (e.getStatusCode().is4xxClientError()) {
throw new HttpClientErrorException(e.getStatusCode(), e.getStatusText());
} else if (e.getStatusCode().is5xxServerError()) {
throw new HttpServerErrorException(e.getStatusCode(), e.getStatusText());
} else {
throw new RuntimeException("HTTP 상태 코드 오류: " + e.getStatusCode(), e);
}
} catch (Exception e) {
throw new RuntimeException("오류 발생: " + e.getMessage(), e);
}
}
public <T> HttpEntity<T> createRequestEntity(@Nullable T body, @Nullable MultiValueMap<String, String> customHeader) {
HttpHeaders headers = new HttpHeaders();
if (customHeader != null) { // 헤더 값을 따로 작성 안 하는 경우
customHeader.forEach((key, values) -> values.forEach(value -> headers.add(key, value)));
} else { // 헤더 값을 따로 작성 하는 경우
headers.set("Content-Type", "application/json");
}
return new HttpEntity<>(body, headers);
}
}
3️⃣ 3차 리팩토링 - 서비스 계층 추가
리팩토링 방법
- Spring context에 new RestTemplate()을 빈으로 등록하여 싱글톤으로 관리되도록 함.(2차 리팩토링이랑 동일)
- API 호출하는 로직을 새로운 서비스 계층으로 분리시켜 API 호출하는 책임만 담당하도록 처리.
장단점
장점(나의 생각)
- RestTemplate를 util 패키지로 빼서 클래스로 작성하여 중복되는 부분을 제거할 수 있다.
- API를 호출하는 부분을 서비스 계층으로 분리하여 한 번 더 깔끔하게 정리할 수 있다.
단점
- 어떻게 보면 번거로운 작업인 것 같기도 하다.
- 서비스 계층이 추가되면서 구조가 복잡해질 수 있다.
서비스 계층 구현
① 어노테이션 생성
- 어노테이션 생성에서 서비스 어노테이션을 추가했다.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Service
public @interface ApiRequestLayer {
}
② 인터페이스 클래스 생성
public interface TestAPIInterface {
// 이하 생략
}
③ 구현 클래스 생성
- 해당 서비스가 호출하는 API를 정의한다.
@Slf4j
@ApiRequestLayer
public class TestAPIImpl implements TestAPIInterface {
// 이하 생략...
}