훈훈훈

Spring Boot :: Kotlin으로 간단한 CRUD API 만들기 본문

Spring Framework/Kotlin

Spring Boot :: Kotlin으로 간단한 CRUD API 만들기

훈훈훈 2020. 5. 30. 16:27

이번에는 스프링 부트를 코틀린을 사용하여 간단하게 CRUD API를 만들어보려고 한다.

해당 예제는 코틀린 마이크로서비스 개발이란 책을 보고 작성하였다.

 

만들고자 하는 API는 유저 관리(CRUD) 기능을 가지고 있으며, 별도로 인증인가 적용은 하지 않았다.

마찬가지로 JPA도 연동없이 목데이터를 사용하여 간단하게 만들려고 한다.

 

여담이지만 이전에 python에 관한 글 작성 시 코드 블럭에 오류 없이 잘 사용했었는데, 코틀린이나 자바로 코드 블럭을 적용해보니 

제대로 적용이 안되는 것 같다..... 이제 코드를 작성해보자 

Customer


package com.kotlin_rest_exam.rest_exam

data class Customer(
        var id: Int = 0,
        var name: String = ""
)

 

먼저 데이터 클래스를 사용하여 Customer 객체를 정의하였다.

 

데이터 클래스는 코틀린에서 지원하는 클래스이다 해당 클래스를 사용하면 getter/setter를 자동을 생성해주며 toString, hashCode, equals를 자동으로 생성해주기 때문에  자바로 작성할때 보다 코드 수를 훨씬 줄일 수 있다.

(하지만 자바도 Lombok을 사용하면 데이터 클래스를 사용한 것 처럼 깔끔하게 작성할 수 있다.)

 

데이터 클래스를 사용하면 정말 간편해 보이지만, 코틀린은 모든 클래스가 디폴트로 final로 생성되며 데이터 클래스는 Open을 지정할 수 없기 때문에 데이터 클래스는 상속을 받지 못한다. 그렇기 때문에 dependency에 all-open 플러그인을 추가해주는 방법으로 해결 가능하다.

 

이 부분은 추후 JPA를 사용하여 구현할때 다시 정리하겠다.

 

만약 JPA를 사용한다고 한다면, 해당 클래스에 @Entity 애노테이션을 붙여주면 된다.

Interface


package com.kotlin_rest_exam.rest_exam

interface CustomerService {
    fun getCustomer(id: Int): Customer?
    fun createCustomer(customer: Customer)
    fun deleteCustomer(id: Int)
    fun updateCustomer(id: Int, customer: Customer)
    fun searchCustomers(name: String): List<Customer>
}

 

이제 사용자에 대한 쿼리 문을 인터페이스로 구현하였다.

간단한 CRUD 형태로 작성하였으며, Read 같은 경우 검색해서 조회하는 기능도 추가하였다.

 

만약 JPA 사용 시, 해당 인터페이스에 @Repository 애노테이션을 붙여주면 된다.

Implement interface


package com.kotlin_rest_exam.rest_exam

import org.springframework.stereotype.Component
import java.util.concurrent.ConcurrentHashMap

@Component
class CustomerServiceImpl : CustomerService {
    companion object {
        val initialCustomers = arrayOf(
                Customer(1,"Kotlin"),
                Customer(2,"Lotlin"),
                Customer(3,"Jotlin")
        )
    }

    val customers = ConcurrentHashMap<Int, Customer>(initialCustomers.associateBy(Customer::id))

    override fun getCustomer(id: Int) = customers[id]

    override fun createCustomer(customer: Customer) {customers[customer.id] = customer}

    override fun deleteCustomer(id: Int) {customers.remove(id)}

    override fun updateCustomer(id: Int, customer: Customer) {customers[customer.id] = customer}

    override fun searchCustomers(name: String): List<Customer> = customers
            .filter{it.value.name.contains(name, true)}
            .map(Map.Entry<Int,Customer>::value).toList()
}

 

Implement interface는 위에서 구현한 인터페이스의 구현체이다.

인터페이스에서 정의한 함수들에 대하여 어떻게 쿼리를 할지에 대하여 작성 후 Override 하였다.

Controller


package com.kotlin_rest_exam.rest_exam

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.convert.Delimiter
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

import java.util.concurrent.ConcurrentHashMap

@RestController
class CustomerController {
    @Autowired
    private lateinit var customerService: CustomerService

    @GetMapping("/customers", produces = ["application/json"])
    fun getCustomers(@RequestParam(
            required = false,
            defaultValue = ""
    )name: String): ResponseEntity<List<Customer?>> = ResponseEntity
            .ok()
            .body(customerService.searchCustomers(name))

    @GetMapping("/customer/{id}", produces = ["application/json"])
    fun getCustomer(@PathVariable id:Int) = ResponseEntity
            .ok()
            .body(customerService.getCustomer(id))

    @PostMapping("/customer")
    fun createCustomer(@RequestBody customer:Customer): ResponseEntity<Any> {
        customerService.createCustomer(customer)
        return ResponseEntity
                .ok()
                .body(true)
    }

    @DeleteMapping("/customer/{id}")
    fun deleteCustomer(@PathVariable id:Int): ResponseEntity<Any>{
        if (customerService.getCustomer(id) != null) {
            customerService.deleteCustomer(id)
            return ResponseEntity
                    .ok()
                    .build()
        }
        throw NotFoundException("customer", "customer id: $id")
    }

    @PutMapping("/customer/{id}")
    fun updateCustomer(
            @PathVariable id:Int,
            @RequestBody customer:Customer
    ): ResponseEntity<Any> {
        if (customerService.getCustomer(id) != null) {
            customerService.updateCustomer(id,customer)
            return ResponseEntity
                    .ok()
                    .body(true)
        }
        throw NotFoundException("customer", "customer id: $customer.id")
    }
}

 

위 코드를 살펴보면 CustomerController 클래스에 CRUD 기능을 가지고 있는 함수를 구현했다.

 

그 중 가장 최상단에 있는 getCustomer 함수를 살펴보자

 

먼저, 요청 받은 건에 대하여 Json 형식으로 응답하기 위해 선언한 클래스에 @RestController 애노테이션을 사용하였다.

 

그 다음 요청 받을 엔드포인트를 정의하기 위해 @GetMapping 애노테이션을 사용하여 value에 URl를 지정하고, produces 옵션을 사용하여 content-type을 정의하였다.

 

각각 함수에 Http 상태 코드를 지정하기 위해 ResponseEntity 인터페이스를 상속 시켰다.

 

@PathVariable 애노테이션을 사용하여 요청받은 URL에서 ID 값을 받아와서 Implement Interface에서 구현한 함수에 파라미터로 넣어주는 것을 볼 수 있다,

 

실행 결과


 서버를 실행시킨 후 간단하게 httpie를 사용해서 Get 요청만 확인한 결과 정상적으로 응답하는 것을 확인할 수 있다,

 

Comments