user-service 와 order-serivce 가 서로 통신하려면 어떻게 해야할까?
하지만 Rest Template 을 사용하면 유레카 서버를 통하지 않고 한 번에 통신할 수 있다.
// UserServiceApplication.class
// Bean 으로 등록하기 때문에 다른 곳에서 주입 받아 사용할 수 있다.
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
// UserServiceImpl.class
@Override
public UserDto getUserById(String userId) {
UserEntity userEntity = userRepository.findByUserId(userId);
if (userEntity==null) {
throw new UsernameNotFoundException("User not found");
}
UserDto userDto = new ModelMapper().map(userEntity , UserDto.class);
List<ResponseOrder> orderList = new ArrayList<>();
userDto.setOrders(orderList);
return userDto;
}
// order-service
@GetMapping("/{userId}/orders")
public ResponseEntity<List<ResponseOrder>> getOrder(@PathVariable("userId") String userId) {
ModelMapper modelMapper = new ModelMapper();
Iterable<OrderEntity> orderList = orderService.getOrdersByUserId(userId);
List<ResponseOrder> result = new ArrayList<>();
orderList.forEach(v-> {
result.add(new ModelMapper().map(v,ResponseOrder.class));
});
return ResponseEntity.status(HttpStatus.OK).body(result);
}
@Override
public UserDto getUserById(String userId) {
UserEntity userEntity = userRepository.findByUserId(userId);
if (userEntity==null) {
throw new UsernameNotFoundException("User not found");
}
UserDto userDto = new ModelMapper().map(userEntity , UserDto.class);
// restTemplate 을 하는 첫 번째 방법
String orderUrl = "http://127.0.0.1:8000/order-service/%s/orders";
ResponseEntity<List<ResponseOrder>> orderListResponse = restTemplate.exchange(orderUrl, HttpMethod.GET, null,
new ParameterizedTypeReference<List<ResponseOrder>>() {
});
List<ResponseOrder> orderList = orderListResponse.getBody();
userDto.setOrders(orderList);
return userDto;
}
위와 같은 방식으로 service 끼리 통신하면 된다.
현재 orderURL 을 하드코딩을 해놨는데 만약 포트 번호가 바뀌거나 endpoint 가 변경될 경우를 고려해보면 좋은 방식은 아니다.
# user-service.yml
order_service:
url: http://127.0.0.1:8000/order-service/%s/orders
// UserServiceImpl.class
String orderUrl = String.format(env.getProperty("order_service.url"), userId);
ResponseEntity<List<ResponseOrder>> orderListResponse = restTemplate.exchange(orderUrl, HttpMethod.GET, null,
new ParameterizedTypeReference<List<ResponseOrder>>() {
});
이때 yml 파일에 127.0.0.1:8000 이 아닌 유레카 서버에 등록한 이름대로 ORDER-SERVICE/order-service 이런식으로 요청하는게 더 좋은 설계이다.
@Bean
// @LoadBalanced 를 붙혀주면 /user-service/ 로 접근할 수 있다.
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
order_service:
url: http://ORDER-SERVICE/order-service/%s/orders
똑같이 동작하지만 변경을 해야할 때 좀 더 편해지는 코드를 작성해봤다.
먼저 user-service 에서 아래에 코드로 라이브러리를 추가한다.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@EnableFeignClients
public class UserServiceApplication { ... }
@FeignClient(name="order-service") // microservice 이름 넣기
public interface OrderServiceClient {
@GetMapping("/order-service/{userId}/orders")
List<ResponseOrder> getOrders(@PathVariable String userId);
}
// UserServiceImpl.class
@Override
public UserDto getUserById(String userId) {
UserEntity userEntity = userRepository.findByUserId(userId);
if (userEntity==null) {
throw new UsernameNotFoundException("User not found");
}
UserDto userDto = new ModelMapper().map(userEntity , UserDto.class);
/**
* using feign client
*/
List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);
userDto.setOrders(orderList);
return userDto;
}
# application.yml
logging:
level:
com.example.userservice.client: DEBUG
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
OrderServiceClient 에서 @GetMapping 에 있는 Endpoing 를 이상한 문자를 넣는다.
List<ResponseOrder> orderList = null;
try {
orderList = orderServiceClient.getOrders(userId);
} catch (FeignException ex) {
log.error(ex.getMessage());
}
@Component
public class FeignErrorDecoder implements ErrorDecoder {
private Environment env;
public FeignErrorDecoder(Environment env) {
this.env = env;
}
@Override
public Exception decode(String methodKey, Response response) {
switch (response.status()) {
case 400:
break;
case 404:
if (methodKey.contains("getOrders")) {
// 404 에러로 번환하고 메시지 담아서 새로운 예외 객체 생성
return new ResponseStatusException(HttpStatus.valueOf(response.status()),
"User's order is empty");
// env.getProperty("order_service.exception.orders_is_empty);
}
default:
// 예외가 발생했던 원인에 대해서 출력
return new Exception(response.reason());
}
return null;
}
}
// UserServiceImpl.class
// ErrorDecode 는 따로 주입 받지 않아도 된다.
List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);
order service 2개를 기동했을 때 각각의 데이터베이스를 가진다.
해결방법
1. 하나의 database 사용
-> 이때는 트랜잭션 관리를 잘해야한다. (여러개의 서비스가 하나의 database 를 사용하기 때문)
2. database 간의 동기화
-> 각 서비스는 데이터베이스에 데이터를 저장하는것이 아니라 Message Queuing Server 에 전달을 한다.
-> Meesage Queuing Server 에 구독신청한 또 다른 서비스에게 서버에서 변경된 데이터를 보내주고 업데이트 하도록 한다.
3. kafka connector + db
-> 1번과 2번의 방법을 모두 사용
-> Message Queuing Server 를 미들웨어 즉 중간 매개체로 사용한다.
-> Service 와 database 사이에 Message Queuing Server 를 놓고 관리한다.
-> 1초 안에 수만건을 처리할 수 있도록 설계되어 있다.
order-service 서버를 기동하고 terminal 에 가서 mvn spring-boot:run 명령어로 하나의 서버를 더 기동한다.
유레카 서버에서 각 서비스의 포트 번호를 가지고 h2-console 로 접근해본다.
order-serivce/{userId}/orders
즉 데이터 동기화에 대한 문제가 있다.
이제 데이터 동기화를 위해 Apache Kafka 를 활용해보자.