배경
이번 과제는 NewsFeed 기능 개발이었는데, 사용자가 팔로잉하는 다른 유저들의 활동 알림과 게시물을 출력하는 로직을 구현하는 것이 목표였다. 구현 과정에서 Post 모듈과 NewsFeed 모듈 간의 데이터 교환에 있어서 응답 데이터가 null로 반환되는 문제에 부딪혔다.
문제 상황
NewsFeed 서비스는 사용자가 팔로잉하는 유저 목록을 Post 모듈에 전달하고, 해당 유저들의 게시글 목록을 받아 출력하는 구조로 설계했다. 아래는 NewsFeed 서비스의 핵심 로직이다.
NewsFeedService
WebClient를 사용하여 Post 모듈에 followingIds(팔로잉 유저 목록)를 전달하고, 게시물 목록을 받아온다.
@Transactional(readOnly = true)
public List<CreateNewsFeedDto> getNewsFeed(Long userId) {
validateUser(userId);
List<Long> followingIds = getFollowingIds(userId);
List<NotificationDto> notificationDtos = getNotifications(followingIds);
List<PostResponseDto> postDtos = getPosts(followingIds).block();
return List.of(new CreateNewsFeedDto(notificationDtos, postDtos));
}
// RestApi
private Mono<List<PostResponseDto>> getPosts(List<Long> followingIds) {
return webClient.post()
.uri("http://localhost:8082/api/internal/posts/follows")
.bodyValue(followingIds)
.retrieve()
.bodyToFlux(PostResponseDto.class)
.collectList();
}
PostToNewsFeedApiController
Post 모듈 내에서 게시물 데이터를 처리하고 응답한다.
@RestController
@RequestMapping("/api/internal/posts")
@RequiredArgsConstructor
public class NewsFeedRestApi {
private final PostService postService;
@PostMapping("/follows")
public List<PostResponseDto> getPostsByUserIds(@RequestBody List<Long> followingIds) {
return postService.getPostsByUserIds(followingIds);
}
}
PostApiService
요청 받은 유저 ID 목록에 해당하는 게시물을 찾아 응답 데이터 형태로 반환한다.
public List<PostResponseDto> getPostsByUserIds(List<Long> followingIds) {
List<Post> posts = postRepository.findByUserIdIn(followingIds);
return posts.stream()
.map(this::convertToPostResponseDto)
.collect(Collectors.toList());
}
private PostResponseDto convertToPostResponseDto(Post post) {
return new PostResponseDto(
post.getId(),
post.getContent(),
post.getUser().getName(),
post.getUser().getProfileImg(),
post.getUser().getId(),
post.getLikes().size(),
post.getComments().size(),
post.getCreatedAt());
}
문제 진단 및 해결 과정
초기 구현에서는 응답 데이터가 예상과 다르게 null을 반환했다. 이를 해결하기 위해 다음과 같은 노력을 기울였다.
- PostResponseDto 위치 변경: 공통 모듈로 이동하여 동일한 DTO를 참조하도록 조정.
- JsonProperty 어노테이션 적용: 데이터 매핑 문제를 해결하기 위해 시도.
- BaseTimeEntity에 Json 관련 어노테이션 추가: 날짜 시간 관련 처리를 위해 추가.
결국 문제의 핵심은 응답 구조의 차이이다. 응답 body를 자세히 분석한 결과, 필요한 데이터가 ApiResponse의 data 필드 내에 포함되어 있는 것을 볼 수 있다. 이에 따라 ApiResponse 클래스를 도입하여 응답 형태를 관리하고, 필요한 데이터만 추출하는 방식으로 로직을 수정했다.
최종 구현
ApiResponse
public class ApiResponse<T> {
private boolean success;
private int status;
private T data;
private LocalDateTime timeStamp;
}
NewsFeedService
private List<PostResponseDto> getPosts(List<Long> followingIds) {
WebClient webClient = WebClient.create("http://localhost:8082");
Mono<ApiResponse<List<PostResponseDto>>> apiResponseMono = webClient.post()
.uri("/api/internal/posts/follows")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(followingIds)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<ApiResponse<List<PostResponseDto>>>() {});
ApiResponse<List<PostResponseDto>> apiResponse = apiResponseMono.block(); // 블로킹 호출로 결과를 기다림
return apiResponse.getData();
}
해결하니까 너무 당연한 문제였던것..
고찰
이 경험을 통해 API 통신 시 응답 데이터 구조의 중요성을 다시 한번 깨달았다. 특히, 서로 다른 모듈 간의 데이터 교환에서는 응답 형태를 명확하게 정의하고 이를 기반으로 데이터를 처리해야 한다는 점을 배웠다. 또한, 문제 해결 과정에서 다양한 시도를 통해 문제를 접근하는 경험은 앞으로의 프로젝트에 큰 도움이 될 것이다.
'프로젝트 (Java) > 예약마켓' 카테고리의 다른 글
[프로젝트] 41. API Gateway의 추가 (0) | 2024.02.13 |
---|---|
[프로젝트] 40. Eureka Server 추가 (0) | 2024.02.13 |
[프로젝트] 38. WebClient 활용 Rest API 구현 (0) | 2024.02.02 |
[프로젝트] 37. MSA(MicroService Architecture) 도입 (0) | 2024.02.02 |
[프로젝트] 36. 트러블 슈팅 - Redis와 JWT 토큰 만료 시간 동기화 문제 (0) | 2024.01.31 |