Light Blue Pointer
본문 바로가기
Developing/Journal

Service에서 다른 Service 를 의존하게 하기

by Greedy 2024. 6. 20.

채팅방 기능을 개발할 때 ChatRoomService에서 좀 동떨어진 Invitation 관련 기능들을 많이 이용하게 되었는데

그게 ChatRoomService에 있으면 좀 의미가 맞지 않는 것 같아서 모두 InvitationRepository로 분리해서 코드를 구축했다

@Service
class ChatRoomService(
  private val chatRoomRepository: ChatRoomRepository,
  private val invitationRepository: InvitationRepository,
) {

fun getChatRoomInvitation(
    chatRoomId: String,
    userId: String,
  ): InvitationResponse {
    verifyChatRoomAccess(chatRoomId, userId)
    val code = invitationRepository.getInvitationCode(userId, chatRoomId)
    return InvitationResponse(code)
  }
}
@Component
class InvitationRepository(
  private val redisTemplate: RedisTemplate<String, String>,
  private val propertyConfig: PropertyConfig,
) {
  fun getInvitationCode(
    userId: String,
    chatRoomId: String,
  ): String {
    val key = generateKey(userId, chatRoomId)
    var value = redisTemplate.opsForValue().get(key)
    if (value == null) {
      value = setInvitation(key, chatRoomId)
    }
    return generateCode(key, value)
  }

  fun setInvitation(
    key: String,
    chatRoomId: String,
  ): String {
    val value = generateValue()
    val ops: ValueOperations<String, String> = redisTemplate.opsForValue()
    ops.set(key, value, propertyConfig.getExpiration())
    return value
  }

  fun getExpiration(
    userId: String,
    chatRoomId: String,
  ): Long {
    val key = generateKey(userId, chatRoomId)
    val expiration = redisTemplate.getExpire(key)
    return expiration
  }

  fun generateCode(
    key: String,
    value: String,
  ): String {
    val combine = "$key,$value"
    val digest = MessageDigest.getInstance("SHA-256")
    val hash = digest.digest(combine.toByteArray(StandardCharsets.UTF_8))
    return Base64.getEncoder().encodeToString(hash)
  }

  private fun generateKey(
    userId: String,
    chatRoomId: String,
  ): String {
    return "$userId:$chatRoomId"
  }

  private fun generateValue(): String {
    return UUID.randomUUID().toString()
  }
}
@Repository
interface ChatRoomRepository : MongoRepository<ChatRoom, String> {
  fun existsByIdAndMembersContaining(
    roomId: String,
    userId: String,
  ): Boolean
}

이렇게 구성되어 있었다

 

✨개선 : Invitation Service 로직 분리

Invitation 관련 Service 로직이 ChatRoomService에 있는 것이 이상해서 Invitation Repository에 넣었었는데

다시 생각해보면 Service가 Service를 의존하지 않아야 한다는 법칙은 없다

꼭 Repository만 들어가지 않아도 괜찮을 것 같아서 Service에 로직을 분리해 보았다

@Service
class ChatRoomService(
  private val chatRoomRepository: ChatRoomRepository,
  private val invitationService: InvitationService,
) {

  fun getChatRoomInvitation(
    chatRoomId: String,
    userId: String,
  ): InvitationResponse {
    verifyChatRoomAccess(chatRoomId, userId)
    var code = invitationService.getInvitation(userId, chatRoomId)
    if (code == null) {
      code = invitationService.setInvitation(userId, chatRoomId)
    }
    val encodedCode = invitationService.generateKeyAndCode(userId, chatRoomId, code)
    return InvitationResponse(encodedCode)
  }
}
@Service
class InvitationService(
  private val invitationRepository: InvitationRepository,
  private val chatRoomProperty: ChatRoomProperty,
) {
  fun getInvitation(
    userId: String,
    chatRoomId: String,
  ): String? {
    val key = generateKey(userId, chatRoomId)
    return invitationRepository.getValue(key)
  }

  fun setInvitation(
    userId: String,
    chatRoomId: String,
  ): String {
    val key = generateKey(userId, chatRoomId)
    val value = generateValue()
    invitationRepository.setValueAndExpiration(key, value, chatRoomProperty.getExpiration())
    return value
  }

  fun generateKeyAndCode(
    userId: String,
    chatRoomId: String,
    code: String,
  ): String {
    val key = generateKey(userId, chatRoomId)
    return generateCode(key, code)
  }

  private fun generateCode(
    key: String,
    value: String,
  ): String {
    val combinedString = "$key,$value"
    val digest = MessageDigest.getInstance("SHA-256")
    val hash = digest.digest(combinedString.toByteArray(StandardCharsets.UTF_8))
    return Base64.getEncoder().encodeToString(hash)
  }

  private fun generateKey(
    userId: String,
    chatRoomId: String,
  ): String {
    return "$userId:$chatRoomId"
  }

  private fun generateValue(): String {
    return UUID.randomUUID().toString()
  }
}
@Component
class InvitationRepository(
  private val redisTemplate: RedisTemplate<String, String>,
) {
  fun getValue(key: String): String? {
    return redisTemplate.opsForValue().get(key)
  }

  fun setValueAndExpiration(
    key: String,
    value: String,
    expiration: Duration,
  ) {
    val ops: ValueOperations<String, String> = redisTemplate.opsForValue()
    ops.set(key, value, expiration)
  }

  fun getExpiration(key: String): Long {
    return redisTemplate.getExpire(key)
  }
}

이렇게 3단 분리를 했다