쿠키를 이용하여 장바구니를 저장하는 로직을 만들었다.
오류
장바구니를 생성하는 API를 테스트 하는 과정에서
계속 error 500 Internal Server Error 가 뜨면서
Failed to encode cart data 라고 떴다.
오류가...
2024-12-07T01:26:24.561+09:00 ERROR 36898 --- [samsung_delivery] [nio-8080-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.RuntimeException: Failed to encode cart data] with root cause
여러가지로 검색해보니 CartDto 또는 CartItemDto 클래스의 필드가 Jackson 라이브러리(ObjectMapper)로 직렬화(serialize) 또는 역직렬화(deserialize)되는 과정에서 발생한 것으로 추측된다.
특히, Jackson은 필드에 대해 적절한 getter 또는 setter가 없거나,
LocalDateTime 같은 특정 타입을 직렬화하는 방법을 몰라서 예외를 던질 가능성도 있다.
해결
1. LocalDateTime 직렬화 지원 추가
Jackson은 기본적으로 LocalDateTime을 직렬화/역직렬화할 수 없다. 이를 해결하려면 JavaTimeModule을 등록한다.
// CartCookieEncoder 클래스 수정
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.databind.SerializationFeature;
private static final ObjectMapper objectMapper = new ObjectMapper()
.registerModule(new JavaTimeModule()) // Java 8 DateTime 지원
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
2. CartDto와 CartItemDto의 Getter/Setter 확인
Jackson은 클래스의 모든 필드에 대해 기본 생성자와 Getter/Setter를 필요로 한다.
모든 필드가 제대로 노출되었는지 확인해보자
- @Getter와 @Setter를 누락 여부
- LocalDateTime 타입은 위의 JavaTimeModule 설정
3. CartDto 클래스 수정
null 값이나 초기화 문제로 인한 직렬화 오류를 방지하려면, 기본값을 설정하거나 명시적인 초기화 코드를 작성한다.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class CartDto {
private Long userId;
private Long storeId;
private List<CartItemDto> items = new ArrayList<>(); // 초기화
private LocalDateTime lastUpdated = LocalDateTime.now(); // 초기화
public boolean isExpired() {
return lastUpdated.plusDays(1).isBefore(LocalDateTime.now());
}
}
4. 디버깅: 예외 발생 원인 로깅 추가
CartCookieEncoder에서 예외 발생 시 로그를 추가한다.
public static String encode(CartDto cart) {
try {
return Base64.getEncoder().encodeToString(objectMapper.writeValueAsString(cart).getBytes());
} catch (JsonProcessingException e) {
e.printStackTrace(); // 에러 로그 출력
throw new RuntimeException("Failed to encode cart data", e);
}
}
5. API 테스트: 디코딩 문제 확인
만약 인코딩된 값이 이상하거나 클라이언트에서 적절히 전달되지 않았다면 디코딩 로직에서도 문제가 생길 수 있다.
이때 디코딩 과정을 테스트하고 로그를 추가한다.
public static CartDto decode(String encodedCart) {
try {
String decoded = new String(Base64.getDecoder().decode(encodedCart));
return objectMapper.readValue(decoded, CartDto.class);
} catch (IOException e) {
e.printStackTrace(); // 에러 로그 출력
throw new RuntimeException("Failed to decode cart data", e);
}
}
6. API 호출 후 디버깅
API 호출 중 어떤 쿠키 데이터가 전달되는지 확인하려면 브라우저 개발자 도구 또는 Postman에서 Set-Cookie 헤더 값을 확인하자.
위 수정사항을 모두 반영하여 다시 테스트하였으나, 별다른 효과가 없어서
설마 Service 단에서 @Transactional 을 모두 걸어버린 것이 문제였을까 싶어서
@Transactional 에 관련한 모든 내용을 찾아보았다.
@Transactional 어노테이션이 모든 메서드에 적용되었을 때 쿠키가 제대로 저장되지 않는 문제는 발생하지 않습니다. @Transactional은 주로 데이터베이스 작업에 영향을 미치며, HTTP 응답과 같은 I/O 작업에는 직접적인 영향을 주지 않습니다. 하지만 특정 경우에 다음과 같은 이유로 문제를 유발할 가능성이 있습니다:
1. @Transactional의 기본 동작
- @Transactional은 기본적으로 RuntimeException이 발생하면 트랜잭션을 롤백한다.
- 만약 setCartCookie나 clearCartCookie에서 예외가 발생하면 트랜잭션 롤백으로 인해 메서드의 결과가 무효화 될 가능성이 있다.
2. 비즈니스 로직에 트랜잭션이 필요하지 않은 경우
setCartCookie와 clearCartCookie 메서드는 데이터베이스 작업이 없으므로 트랜잭션이 필요하지 않는다는 사실. 트랜잭션 관리 오버헤드는 발생하지 않겠지만, 의도치 않은 예외가 발생할 경우 메서드 동작에 영향을 미칠 수 있다.
3. 쿠키 저장은 트랜잭션의 대상이 아님
쿠키 설정(response.addCookie)은 HTTP 응답에 포함되는 작업으로, 데이터베이스 트랜잭션과는 무관하다. 그러나 메서드에서 트랜잭션 롤백이 발생하거나 메서드의 실행 흐름이 조기 종료되면 쿠키 설정이 실행되지 않을 수 있다.
- @Transactional 제거
데이터베이스 작업이 없는 쿠키 메서드 부분의 @Transactional을 없앴다.
public void setCartCookie(HttpServletResponse response, CartDto cart) {
Cookie cookie = new Cookie("cart", CartCookieEncoder.encode(cart));
cookie.setHttpOnly(true);
cookie.setMaxAge(24 * 60 * 60); // 1일
response.addCookie(cookie);
}
public void clearCartCookie(HttpServletResponse response) {
Cookie cookie = new Cookie("cart", null);
cookie.setMaxAge(0);
response.addCookie(cookie);
}
- 트랜잭션 범위를 축소
트랜잭션이 필요한 메서드(데이터베이스 작업 포함)에만 @Transactional을 유지한다.
- 예외 로깅 추가
쿠키 관련 작업에서 예외가 발생했는지 확인하기 위해 로깅을 추가한다.
public void setCartCookie(HttpServletResponse response, CartDto cart) {
try {
Cookie cookie = new Cookie("cart", CartCookieEncoder.encode(cart));
cookie.setHttpOnly(true);
cookie.setMaxAge(24 * 60 * 60); // 1일
response.addCookie(cookie);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to set cart cookie", e);
}
}
- HTTP 응답에서 쿠키 확인 Postman이나 브라우저 개발자 도구를 사용해 Set-Cookie 헤더를 확인
- 쿠키가 제대로 생성되지 않았다면 서버 측 로직에 문제가 있는지 확인한다.
- 쿠키가 생성되었지만 클라이언트에서 저장되지 않는다면, HttpOnly, SameSite, Domain 등의 설정을 점검하기
- 서비스 호출 흐름 점검
setCartCookie가 호출되지 않는 경우를 포함하여 점검하기 위해
서비스 계층의 메서드 호출 순서를 확인하고, 트랜잭션 경계를 다시 검토.
기존의 테스트 방식은 Intellij 내부의 API.http 파일을 만들어서 호출하는 방식으로 테스트를 진행하였으나,
쿠키 응답이 잘 되었는지 확인하기 위해서 Postman 에서 Set-Cookie 헤더를 확인하는 방식으로 테스트를 진행하였다.
장바구니의 데이터를 DB에 저장하는 것이 아니라 쿠키에 저장하는 것이기 때문에 위와 같은 방법으로 다시 테스트 하였다.
API를 호출하여 쿠키를 생성할 때 반드시 request 요청 보내는 값을 올바르게 입력해주어야한다 ! ! !
TIL 12월 7일
'TIL (ToDay I LearNEd) > K P T (keeP, pRoBlem, Try) & 트러블슈팅' 카테고리의 다른 글
[Spring_JPA] N+1 문제 해결 방법 (1) | 2024.12.19 |
---|---|
아웃소싱 프로젝트 KPT (0) | 2024.12.08 |
은행 환전 개인과제 트러블 슈팅_TIL (1) | 2024.11.29 |
뉴스피드 프로젝트_트러블 슈팅.TIL (0) | 2024.11.21 |
순환 참조란? Circular Dependency (일정 관리 앱 서버 DEVELOP 트러블 슈팅) (1) | 2024.11.15 |