Users Microservice 개요
APIs
기능 | URI(API Gateway 사용 시) | URI(API Gateway 미사용 시) | HTTP Method |
사용자 정보 등록 | /user-service/users | /users | POST |
전체 사용자 조회 | /user-service/users | /users | GET |
사용자 정보, 주문 내역 조회 |
/user-service/users/{user_id} | /users/{user_id} | GET |
작동 상태 확인 | /user-service/users/health_check | /users/health_check | GET |
환영 메시지 | /user-service/users/welcome | /users/welcome | GET |
User Microservice와 Spring Cloud Gateway 연동
Controller에서 port 출력(Environment 사용)
public class UserController {
private Environment env;
private UserService userService;
private Greeting greeting; // # @Value
@Autowired
public UserController(Environment env, UserService userService, Greeting greeting) {
this.env = env;
this.userService = userService;
this.greeting = greeting;
@GetMapping("/health_check")
public String status() {
return String.format("It's Working in User Service on PORT %s",
env.getProperty("local.server.port"));
}
apigateway-service 프로젝트 route 정보 입력
application.yml
routes:
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user-service/**
코드 전체 보기
spring:
application:
name: apigateway-service
cloud:
gateway:
default-filters:
- name: GlobalFilter #GlobalFilter클래스 이름
args:
baseMessage: Spring Cloud Gateway Global Filter
preLogger: true
postLogger: true
routes:
- id: user-service
uri: lb://USER-SERVICE # 마이크로 서비스 이름을 넣어주면 됨.. #2 여기로 포워드 시키겠다..
predicates: # 조건식
- Path=/user-service/** #1 /user-service/** 로 들어오는 것은
- id: first-service
uri: lb://MY-FIRST-SERVICE # 마이크로 서비스 이름을 넣어주면 됨..
predicates:
- Path=/first-service/**
filters:
# - AddRequestHeader=first-request, first-request-header2
# - AddResponseHeader=first-response, first-response-header2
- CustomFilter
두 개의 프로젝트(user-service, gatewaye-service)를 실행한다.
유레카 대시보드 확인
health_check 메서드 동작 확인
게이트웨이를 통과하는 uri 주소 입력
application.yml에서 route 정보를 입력시켰음에도 불구하고 404 에러 페이지가 뜬다.
이유는 uri가 '/user-service/health_check'인데 user-service 프로젝트의 controller에서는 '/user-service/health_check'가 아닌 '/health_check'를 GetMapping하기 때문에 요청에 맞는 응답을 해주지 못한 것이다.
@RequestMapping("/")
public class UserController {
@GetMapping("/health_check")
public String status() {
return String.format("It's Working in User Service on PORT %s",
env.getProperty("local.server.port"));
}
}
위 user-service의 UserController 코드로 봤을 때는 'IP:포트번호/health_check'한 uri 요청에만 응답할 수 있다.
그래서 Controller의 GetMapping()을 수정해주면 된다.
@GetMapping("/user-service/health_check") // ✔ 여기에 user-sevice 추가!
public String status() {
return String.format("It's Working in User Service on PORT %s",
env.getProperty("local.server.port"));
}
@GetMapping("/user-service/welcome")
public String welcome() {
//return env.getProperty("greeting.message");
return greeting.getMessage();
}
서버 재실행 후 크롬에서 확인
Feat. 사용자 전체 조회
UserController의 각 메서드 uri 매핑 주소에 prefix 격으로 '/user-service/'가 들어간다. 그러므로 클래스 블럭에 있는 @RequestMapping() 어노테이션에 해당 uri를 기재하고, 각 메서드 Get/PostMapping()에 적혀있는 '/user-service/'는 지워주자.
@RequestMapping("/user-service/")
public class UserController { ... }
- ResponseUser.java 생성
ResponseUser vo에 회원 주문 목록을 볼 수 있도록 필드를 하나 추가한다. 아직 ResponseOrder vo를 만들지 않아 빨간 줄이 뜨는데 alt + enter 단축키를 입력하고 Create class 'ResponseOrder'를 클릭하면 해당 vo 클래스를 생성할 수 있다.
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseUser {
private String email;
private String name;
private String userId;
private List<ResponseOrder> orders;
}
💡 @JsonInclude(JsonInclude.Include.NON_NULL)
💡 @JsonInclude(JsonInclude.Include.NON_NULL)
참고링크
- Jackson 라이브러리에서 사용되는 어노테이션
- JSON 직렬화 시에 특정 속성의 값이 null인 경우 해당 속성을 생성하지 않도록 지시
구체적으로 말하면, Jackson이 객체를 JSON으로 변환할 때, 해당 객체의 속성 중 값이
null인 속성들을 제외하고 JSON에 포함하도록 지정한다.
이렇게 함으로써 생성된 JSON이 불필요한 null값을 가지지 않게 되며, 일부 상황에서 JSON을 더 간결하게 만들 수 있다.
- ResponseOrder.java
@Data
public class ResponseOrder {
private String productId;
private Integer qty;
private Integer unitPrice;
private Integer totalPrice;
private LocalDateTime createdAt;
private String orderId;
}
강의에서는 createdAt 필드를 Date 타입으로 생성했다. Date 타입은 deprecated 되었으므로 나는 LocalDateTime 패키지를 사용했다.
💡 Date 타입 vs. LocalDateTime 타입
💡 Date 타입 vs. LocalDateTime 타입
Date 타입과 LocalDateTime은 Java에서 사용되는 날짜와 시간을 나타내는 두 가지 서로 다른 클래스이다.
• Date 타입
- java.util.Date 클래스는 Java 초기 버전부터 존재하며, 날짜와 시간을 표현하는 데 사용된다.
- Date는 특정 시점의 타임스탬프를 나타내는데, 이 타임스탬프는 1970년 1월 1일 00:00:00 GMT (Epoch)로부터의 밀리초로 표현된다. Date는 가변(mutable)하며, 스레드 안전하지 않다.
- Java 8 이전에는 Date 클래스가 주로 사용되었지만, 이후에는 java.time 패키지가 추가되어 새로운 API인 LocalDateTime을 제공하게 되었다.
• LocalDateTime 타입 ✔
- java.time.LocalDateTime 클래스는 Java 8부터 도입된 새로운 날짜와 시간 API에서 제공하는 클래스 중 하나이다.
- LocalDateTime은 시간대(time zone)를 고려하지 않는 날짜와 시간 정보를 나타낸다.
- 불변(immutable)하며 스레드 안전하다.
- LocalDateTime은 특정 시간대의 날짜와 시간을 나타내는 클래스가 아니기 때문에, 시간대 관련 작업이 필요한 경우 ZonedDateTime 등을 사용해야 한다.
- UserService.interface
UserService.interface에서 두 개의 메서드를 생성했으니 구현체에도 두 개의 메서드 getUserById와 getUserByAll 메서드를 구현한다.
getUserByAll() 같은 경우는 이미 JPA에서 findAll()이라는 메서드를 제공하기 때문에 별 다른 로직 없이 해당 메서드의 반환 값을 리턴해주면 끝이 난다.
public interface UserService {
UserDto getUserByUserId(String userId);
}
- UserServiceImpl
@Override
public Iterable<UserEntity> getUserByAll() {
return userRepository.findAll();
}
💡 Iterable
💡 Iterable
참고 - iterable vs iterator
참고 - [Java] - 자바의 자료구조(1)
Java에서 컬렉션을 나타내는 인터페이스
이 인터페이스는 컬렉션을 순회할 수 있는 반복자(iterator)를 반환할 수 있는 메서드 iterator()를 정의
- UserController.java
@GetMapping("/users")
public ResponseEntity<List<ResponseUser>> getUsers() {
Iterable<UserEntity> userList = userService.getUserByAll();
//userEntity > ResponseEntity로 변경
List<ResponseUser> result = new ArrayList<>();
userList.forEach(v -> {
result.add(new ModelMapper().map(v, ResponseUser.class));
});
return ResponseEntity.status(HttpStatus.OK).body(result);
}
Feat. 사용자 ID로 조회
userRepository에 findByUserId() 메서드는 기본으로 제공해주지 않기 때문에 따로 만들어줘야한다. UserRepositoy.Interface에 해당 메서드를 추가해준다.
- UserRepository.interface
UserEntity findByUserId(String userId);
- UserServiceImpl
@Override
public UserDto getUserById(String userId) {
UserEntity userEntity = userRepository.findByUserId(userId);
if(userEntity == null)
throw new UsernameNotFoundException("User not found");
// UserEntity -> UserDto로 변환
UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);
List<ResponseOrder> orders = new ArrayList<>();
userDto.setOrders(orders);
return userDto;
}
- UserController.java
하나의 사용자에 대한 상세 정보를 가져오는 method를 구현하자.
@GetMapping("/users/{userId}")
public ResponseEntity<ResponseUser> getUser(@PathVariable("userId") String userId) {
UserDto userDto = userService.getUserById(userId);
//userDto > ResponseUser로 변경
ResponseUser result = new ModelMapper().map(userDto, ResponseUser.class);
return ResponseEntity.status(HttpStatus.OK).body(result);
}
참고 - Spring Cloud로 개발하는 마이크로서비스 애플리케이션_Catalogs and Orders Microservice_2