ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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. 첫 번째 요구사항 클리어!

    댓글

Designed by Tistory.