이번 Spring MVC 미션을 수행하면서 생각했던 점들을 적어볼까 한다.
2단계
1. 날짜 포맷은 어떻게 써야하는가?
이번 문제에서 날짜와 시간을 저장하는 필드가 존재했다.
- Java 8 이전에는 Data를 이용하여 날짜를 저장하였지만, Java 8 이후부턴 LocalDate, LocalTime, LocalDateTime을 이용
- 날짜 포맷을 맞추기 위해 Reservation에서 @DataTimeFormat을 이용하여 포맷을 정했다.
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date;
@DateTimeFormat(pattern = "HH:mm")
private LocalTime time;
Q. 날짜 타입 객체는 어떻게 만들지?
처음에 당연히 이게 정상적으로 작동할거라 생각했다...
LocalDate date = new LocalDate(2023, 1, 30);
그러나 LocalDate, LocalTime, LocalDateTime 에선 .of()를 이용하여 손쉽게 만들 수 있다.
LocalDate.of(2023,1,1)
Q. 날짜 포맷을 만들고 싶은데 어떻게 하지?
ofPattern() 을 이용하여 포맷을 지정할 수 있다.
이번 코드에서 날짜 포맷을 static final로 선언하여 사용하도록 설계하였다.
public class TimeFormatter {
public static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
}
Q. String을 날짜 타입으로 어떻게 바꾸지?
이 또한 간편하게 .parse() 를 이용하여 손쉽게 Parsing이 가능하다
LocalDate date = LocalDate.parse(param.get("date"), TimeFormatter.dateFormatter);
LocalTime time = LocalTime.parse(param.get("time"), TimeFormatter.timeFormatter);
두번째 파라미터로 포맷을 설정할 수 있다!
3단계
1. Create(URI)에서 URI는 무엇인가?
- created는 새로운 자원이 정상적으로 생성되었다는 것을 의미한다.
- 생성된 자원에 접근할 수 있도록 식별자가 포함된 URI를 반환한다.
// 테스트 코드 중 일부
.header("Location", "/reservations/1")
// 컨트롤러 중 일부
return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation);
HttpResponse 중 location 헤더를 제공하는데 이와 연관된 것으로 보인다..
피드백 과정에서 새롭게 발견(?)한 부분들도 있었다.
1. @DateTimeFormat을 이용한 경우, 예외 처리는 어떻게 되는가?
@DateTimeFormat에 지정한 날짜 형식과 다를 경우,
Spring은 예외를 발생시키고 기본적으로 400 Bad Request 응답을 생성한다는 것을 처음 알았다.
따라서, ExceptionHandler를 통해 Bean에서 예외를 관리하도록 설정해주었다.
@ExceptionHandler(DateTimeParseException.class)
public ResponseEntity<Object> handleDateTimeParseException(DateTimeParseException ex) {
return ResponseEntity.badRequest().body("날짜/시간 형식이 잘못되었습니다.");
}
2. MVC 패턴에서 Service와 Repository는 어디에 해당하는가?
이 부분이 문제를 접근하면서 많이 햇갈렸다.
MVC 패턴에서 Model의 경우, 비즈니스 계층과 DAO 계층가 이에 해당한다. 따라서, 비즈니스 계층에 포함된 Service는 Model의 부분인 것을 확인할 수 있다.
3. Validation은 어디서 하는게 좋은가?
진짜 진짜 많이 조사를 해봐도 햇갈리는 문제인 것 같다. 햇갈린다기 보다 딱 정해진 게 없는 느낌이다.
MVC 패턴의 경우, 모든 계층에서 검증을 수행하는 것이 가능하다는 것은 알 수 있다.
그러나 왜 검증 위치를 이렇게 고려할까?
(뇌피셜) 여러 계층에서 Validation을 처리하면 쉽게 찾아보기가 어렵다. 내가 어떤 Validaiton을 처리했는지, 혹은 코드를 잘못작성하여 어디서 Validation됐는지 확인하기 어려울 수 있다고 생각했다.
여러 블로그들을 참고하여 의견을 종합해봤다.
- 여러 Controller에서 공통의 Service 메소드를 사용하는 경우가 있기 때문에, Service 내에서 수행하는 것이 좀 더 적합
- Service 보다 Model의 경우 모든 계층을 통과하기 때문에 자체적인 Validation이 필요하다.
이번 미션에서 Validaiton이 필요한 부분은 두 가지이다.
- 존재 하지 않는 예약에 대한 접근
- 예약 추가 시, 날짜 형식이 틀린 경우
공통 피드백 단계에서 각 예외처리는 다음의 특징이 있다.
(1) → DB 조회 후 알 수 있음
(2) → 단순 요청으로도 검증이 가능
2번의 경우, @Valid를 통해 쉽게 해결이 가능하다.
하지만, 1번의 경우 Repository 단에서 예외가 발생할지 안할지 결정된다.
이는 위에서 살펴본 Model이나 Service 단에서 따로 처리하는게 비효율적이라고 느껴졌다.
어차피 Repository에서 확인이 가능한 이상, 조회 과정에서 예외처리를 하자고 생각했다.
따라서, Validation의 절대위치는 없는것 같다.
다만, 가능하면 Model 자체적으로 Validation을 갖도록 유지하는게 중요한 것 같다.
4. fast-fail과 fast-safe란 무엇인가?
- Fail-Fast 방식은 동작중 오류가 발생하면 바로 오류를 알리고 작업 중단
- Fail-Safe 방식은 동작중 오류가 발생해도 작업을 중단하지 않고 진행
예를 들면, Fali-Fast는 실행을 중단하고 빠르게 예외를 던진다. 반면, Fail-Safe는 try-cath와 같이
이 중 무엇이 옳은 방법일까?
현재 내 생각으로 fail-fast가 올바르다고 생각한다.
fail-safe의 경우, 예외에 대한 로그를 남기지 않는 경우라고 생각했다.
빠르게 실패하기(fail-fast) VS 안전하게 실패하기(fail-safe)
이 주제에 대해서 "엘레강트 오브젝트-조영호 번역" 책에서 다루고 있습니다. 이론상으로만, 이해하고 있었습니다. 그러나, 최근 사내 서비스를 트러블 슈팅하며, 다른 분의 코드를 리뷰하며 이
happy-coding-day.tistory.com
fail-fast는 바로 예외를 처리하므로 예외에 대해 바로 확인할 수 있기 때문에 더 좋은 방법이라고 생각이 든다.
추가로 공부해볼 점
1. @RequestBody의 정확한 원리
이번 미션에서 테스트 코드에선 RestAssured를 이용하였다.
해당 코드에선 Map을 Body에 담아 API 요청을 보낸다.
Map<String, String> params = new HashMap<>();
params.put("name", "브라운");
params.put("date", "2023-08-05");
params.put("time", "15:40");
RestAssured.given().log().all()
.contentType(ContentType.JSON)
.body(params)
.when().post("/reservations")
.then().log().all()
.statusCode(201)
.header("Location", "/reservations/1")
.body("id", is(1));
이를 컨트롤러에서 객체로 매핑받기 위해 우리는 @RequestBody를 사용한다.
처음에 Map 자료형이므로 @ReuqestBody Map<String, String> 을 사용했다.
@PostMapping("/reservations")
public ResponseEntity<Reservation> makeReservation(@RequestBody Map<String, String> params) {
}
이럴 경우, Map에 담긴 항목들의 Validation이 힘들어진다. 따라서 DTO를 사용하여 처리하는 것이 더 완벽한 코드라고 생각이 든다.
그러나 @RequestBody는 Map을 어떻게 DTO 객체로 변환해주는 걸까??
2. record 사용해보기
불변 객체의 경우, record를 이용한다고 한다.
왜 이를 사용하는지 더 조사해보고 리펙토링 해보자.
레퍼런스
Validation(입력값 검증)의 최적의 장소는 어디일까?
일반적인 3-Tier 구조로 웹 애플리케이션을 만든다고 가정할 때, Controller - Service - Repository 모두에서 입력값 검증(Validation)을 수행할 수 있다. Controller에서 @Valid annotation을 사용한다던지.. 그런 것
starkying.tistory.com
Validation(입력값 검증)의 최적의 장소는 어디일까?
일반적인 3-Tier 구조로 웹 애플리케이션을 만든다고 가정할 때, Controller - Service - Repository 모두에서 입력값 검증(Validation)을 수행할 수 있다. Controller에서 @Valid annotation을 사용한다던지.. 그런 것
starkying.tistory.com
'🛠 백엔드 > Spring' 카테고리의 다른 글
[Spring] 민감한 정보 관리하기 wt. secretKey (0) | 2024.07.01 |
---|---|
RequestBody의 원리와 기본 생성자 (0) | 2024.05.23 |
JDBC란 무엇인가? (+ Spring JDBC) (0) | 2024.05.19 |
[Java] 일급 컬렉션을 왜 사용할까? (0) | 2024.05.05 |
MVC 패턴 정의 및 규칙 (0) | 2024.04.30 |