[#30] 코드 구조 개선 #31

Merged
pricelees merged 31 commits from refactor/#30 into main 2025-08-06 10:16:08 +00:00
5 changed files with 48 additions and 53 deletions
Showing only changes of commit 35b7f06c2d - Show all commits

View File

@ -3,64 +3,56 @@ package roomescape.auth.business
import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import roomescape.auth.exception.AuthErrorCode import roomescape.auth.exception.AuthErrorCode
import roomescape.auth.exception.AuthException import roomescape.auth.exception.AuthException
import roomescape.auth.infrastructure.jwt.JwtHandler import roomescape.auth.infrastructure.jwt.JwtHandler
import roomescape.auth.web.LoginCheckResponse import roomescape.auth.web.LoginCheckResponse
import roomescape.auth.web.LoginRequest import roomescape.auth.web.LoginRequest
import roomescape.auth.web.LoginResponse import roomescape.auth.web.LoginResponse
import roomescape.common.exception.RoomescapeException import roomescape.member.implement.MemberFinder
import roomescape.member.business.MemberService
import roomescape.member.infrastructure.persistence.MemberEntity import roomescape.member.infrastructure.persistence.MemberEntity
private val log: KLogger = KotlinLogging.logger {} private val log: KLogger = KotlinLogging.logger {}
@Service @Service
class AuthService( class AuthService(
private val memberService: MemberService, private val memberFinder: MemberFinder,
private val jwtHandler: JwtHandler, private val jwtHandler: JwtHandler,
) { ) {
@Transactional(readOnly = true)
fun login(request: LoginRequest): LoginResponse { fun login(request: LoginRequest): LoginResponse {
val params = "email=${request.email}, password=${request.password}" val params = "email=${request.email}, password=${request.password}"
log.debug { "[AuthService.login] 로그인 시작: $params" } log.info { "[AuthService.login] 시작: $params" }
val member: MemberEntity = fetchMemberOrThrow(AuthErrorCode.LOGIN_FAILED, params, "login") { val member: MemberEntity = fetchOrThrow(AuthErrorCode.LOGIN_FAILED) {
memberService.findByEmailAndPassword(request.email, request.password) memberFinder.findByEmailAndPassword(request.email, request.password)
} }
val accessToken: String = jwtHandler.createToken(member.id!!) val accessToken: String = jwtHandler.createToken(member.id!!)
return LoginResponse(accessToken) return LoginResponse(accessToken)
.also { log.info { "[AuthService.login] 로그인 완료: memberId=${member.id}" } } .also { log.info { "[AuthService.login] 완료: email=${request.email}, memberId=${member.id}" } }
} }
@Transactional(readOnly = true)
fun checkLogin(memberId: Long): LoginCheckResponse { fun checkLogin(memberId: Long): LoginCheckResponse {
log.debug { "[AuthService.checkLogin] 로그인 확인 시작: memberId=$memberId" } log.info { "[AuthService.checkLogin] 시작: memberId=$memberId" }
val member: MemberEntity =
fetchMemberOrThrow(AuthErrorCode.MEMBER_NOT_FOUND, "memberId=$memberId", "checkLogin") { val member: MemberEntity = fetchOrThrow(AuthErrorCode.MEMBER_NOT_FOUND) { memberFinder.findById(memberId) }
memberService.findById(memberId)
}
return LoginCheckResponse(member.name, member.role.name) return LoginCheckResponse(member.name, member.role.name)
.also { log.info { "[AuthService.checkLogin] 로그인 확인 완료: memberId=$memberId" } } .also { log.info { "[AuthService.checkLogin] 완료: memberId=$memberId, role=${it.role}" } }
}
private fun fetchOrThrow(errorCode: AuthErrorCode, block: () -> MemberEntity): MemberEntity {
try {
return block()
} catch (e: Exception) {
throw AuthException(errorCode, e.message ?: errorCode.message)
}
} }
fun logout(memberId: Long) { fun logout(memberId: Long) {
log.info { "[AuthService.logout] 로그아웃: memberId=$memberId" } log.info { "[AuthService.logout] 로그아웃: memberId=$memberId" }
} }
private fun fetchMemberOrThrow(
errorCode: AuthErrorCode,
params: String,
calledBy: String,
block: () -> MemberEntity,
): MemberEntity {
try {
return block()
} catch (e: Exception) {
if (e !is RoomescapeException) {
log.warn(e) { "[AuthService.$calledBy] 회원 조회 실패: $params" }
}
throw AuthException(errorCode)
}
}
} }

View File

@ -7,8 +7,8 @@ import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import roomescape.auth.docs.AuthAPI
import roomescape.auth.business.AuthService import roomescape.auth.business.AuthService
import roomescape.auth.docs.AuthAPI
import roomescape.auth.web.support.MemberId import roomescape.auth.web.support.MemberId
import roomescape.common.dto.response.CommonApiResponse import roomescape.common.dto.response.CommonApiResponse

View File

@ -11,14 +11,14 @@ import org.springframework.web.servlet.HandlerInterceptor
import roomescape.auth.exception.AuthErrorCode import roomescape.auth.exception.AuthErrorCode
import roomescape.auth.exception.AuthException import roomescape.auth.exception.AuthException
import roomescape.auth.infrastructure.jwt.JwtHandler import roomescape.auth.infrastructure.jwt.JwtHandler
import roomescape.member.business.MemberService import roomescape.member.implement.MemberFinder
import roomescape.member.infrastructure.persistence.MemberEntity import roomescape.member.infrastructure.persistence.MemberEntity
private val log: KLogger = KotlinLogging.logger {} private val log: KLogger = KotlinLogging.logger {}
@Component @Component
class AuthInterceptor( class AuthInterceptor(
private val memberService: MemberService, private val memberFinder: MemberFinder,
private val jwtHandler: JwtHandler private val jwtHandler: JwtHandler
) : HandlerInterceptor { ) : HandlerInterceptor {
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean { override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
@ -50,10 +50,10 @@ class AuthInterceptor(
private fun findMember(accessToken: String?): MemberEntity { private fun findMember(accessToken: String?): MemberEntity {
try { try {
val memberId = jwtHandler.getMemberIdFromToken(accessToken) val memberId = jwtHandler.getMemberIdFromToken(accessToken)
return memberService.findById(memberId) return memberFinder.findById(memberId)
.also { MDC.put("member_id", "$memberId") } .also { MDC.put("member_id", "$memberId") }
} catch (e: Exception) { } catch (e: Exception) {
log.info { "[AuthInterceptor] 회원 조회 실패. accessToken = ${accessToken}" } log.info { "[AuthInterceptor] 회원 조회 실패. accessToken = $accessToken" }
val errorCode = AuthErrorCode.MEMBER_NOT_FOUND val errorCode = AuthErrorCode.MEMBER_NOT_FOUND
throw AuthException(errorCode, e.message ?: errorCode.message) throw AuthException(errorCode, e.message ?: errorCode.message)
} }

View File

@ -6,23 +6,21 @@ import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import org.springframework.data.repository.findByIdOrNull
import roomescape.auth.exception.AuthErrorCode import roomescape.auth.exception.AuthErrorCode
import roomescape.auth.exception.AuthException import roomescape.auth.exception.AuthException
import roomescape.auth.infrastructure.jwt.JwtHandler import roomescape.auth.infrastructure.jwt.JwtHandler
import roomescape.member.business.MemberService import roomescape.member.exception.MemberErrorCode
import roomescape.member.exception.MemberException
import roomescape.member.implement.MemberFinder
import roomescape.member.infrastructure.persistence.MemberEntity import roomescape.member.infrastructure.persistence.MemberEntity
import roomescape.member.infrastructure.persistence.MemberRepository
import roomescape.util.JwtFixture import roomescape.util.JwtFixture
import roomescape.util.MemberFixture import roomescape.util.MemberFixture
import roomescape.util.TsidFactory
class AuthServiceTest : BehaviorSpec({ class AuthServiceTest : BehaviorSpec({
val memberRepository: MemberRepository = mockk() val memberFinder: MemberFinder = mockk()
val memberService = MemberService(TsidFactory, memberRepository)
val jwtHandler: JwtHandler = JwtFixture.create() val jwtHandler: JwtHandler = JwtFixture.create()
val authService = AuthService(memberService, jwtHandler) val authService = AuthService(memberFinder, jwtHandler)
val user: MemberEntity = MemberFixture.user() val user: MemberEntity = MemberFixture.user()
Given("로그인 요청을 받으면") { Given("로그인 요청을 받으면") {
@ -31,7 +29,7 @@ class AuthServiceTest : BehaviorSpec({
Then("회원이 있다면 JWT 토큰을 생성한 뒤 반환한다.") { Then("회원이 있다면 JWT 토큰을 생성한 뒤 반환한다.") {
every { every {
memberRepository.findByEmailAndPassword(request.email, request.password) memberFinder.findByEmailAndPassword(request.email, request.password)
} returns user } returns user
val accessToken: String = authService.login(request).accessToken val accessToken: String = authService.login(request).accessToken
@ -42,8 +40,8 @@ class AuthServiceTest : BehaviorSpec({
Then("회원이 없다면 예외를 던진다.") { Then("회원이 없다면 예외를 던진다.") {
every { every {
memberRepository.findByEmailAndPassword(request.email, request.password) memberFinder.findByEmailAndPassword(request.email, request.password)
} returns null } throws MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
val exception = shouldThrow<AuthException> { val exception = shouldThrow<AuthException> {
authService.login(request) authService.login(request)
@ -59,7 +57,7 @@ class AuthServiceTest : BehaviorSpec({
val userId: Long = user.id!! val userId: Long = user.id!!
Then("회원이 있다면 회원의 이름을 반환한다.") { Then("회원이 있다면 회원의 이름을 반환한다.") {
every { memberRepository.findByIdOrNull(userId) } returns user every { memberFinder.findById(userId) } returns user
val response = authService.checkLogin(userId) val response = authService.checkLogin(userId)
@ -69,7 +67,9 @@ class AuthServiceTest : BehaviorSpec({
} }
Then("회원이 없다면 예외를 던진다.") { Then("회원이 없다면 예외를 던진다.") {
every { memberRepository.findByIdOrNull(userId) } returns null every {
memberFinder.findById(userId)
} throws MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
val exception = shouldThrow<AuthException> { val exception = shouldThrow<AuthException> {
authService.checkLogin(userId) authService.checkLogin(userId)

View File

@ -4,12 +4,13 @@ import com.ninjasquad.springmockk.SpykBean
import io.mockk.every import io.mockk.every
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.data.repository.findByIdOrNull
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import roomescape.auth.business.AuthService import roomescape.auth.business.AuthService
import roomescape.auth.exception.AuthErrorCode import roomescape.auth.exception.AuthErrorCode
import roomescape.common.exception.CommonErrorCode import roomescape.common.exception.CommonErrorCode
import roomescape.common.exception.ErrorCode import roomescape.common.exception.ErrorCode
import roomescape.member.exception.MemberErrorCode
import roomescape.member.exception.MemberException
import roomescape.util.MemberFixture import roomescape.util.MemberFixture
import roomescape.util.RoomescapeApiTest import roomescape.util.RoomescapeApiTest
@ -31,7 +32,7 @@ class AuthControllerTest(
val expectedToken = "expectedToken" val expectedToken = "expectedToken"
every { every {
memberRepository.findByEmailAndPassword(userRequest.email, userRequest.password) memberFinder.findByEmailAndPassword(userRequest.email, userRequest.password)
} returns user } returns user
every { every {
@ -52,8 +53,8 @@ class AuthControllerTest(
When("회원을 찾지 못하면") { When("회원을 찾지 못하면") {
every { every {
memberRepository.findByEmailAndPassword(userRequest.email, userRequest.password) memberFinder.findByEmailAndPassword(userRequest.email, userRequest.password)
} returns null } throws MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
Then("에러 응답을 받는다.") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.LOGIN_FAILED val expectedError = AuthErrorCode.LOGIN_FAILED
@ -111,7 +112,9 @@ class AuthControllerTest(
val invalidMemberId: Long = -1L val invalidMemberId: Long = -1L
every { jwtHandler.getMemberIdFromToken(any()) } returns invalidMemberId every { jwtHandler.getMemberIdFromToken(any()) } returns invalidMemberId
every { memberRepository.findByIdOrNull(invalidMemberId) } returns null every {
memberFinder.findById(invalidMemberId)
} throws MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
Then("에러 응답을 받는다.") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.MEMBER_NOT_FOUND val expectedError = AuthErrorCode.MEMBER_NOT_FOUND
@ -134,7 +137,7 @@ class AuthControllerTest(
} returns 1L } returns 1L
every { every {
memberRepository.findByIdOrNull(1L) memberFinder.findById(1L)
} returns MemberFixture.create(id = 1L) } returns MemberFixture.create(id = 1L)
Then("정상 응답한다.") { Then("정상 응답한다.") {