토이 프로젝트 배우게 된 것들 & 오류 해결🐰

마이크로서비스에서 서비스간 통신 방법 차이

j_estory 2022. 12. 21. 17:00

🤔 의문점


토이프로젝트를 진행하면서 서버 간 통신을 위해 RestTemplate과 Feign-Client 의 방식 중 어떤 방식을 선택하는게 좋을까에 대한 의문이 들었다.

 

실무에서는 두 방식을 모두 사용하지만, 두 방식에 대한 차이점을 정확히 인지하지 않고 어떤 프로젝트에서는 FeignClient 을 사용하고 있으면 동일하게 따라가고 어떤 프로젝트에서 RestTemplate를 사용하면 동일하게 따라가는 방식으로 코딩을 해왔던 것 같다.

 

아무래도 서비스 회사다 보니, 프로젝트를 처음부터 시작할 기회는 거의 없고 기존에 생성되어 있는 프로젝트에서 API를 추가가 대부분이어서 각각의 방식에 대해서는 인지하고 있었지만 어떤걸 택하면 좋을까? 라는 의문점은 들지 않았던 것 같다. 

 

그래서 이번을 계기로 이 두 방식에 대해서 실질적으로 비교를 해보려고 한다.

  1.  선언 방식과 가독성
  2.  예외 처리
  3.  테스트 코드 - 추후 정리 예정
  4.  종합적으로 정리

 

✅ 선언 방식과 가독성

 

 

두 방식의 선언을 봐보자!

 

RestTemplate, Feign 모두 아래와 같이 URL을 직접 명시해줘야 한다. 

 

하지만 !! 다른 점이 있다면 바로 관심사의 분리이다.

 

코드를 봐보자

public class UserService {
	
    // 생략
    public UserResponseDto getUserById(Long id) {
    	User userOptional = userRepository.findById(id).orElseThrow(RuntimeException::new);
        
        TeamResponseDto team = teamServiceClient.getTeam(id);
        
        return // 생략
    }
    
    public UserResponseData getUserById(Long id) {
    	User userOptional = userRepository.findById(id).orElseThrow(RuntimeException::new);
        
        String url = String.format("http://team-service/%s/teams",id);
        ResponseEntity<TeamResponseData> responseData = restTemplate.exchange(
        					url,
                            HttpMethod.GET,
                            null,
                            TeamResponseData.class);
        TeamResponseData team = responseData.getBody();
        
        return // 생략
    }
}

Service 의 행동에 대한 관심사는 Team-Service 에게 호출을 보내는 것으로 두 방식 모두 동일하다.

 

하지만 uri 에 대한 직접적인 설정 정보는 UserService가 가져야 하는게 맞을까?

 

책임의 관심사로 본다면 어떻게 될까?

 

만약 Team-Service의 호출 경로가 달라졌다면 그에 대한 책임은 UserService가 아니라 호출하는 로직 자체에 존재한다. 

 

하지만 RestTemplate 에서는 설정 정보가 UserService.class 내에 있기 때문에 UserService 가 그에 대한 책임을 가지고 있다.

 

그에 반해서 Feign은 어떨까?

 

아예 Feign을 사용하기 위해서는 호출에 관한 설정을 다 FeignClient.interface에서 수행하도록 강제화 되어 있기 때문에 관심사가 분리 되어 있다. 

 

결론 !!

 

책임의 관심사로 볼때, RestTemplate는 호출에 대한 책임이 Service 단에 있다. 하지만 Feign은 Service단과 관심사의 분리가 된다.

가독성으로 보았을 때에도 코드에서도 보이듯이 Feign이 훨씬 좋지만 관심사의 분리로 생각했을때에도 Feign이 좋다고 생각한다.

 

 

✅ 예외처리

 

 

만약, UserService 에서 Team-Service 의 존재하지 않는 사용자의 요청을 보낸다고 가정해보자!

 

그럼 응답으로는 500 에러가 반환되게 된다.

사실 이 500 에러는 Team-Service 에서 제대로된 값을 반환받지 못하여 발생하는 UserService의 에러이다.

 

즉, Team-Service에서는 404 에러가 발생했고 해당 결과를 제대로 처리하지 못하여 UserService에서 500에러를 반환한 것이다.

 

위 방법을 수정하자면 !! 

 

UserService는 Team-Service 에서 반환한 에러를 그대로 반환해야 한다.

 

두 방식은 각각 어떻게 처리할까?

 

✔️ RestTemplate 는 try-catch 를 이용하여 처리를 해야 한다.

public UserResponseData getUserById(Long id) {
    	User userOptional = userRepository.findById(id).orElseThrow(RuntimeException::new);
        
        String url = String.format("http://team-service/%s/teams",id);
        try {
            ResponseEntity<TeamResponseData> responseData = restTemplate.exchange(
                                url,
                                HttpMethod.GET,
                                null,
                                TeamResponseData.class);
            TeamResponseData team = responseData.getBody();
        } catch (Exceptiion e) {
        	return new UserNotEnrolledTeamException("사용자는 팀에 가입되어있지 않습니다.");
        }
        return // 생략
}

 

✔️ Feign 은 장접 중 하나인 Microservice 내부적으로 API를 호출 했을 때, 예외를 핸들링하는 방법을 ErrorDecoder로 제공한다.

더보기

ErrorDecoder에 대하여 좀 더 공부를 해봐도 좋을 것 같다는 생각이 들어 조만간 블로그 글을 적을 예정이다 🙂

 

즉, 각각의 통신에서 에러를 변환시켜줄 ErrorDecoder 인터페이스를 상속받는 Concrete 클래스를 하나 생성하면 된다.

 

public class FeignError implements ErrorDecoder {
	@Override
    public Exception decode(String methodKey, Response response) {
    	switch(response.status()) {
        	case 404:
            	if (methodKey.contains("getOrders")) {
                	return new UserNotEnrolledTeamException("사용자는 팀에 가입되어 있지 않습니다.");
                }
        }
        return null;
    }
}

 

결론!! 

두가지 방식 (try-catch, ErrorDecoder) 이 있는데, 개인적으로 ErrorDecoder을 사용하는 방식이 가독성도 좋고, 코드가 깔끔해진다고 생각한다.

 

 

☝🏻 종합 정리

 

모든 사항을 봤을 때, feign-client를 적용하는 것이 더 좋은 것 같아 

나의 토이 프로젝트에서는 feign을 사용하기로 결정했다.