-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/#163/participant service #168
Changes from 13 commits
e656edc
672f8e8
220227c
a4d3d44
f87f52e
138efdb
e25dd7e
eb6a1d7
f092595
e82c966
b920fed
52f4414
794090f
ef3f87a
df42b84
7004c28
20d23b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package space.space_spring.dto.VoiceRoom; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.function.Function; | ||
import java.util.stream.Collectors; | ||
|
||
public class ParticipantListDto { | ||
|
||
|
||
private List<ParticipantDto> participantDtoList; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. final을 적용하시지 않은 이유가 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 불변성이 없어서 final을 붙이지 않았습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 만약 불변성까지 보장하려면 생성할 때 unmodifiable로 넣어주면 될 것 같습니다 |
||
|
||
private ParticipantListDto(List<ParticipantDto> participantDtoList){ | ||
this.participantDtoList=participantDtoList; | ||
} | ||
|
||
public static ParticipantListDto from(List<ParticipantDto> participantDtoList ){ | ||
return new ParticipantListDto(participantDtoList); | ||
} | ||
public static ParticipantListDto empty(){ | ||
return new ParticipantListDto(Collections.emptyList()); | ||
} | ||
|
||
public static ParticipantListDto nullList(){ | ||
return new ParticipantListDto(null); | ||
} | ||
|
||
public void setProfileImage(Function<Long, String> profileFinder){ | ||
this.participantDtoList.forEach(participantDto -> { | ||
String profileImage = profileFinder.apply(participantDto.getUserSpaceId()); | ||
participantDto.setProfileImage(profileImage); | ||
}); | ||
} | ||
|
||
|
||
|
||
//Todo 생성/변환 책임분리 필요 | ||
public List<GetParticipantList.ParticipantInfo> convertParticipantDtoList(){ | ||
if(this.participantDtoList==null){System.out.print("\n[DEBUG] participant List is NULL\n"); return null;} | ||
if(this.participantDtoList.isEmpty()){System.out.print("\n[DEBUG] participant List is Empty\n"); return Collections.emptyList();} | ||
return this.participantDtoList.stream() | ||
.map(GetParticipantList.ParticipantInfo::convertParticipantDto) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
public List<ParticipantDto> getParticipantDtoList() { | ||
return participantDtoList; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package space.space_spring.service; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.core.task.TaskExecutor; | ||
import org.springframework.stereotype.Service; | ||
import space.space_spring.dao.UserSpaceDao; | ||
import space.space_spring.dao.VoiceRoomRepository; | ||
import space.space_spring.domain.user.repository.UserDao; | ||
import space.space_spring.dto.VoiceRoom.GetParticipantList; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LGTM |
||
import space.space_spring.dto.VoiceRoom.ParticipantDto; | ||
import space.space_spring.dto.VoiceRoom.ParticipantListDto; | ||
import space.space_spring.dto.VoiceRoom.RoomDto; | ||
import space.space_spring.entity.Space; | ||
import space.space_spring.entity.User; | ||
import space.space_spring.entity.UserSpace; | ||
import space.space_spring.util.LiveKitUtils; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.stream.Collectors; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Slf4j | ||
public class VoiceRoomParticipantService { | ||
final private UserSpaceDao userSpaceDao; | ||
final private UserDao userDao; | ||
final private VoiceRoomRepository voiceRoomRepository; | ||
final private LiveKitUtils liveKitUtils; | ||
private final TaskExecutor taskExecutor; | ||
public List<GetParticipantList.ParticipantInfo> getParticipantInfoListById(long voiceRoomId){ | ||
return getParticipantDtoListById(voiceRoomId).convertParticipantDtoList(); | ||
} | ||
private ParticipantListDto getParticipantDtoListById(long voiceRoomId){ | ||
Space space = voiceRoomRepository.findById(voiceRoomId).getSpace(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. voiceRoomRepository.findById() 메서드가 Optional 객체를 return 하고, 이 결과값이 null 인지 검증하는 로직이 추가되면 좋을거 같습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
이런 식으로 한 줄에 null 처리도 가능합니당 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 내용 수정해서 push 했습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 앗 추가로 ((이미 알고 계실 수도 있겠지만)) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저번에 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그럼 null인 경우 자체가 없어도 Optional로 통일하는 건가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오류나 예외적인 상황 (예를 들어 글을 읽는 도중 글이 삭제됨. Post Entity 검색 불가) 발생하면, null-pointer error가 발생할 수 있다고 생각합니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
아 제가 말씀드린 건 이 케이스였습니다! 단일 엔티티를 반환하는 케이스는 Optional로 통일하는 것이 좋은데 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
오 이부분은 공통적으로 생각해봐야할거같은데요? 레포지토리의 반환값이 list 인 메서드를 서비스단이 사용할 때는, 해당 이슈 생각해서 검증하면 좋을 듯 합니다 |
||
//Todo 다른 네이밍 고려 | ||
List<ParticipantDto> participantDtos = liveKitUtils.getParticipantInfo(String.valueOf(voiceRoomId)); | ||
if(participantDtos==null || participantDtos.isEmpty()){ | ||
participantDtos = Collections.emptyList(); | ||
} | ||
|
||
ParticipantListDto participantListDto = ParticipantListDto.from(participantDtos); | ||
participantListDto.setProfileImage(this::findProfileImageByUserSpaceId); | ||
return participantListDto; | ||
} | ||
private String findProfileImageByUserSpaceId(Long userSpaceId){ | ||
return userSpaceDao.findProfileImageById(userSpaceId).orElse(""); | ||
} | ||
|
||
|
||
|
||
|
||
//1. 이 함수를 VoiceRoomListDto의 parameter로 넘긴다 | ||
//장점. 동기 처리의 책임을 service가 질 수 있음 | ||
//단점. 굳이 이렇게까지 코드를 꼬야야하나 싶을 수 있음. 그냥 getRoomList해서 RoomList를 밖으로 빼는게 나을지도 | ||
|
||
//2. 이 함수를 VoiceRoomListDto 내부로 이전 | ||
//장점. findProfileImageByUserSpaceId만 function parameter로 넘기면 됨 -> 책임과 구조가 더 명확하게 보인다고 | ||
//단점. 비동기 병렬처리의 책임을 VoiceRoomListDto가 가져야함. | ||
public void setParticipant(List<RoomDto> roomDtoList){ | ||
List<CompletableFuture<Void>> roomDtoFutureList = roomDtoList.stream() | ||
.map(r->CompletableFuture.runAsync(()->r.setParticipantDTOList(getParticipantDtoListById(r.getId())),taskExecutor) | ||
//.exceptionally(ex->{throws ex;}) | ||
) | ||
.collect(Collectors.toList()); | ||
|
||
|
||
// 모든 Future의 완료를 기다림 | ||
CompletableFuture<Void> allOf = CompletableFuture.allOf( | ||
roomDtoFutureList.toArray(new CompletableFuture[0])); | ||
|
||
// 결과 수집 및 출력 | ||
allOf.join(); | ||
} | ||
public Map<Long,ParticipantListDto> getParticipantList(List<Long> roomIdList){ | ||
|
||
Map<Long,CompletableFuture<ParticipantListDto>> futureMap = roomIdList.stream() | ||
.collect(Collectors.toMap( | ||
roomId->roomId, | ||
roomId->CompletableFuture.supplyAsync( | ||
()-> getParticipantDtoListById(roomId), | ||
taskExecutor | ||
|
||
).exceptionally(throwable -> { | ||
log.error("failed to fetch and get participantList",throwable); | ||
return null;//empty ParticipantListDto | ||
}) | ||
)); | ||
try { | ||
// 모든 Future의 완료를 기다림 | ||
CompletableFuture.allOf( | ||
futureMap.values().toArray(new CompletableFuture[0])) | ||
.exceptionally(throwable -> { | ||
log.error("Error while waiting for all participant fetches to complete",throwable); | ||
return null; | ||
}) | ||
.join(); | ||
|
||
return futureMap.entrySet().stream() | ||
.collect(Collectors.toMap( | ||
entry -> entry.getKey(), | ||
entry->entry.getValue().getNow(ParticipantListDto.empty()) | ||
)); | ||
|
||
}catch (Exception e){ | ||
log.error("Critical error while processing participant fetches", e); | ||
return Collections.emptyMap(); // 심각한 오류 발생 시 빈 Map 반환 | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
혹시 필드 네이밍을 userId 에서 id 로 변경하신 특별한 이유가 있을까요??
프로젝트 전체 코드들에서 userId 로 통일된 느낌이 있는데, 이 필드의 네이밍을 수정하신 이유가 궁금합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
의도와 맞는진 모르겠지만 클린코드를 실천하려면 불필요한 중복은 피하는 게 좋다고 하니 변경한 네이밍이 더 적절할 것 같아요!
https://dining-developer.tistory.com/68
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
participant의 id는 LiveKit 에서 각 참가자를 구분하는 id입니다. userId와 userSpaceId는 저희 서비스에서 사용하는 Id입니다.
원래는 LiveKit에서 Id값을 userId 로 사용했습니다. 하지만 개발 과정에서 userId가 아닌 userSpaceId로 변경이 되었고, 이 과정에서 코드를 수정하는데 꽤 번거로움?헷갈림?이 발생했었습니다.
따라서 LiveKit id와 서비스 로직의 userSpaceId를 구분하여 코드를 작성해야겠다고 생각해 위와 같이 수정하였습니다.
현재 participant 내의 userSpaceId와 id는 값은 같고, 의미적으로 분리해놓은 상태입니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아하 ParticipantInfo 자체의 고유한 id 라는 말씀이시군요! 이해했습니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 제가 예전에 작성한 코드보면서 느낀건데, 굳이 Entity 내부의 id 필드명이 userId, spaceId 와 같이 엔티티의 이름이 한번 더 중복해서 들어가 있는게 불필요해 보이더라고요. 레퍼런스 참고해서 이 부분도 수정해보겠습니다!