-
3. 첫 번째 요구사항 추가하기 - 책의 분야Kotlin/실전! 코틀린과 스프링 부트로 ... 개발 2023. 8. 5. 10:39
21. 책의 분야 추가하기
1. Type, Status 등을 서버에서 관리하는 방법들을 살펴보고 장단점을 이해한다.
2. Test Fixture의 필요성을 느끼고 구성하는 방법을 알아본다
3. Kotlin에서 Enum + JPA + Spring Boot를 활용하는 방법을 알아본다.
Object Modal Pattern - Test Fixture
@Entity class Book( val name: String, val type: String, @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null, ) { init { if (name.isBlank()) { throw IllegalArgumentException("이름은 비어 있을 수 없습니다") } } companion object { fun fixture( name: String = "책 이름", type: String = "COMPUTER", id: Long? = null, ): Book { return Book( name = name, type = type, id = id, ) } } }
- 도메인 클래스가 변경되도 테스트 코드 변경은 필요 없음
- DTO 도 상황에 따라 Test Fixture을 만들기도 하고 안만들기도 한다.
22. Enum Class를 활용해 책의 분야 리팩토링 하기
- 기존 구조의 문제점1 : 요청을 검증하고 있지 않다.
init { if (name.isBlank()) { throw IllegalArgumentException("이름은 비어 있을 수 없습니다") } if (type !in AVAILABLE_BOOK_TYPES) { throw IllegalArgumentException("들어올 수 없는 타입입니다.") } } companion object { private val AVAILABLE_BOOK_TYPES = listOf("COMPUTER", "ECONOMY", "SOCIETY", "LANGUAGE", "SCIENCE")
ㆍ검증할 수 있지만, 번거롭다.
- 기존 구조의 문제점2 : 코드만 보았을 때, DB 테이블에 어떤 값이 들어가는지 알 수 없다.
- 기존 구조의 문제점3 : type과 관련된 새로운 로직을 작성할 때 번거롭다.
ㆍ예를 들어, 책을 대출할 때마다 분야별로 '이벤트 점수'를 준다면..?!
fun getEventScore(): Int { return when (type) { "COMPUTER" -> 10 "ECONOMY" -> 8 "SOCIETY", "LANGUAGE", "SCIENCE" -> 5 else -> throw IllegalArgumentException("잘못된 타입니다") } }
ㆍ1) 코드에 분기가 들어가고
ㆍ2) 실행되지 않을 else문이 존재하며
ㆍ3) 문자열 타이핑은 실수할 여지가 많고,
ㆍ4) 새로운 type이 생기는 경우 로직 추가를 놓칠 수 있다.
type: String의 단점 정리
1. 현재 검증이 되지 있지 않으며, 검증 코드를 추가 작성하기 번거롭다.
2. 코드만 보았을 때 어떤 값이 DB에 있는지 알 수 없다.
3. type과 관련한 새로운 로직을 작성할 때 번거롭다.
- Enum Class를 활용하자!
enum class BookType { COMPUTER, ECONOMY, SOCIETY, LANGUAGE, SCIENCE }
enum class BookType(val score: Int) { COMPUTER(10), ECONOMY(8), SOCIETY(5), LANGUAGE(5), SCIENCE(5) }
fun getEventScore(): Int { // return when (type) { // BookType.COMPUTER -> 10 // BookType.ECONOMY -> 8 // BookType.SOCIETY, BookType.LANGUAGE, BookType.SCIENCE -> 5 // else -> throw IllegalArgumentException("잘못된 타입니다") // } return type.score }
- 1) 다형성을 활용해 코드에 분기가 없고
- 2) 실행되지 않을 else문도 제거되어 함수가 깔끔해 져씅며
- 3) BookType 클래스에 score을 위임해 문자열 타이핑도 사라졌고
- 4) 새로운 Type이 추가될 때 score를 빠뜨릴 수 없다.
Enum이 숫자로 DB에 저장되면 발생하는 문제
- 1. 기존 Enum의 순서가 바뀌면 아주 큰 일이 난다
- 2. 기존 Enum 타입의 삭제, 새로운 Enum 타입의 추가가 제한적이다.
@Enumerated(EnumType.STRING) val type: BookType,
- 해당과 같이 코드를 입력하면 숫자로 DB에 저장되는 것이 아니라 문자로 DB에 저장된다.
정리
1. Type을 문자열로 관리할 때는 몇 가지 단점이 존재한다.
2. Enum Class를 활용하면 손쉽게 단점을 제거할 수 있다.
3. Enum Class를 Entity에 사용할 때는 @Enumerated(EnumType.String)을 잘 활용해 주어야 한다.
23. Boolean에도 Enum 활용하기 - 책 반납 로직 수정
@Entity class User( val name: String, val isActive: Boolean, // 휴면 여부 val isDeleted: Boolean, // 탈퇴 여부 @Id @GeneratedValue(strategy = Generation.IDENTITY) val id: Long? = null, )
- Boolean이 2개가 되면 문제가 생긴다!
- 문제1. Boolean이 2개 있기 때문에 코드가 이해하기 어려워진다.
ㆍ한 객체가 여러 상태를 표현할 수록 이해하기 어렵다.
ㆍ현재 경우의 수는 2^2, 즉 4가지이다. 1개만 더 늘어나도 8가지다.
- 문제2. Boolean 2개로 표현되는 4가지 상태가 모두 유의미하지 않다.
ㆍ(isActive, isDeleted) 는 총 4가지 경우가 있다.
▷ (false, false) : 휴면 상태인 유저
▷ (false, true) : 휴면이면서 탈퇴한 유저일 수는 없다.
▷ (true, false) : 활성화된 유저
▷ (true, true) : 탈퇴한 유저
ㆍ2번쨰 경우 DB에 존재할 수 없는 조합이고, 이런 경우가 '코드'에서 가능한 것은 유지보수를 어렵게 만든다.
- Enum을 도입하면 해결 가능!
@Entity class User( val name: String, @Enumerated(EnumType.STRING) val status: UserStatus, @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null, )
enum class UserStatus { ACTIVE, IN_ACTIVE, DELETE, }
- 이렇게 Enum을 활용하게 되면
ㆍ1. 필드 1개로 여러 상태를 표현할 수 있기 때문에 코드의 이해가 쉬워지고
ㆍ2. 정호가하게 유의미한 상태만 나타낼 수 있기 때문에 코드의 유지보수가 용이해진다.
24. 첫 번째 요구사항 클리어!
'Kotlin > 실전! 코틀린과 스프링 부트로 ... 개발' 카테고리의 다른 글
6. 네 번째 요구사항 추가하기 - Querydsl (0) 2023.08.08 5. 세 번째 요구사항 추가하기 - 책 통계 (0) 2023.08.08 4. 두 번째 요구사항 추가하기 - 도서 대출 현황 (0) 2023.08.07 2. Java 서버를 Kotlin 서버로 리팩토링 (0) 2023.08.02 1. 리펙토링 준비 (0) 2023.07.31