generated from pricelees/issue-pr-template
feat: member 도메인에 Finder, Writer, Validator 추가 및 서비스 로직 수정
This commit is contained in:
parent
d3e22888ed
commit
e92878a84a
@ -1,72 +1,52 @@
|
|||||||
package roomescape.member.business
|
package roomescape.member.business
|
||||||
|
|
||||||
import com.github.f4b6a3.tsid.TsidFactory
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import roomescape.common.config.next
|
import roomescape.member.implement.MemberFinder
|
||||||
import roomescape.member.exception.MemberErrorCode
|
import roomescape.member.implement.MemberWriter
|
||||||
import roomescape.member.exception.MemberException
|
|
||||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||||
import roomescape.member.infrastructure.persistence.MemberRepository
|
|
||||||
import roomescape.member.infrastructure.persistence.Role
|
import roomescape.member.infrastructure.persistence.Role
|
||||||
import roomescape.member.web.*
|
import roomescape.member.web.*
|
||||||
|
|
||||||
private val log = KotlinLogging.logger {}
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(readOnly = true)
|
|
||||||
class MemberService(
|
class MemberService(
|
||||||
private val tsidFactory: TsidFactory,
|
private val memberWriter: MemberWriter,
|
||||||
private val memberRepository: MemberRepository,
|
private val memberFinder: MemberFinder,
|
||||||
) {
|
) {
|
||||||
|
@Transactional(readOnly = true)
|
||||||
fun findMembers(): MemberRetrieveListResponse {
|
fun findMembers(): MemberRetrieveListResponse {
|
||||||
log.debug { "[MemberService.findMembers] 회원 조회 시작" }
|
log.info { "[MemberService.findMembers] 시작" }
|
||||||
|
|
||||||
return memberRepository.findAll()
|
return memberFinder.findAll()
|
||||||
.also { log.info { "[MemberService.findMembers] 회원 ${it.size}명 조회 완료" } }
|
|
||||||
.toRetrieveListResponse()
|
.toRetrieveListResponse()
|
||||||
|
.also { log.info { "[MemberService.findMembers] 완료. ${it.members.size}명 반환" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
fun findById(memberId: Long): MemberEntity {
|
fun findById(memberId: Long): MemberEntity {
|
||||||
return fetchOrThrow("findById", "memberId=$memberId") {
|
log.info { "[MemberService.findById] 시작" }
|
||||||
memberRepository.findByIdOrNull(memberId)
|
|
||||||
}
|
return memberFinder.findById(memberId)
|
||||||
|
.also { log.info { "[MemberService.findById] 완료. memberId=${memberId}, email=${it.email}" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
fun findByEmailAndPassword(email: String, password: String): MemberEntity {
|
fun findByEmailAndPassword(email: String, password: String): MemberEntity {
|
||||||
return fetchOrThrow("findByEmailAndPassword", "email=$email, password=$password") {
|
log.info { "[MemberService.findByEmailAndPassword] 시작" }
|
||||||
memberRepository.findByEmailAndPassword(email, password)
|
|
||||||
}
|
return memberFinder.findByEmailAndPassword(email, password)
|
||||||
|
.also { log.info { "[MemberService.findByEmailAndPassword] 완료. email=${email}, memberId=${it.id}" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun createMember(request: SignupRequest): SignupResponse {
|
fun createMember(request: SignupRequest): SignupResponse {
|
||||||
memberRepository.findByEmail(request.email)?.let {
|
log.info { "[MemberService.createMember] 시작" }
|
||||||
log.info { "[MemberService.createMember] 회원가입 실패(이메일 중복): email=${request.email}" }
|
|
||||||
throw MemberException(MemberErrorCode.DUPLICATE_EMAIL)
|
|
||||||
}
|
|
||||||
|
|
||||||
val member = MemberEntity(
|
return memberWriter.create(request.name, request.email, request.password, Role.MEMBER)
|
||||||
_id = tsidFactory.next(),
|
.toSignupResponse()
|
||||||
name = request.name,
|
.also { log.info { "[MemberService.create] 완료: email=${request.email} memberId=${it.id}" } }
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
|
||||||
interface MemberRepository : JpaRepository<MemberEntity, Long> {
|
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`("같은 이메일이 없으면") {
|
`when`("같은 이메일이 없으면") {
|
||||||
every {
|
every {
|
||||||
memberRepository.findByEmail(request.email)
|
memberRepository.existsByEmail(request.email)
|
||||||
} returns null
|
} returns false
|
||||||
|
|
||||||
every {
|
every {
|
||||||
memberRepository.save(any())
|
memberRepository.save(any())
|
||||||
@ -124,8 +124,8 @@ class MemberControllerTest(
|
|||||||
|
|
||||||
`when`("같은 이메일이 있으면") {
|
`when`("같은 이메일이 있으면") {
|
||||||
every {
|
every {
|
||||||
memberRepository.findByEmail(request.email)
|
memberRepository.existsByEmail(request.email)
|
||||||
} returns mockk()
|
} returns true
|
||||||
|
|
||||||
then("에러 응답") {
|
then("에러 응답") {
|
||||||
val expectedError = MemberErrorCode.DUPLICATE_EMAIL
|
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