BindProject
[Backend] 유저 프로필 모듈 구현5편: 응답 DTO 최적화와 Kafka 연동 흐름 정리
dding-shark
2025. 6. 23. 21:41
728x90
유저 프로필 기술 블로그 5편: 응답 DTO 최적화와 Kafka 연동 흐름 정리
1. UserProfileResponse DTO란?
유저의 공개 프로필 정보를 클라이언트에게 전달할 때 사용하는 응답 객체이다.
@Getter
@Builder
public class UserProfileResponse {
private Long userId;
private String nickname;
private String profileImageUrl;
private String introduction;
private String gender;
private String location;
private List<String> genres;
private List<String> interests;
}
- 장르 및 관심사는 별도 테이블에 저장되므로 join이 필요
- API 응답에서는 단일 객체로 묶어서 반환해야 가독성 좋고, 클라이언트도 처리하기 쉬움
2. DSL 매핑 최적화
기존에는 UserProfile 엔티티 자체를 반환하거나, 서비스단에서 DTO 변환을 수동으로 했음.
성능 이슈와 재사용성을 고려해 QueryDSL에서 DTO 직접 매핑으로 개선.
방식 1: Fetch Join + 변환
List<UserProfile> results = queryFactory
.selectFrom(profile)
.leftJoin(profile.userGenres, userGenre).fetchJoin()
.leftJoin(profile.userInterests, userInterest).fetchJoin()
.where(조건)
.fetch();
return results.stream()
.map(UserProfileResponse::fromEntity)
.toList();
방식 2: DTO 직접 매핑 (더 선호되는 방식)
queryFactory
.select(Projections.constructor(
UserProfileResponse.class,
profile.userId,
profile.nickname,
profile.profileImageUrl,
profile.introduction,
profile.gender.stringValue(),
profile.location.stringValue(),
JPAExpressions.select(userGenre.genre.stringValue())
.from(userGenre)
.where(userGenre.userProfile.userId.eq(profile.userId)),
JPAExpressions.select(userInterest.interest.stringValue())
.from(userInterest)
.where(userInterest.userProfile.userId.eq(profile.userId))
))
.from(profile)
.where(조건)
.fetch();
성능 비교 결과: DTO 직접 매핑 방식은 JSON 직렬화 시점에도 유리하고 컨트롤러/서비스 계층에서 로직이 간결해짐.
3. Kafka 연동 흐름
유저가 회원가입할 때 UserCreatedEvent가 Kafka로 발행됨. 유저 프로필 모듈에서는 이를 수신하여 기본 프로필을 자동 생성.
Kafka 이벤트 구조 예시
@Getter
@Builder
public class UserCreatedEvent {
private Long userId;
private String email;
private String profileImageUrl;
}
수신 Consumer
@KafkaListener(topics = "user.created", groupId = "profile-group")
public void handleUserCreated(String message) {
UserCreatedEvent event = objectMapper.readValue(message, UserCreatedEvent.class);
userProfileService.createProfileFromEvent(event);
}
프로필 생성 로직
@Transactional
public void createProfileFromEvent(UserCreatedEvent event) {
if (userProfileRepository.existsById(event.getUserId())) return;
UserProfile profile = UserProfile.builder()
.userId(event.getUserId())
.nickname("user_" + UUID.randomUUID().toString().substring(0, 6))
.email(event.getEmail())
.profileImageUrl(event.getProfileImageUrl())
.createdAt(LocalDateTime.now())
.build();
userProfileRepository.save(profile);
}
4. 전체 서비스 흐름도
[Auth 서비스]
↓ (UserCreatedEvent)
[Kafka - user.created 토픽]
↓ (Consumer 수신)
[UserProfile 모듈 - UserProfileService.createProfileFromEvent()]
↓
[UserProfile + 기본 정보 저장]
↓
[검색 API 응답: UserProfileResponse DTO 반환]5. 마무리 및 회고
이번 편에서는 다음을 정리했다:
- 복합 구조 DTO 응답 최적화 전략
- QueryDSL로 직접 DTO 매핑하여 N+1 방지
- Kafka 이벤트 처리 기반 자동 프로필 생성
- 전체 서비스 흐름에서의 Kafka 역할
728x90