Skip to content
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

Merged
merged 17 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import space.space_spring.exception.CustomException;
import space.space_spring.response.BaseResponse;
import space.space_spring.service.LiveKitService;
import space.space_spring.service.VoiceRoomParticipantService;
import space.space_spring.service.VoiceRoomService;
import space.space_spring.util.space.SpaceUtils;
import space.space_spring.util.user.UserUtils;
Expand All @@ -38,6 +39,7 @@ public class VoiceRoomController {
private final UserSpaceDao userSpaceDao;
private final UserUtils userUtils;
private final SpaceUtils spaceUtils;
private final VoiceRoomParticipantService voiceRoomParticipantService;

//VoiceRoom 생성/수정
@PostMapping("")
Expand Down Expand Up @@ -131,10 +133,11 @@ public BaseResponse<GetParticipantList.Response> getParticipants(
//해당 voiceRoom이 해당 space에 속한것이 맞는지 확인
validateVoiceRoomInSpace(spaceId,roomId);

List<GetParticipantList.ParticipantInfo> participantInfoList = voiceRoomService.getParticipantInfoListById(roomId);
//List<GetParticipantList.ParticipantInfo> participantInfoList = voiceRoomService.getParticipantInfoListById(roomId);
List<GetParticipantList.ParticipantInfo> participantInfoList = voiceRoomParticipantService.getParticipantInfoListById(roomId);

return new BaseResponse<GetParticipantList.Response>(new GetParticipantList.Response(participantInfoList));
}

@PatchMapping("")
public BaseResponse<String> updateVoiceRoom(
@PathVariable("spaceId") @NotNull long spaceId,
Expand Down Expand Up @@ -202,7 +205,8 @@ private boolean validateVoiceRoomNameExist(String voiceRoomName){
return true;
}
private boolean validateVoiceRoomInSpace(long spaceId,long voiceRoomId){
if(! (voiceRoomRepository.findById(voiceRoomId).getSpace().getSpaceId().equals(spaceId))){
if(! (voiceRoomRepository.findById(voiceRoomId).orElseThrow(()->new CustomException(VOICEROOM_NOT_EXIST))
.getSpace().getSpaceId().equals(spaceId))){
throw new CustomException(VOICEROOM_NOT_IN_SPACE);
}
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import space.space_spring.entity.VoiceRoom;

import java.util.List;
import java.util.Optional;

@Repository
public interface VoiceRoomRepository extends JpaRepository<VoiceRoom,Long> {

Expand All @@ -24,7 +26,7 @@ public interface VoiceRoomRepository extends JpaRepository<VoiceRoom,Long> {
@Query("SELECT CASE WHEN COUNT(v) > 0 THEN true ELSE false END FROM VoiceRoom v WHERE v.name = :voiceRoomName AND v.status = 'ACTIVE'")
boolean existsByName(@Param("voiceRoomName") String voiceRoomName);
@Query("SELECT v FROM VoiceRoom v WHERE v.voiceRoomId = :id AND v.status = 'ACTIVE'")
VoiceRoom findById(@Param("id") long Id);
Optional<VoiceRoom> findById(@Param("id") long Id);

@Query("SELECT v FROM VoiceRoom v WHERE v.space.id = :spaceId AND v.status = 'ACTIVE'")
List<VoiceRoom> findActiveVoiceRoomsBySpaceId(@Param("spaceId") Long spaceId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static class Response{
@Setter
public static class ParticipantInfo{
private String name;
private Long userId;
private Long id;
private Long userSpaceId;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 필드 네이밍을 userId 에서 id 로 변경하신 특별한 이유가 있을까요??
프로젝트 전체 코드들에서 userId 로 통일된 느낌이 있는데, 이 필드의 네이밍을 수정하신 이유가 궁금합니다!

Copy link
Member

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

Copy link
Collaborator Author

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는 값은 같고, 의미적으로 분리해놓은 상태입니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 ParticipantInfo 자체의 고유한 id 라는 말씀이시군요! 이해했습니다

Copy link
Collaborator

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

저도 제가 예전에 작성한 코드보면서 느낀건데, 굳이 Entity 내부의 id 필드명이 userId, spaceId 와 같이 엔티티의 이름이 한번 더 중복해서 들어가 있는게 불필요해 보이더라고요. 레퍼런스 참고해서 이 부분도 수정해보겠습니다!

private boolean isMute;
private String profileImage;
Expand All @@ -39,7 +39,7 @@ public static ParticipantInfo convertParticipantDto(ParticipantDto participantDt
.name(participantDto.getName())
.isMute(participantDto.isMicMute())
.profileImage(participantDto.getProfileImage())
.userId(participantDto.getId())
.id(participantDto.getId())
.userSpaceId(participantDto.getUserSpaceId())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public static VoiceRoomInfo convertRoomDto(RoomDto roomDto){
.order(roomDto.getOrder())
.metadate(roomDto.getMetadata())
.participantInfoList(
GetParticipantList.ParticipantInfo.convertParticipantDtoList(roomDto.getParticipantDTOList())
GetParticipantList.ParticipantInfo.convertParticipantDtoList(roomDto.getParticipantListDto().getParticipantDtoList())
)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ public void setProfileImage(String imageUrl){

public static ParticipantDto convertParticipant(LivekitModels.ParticipantInfo participantInfo){
if(participantInfo==null){return null;}
Long id = Long.valueOf(participantInfo.getIdentity());
Long userSpaceId = id;
return ParticipantDto.builder()
.id(Long.valueOf(participantInfo.getIdentity()))
.id(id)
.userSpaceId(userSpaceId)
.name(participantInfo.getName())
.isMicMute(checkMicMute(participantInfo.getTracksList()))

Expand Down
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 {


final private List<ParticipantDto> participantDtoList;

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;
}
}
35 changes: 23 additions & 12 deletions src/main/java/space/space_spring/dto/VoiceRoom/RoomDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,34 @@ public class RoomDto {
private int numParticipants;
private long startTime; //time Stamp
private long createdAt;
private List<ParticipantDto> participantDTOList;
//private List<ParticipantDto> participantDTOList;
private ParticipantListDto participantListDto;
private int order;
private String sid;
private String metadata;


public void setParticipantDTOListByInfo(List<LivekitModels.ParticipantInfo> participantInfoList){
if(participantInfoList==null||participantInfoList.isEmpty()){return;}
this.participantDTOList = participantInfoList.stream()
.map(ParticipantDto::convertParticipant)
.collect(Collectors.toList());
}
// public void setParticipantDTOListByInfo(List<LivekitModels.ParticipantInfo> participantInfoList){
// if(participantInfoList==null||participantInfoList.isEmpty()){return;}
// this.participantDTOList = participantInfoList.stream()
// .map(ParticipantDto::convertParticipant)
// .collect(Collectors.toList());
// }
public RoomDto setParticipantDTOList(List<ParticipantDto> participantDtoList){
this.participantDTOList = participantDtoList;
if(participantDtoList==null){
this.participantListDto = ParticipantListDto.empty();
}
this.participantListDto = ParticipantListDto.from(participantDtoList);
return this;
}

public RoomDto setParticipantDTOList(ParticipantListDto participants){
if(participants==null){
this.participantListDto = ParticipantListDto.empty();
}
this.participantListDto = participants;
return this;
}
public static RoomDto convertRoom(LivekitModels.Room room){
if(room==null){return null;}
return RoomDto.builder()
Expand All @@ -45,7 +56,7 @@ public static RoomDto convertRoom(LivekitModels.Room room){
.startTime(room.getCreationTime())
.sid(room.getSid())
.metadata(room.getMetadata())
.participantDTOList(null)
.participantListDto(ParticipantListDto.nullList())
.build();
}

Expand All @@ -67,7 +78,7 @@ public static RoomDto convertRoom(VoiceRoom voiceRoom){
.sid(null)
.metadata(null)
//.startTime()
.participantDTOList(null)
.participantListDto(ParticipantListDto.nullList())
.build();
}

Expand Down Expand Up @@ -128,6 +139,6 @@ public String toString() {
}
}



public ParticipantListDto getParticipantListDto(){return participantListDto;}
//public List<ParticipantDto> getParticipantDtoList(){return participantListDto.getParticipantDtoList();}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

public class VoiceRoomEntityList {

private List<VoiceRoom> voiceRoomEntityList;
final private List<VoiceRoom> voiceRoomEntityList;

public void VoiceRoomList(List<VoiceRoom> voiceRoomEntityList) {
private VoiceRoomEntityList(List<VoiceRoom> voiceRoomEntityList) {
this.voiceRoomEntityList = voiceRoomEntityList;
}

public VoiceRoomEntityList from(List<VoiceRoom> voiceRoomEntityList){
return new VoiceRoomEntityList(voiceRoomEntityList);
}
public List<RoomDto> convertRoomDto(){
if(this.voiceRoomEntityList==null||this.voiceRoomEntityList.isEmpty()){return null;}
return this.voiceRoomEntityList.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import java.util.stream.Stream;

public class VoiceRoomListDto {
private List<RoomDto> roomDtoList;
final private List<RoomDto> roomDtoList;

public VoiceRoomListDto(List<RoomDto> roomDtos){
this.roomDtoList=roomDtos;
Expand Down Expand Up @@ -65,5 +65,11 @@ public List<GetVoiceRoomList.VoiceRoomInfo> convertVoicRoomInfoList() {
return convertVoicRoomInfoList( null);
}

public void setParticipantListDto(Map<Long,ParticipantListDto> participantListDtoMap){

for(RoomDto roomDto : this.roomDtoList){
roomDto.setParticipantDTOList(participantListDtoMap.get(roomDto.getId()));
}
}

}
2 changes: 1 addition & 1 deletion src/main/java/space/space_spring/entity/VoiceRoom.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public RoomDto convertRoomDto(){
.sid(null)
.metadata(null)
//.startTime()
.participantDTOList(null)
.participantListDto(null)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
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.VoiceRoomRepository;
import space.space_spring.domain.space.model.entity.Space;
import space.space_spring.domain.user.repository.UserDao;
import space.space_spring.domain.userSpace.repository.UserSpaceDao;
import space.space_spring.dto.VoiceRoom.GetParticipantList;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.exception.CustomException;
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;

import static space.space_spring.response.status.BaseExceptionResponseStatus.VOICEROOM_NOT_EXIST;

@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).orElseThrow(()->new CustomException(VOICEROOM_NOT_EXIST)).getSpace();
//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 반환
}
}
}
Loading
Loading