-
Notifications
You must be signed in to change notification settings - Fork 5
[Chat] 채팅방에 userId로 초대하기 API 구현 #163
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
base: dev
Are you sure you want to change the base?
Changes from all commits
b80404b
e151f6a
57ca844
734f133
e806dc0
61c0001
53e549e
8e12a1a
7b8bed9
f3e85a3
4790069
bbcd4bc
ea2c279
31a5584
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,7 @@ | ||
| package kpring.chat.chatroom.dto | ||
|
|
||
| data class Lock( | ||
| val lockId: String, | ||
| val owner: String, | ||
| val acquired: Boolean, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package kpring.chat.chatroom.model | ||
|
|
||
| import org.springframework.data.annotation.Id | ||
| import org.springframework.data.mongodb.core.mapping.Document | ||
|
|
||
| @Document(collection = "distributedLocks") | ||
| data class DistributedLock( | ||
| @Id | ||
| val id: String, | ||
| val owner: String, | ||
| val expiresAt: Long, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package kpring.chat.chatroom.repository | ||
|
|
||
| import kpring.chat.chatroom.model.DistributedLock | ||
| import org.springframework.data.mongodb.repository.MongoRepository | ||
| import org.springframework.stereotype.Repository | ||
|
|
||
| @Repository | ||
| interface DistributedLockRepository : MongoRepository<DistributedLock, String> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,10 +6,12 @@ import kpring.chat.global.exception.ErrorCode | |
| import kpring.chat.global.exception.GlobalException | ||
| import kpring.core.chat.chatroom.dto.request.CreateChatRoomRequest | ||
| import org.springframework.stereotype.Service | ||
| import org.springframework.transaction.annotation.Transactional | ||
|
|
||
| @Service | ||
| class ChatRoomService( | ||
| private val chatRoomRepository: ChatRoomRepository, | ||
| private val lockService: DistributedLockService, | ||
| ) { | ||
| fun createChatRoom( | ||
| request: CreateChatRoomRequest, | ||
|
|
@@ -30,6 +32,31 @@ class ChatRoomService( | |
| chatRoomRepository.save(chatRoom) | ||
| } | ||
|
|
||
| @Transactional | ||
| fun inviteToChatRoomByUserIdWithLock( | ||
| userId: String, | ||
| inviterId: String, | ||
| chatRoomId: String, | ||
| ): Boolean { | ||
| val lock = lockService.getLock(chatRoomId) | ||
|
|
||
| inviteToChatRoomByUserId(userId, inviterId, chatRoomId) | ||
|
|
||
| lockService.releaseLock(lock.lockId, lock.owner) | ||
|
Comment on lines
+41
to
+45
Contributor
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. inviteToChatRoomByUserId에서 예외가 발생하면 락을 회수하지 못하는 상황이 발생할 것 같아요. |
||
| return true | ||
| } | ||
|
|
||
| fun inviteToChatRoomByUserId( | ||
| userId: String, | ||
| inviterId: String, | ||
| chatRoomId: String, | ||
| ) { | ||
| verifyChatRoomAccess(chatRoomId, inviterId) | ||
| val chatRoom: ChatRoom = getChatRoom(chatRoomId) | ||
| chatRoom.addUser(userId) | ||
| chatRoomRepository.save(chatRoom) | ||
| } | ||
|
|
||
| fun verifyChatRoomAccess( | ||
| chatRoomId: String, | ||
| userId: String, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package kpring.chat.chatroom.service | ||
|
|
||
| import kpring.chat.chatroom.dto.Lock | ||
| import kpring.chat.chatroom.model.DistributedLock | ||
| import kpring.chat.chatroom.repository.DistributedLockRepository | ||
| import kpring.chat.global.exception.ErrorCode | ||
| import kpring.chat.global.exception.GlobalException | ||
| import org.springframework.stereotype.Service | ||
| import java.util.* | ||
|
|
||
| @Service | ||
| class DistributedLockService( | ||
| private val lockRepository: DistributedLockRepository, | ||
| ) { | ||
| fun acquireLock( | ||
| lockId: String, | ||
| owner: String, | ||
| expireInMillis: Long, | ||
| ): Boolean { | ||
| val now = System.currentTimeMillis() | ||
| val expiresAt = now + expireInMillis | ||
|
|
||
| val optionalLock = lockRepository.findById(lockId) | ||
| return if (optionalLock.isPresent) { | ||
| val lock = optionalLock.get() | ||
| if (lock.expiresAt < now) { | ||
| lockRepository.save(DistributedLock(lockId, owner, expiresAt)) | ||
| true | ||
| } else { | ||
| false | ||
| } | ||
| } else { | ||
| lockRepository.save(DistributedLock(lockId, owner, expiresAt)) | ||
| true | ||
| } | ||
| } | ||
|
|
||
| fun releaseLock( | ||
| lockId: String, | ||
| owner: String, | ||
| ) { | ||
| val optionalLock = lockRepository.findById(lockId) | ||
| if (optionalLock.isPresent && optionalLock.get().owner == owner) { | ||
| lockRepository.deleteById(lockId) | ||
| } | ||
| } | ||
|
|
||
| fun getLock(chatRoomId: String): Lock { | ||
| val lockId = "chatRoom:$chatRoomId:lock" | ||
|
Contributor
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. lock service인데 chatRoom의 도메인이 쓰였기 때문에 결합도가 높아지는 원인으로 보이네요. |
||
| val owner = UUID.randomUUID().toString() | ||
|
|
||
| val lockAcquired = acquireLock(lockId, owner, 10000) // 10초 동안 잠금 유지 | ||
|
Contributor
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. 트랜잭션의 평균 실행시간이 10초 보다는 짧을 것 같아서 1초로 시작해서 튜닝하는 것이 좋아보여요. 설정 값은 하드 코딩하기 보다는 설정을 따로 빼는 것이 좋아보여요. |
||
| if (!lockAcquired) { | ||
| throw GlobalException(ErrorCode.CONCURRENCY_CONFLICTION) | ||
| } | ||
| return Lock(lockId = lockId, owner = owner, acquired = lockAcquired) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,4 +12,7 @@ enum class ErrorCode(val httpStatus: Int, val message: String) { | |
|
|
||
| // 404 | ||
| CHATROOM_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 id로 chatroom을 찾을 수 없습니다"), | ||
|
|
||
| // 500 | ||
| CONCURRENCY_CONFLICTION(HttpStatus.CONFLICT.value(), "동시성 충돌이 발생했습니다."), | ||
|
Contributor
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 comment
The reason will be displayed to describe this comment to others. Learn more.
id와 member를 public이면서 var이면 위험해보여요