728x90
Boolean 필드의 단점
현재 UserLoanHistory 엔티티 같은 경우 isReturn 필드가 Boolean으로 되어있다. -> Enum으로 사용하는 것이 더 좋아 보인다.
- 만약 Boolean 필드를 사용하는 필드가 많이 발생하게 된다면? -> 2^n 을 경우에 수가 발생하고, 개발자가 고려해야 할 로직이 많아진다.
- Enum 필드로 관심사가 같은 필드끼리는 묶어서 개발하면, 관리하는 필드가 줄고, 경우의 수도 줄어든다.
- 또한 Boolean 필드를 많이 가지게 되면, 비즈니스 적으로 불가능한 경우(DB에 존재할 수 없는 조합)가 발생할 수도 있다.
- EnumType을 사용하면, 필드 1개로 여러 상태를 표현할 수 있기에 코드의 이해가 쉬워지고, 정확하게 유의미한 상태만 나타낼 수 있기 때문에 코드의 유지보수가 용이해진다.
- 현재 isReturn 필드 같은 경우 지금은 단순 대출/ 반납 기능만 하지만 나중에는 장기 대출, 임시 반납 등 새로운 기능이 추가될 확률이 크기 때문에 Enum을 적극적으로 활용하는 게 좋다.
Enum
UserLoanStatus
enum class UserLoanStatus {
RETURNED, //반납 되어 있는 상태
LOANED, //대출 중인 상태
}
UserLoanHistory
@Entity
class UserLoanHistory(
@ManyToOne
val user: User,
val bookName: String,
var status: UserLoanStatus = UserLoanStatus.LOANED, //Enum으로 설계하는것이 더 좋다
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
) {
fun doReturn() {
this.status = UserLoanStatus.RETURNED
}
companion object {
fun fixture(
user: User,
bookName: String = "이상한 나라의 엘리스",
status: UserLoanStatus = UserLoanStatus.LOANED,
id: Long? = null,
): UserLoanHistory {
return UserLoanHistory(
user = user,
bookName = bookName,
status = status,
id = id,
)
}
}
}
BookService
@Transactional
fun loanBook(request: BookLoanRequest) {
val book =
bookRepository.findByName(request.bookName) ?: fail() //항상 반복되므로 리펙토링 가능
if (userLoanHistoryRepository.findByBookNameAndStatus(request.bookName, UserLoanStatus.LOANED) != null) {
throw IllegalArgumentException("진작 대출되어 있는 책입니다")
}
val user =
userRepository.findByName(request.userName) ?: fail()
user.loanBook(book)
}
UserLoanHistoryRepository
interface UserLoanHistoryRepository : JpaRepository<UserLoanHistory, Long> {
fun findByBookNameAndStatus(bookName: String, status: UserLoanStatus): UserLoanHistory?
}
BookServiceTest
@SpringBootTest
class BookServiceTest @Autowired constructor(
private val bookService: BookService,
private val bookRepository: BookRepository, //마지막에 콤마를 찍을 수 있다 -> git diff에 변하는 부분만 보기 위해 해당 기능 허용
private val userRepository: UserRepository,
private val userLoanHistoryRepository: UserLoanHistoryRepository,
) {
@AfterEach
fun clean() {
bookRepository.deleteAll()
userRepository.deleteAll()
}
@Test
@DisplayName("책 등록이 정상 동작한다")
fun saveBookTest() {
//given
val request = BookRequest("이상한 나라의 엘리스", BookType.COMPUTER)
//when
bookService.saveBook(request)
//then
val books = bookRepository.findAll()
assertThat(books).hasSize(1)
assertThat(books[0].name).isEqualTo("이상한 나라의 엘리스")
assertThat(books[0].type).isEqualTo(BookType.COMPUTER)
}
@Test
@DisplayName("책 대출이 정상 동작한다")
fun loanBookTest() {
//given
bookRepository.save(Book.fixture("이상한 나라의 엘리스"))
val savedUser = userRepository.save(
User(
"최태현",
null
)
)
val request = BookLoanRequest("최태현", "이상한 나라의 엘리스")
//when
bookService.loanBook(request)
//then
val results = userLoanHistoryRepository.findAll()
assertThat(results).hasSize(1)
assertThat(results[0].bookName).isEqualTo("이상한 나라의 엘리스")
assertThat(results[0].user.id).isEqualTo(savedUser.id)
assertThat(results[0].status).isEqualTo(UserLoanStatus.LOANED)
}
@Test
@DisplayName("책이 진작 대출되어 있다면, 신규 대출이 실패한다")
fun loanBookFailTest() {
//given
bookRepository.save(Book.fixture("이상한 나라의 엘리스"))
val savedUser = userRepository.save(User("최태현", null))
userLoanHistoryRepository.save(UserLoanHistory.fixture(savedUser, "이상한 나라의 엘리스")) //대출 중인 상태
val request = BookLoanRequest("최태현", "이상한 나라의 엘리스")
//when & then
assertThrows<IllegalArgumentException> {
bookService.loanBook(request)
}.apply {
assertThat(message).isEqualTo("진작 대출되어 있는 책입니다")
}
}
@Test
@DisplayName("책 반납이 정상 동작한다")
fun returnBookTest() {
//given
val savedUser = userRepository.save(
User(
"최태현",
null
)
)
userLoanHistoryRepository.save(
UserLoanHistory.fixture(
savedUser,
"이상한 나라의 엘리스",
)
) //대출 중인 상태
val request = BookReturnRequest("최태현", "이상한 나라의 엘리스")
//when
bookService.returnBook(request)
//then
val results = userLoanHistoryRepository.findAll()
assertThat(results).hasSize(1)
assertThat(results[0].status).isEqualTo(UserLoanStatus.RETURNED)
}
}
- Test 같은 경우 fixture 메서드로 변경해 최대한 영향을 받지 않게 하자
- Enum 필드로 변경해, 오류가 나는 부분을 수정하자
- Repository 같은 경우, 실제 런타임 오류(SpringDataJPA가 인터페이스를 만들면서 오류가 발생)가 발생하기 때문에 좋지 않다 -> 후에 QueryDSL로 변경하면 이러한 문제를 해결할 수 있다.
728x90
'실전! 코틀린과 스프링 부트로 도서관리 애플리케이션 개발하기' 카테고리의 다른 글
| Ch03. 요구사항 추가(type, 대출현황) - 유저 대출 현황 - 테스트 코드 작성 (0) | 2022.11.06 |
|---|---|
| Ch03. 요구사항 추가(type, 대출현황) - 도서 대출 현황 요구사항 추가 (0) | 2022.11.06 |
| Ch03. 요구사항 추가(type, 대출현황) - Enum Class 를 활용해 type 리펙토링 하기 (0) | 2022.11.05 |
| Ch03. 요구사항 추가(type, 대출현황) - Java 서버를 Kotlin 서버로 리팩토링하자 - 책의 분야 추가하기 (0) | 2022.11.05 |
| Ch02. Java 서버를 Kotlin 서버로 리팩토링하자 - DTO,Controller를 Kotlin으로 변경하기 (0) | 2022.11.01 |