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

[Chat] 채팅방에 초대할 때 사용할 코드 생성 API 구현 #149

Merged

Conversation

minisundev
Copy link
Member

#️⃣연관된 이슈

#87

📝작업 내용

채팅방에 초대할 때 사용할 코드 생성

💬리뷰 요구사항

채팅방 참가자가 채팅방에 초대하기 위해서 참여코드를 발급받을때에 쓰는
key: 채팅방id,유저id
value: 참여 코드
인 UserChatRoomInvitationRepository를 하나 만들고

채팅방 비참여자가 채팅방에 참여할때 쓸 (아직 api 구현이 안 되어서 사용하지는 않고 있습니다)
key: 참여 코드
value: 채팅방id 인 InvitationChatRoomRepository를 하나 만들었습니다

그런데 UserChatRoomInvitationRepository에서 참여 코드를 새로 생성할때
참여코드 -> 채팅방 id도 InvitationChatRoomRepository에 저장해야해서
UserChatRoomInvitationRepository에서 InvitationChatRoomRepository를 주입받아

fun setInvitation(
    key: String,
    chatRoomId: String,
  ): String {
    val invitationCode = generateCode()
    val ops: ValueOperations<String, String> = redisTemplate.opsForValue()
    ops.set(key, invitationCode, propertyConfig.getExpiration())
    invitationChatRoomRepository.setInvitationCode(invitationCode, chatRoomId)//-> 이 부분
    return invitationCode
  }

이런식으로 사용하게 했는데 Repository끼리 저런식으로 결합되는게 좋은지 의문입니다
Service단에서 처리하려면 Return되는 DTO에 필드 하나를 추가해야할 것 같아서 저렇게 했는데
필드를 하나 추가하고 결합도를 낮추는게 더 나을까요?

@minisundev minisundev added enhancement 추가 기능 API 상세 api 문서 Chat 채팅 관련 기능 labels Jun 3, 2024
@minisundev minisundev added this to the MSA 채팅 서비스 개발 milestone Jun 3, 2024
@minisundev minisundev requested a review from a team June 3, 2024 17:33
@minisundev minisundev self-assigned this Jun 3, 2024
@yudonggeun
Copy link
Contributor

UserChatRoomInvitationRepository, InvitationChatRoomRepository 하나만 있어도 기능 구현이 가능하지 않을까요?

@minisundev
Copy link
Member Author

minisundev commented Jun 4, 2024

UserChatRoomInvitationRepository, InvitationChatRoomRepository 하나만 있어도 기능 구현이 가능하지 않을까요?

그렇긴 한데 채팅방에 링크로 참여하는 경우에는 참여코드 -> 채팅방 id 이렇게 읽어야 하잖아요

찾아보니까 Redis는 Value로 검색하는 경우에 시간복잡도가 굉장히 증가한다고 하더라고요 인덱스 설정을 잘 하면 된다고 하긴 하던데 인덱스 설정하는것보다 그냥 key랑 value를 반대로 해서 저장하면 성능이 좋다고 생각했어요

어차피 key랑 value를 반대로 해서 두번 저장할거면 다른 레파지토리에 저장하는게 나아보여서 분리했어요 (같은 레파지토리에다 저장하면 채팅방id:유저id로 참여코드 찾을때는 참여코드->채팅방id는 볼 필요도 없는데 같이 보여서 성능이 안 좋아질 거 같아서요)

chat/compose.yml Outdated
Comment on lines 6 to 19
- "27018:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: 58155815
MONGO_INITDB_DATABASE: mongodb No newline at end of file
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: testpassword1234
MONGO_INITDB_DATABASE: mongodb

redis:
image: redis:alpine
container_name: redis_link
ports:
- "6379:6379"
environment:
- REDIS_PASSWORD = "testpassword1234"
command: [ "redis-server","--requirepass","${REDIS_PASSWORD}" ]
Copy link
Contributor

Choose a reason for hiding this comment

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

저번에 한번 본 코드처럼 보이는데요? 중복된 코드가 많으면 코드 리뷰가 힘들어요.

Copy link
Member Author

Choose a reason for hiding this comment

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

저번 pr에 올렸는데 closed되어서 다시 올렸어요

fun getInvitation(
userId: String,
chatRoomId: String,
): InvitationResponse {
Copy link
Contributor

Choose a reason for hiding this comment

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

repository에서 InvitationResponse를 반환하는 로직은 어울리지 않은 것 같아요. response는 http 와 관련된 용어라서 오해의 소지가 있어보이는데요? InvitationResponse의 다른 이름은 없을까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

좋은 지적이네요 더 고민해볼게요ㅕ!

page:
size: 100
chatroom:
expiration: 1d
baseurl: "http://localhost:8081/"
Copy link
Contributor

Choose a reason for hiding this comment

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

baseurl을 사용한 곳이 있나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

지금은 사용하지 않고 있어요 빼겠습니다!


@Configuration
@ConfigurationProperties(prefix = "chatroom")
class PropertyConfig {
Copy link
Contributor

Choose a reason for hiding this comment

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

Config 보다는 Property가 더 어울리는 이름인 것 같아요. ChatroomProperty

Copy link
Member Author

Choose a reason for hiding this comment

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

일단 설정파일이라 그렇게 했는데 의미를 생각하면 그게 나아보이네요~!

@Component
class InvitationChatRoomRepository(
private val redisTemplate: RedisTemplate<String, String>,
private val propertyConfig: PropertyConfig,
Copy link
Contributor

Choose a reason for hiding this comment

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

init을 통해서 property의 값으로 미리 초기화 하도록 구성하면 더 좋을 것 같아요.

class InvitationChatRoomRepository(
..
){
  val ttl = propertyConfig.getExpiration()
}

Copy link
Member Author

Choose a reason for hiding this comment

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

static이 아니라서 주입받아야 할 것 같고 뭔가 변수가 추가되었을때도 한번만 주입받기 위해서 저렇게 사용했습니다!

@@ -12,4 +12,7 @@ enum class ErrorCode(val httpStatus: Int, val message: String) {

// 404
CHATROOM_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 id로 chatroom을 찾을 수 없습니다"),

// 500
INVITATION_LINK_SAVE_FAILURE(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Invitation Code가 저장되지 않았습니다"),
Copy link
Contributor

Choose a reason for hiding this comment

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

client로 노출할 메시지라서 에러 메시지가 너무 상세하지 않은가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

좋은 지적이네요 다시 생각해볼게요ㅕ

@yudonggeun
Copy link
Contributor

yudonggeun commented Jun 4, 2024

그렇긴 한데 채팅방에 링크로 참여하는 경우에는 참여코드 -> 채팅방 id 이렇게 읽어야 하잖아요

찾아보니까 Redis는 Value로 검색하는 경우에 시간복잡도가 굉장히 증가한다고 하더라고요 인덱스 설정을 잘 하면 된다고 하긴 하던데 인덱스 설정하는것보다 그냥 key랑 value를 반대로 해서 저장하면 성능이 좋다고 생각했어요

어차피 key랑 value를 반대로 해서 두번 저장할거면 다른 레파지토리에 저장하는게 나아보여서 분리했어요 (같은 레파지토리에다 저장하면 채팅방id:유저id로 참여코드 찾을때는 참여코드->채팅방id는 볼 필요도 없는데 같이 보여서 성능이 안 좋아질 거 같아서요)

조회할 때, 초대받은 유저가 코드로 검색하지않으면 되지 않을까요?
초대를 받을 때 채팅방id와 (초대받은)유저 id 조합을 그대로 사용하면 가능할 것 같아서요.

@minisundev
Copy link
Member Author

그렇긴 한데 채팅방에 링크로 참여하는 경우에는 참여코드 -> 채팅방 id 이렇게 읽어야 하잖아요
찾아보니까 Redis는 Value로 검색하는 경우에 시간복잡도가 굉장히 증가한다고 하더라고요 인덱스 설정을 잘 하면 된다고 하긴 하던데 인덱스 설정하는것보다 그냥 key랑 value를 반대로 해서 저장하면 성능이 좋다고 생각했어요
어차피 key랑 value를 반대로 해서 두번 저장할거면 다른 레파지토리에 저장하는게 나아보여서 분리했어요 (같은 레파지토리에다 저장하면 채팅방id:유저id로 참여코드 찾을때는 참여코드->채팅방id는 볼 필요도 없는데 같이 보여서 성능이 안 좋아질 거 같아서요)

조회할 때, 초대받은 유저가 코드로 검색하지않으면 되지 않을까요? 초대를 받을 때 채팅방id와 (초대받은)유저 id 조합을 그대로 사용하면 가능할 것 같아서요.

이 방향으로 처음에 개발을 시작했었는데
채팅방id,초대자 user id마다 참여코드를 생성하지만
채팅방 참여 코드를 참여자가 발급받으면 1시간 후에 만료되어야 하는 조건이 있어서 그렇게는 조건을 만족시키면서 구현할 수 없어요

만약 채팅방id:유저id의 정보로만 참여하게 되면
지금 활성화된 참여코드(채팅방id:유저id)가 있다면
한 달(무작위한 1시간보다 긴 시간) 전에 발급된 채팅방 id:유저id의 정보가 포함된 참여코드로도 채팅방에 참여하는게 가능해질 것 같아서 저렇게 분리했습니다!

Copy link
Contributor

@yudonggeun yudonggeun left a comment

Choose a reason for hiding this comment

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

초대 코드를 생성하고 조회에 문제가 없는지 단위 테스트를 작성하면 더 좋을 것 같아요! 다음에는 테스트를 통해서 어플리케이션을 신뢰적으로 만들어보죠

Comment on lines 16 to 25
fun getInvitationCode(
userId: String,
chatRoomId: String,
): InvitationResponse {
): String {
val key = generateKey(userId, chatRoomId)
var value = redisTemplate.opsForValue().get(key)
if (value == null) {
value = setInvitation(key, chatRoomId)
}

val expiration = redisTemplate.getExpire(key)

return InvitationResponse(value, LocalDateTime.now().plusSeconds(expiration).toString())
return generateCode(key, value)
Copy link
Contributor

Choose a reason for hiding this comment

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

getInviationCode이지만 코드를 생성하고 저장하는 역할을 같이 구현되어있는 것 같아요. 겳합도를 낮추는 방향으로 개선하면 좋을 것 같아요.

Copy link
Member Author

Choose a reason for hiding this comment

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

네 좋습니다

key: String,
value: String,
): String {
val combine = "$key,$value"
Copy link
Contributor

Choose a reason for hiding this comment

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

combine 동사이기도 하고 무슨 의미인지 직관적으로 파악이 힘든 것 같아요!, key, value의 의미가 무엇인지 파악이 힘들어서 주석으로 무슨 의미인지를 좀 더 설명을 해주면 좋을 것 같아요

Copy link
Member Author

Choose a reason for hiding this comment

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

네~! 변수명이 가독성이 떨어지는거 같긴 하네요

import java.util.*

@Component
class UserChatRoomInvitationRepository(
class InvitationRepository(
Copy link
Contributor

Choose a reason for hiding this comment

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

초대 저장소가 하는 역할이 무엇인가요? 서비스 layer에 더 어울리는 역할을 수행하는 것 같은 느낌?

Copy link
Contributor

Choose a reason for hiding this comment

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

재사용할 수 있는 컴포넌트인가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

findbyId 나 save등만 있는게 아니라서 재사용이 불가능할 것 같아요
chatService랑은 좀 동떨어진 일을 하는 것 같아서 저 repository로 초대/참여 관련 로직을 분리했었는데 생각해보면 다른 Service로 분리하는게 더 나아보이네요

private val invitationRepository: InvitationRepository,
private val chatRoomProperty: ChatRoomProperty,
) {
fun getOrCreateInvitation(
Copy link
Contributor

Choose a reason for hiding this comment

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

get과 create 두가지 책임을 담당하는 메서드라서 분리하는 것이 좋다고 생각합니다.

@minisundev minisundev merged commit 712ea7b into kSideProject:dev Jun 10, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API 상세 api 문서 Chat 채팅 관련 기능 enhancement 추가 기능
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

2 participants