์ด ๊ธ์ 2๊ฐ์ ๊ธ๋ก ๋๋์ด์ ธ ์์ต๋๋ค.
- ๋ง์ดํฌ๋ก์๋น์ค์์ ์๋น์ค๊ฐ ํต์ ์ ์ํ 2๊ฐ์ง ๋ฐฉ๋ฒ ๋น๊ต [OpenFeign vs Rest Template] - ์๋น์ค ๊ตฌํ
- ๋ง์ดํฌ๋ก์๋น์ค์์ ์๋น์ค๊ฐ ํต์ ์ ์ํ 2๊ฐ์ง ๋ฐฉ๋ฒ ๋น๊ต [OpenFeign vs Rest Template] - ๊ฐ๊ฐ์ ๋น๊ต
ํด๋น ๊ธ์์ ๋์ค๋ ์ค์ต ๋ด์ฉ์ Spring Cloud๋ฅผ ์ด์ฉํ MSA ๊ตฌ์ฑ์ ์ ๋ฐ์ ์ธ ์ดํด๊ฐ ํ์ํ ๋ด์ฉ์ ๋๋ค.
์ค์ต์ Eureka + Gateway + Microservices (2)๋ฅผ ์ด์ฉํ ํ๊ฒฝ์ผ๋ก ํด๋น ๊ธ์์๋ Eureka์ Gateway ์ค์ ์ ๋ํด์๋ ์ธ๊ธํ์ง ์ง๋ง ๋ง์ฝ Eureka๋ Gateway์ ์ดํด๊ฐ ๋ถ์กฑํ์ ๋ถ๋ค์ด๋ ๋ ์์๋ณด๊ณ ์ค์ต ํ๊ฒฝ์ ๋ฐ๋ผ ํด๋ณด๊ณ ์ถ์ ์ฌ๋์ ์๋์ ์ค์ต ๊ณผ์ ์ ์กด์ฌํ๋ URL์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
๋ชฉ์ฐจ
์ด์ ์๊ฐ
- ์๋น์ค๊ฐ ํต์
- ์๋น์ค ๊ตฌ์กฐ
- ํต์ ๊ณผ์
- ์๋น์ค ๊ตฌํ
- ์๋น์ค ๊ตฌ์ฑ
- Eureka Service
- Gateway Service
- User Service
- Team Service
- ์๋น์ค ๊ตฌ์ฑ
์ด๋ฒ ์๊ฐ
- ์ง๋ ์๊ฐ์ ์ ๋ฆฌ
- Rest Template ์ผ๋ก API ํธ์ถํ๊ธฐ
- Spring Cloud OpenFeign ์ผ๋ก API ํธ์ถํ๊ธฐ
- ๋ ํต์ ๋ฐฉ๋ฒ์ ์ฐจ์ด
- ์ ์ธ ๋ฐฉ์๊ณผ ์ฝ๋ ๊ฐ๋ ์ฑ
- ์์ธ ์ฒ๋ฆฌ
- ํ ์คํธ ์ฝ๋
- ์ข
ํฉ ์ ๋ฆฌ ๋ฐ ๊ฒฐ๋ก
- ํ๋ก ์ ๋ฆฌ
์ง๋ ์๊ฐ์ ์ ๋ฆฌ
์ง๋ ์๊ฐ ์ฐ๋ฆฌ๋ User-Service์ Team-Service ๋ฅผ ๊ตฌํํ์์๋ค.
๊ทธ๋ฆฌ๊ณ ๋ค์๊ณผ ๊ฐ์ API Endpoint ๋ฅผ ๋ง๋ค์๋ค.
- Discovery Service
http://localhost:8761/eureka
- Gateway Service
http://localhost:8000/
- User Service
- ์ ์ ์์ฑ :
http://localhost:50010/users
POST - ์ ์ ์กฐํ :
http://localhost:50010/users/{userId}
GET
- ์ ์ ์์ฑ :
- Team Service
- ํ ์์ฑ :
http://localhost:60010/teams
POST - ํ์ ์ถ๊ฐ :
http://localhost:60010/{userId}/teams
POST - ์ฌ์ฉ์ ๋ฒํธ๋ก ํ ๊ฒ์ :
http://localhost:60010/{userId}/teams
GET
- ํ ์์ฑ :
์ฐ๋ฆฌ๋ API Gateway ํจํด์ ์ด์ฉํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ Gateway Service์ ํฌํธ์ธ 8000 ํฌํธ๋ก ์์ฒญ์ ๋ณด๋ด๊ณ Gateway ๊ฐ ๊ฐ๊ฐ์ ์๋น์ค์ ์์ฒญ์ ๋ถ์ฐ์์ผ ์๋ต์ ์ ํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํด์ค๋ค.
์์ ์๋ํฌ์ธํธ์์ ํต์ฌ์ ์ ์ ์กฐํ์ด๋ค.
์ ์ ๋ฅผ ์กฐํํ ๋ ํด๋น๋๋ ์ฌ์ฉ์์ Team ์ ๋ณด๋ฅผ ํจ๊ป ์ถ๋ ฅํ๊ธฐ ์ํด์ User-Service ์์ Team-Service ๋ก API ์์ฒญ์ ๋ณด๋ด๊ฒ ๋๋ค.
๊ทธ๋ผ Team-Service ์์๋ ์์ฒญ์ Path Variable ๋ก ๋์ด์จ ์ฌ์ฉ์์ ID ์ ๋ฐ๋ผ์ Team ์ ์กฐํํ๊ณ , Team ์ ์ ๋ณด๋ฅผ ๋ฐํํด์ค๋ค.
ํด๋น ์ฝ๋๋ฅผ ์ ์ ์ฐธ๊ณ ํด๋ณด์.
- User-Service ์์๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ฌ ๋ Team ์ ๋ณด๋ฅผ ํจ๊ป ๋ฐ์์์ ๋ฐํํด์ค์ผ ํ๋ค.
- Team-Service ์์๋ ์ฌ์ฉ์ Id ๋ฅผ ๋ฐ์์ Team ์ ๋ณด๋ฅผ ๋ฐํํด์ค๋ค.
์์ธํ ์ฝ๋๋ ์ด์ ๊ธ, OpenFeign vs Rest Template - ์๋น์ค ๊ตฌํ ์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
์ฐ๋ฆฌ๊ฐ ๊ด์ฌ๊ฐ์ ธ์ผ ํ ์ฝ๋๋ User-Service์ getUserById()
๋ฉ์๋์ด๊ณ , ํด๋น ๋ฉ์๋์์ Team-Service์ getTeamByUserId()
๋ฅผ ํธ์ถํ๋ค.
๊ฐ๊ฐ์ ์๋น์ค๋ Controller ์์ Endpoint ๋ฅผ ์ฒ๋ฆฌํ๊ณ ์๋๋ฐ, ๋ค์๊ณผ ๊ฐ๋ค.
// User-Service-Application
@RestController
public class UserController {
// ์๋ต
@GetMapping("/users/{userId}")
public UserResponseData getUser(@PathVariable("userId") Long id) {
return userService.getUserById(id);
}
}
// Team-Service-Application
@RestController
public class TeamController {
// ์๋ต
@GetMapping("/{userId}/teams")
public TeamResponseData getTeamByUserId(@PathVariable("userId") Long userId) {
return teamService.getTeamByUserId(userId);
}
}
์ด์ ํด๋น ์๋น์ค์์ API ํธ์ถ์ ์ํด RestTemplate๊ณผ OpenFeign์ ์ด์ฉํด๋ณด์.
๊ทธ๋ฆฌ๊ณ ๊ฐ๊ฐ์ ์ค์ต์ ์ํด์ ๋จผ์ ๋ฐ์ดํฐ๋ฅผ ์ค๋นํด๋์.
์ค์ต ์ธํ
์ค์ต ํ๊ฒฝ์ ์ํด์ ๋ฏธ๋ฆฌ ์์ฒญ์ ๋ณด๋ด์ ์ฌ์ฉ์ ์ ๋ณด์ Team ์ ์ธํ ํด์ฃผ์.
์์ฒญ ์์
- ์ฌ์ฉ์ ์์ฑ :
http://localhost:8000/user/users
POST- Request Body ์
{"username":"์ฅ์์ต"}
์ ๋ด์์ ์ฌ์ฉ์๋ฅผ ์์ฑํ๋ค.
- Request Body ์
- ํ ์์ฑ :
http://localhost:8000/team/teams
: POST- Request Body ์
{ "name": "๋ฆฌ๋ฒํ", "address": "์๊ตญ ๋จธ์ง์ฌ์ด๋"}
์ ๋ด์์ ํ์ ์์ฑํ๋ค.
- Request Body ์
- ํ ๋ฉค๋ฒ ์ถ๊ฐ :
http://localhost:8000/team/1/teams
: POST- Request Body ์
{ "name": "๋ฆฌ๋ฒํ" }
์ ๋ด์์ ํ์์ ํ์ ์ ์ฅํ๋ค.
- Request Body ์
RestTemplate ์ผ๋ก API ํธ์ถํ๊ธฐ
- ์ฌ์ฉ๋ฒ
- Bean ์ถ๊ฐ
- UserService.class ์์ RestTemplate ์์กด์ฑ ์ฃผ์
- ์๋น์ค์์ RestTemplate์ผ๋ก ํธ์ถ
Bean ๋ฑ๋ก
Rest Template ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์ UserServiceApplication.java ์์ Bean์ผ๋ก RestTemplate ์ ๋ฑ๋กํด์ฃผ์.
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
์์์ @LoadBalanced ์ด๋ ธํ ์ด์ ์ RestTemplate ๋ก ์ง์ URL์ ํธ์ถํ์ง ์๊ณ Eureka ์ ์๋ ์ธ์คํด์ค๋ฅผ discovery ํ์ฌ ํฌํธ ๋ฒํธ์ uri ๋ฅผ ์๋์ผ๋ก ๋งคํํด์ค๋ค.
RestTemplate ์์กด์ฑ ์ฃผ์
Bean์ผ๋ก RestTemplate ์ ๋ฑ๋ก์์ผฐ์ผ๋ Service.class์์ RestTemplate ์ ๋ํ ์์กด์ฑ์ ์ฃผ์ ํด์ฃผ์
์๋น์ค์์ RestTemplate์ผ๋ก ํธ์ถ
์ด์ RestTemplate ์ ์ด์ฉํด์ Team-Service์ getTeamByUserId()
๋ฅผ ํธ์ถํ ๊ฒ์ด๋ค.
@Service
@Transactional
public class UserService {
private final UserRepository userRepository;
private final RestTemplate restTemplate;
public UserService(UserRepository userRepository, RestTemplate restTemplate) {
this.userRepository = userRepository;
this.restTemplate = restTemplate;
}
/**
* ์ฌ์ฉ์๋ฅผ ์กฐํํ๋ค.
*
* @param id ์ฌ์ฉ์ id
* @return ์ ์ฅ๋ ์ฌ์ฉ์์ ํ ์ ๋ณด
*/
public UserResponseData getUserById(Long id) {
User userOptional = userRepository.findById(id)
.orElseThrow(RuntimeException::new);
// Team team = GET team-service/{userId}/teams
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 UserResponseData.builder()
.userId(userOptional.getId())
.username(userOptional.getUsername())
.team(team) // Team-Service ๋ก ์กฐํํ Team ์ ๋ณด๋ฅผ ๋ด์์ ๋ฐํ
.build();
}
}
์์์ RestTemplate ๋ฅผ bean ์ผ๋ก ์ฃผ์
ํ ๋, @LoadBalanced
์ด๋
ธํ
์ด์
์ ์ถ๊ฐํ์๊ธฐ ๋๋ฌธ์ ์ง์ ์ ์ธ team-service์ ์ฃผ์ ์ฒด๊ณ๋ฅผ ์ด์ฉํ์ง ์๊ณ Microservice์ ์ด๋ฆ ์ฒด๊ณ๋ฅผ ์ด์ฉํ๋ค.
RestTemplate ์์๋ exchange
๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ํต์ ์ ์ํํ๋ค.
ํด๋น ๋ฉ์๋์์๋ 4๊ฐ์ ๋งค๊ฐ๋ณ์๋ฅผ ๋ฐ๋๋ค.
- url
- HTTP Method
- Request Body
- Response Data Type Reference
๊ฒฐ๊ณผ ํ์ธํ๊ธฐ
๊ฒฐ๊ณผ๋ฅผ ํ์ธํ๋ฉด ์ ๋์ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ง๊ธ์ RestTemplate์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ๋ฒ์ ์ด์ฉํด์ API๋ฅผ ํธ์ถํ๊ณ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์์๋ค.
์ด์ ์ด์ ๋น์ทํ API๋ก Spring Cloud์ Netflix OpenFeign ์ ์ด์ฉํด๋ณด๋๋ก ํ์.
Spring Cloud Netflix OpenFeign ์ผ๋ก API ํธ์ถํ๊ธฐ
Feign Client ๋ REST ํธ์ถ์ ์ถ์ํ ํ Spring Cloud Netflix ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
์์ธํ ์ด์ผ๊ธฐ๋ ์๋ ๋น๊ต ์์ ํ๋๋ก ํ๊ณ ์ง๊ธ์ ์ฌ์ฉ ๋จผ์ ํด๋ณด์.
- ์ฌ์ฉ๋ฒ
- ์์กด์ฑ ์ถ๊ฐ
- HTTP Endpoint์ ๋ํ Client ์ธํฐํ์ด์ค ์์ฑ
- ํธ์ถ
์์กด์ฑ ์ถ๊ฐ
FeigntClient ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ OpenFeing ์์กด์ฑ์ ์ถ๊ฐํด์ค์ผ ํ๋ค.
build.gradle์ ๊ฐ์ ๋ค์๊ณผ ๊ฐ์ด ์์กด์ฑ์ ์ถ๊ฐ์์ผ๋ณด์.
build.gradle
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
๊ทธ๋ฆฌ๊ณ Application.java ๋ก ๊ฐ์ @EnableFeignClients
์ด๋
ธํ
์ด์
์ ์ถ๊ฐ์์ผ์ฃผ์.
Http Endpint ์ ๋ํ Client ์ธํฐํ์ด์ค ์์ฑ
User-Service๊ฐ ํธ์ถํด์ผํ Http Endpoint ๋ Team-Service์ http://localhost:8000/team/{userId}/teams
์ด๋ค.
์ด๋ฅผ ํธ์ถํ ์ธํฐํ์ด์ค๋ฅผ ์์ฑํด๋ณด์.
@FeignClient(name = "team")
public interface TeamServiceClient {
@GetMapping("/{userId}/teams")
TeamResponseData getTeam(@PathVariable("userId") Long id);
}
FeignClient ๋ผ๋ ์ด๋ ธํ ์ด์ ์ ์ด์ฉํ๋ฉด ์ง์ ํด๋น URL์ ๋ช ์ํ์ง ์๋๋ผ๋ Eureka ์ registerํ Instance ์ด๋ฆ์ ์ฐพ์์ URL์ ๋งคํํด์ค๋ค.
ํธ์ถ
์ด์ ์๋น์ค ํด๋์ค๋ก ๋์๊ฐ์ Feign ์ ์ด์ฉํด ํธ์ถ์ ๋ณด๋ด๋ณด์.
UserService.class ์๋ ์๊น Feign ์ธํฐํ์ด์ค๋ฅผ ์์ฑ์๋ก ์์กด์ฑ์ ๋ฃ์ด์ค๋ค.
@Service
@Transactional
public class UserService {
private final UserRepository userRepository;
private final RestTemplate restTemplate;
private final TeamServiceClient teamServiceClient;
public UserService(UserRepository userRepository,
RestTemplate restTemplate,
TeamServiceClient teamServiceClient) {
this.userRepository = userRepository;
this.restTemplate = restTemplate;
this.teamServiceClient = teamServiceClient;
}
public UserResponseData getUserById(Long id) {
User userOptional = userRepository.findById(id)
.orElseThrow(RuntimeException::new);
TeamResponseData team = teamServiceClient.getTeam(id);
return UserResponseData.builder()
.userId(userOptional.getId())
.username(userOptional.getUsername())
.team(team) // Team-Service ๋ก ์กฐํํ Team ์ ๋ณด๋ฅผ ๋ด์์ ๋ฐํ
.build();
}
}
๊ฒฐ๊ณผ ํ์ธํ๊ธฐ
๊ฒฐ๊ณผ๋ฅผ ํ์ธํ๋ฉด ์ ๋์ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ด์ ์ด ๋์ ์ค์ง์ ์ผ๋ก ๋น๊ตํด๋ณด๋๋ก ํ์.
์ ์ธ ๋ฐฉ์๊ณผ ๊ฐ๋ ์ฑ
๋ ๋ฐฉ์์ ์ ์ธ์ ๋ด๋ณด์.
RestTemplate๋ Feign ๋ชจ๋ ์์์ ๋ณธ ๋ฐ์ ๊ฐ์ด URL์ ์ง์ ๋ช ์ํด์ค์ผ ํ๋ค.
ํ์ง๋ง ๋ค๋ฅธ ์ ์ด ์๋ค๋ฉด ๋ฐ๋ก ๊ด์ฌ์ฌ์ ๋ถ๋ฆฌ์ด๋ค.
์ฝ๋๋ฅผ ๋ด๋ณด์.
@Service
@Transactional
public class UserService {
// ์๋ต
public UserResponseData getUserById(Long id) {
User userOptional = userRepository.findById(id)
.orElseThrow(RuntimeException::new);
TeamResponseData team = teamServiceClient.getTeam(id);
return // ์๋ต
}
public UserResponseData getUserById(Long id) {
User userOptional = userRepository.findById(id)
.orElseThrow(RuntimeException::new);
// Team team = GET team-service/{userId}/teams
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์๊ฒ ํธ์ถ์ ๋ณด๋ด๋ ๊ฒ์ผ๋ก Feign ์ด๋ RestTemplate์ด๋ ๋์ผํ๋ค.
ํ์ง๋ง Uri ์ ๋ํ ์ง์ ์ ์ธ ์ค์ ์ ๋ณด๋ UserService๊ฐ ๊ฐ์ ธ์ผ ํ๋๊ฒ ๋ง์๊น?
์ฑ ์์ ๊ด์ฌ์ฌ๋ก ๋ณธ๋ค๋ฉด ์ด๋ป๊ฒ ๋ ๊น?
๋ง์ฝ Team-Service์ ํธ์ถ ๊ฒฝ๋ก๊ฐ ๋ฌ๋ผ์ก๋ค๋ฉด ๊ทธ์ ๋ํ ์ฑ
์์ UserService
๊ฐ ์๋๋ผ ํธ์ถ์ ํ๋ ๋ก์ง ์์ฒด์ ์กด์ฌํ๋ค.
ํ์ง๋ง RestTemplate ์์๋ ์ค์ ์ ๋ณด๊ฐ UserService.class ๋ด์ ์๊ธฐ ๋๋ฌธ์ UserService๊ฐ ๊ทธ ์ฑ
์์ ์ง๊ณ ์๋ค.
๊ทธ์ ๋ฐํด์ Feign์ ์ด๋จ๊น?
์์ Feign์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ํธ์ถ์ ๊ดํ ์ค์ ์ ๋ค FeignClient.interface ์์ ์ํํ๋๋ก ๊ฐ์ ํ๋์ด ์๊ธฐ ๋๋ฌธ์ ๊ด์ฌ์ฌ๊ฐ ๋ถ๋ฆฌ๋์ด์๋ค.
๊ฒฐ๊ตญ ์ด๋ฅผ ๊ฐ์ ธ๋ค ์ฐ๋ UserService
์์๋ ๋ฐํ์ ๋ํ ๊ฒฐ๊ณผ๋ง์ ์ฑ
์์ผ๋ก ๊ฐ๊ณ ์๋ ๊ฒ์ผ๋ก ์ ์ ํ๋ค๊ณ ํ ์ ์๋ค.
๊ฐ๋ ์ฑ์ ์ด์ผ๊ธฐ ํ์ง ์๋๋ผ๋ Feign ์ด ์ข๋ค๊ณ ์๊ฐํ๋ค.
์์ธ ์ฒ๋ฆฌ
์ด ์ํฉ์ ๋ด๋ณด์.
๋ง์ฝ User-Service ์์ Team-Service ์ ์กด์ฌํ์ง ์๋ ์ฌ์ฉ์ ์์ฒญ์ ๋ณด๋ธ๋ค๊ณ ๊ฐ์ ํด๋ณด์.
๊ทธ๋ผ ์๋ต์ผ๋ก 500 ์๋ฌ๊ฐ ๋์ค๊ฒ ๋๋ค.
์ฌ์ค 500 ์๋ฌ๋ Team-Service ์์ ์ ๋๋ก๋ ๊ฐ์ ๋ฐํ๋ฐ์ง ๋ชปํด์ ๋ฐ์ํ๋ User-Service์ ์๋ฌ์ด๋ค.
Team-Service ์์๋ ์ค์ ๋ก 404 Not Found ๊ฐ ๋ฐ์ํ๊ณ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์ฒ๋ฆฌํ์ง ๋ชปํ๋ User-Service ์์ 500 ์๋ฌ๋ก ๋ฐํํ๊ณ ์๋ค.
๊ทธ๋ผ User-Service ์์๋ 500 ์๋ฌ๊ฐ ์๋ ํธ์ถํ๋ Team-Service ์ 404 ์๋ฌ๊ฐ ๋ฐ์ํ ๊ทธ ์ํ๋ฅผ ๋ฐํํด์ผ ํ๋ค.
์ด๋ด ๋ RestTemplate๊ณผ Feign์ ๊ฐ๊ฐ์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ๊น?
RestTemplate์ try-catch ๋ฅผ ์ด์ฉํ์ฌ ์ฒ๋ฆฌ๋ฅผ ํด์ผ ํ๋ค.
public UserResponseData getUserById(Long id) {
User userOptional = userRepository.findById(id)
.orElseThrow(RuntimeException::new);
// Team team = GET team-service/{userId}/teams
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 (Excepotion e) {
return new UserNotEnrolledTeamException("์ฌ์ฉ์๋ ํ์ ๊ฐ์
๋์ด์์ง ์์ต๋๋ค.");
}
return UserResponseData.builder()
.userId(userOptional.getId())
.username(userOptional.getUsername())
.team(team) // Team-Service ๋ก ์กฐํํ Team ์ ๋ณด๋ฅผ ๋ด์์ ๋ฐํ
.build();
}
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;
}
}
๊ทธ๋ฆฌ๊ณ service ์์์ ์ฌ์ฉ์ ์ฝ๋ ๋ณํ ์์ด ์ฌ์ฉํ๋ค๊ฐ ๋ง์ฝ ๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด ํด๋น ์๋ฌ๋ฅผ ๋ฐํ์ํค๊ฒ ๋๋ค.
public UserResponseData getUserById(Long id) {
User userOptional = userRepository.findById(id)
.orElseThrow(RuntimeException::new);
// without try - catch
TeamResponseData team = teamServiceClient.getTeam(id);
return UserResponseData.builder()
.userId(userOptional.getId())
.username(userOptional.getUsername())
.team(team) // Team-Service ๋ก ์กฐํํ Team ์ ๋ณด๋ฅผ ๋ด์์ ๋ฐํ
.build();
}
ํ ์คํธ ์ฝ๋
ํ ์คํธ ์ฝ๋๋ ์ ๋ง ์ค์ํ๋ค.
์ฌ์ค ์ด๊ฑฐ ํ๋ ๋ง์ผ๋ก ์์ ๋ชจ๋ ์ฅ์ ๊ณผ ๋จ์ ๋ค์ ๋ฌด์ํ ์ ์๋ ๊ฒ์ธ๋ฐ, UnitTest ์์๋ ์ธ๋ถ ๋ชจ๋๊ณผ์ ์์กด ๊ด๊ณ๋ฅผ ๋์ด ํ์คํ ๊ณ ๋ฆฝ์ด ์ค์ํ๋ค.
๋ณดํต ํ๋์ Unit ์ ํ
์คํธํ๊ธฐ ์ํด์ ๋ค๋ฅธ ์์กด ๊ด๊ณ๋ฅผ ๋๊ณ mock ๊ฐ์ฒด๋ฅผ stubbing ํ์ฌ ์ฌ์ฉํ๋ค.
๊ทธ๋ผ UserService ์์ RestTemplate ์ ์ฃผ์ ๋ฐ๊ฒ ๋๋ค๋ฉด RestTemplate ์ exchange ๋ ์ด๋ป๊ฒ stubbing ํด์ผํ ๊น?
๊ฐ๋จํ๊ฒ stubbing ํ๋ ์ฝ๋๋ฅผ ๋น๊ตํด๋ณด์.
class UserServiceTest {
private UserService userService;
private final UserRepository userRepository = mock(UserRepository.class);
private final TeamServiceClient teamServiceClient = mock(TeamServiceClient.class);
@Autowired
private RestTemplate restTemplate;
@BeforeEach
void setUp() {
userService = new UserService(userRepository, restTemplate, teamServiceClient);
User user = User.builder()
.id(1L)
.username("name")
.build();
TeamResponseData responseData = TeamResponseData.builder()
.id(1L)
.name("team name")
.address("address")
.build();
given(userRepository.findById(anyLong())).willReturn(Optional.of(user));
// Feign Test
given(teamServiceClient.getTeam(anyLong())).willReturn(responseData);
// RestTemplate Test
given(restTemplate.exchange(eq("http://localhost:8000/team/1L/teams"),
HttpMethod.GET, null, UserResponseData.class))
.will(invocation ->
ResponseEntity.status(HttpStatus.OK).body(responseData)
);
}
}
๋ง์ฝ ์ฌ๊ธฐ์ RestTemplate ์ด ํธ์ถํ๋ URL์ด ๋ ๊ฐ๋ผ๋ฉด ์ด๋ป๊ฒ ๋ ๊น?
๊ทธ๋ผ RestTemplateBuilder
๋ฅผ ์ด์ฉํด์ ํ๋ ํ๋ ๋งคํ์ ํด์ผํ๋ค.
์ฐ๋ฆฌ๋ Eureka ๋ฅผ ์ด์ฉํด์ API ํธ์ถ Endpoint ๋ฅผ MSA ์ธ์คํด์ค ์ด๋ฆ์ผ๋ก ์ง์ ํ๋๋ฐ, ์ด๋ ๊ฒ ํ ์คํธ ์ฝ๋์์๋ ์ผ์ผ์ด ์ ์ด์ค์ผ ํ๊ณ , ๋ง์ฝ ํด๋น URI๊ฐ ๋ณ๊ฒฝ๋๋ค๋ฉด ์ด์ ๊ด๋ จ๋ ๋ชจ๋ API ๋ฅผ ํธ์ถํ๋ ํ ์คํธ๋ฅผ ๋ณ๊ฒฝํด์ผ ํ๋ค.
๊ทธ์ ๋ฐํด Feign ์ ์ธํฐํ์ด์ค์ ๋ฐํ ๊ฐ์ฒด๋ง ๋งคํํด์ค๋ค๋ ์ ์์ ์์ฃผ ๋งค๋ ฅ์ ์ด๋ค.
์ข ํฉ ์ ๋ฆฌ ๋ฐ ์ถ๊ฐ ์ฌํญ
์ง๊ธ๊น์ง ์์ฃผ ๋ง์ ์ด์ผ๊ธฐ๋ฅผ ํ์๋ค.
์ด์ ์์์ ๋ด์ฉํ ์ด์ผ๊ธฐ๋ค์ ์ ๋ฆฌํด๋ณด์๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
์ด๋ฆ | ์ฝ๋ ๊ฐ๋ ์ฑ, ์ง๊ด์ฑ | ์์ธ ์ฒ๋ฆฌ | ํ ์คํธ ์ฉ์ด์ฑ | ๋ฌ๋ ์ปค๋ธ |
Open Feign | ๊ฐ๋ ์ฑ ์ข์ | ErrorDecoder ์ ๊ณต | ์ผ๋ฐ์ ์ธ ์ธํฐํ์ด์ค์ ๊ฐํธํ stubbing | ๋ฎ์ |
Rest Template | ๊ฐ๋ ์ฑ์ด ์ข๊ฒ ๋๊ธฐ ์ํด ๋ค๋ฅธ ์์ ํ์ | try-catch | Spring ์ด ๊ตฌํํด๋์ ๊ฐ์ฒด์ ๋ณต์กํ stubbing | ๋ฎ์ |
๋๊ธ