실전! 코틀린과 스프링 부트로 도서관리 애플리케이션 개발하기

Ch02. Java 서버를 Kotlin 서버로 리팩토링하자 - Domain 계층 리펙토링하기

webmaster 2022. 11. 1. 00:44
728x90

Book 도메인 리펙토링

기존 JAVA의 Book 클래스를 지우고, Kotlin코드로 Book을 작성하자

import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id

@Entity
class Book(
    val name: String,
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
) {
    //기본 생성자가 없는 오류가 발생(플러그인을 받으면 오류 해결)

    init { // 초기화 블록
        if (name.isBlank()) {
            throw IllegalArgumentException("이름은 비어 있을 수 없습니다")
        }
    }
}
plugins {
    id 'org.springframework.boot' version '2.6.8'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
    id 'org.jetbrains.kotlin.jvm' version '1.6.21'
    id 'org.jetbrains.kotlin.plugin.jpa' version '1.6.21'
}

group = 'com.group'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
    implementation 'org.jetbrains.kotlin:kotlin-reflect:1.6.21'

    runtimeOnly 'com.h2database:h2'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

compileKotlin {
    kotlinOptions {
        jvmTarget = "11"
    }
}
compileTestKotlin {
    kotlinOptions {
        jvmTarget = "11"
    }
}
  • JPA에서는 기본 생성자를 필수로 가지고 있어야 하는데 코틀린 코드를 사용하면, 기본 생성자가 없기 때문에 플러그인을 설치해야 한다.
    • id 'org.jetbrains.kotlin.plugin.jpa' version '1.6.21'
  • 리펙토링 후, kotlin 코드를 실행해도, 동작하지 않는데, 오류 로그를 읽어보면 reflect 문제인데, 의존성을 추가해 주면 된다.
    • implementation 'org.jetbrains.kotlin:kotlin-reflect:1.6.21'
  • id 같은 경우 null이 들어갈 수 있으므로? 연산자를 통해 null을 허용해주고, default 파라미터를 사용해 null값을 넣어준다.
  • 초기화 블록을 이용해 name값이 들어오지 않을 경우 오류를 발생시켜준다.

UserLoanHistory 도메인 리펙토링

@Entity
class UserLoanHistory(
    @ManyToOne
    val user: User ,
    val bookName: String,
    var isReturn: Boolean,

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
) {
    fun doReturn() {
        this.isReturn = true
    }
}
  • 이전과 크게 차이나는 부분은 없다.
  • isReturn 같은 경우 값이 변하므로 가변으로 선언하자(var)

User 도메인 리펙토링

@Entity
class User(
    var name: String,
    val age: Int?,
    @OneToMany(mappedBy = "user", cascade = [CascadeType.ALL], orphanRemoval = true)
    val userLoanHistories: MutableList<UserLoanHistory> = mutableListOf(),
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null
) {
    init {
        if (name.isBlank()) {
            throw IllegalArgumentException("이름은 비어 있을 수 없습니다")
        }
    }

    fun updateName(name: String) {
        this.name = name
    }

    fun loanBook(book: Book) {
        this.userLoanHistories.add(UserLoanHistory(this, book.name, false))
    }

    fun returnBook(bookName: String) {
        this.userLoanHistories.first { history ->
            history.bookName == bookName
        }.doReturn()
    }
}
  • @OneToMany에서 cascade로 받는 배열은 []를 사용해서 받는다.
  • returnBook 같은 경우 람다식을 사용해서 해당 조건에 맞는 첫 번째 요소를 반환받아 doReturn() 함수를 호출해 준다.
728x90