generated from pricelees/issue-pr-template
[#30] 코드 구조 개선 #31
@ -1,72 +1,52 @@
|
||||
package roomescape.member.business
|
||||
|
||||
import com.github.f4b6a3.tsid.TsidFactory
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import roomescape.common.config.next
|
||||
import roomescape.member.exception.MemberErrorCode
|
||||
import roomescape.member.exception.MemberException
|
||||
import roomescape.member.implement.MemberFinder
|
||||
import roomescape.member.implement.MemberWriter
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.member.infrastructure.persistence.MemberRepository
|
||||
import roomescape.member.infrastructure.persistence.Role
|
||||
import roomescape.member.web.*
|
||||
|
||||
private val log = KotlinLogging.logger {}
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class MemberService(
|
||||
private val tsidFactory: TsidFactory,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val memberWriter: MemberWriter,
|
||||
private val memberFinder: MemberFinder,
|
||||
) {
|
||||
@Transactional(readOnly = true)
|
||||
fun findMembers(): MemberRetrieveListResponse {
|
||||
log.debug { "[MemberService.findMembers] 회원 조회 시작" }
|
||||
log.info { "[MemberService.findMembers] 시작" }
|
||||
|
||||
return memberRepository.findAll()
|
||||
.also { log.info { "[MemberService.findMembers] 회원 ${it.size}명 조회 완료" } }
|
||||
return memberFinder.findAll()
|
||||
.toRetrieveListResponse()
|
||||
.also { log.info { "[MemberService.findMembers] 완료. ${it.members.size}명 반환" } }
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findById(memberId: Long): MemberEntity {
|
||||
return fetchOrThrow("findById", "memberId=$memberId") {
|
||||
memberRepository.findByIdOrNull(memberId)
|
||||
}
|
||||
log.info { "[MemberService.findById] 시작" }
|
||||
|
||||
return memberFinder.findById(memberId)
|
||||
.also { log.info { "[MemberService.findById] 완료. memberId=${memberId}, email=${it.email}" } }
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findByEmailAndPassword(email: String, password: String): MemberEntity {
|
||||
return fetchOrThrow("findByEmailAndPassword", "email=$email, password=$password") {
|
||||
memberRepository.findByEmailAndPassword(email, password)
|
||||
}
|
||||
log.info { "[MemberService.findByEmailAndPassword] 시작" }
|
||||
|
||||
return memberFinder.findByEmailAndPassword(email, password)
|
||||
.also { log.info { "[MemberService.findByEmailAndPassword] 완료. email=${email}, memberId=${it.id}" } }
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun createMember(request: SignupRequest): SignupResponse {
|
||||
memberRepository.findByEmail(request.email)?.let {
|
||||
log.info { "[MemberService.createMember] 회원가입 실패(이메일 중복): email=${request.email}" }
|
||||
throw MemberException(MemberErrorCode.DUPLICATE_EMAIL)
|
||||
}
|
||||
log.info { "[MemberService.createMember] 시작" }
|
||||
|
||||
val member = MemberEntity(
|
||||
_id = tsidFactory.next(),
|
||||
name = request.name,
|
||||
email = request.email,
|
||||
password = request.password,
|
||||
role = Role.MEMBER
|
||||
)
|
||||
|
||||
return memberRepository.save(member).toSignupResponse()
|
||||
.also { log.info { "[MemberService.create] 회원가입 완료: email=${request.email} memberId=${it.id}" } }
|
||||
}
|
||||
|
||||
private fun fetchOrThrow(calledBy: String, params: String, block: () -> MemberEntity?): MemberEntity {
|
||||
log.debug { "[MemberService.$calledBy] 회원 조회 시작: $params" }
|
||||
return block()
|
||||
?.also { log.info { "[MemberService.$calledBy] 회원 조회 완료: memberId=${it.id}" } }
|
||||
?: run {
|
||||
log.info { "[MemberService.$calledBy] 회원 조회 실패: $params" }
|
||||
throw MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
|
||||
}
|
||||
return memberWriter.create(request.name, request.email, request.password, Role.MEMBER)
|
||||
.toSignupResponse()
|
||||
.also { log.info { "[MemberService.create] 완료: email=${request.email} memberId=${it.id}" } }
|
||||
}
|
||||
}
|
||||
|
||||
47
src/main/kotlin/roomescape/member/implement/MemberFinder.kt
Normal file
47
src/main/kotlin/roomescape/member/implement/MemberFinder.kt
Normal file
@ -0,0 +1,47 @@
|
||||
package roomescape.member.implement
|
||||
|
||||
import io.github.oshai.kotlinlogging.KLogger
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Component
|
||||
import roomescape.member.exception.MemberErrorCode
|
||||
import roomescape.member.exception.MemberException
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.member.infrastructure.persistence.MemberRepository
|
||||
|
||||
private val log: KLogger = KotlinLogging.logger {}
|
||||
|
||||
@Component
|
||||
class MemberFinder(
|
||||
private val memberRepository: MemberRepository
|
||||
) {
|
||||
|
||||
fun findAll(): List<MemberEntity> {
|
||||
log.debug { "[MemberFinder.findAll] 회원 조회 시작" }
|
||||
|
||||
return memberRepository.findAll()
|
||||
.also { log.debug { "[MemberFinder.findAll] 회원 ${it.size}명 조회 완료" } }
|
||||
}
|
||||
|
||||
fun findById(id: Long): MemberEntity {
|
||||
log.debug { "[MemberFinder.findById] 조회 시작: memberId=$id" }
|
||||
|
||||
return memberRepository.findByIdOrNull(id)
|
||||
?.also { log.debug { "[MemberFinder.findById] 조회 완료: memberId=$id, email=${it.email}" } }
|
||||
?: run {
|
||||
log.info { "[MemberFinder.findById] 조회 실패: id=$id" }
|
||||
throw MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
|
||||
}
|
||||
}
|
||||
|
||||
fun findByEmailAndPassword(email: String, password: String): MemberEntity {
|
||||
log.debug { "[MemberFinder.findByEmailAndPassword] 조회 시작: email=$email, password=$password" }
|
||||
|
||||
return memberRepository.findByEmailAndPassword(email, password)
|
||||
?.also { log.debug { "[MemberFinder.findByEmailAndPassword] 조회 완료: email=${email}, memberId=${it.id}" } }
|
||||
?: run {
|
||||
log.info { "[MemberFinder.findByEmailAndPassword] 조회 실패: email=${email}, password=${password}" }
|
||||
throw MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/main/kotlin/roomescape/member/implement/MemberWriter.kt
Normal file
34
src/main/kotlin/roomescape/member/implement/MemberWriter.kt
Normal file
@ -0,0 +1,34 @@
|
||||
package roomescape.member.implement
|
||||
|
||||
import com.github.f4b6a3.tsid.TsidFactory
|
||||
import io.github.oshai.kotlinlogging.KLogger
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.springframework.stereotype.Component
|
||||
import roomescape.common.config.next
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.member.infrastructure.persistence.MemberRepository
|
||||
import roomescape.member.infrastructure.persistence.Role
|
||||
|
||||
private val log: KLogger = KotlinLogging.logger {}
|
||||
|
||||
@Component
|
||||
class MemberWriter(
|
||||
private val tsidFactory: TsidFactory,
|
||||
private val memberValidator: MemberValidator,
|
||||
private val memberRepository: MemberRepository
|
||||
) {
|
||||
fun create(name: String, email: String, password: String, role: Role): MemberEntity {
|
||||
memberValidator.validateCanSignup(email)
|
||||
|
||||
val member = MemberEntity(
|
||||
_id = tsidFactory.next(),
|
||||
name = name,
|
||||
email = email,
|
||||
password = password,
|
||||
role = role
|
||||
)
|
||||
|
||||
return memberRepository.save(member)
|
||||
.also { log.info { "[MemberWriter.create] 회원 저장 완료: email=$email, memberId=${it.id}" } }
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ package roomescape.member.infrastructure.persistence
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
|
||||
interface MemberRepository : JpaRepository<MemberEntity, Long> {
|
||||
fun findByEmailAndPassword(email: String, password: String): MemberEntity?
|
||||
fun existsByEmail(email: String): Boolean
|
||||
|
||||
fun findByEmail(email: String): MemberEntity?
|
||||
fun findByEmailAndPassword(email: String, password: String): MemberEntity?
|
||||
}
|
||||
|
||||
@ -96,8 +96,8 @@ class MemberControllerTest(
|
||||
)
|
||||
`when`("같은 이메일이 없으면") {
|
||||
every {
|
||||
memberRepository.findByEmail(request.email)
|
||||
} returns null
|
||||
memberRepository.existsByEmail(request.email)
|
||||
} returns false
|
||||
|
||||
every {
|
||||
memberRepository.save(any())
|
||||
@ -124,8 +124,8 @@ class MemberControllerTest(
|
||||
|
||||
`when`("같은 이메일이 있으면") {
|
||||
every {
|
||||
memberRepository.findByEmail(request.email)
|
||||
} returns mockk()
|
||||
memberRepository.existsByEmail(request.email)
|
||||
} returns true
|
||||
|
||||
then("에러 응답") {
|
||||
val expectedError = MemberErrorCode.DUPLICATE_EMAIL
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
package roomescape.member.implement
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldHaveSize
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import roomescape.member.exception.MemberErrorCode
|
||||
import roomescape.member.exception.MemberException
|
||||
import roomescape.member.infrastructure.persistence.MemberRepository
|
||||
|
||||
class MemberFinderTest : FunSpec({
|
||||
|
||||
val memberRepository: MemberRepository = mockk()
|
||||
val memberFinder = MemberFinder(memberRepository)
|
||||
|
||||
context("findAll") {
|
||||
test("모든 회원을 조회한다.") {
|
||||
every {
|
||||
memberRepository.findAll()
|
||||
} returns listOf(mockk(), mockk(), mockk())
|
||||
|
||||
memberFinder.findAll() shouldHaveSize 3
|
||||
}
|
||||
}
|
||||
|
||||
context("findById") {
|
||||
val memberId = 1L
|
||||
test("동일한 ID인 회원을 찾아 응답한다.") {
|
||||
every {
|
||||
memberRepository.findByIdOrNull(memberId)
|
||||
} returns mockk()
|
||||
|
||||
memberFinder.findById(memberId)
|
||||
|
||||
verify(exactly = 1) {
|
||||
memberRepository.findByIdOrNull(memberId)
|
||||
}
|
||||
}
|
||||
|
||||
test("동일한 ID인 회원이 없으면 실패한다.") {
|
||||
every {
|
||||
memberRepository.findByIdOrNull(memberId)
|
||||
} returns null
|
||||
|
||||
shouldThrow<MemberException> {
|
||||
memberFinder.findById(memberId)
|
||||
}.also {
|
||||
it.errorCode shouldBe MemberErrorCode.MEMBER_NOT_FOUND
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("findByEmailAndPassword") {
|
||||
val email = "email"
|
||||
val password = "password"
|
||||
|
||||
test("동일한 이메일과 비밀번호를 가진 회원을 찾아 응답한다.") {
|
||||
every {
|
||||
memberRepository.findByEmailAndPassword(email, password)
|
||||
} returns mockk()
|
||||
|
||||
memberFinder.findByEmailAndPassword(email, password)
|
||||
|
||||
verify(exactly = 1) {
|
||||
memberRepository.findByEmailAndPassword(email, password)
|
||||
}
|
||||
}
|
||||
|
||||
test("동일한 이메일과 비밀번호를 가진 회원이 없으면 실패한다.") {
|
||||
every {
|
||||
memberRepository.findByEmailAndPassword(email, password)
|
||||
} returns null
|
||||
|
||||
shouldThrow<MemberException> {
|
||||
memberFinder.findByEmailAndPassword(email, password)
|
||||
}.also {
|
||||
it.errorCode shouldBe MemberErrorCode.MEMBER_NOT_FOUND
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -0,0 +1,44 @@
|
||||
package roomescape.member.implement
|
||||
|
||||
import io.kotest.assertions.throwables.shouldNotThrow
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.core.spec.style.StringSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import roomescape.member.exception.MemberErrorCode
|
||||
import roomescape.member.exception.MemberException
|
||||
import roomescape.member.infrastructure.persistence.MemberRepository
|
||||
|
||||
class MemberValidatorTest : FunSpec({
|
||||
|
||||
val memberRepository: MemberRepository = mockk()
|
||||
val memberValidator = MemberValidator(memberRepository)
|
||||
|
||||
context("validateCanSignup") {
|
||||
val email = "email@email.com"
|
||||
|
||||
test("같은 이메일을 가진 회원이 있으면 예외를 던진다.") {
|
||||
every {
|
||||
memberRepository.existsByEmail(email)
|
||||
} returns true
|
||||
|
||||
shouldThrow<MemberException> {
|
||||
memberValidator.validateCanSignup(email)
|
||||
}.also {
|
||||
it.errorCode shouldBe MemberErrorCode.DUPLICATE_EMAIL
|
||||
}
|
||||
}
|
||||
|
||||
test("같은 이메일을 가진 회원이 없으면 종료한다.") {
|
||||
every {
|
||||
memberRepository.existsByEmail(email)
|
||||
} returns false
|
||||
|
||||
shouldNotThrow<MemberException> {
|
||||
memberValidator.validateCanSignup(email)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -0,0 +1,65 @@
|
||||
package roomescape.member.implement
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.core.spec.style.StringSpec
|
||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import roomescape.member.exception.MemberErrorCode
|
||||
import roomescape.member.exception.MemberException
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.member.infrastructure.persistence.MemberRepository
|
||||
import roomescape.member.infrastructure.persistence.Role
|
||||
import roomescape.util.MemberFixture
|
||||
import roomescape.util.TsidFactory
|
||||
|
||||
class MemberWriterTest : FunSpec({
|
||||
val memberRepository: MemberRepository = mockk()
|
||||
val memberValidator = MemberValidator(memberRepository)
|
||||
val memberWriter = MemberWriter(TsidFactory, memberValidator, memberRepository)
|
||||
|
||||
context("create") {
|
||||
val name = "name"
|
||||
val email = "email"
|
||||
val password = "password"
|
||||
val role = Role.MEMBER
|
||||
|
||||
test("중복된 이메일이 있으면 실패한다.") {
|
||||
every {
|
||||
memberRepository.existsByEmail(any())
|
||||
} returns true
|
||||
|
||||
shouldThrow<MemberException> {
|
||||
memberWriter.create(name, email, password, role)
|
||||
}.also {
|
||||
it.errorCode shouldBe MemberErrorCode.DUPLICATE_EMAIL
|
||||
}
|
||||
}
|
||||
|
||||
test("중복된 이메일이 없으면 저장한다.") {
|
||||
every {
|
||||
memberRepository.existsByEmail(any())
|
||||
} returns false
|
||||
|
||||
every {
|
||||
memberRepository.save(any())
|
||||
} returns MemberFixture.create(
|
||||
name = name,
|
||||
account = email,
|
||||
password = password,
|
||||
role = role
|
||||
)
|
||||
|
||||
memberWriter.create(name, email, password, role)
|
||||
|
||||
verify(exactly = 1) {
|
||||
memberRepository.save(any())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user