educative - kotlin - 16

Sung Jun Jin·2021년 7월 4일
0
post-custom-banner

Programming Spring Applications with Kotlin

Creating a Starter Project

코틀린 + Spring을 사용하여 첫 프로젝트의 국룰인 RESTful한 TODO 리스트를 만들어보자.

task 엔드포인트에서 구현해야할 HTTP method는 다음과 같다

  • GET: 모든 TODO 리스트를 가져옴
  • POST: 새로운 TODO 리스트를 추가함
  • DELETE: 해당 TODO 리스트를 제거함

우선 코틀린의 main() 메소드로 엔트리 포인트를 만들어준다.

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class TodoApplication

fun main(args: Array<String>) {
	runApplication<TodoApplication>(*args)
}

위에서 @SpringBootApplication 어노테이션은 스프링 부트의 가장 기본적인 설정을 선언해주는 역할을 한다.

프로젝트 빌드가 제대로 되어있는지를 확인해보기 위해 서버를 돌려보자. Maven, Gradle의 사용 여부에 따라서 커맨드는 달라진다

  • Maven
./mvnw clean install
java -jar target/todo-0.0.1-SNAPSHOT.jar
  • Gradle
./gradlew build
java -jar build/libs/todo-0.0.1-SNAPSHOT.jar

Creating a Controller

구현하고자 하는 task 엔드포인트의 첫번째 함수 GET의 초안을 작성해보자. 이 함수는 간단하게 호출 시 "to be implemented" 라는 문자열 응답을 준다.

package com.agiledeveloper.todo

import org.springframework.web.bind.annotation.* 
import org.springframework.http.ResponseEntity

@RestController 
@RequestMapping("/task") 
class TaskController {
    @GetMapping
    fun tasks() = "to be implemented" 
}

각각의 어노테이션에 대한 설명이다

  • @RestController은 요청에 대한 응답을 JSON reponse로 처리해준다.
  • @RequestMapping은 엔드포인트의 URI를 정의해준다.
  • @GetMapping 어노테이션은 해당 엔드포인트의 URI를 지정해주는 역할을 한다.

curl을 사용해 request를 날려보자

curl -w "\n" http://localhost:8080/task

Creating an Entity Class

TODO 리스트의 데이터를 책임질 엔티티를 생성해주자.

package com.agiledeveloper.todo

import javax.persistence.*

@Entity
data class Task(@Id @GeneratedValue val id: Long, val description: String)

첫번째로 해당 엔티티의 pk인 id 프로퍼티를 정의해준다. @Id@GeneratedValue 어노테이션은 해당 프로퍼티를 pk로 선정하고 unique value를 DB에서 자동 생성되게 해준다.

두번째 프로퍼티인 description의 타입은 String이다.

해당 엔티티를 애초에 데이터 클래스로 구현했으므로 equals(), hashCode(), toString()과 같은 함수들은 자동적으로 구현이 되어 있다.

Creating a Repository Interface

코틀린 스프링에서 Repository란 여러 data source들을 관리하는데 생성하는 클래스이다.
CrudRepository는 엔티티 클래스에 대한 CRUD 기능을 제공해준다. 이걸 사용해보자.

package com.agiledeveloper.todo

import org.springframework.data.repository.CrudRepository 

interface TaskRepository : CrudRepository<Task, Long>

CrudRepository<Task, Long>을 상속받은 TaskRepository 인터페이스의 첫번째 parametric type인 Task는 엔티티 타입을 지정해주고, 두번째 parametric type인 Long은 PK에 대한 타입을 지정해준다.

Creating a Service

이제 엔티티 정의가 모두 마무리되었으니 서비스 레이어를 작업해보자.

TaskService 클래스를 정의해준다. 서비스 클래스를 담당하고 있으므로 Service 어노테이션을 달아준다. Transactional 어노테이션은 장고의 @transcation.atomic과 비슷하다고 볼 수 있다.

package com.agiledeveloper.todo

import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
            
@Service
@Transactional
class TaskService(val repository: TaskRepository) {
  fun getAll() = repository.findAll()
  
  fun save(task: Task) = repository.save(task)
  
  fun delete(id: Long):  Boolean {
    val found = repository.existsById(id)
    
    if (found) {
      repository.deleteById(id)
    }
    
    return found
  }
}

전체적으로 CrudRepository를 상속받은 CRUD 함수들이므로 많은 추가구현은 필요없다. 각 함수에 대한 요약이다.

  • getAll() 함수는 CrudRepositoryfindAll() 함수를 호출해 전체 TODO 리스트를 가지고 온다.

  • save() 함수는 CrudRepository에서 기본적으로 제공하는 save() 메소드를 사용해준다.

  • delete() 함수는 매개변수로 들어온 id에 해당하는 로우를 deleteById()를 통해 제거한다.

Integrating the Service with Controller

이제 모든 CRUD를 구현했으므로 앞서 테스트용으로 구현했던 컨트롤러로 다시 돌아가 완성을 시켜주자.

package com.agiledeveloper.todo

import org.springframework.web.bind.annotation.*
import org.springframework.http.ResponseEntity

@RestController
@RequestMapping("/task")

class TaskController(val service: TaskService) {
  
  @GetMapping
  fun tasks() = ResponseEntity.ok(service.getAll())

  @PostMapping
  fun create(@RequestBody task: Task): ResponseEntity<String> {
    val result = service.save(task)

    return ResponseEntity.ok(
      "added task with description ${result.description}")
  }

  @DeleteMapping("/{id}")
  fun delete(@PathVariable id: Long) = if (service.delete(id)) {
    ResponseEntity.ok("Task with id $id deleted")
  } else {
    ResponseEntity.status(404).body("Task with id $id not found")
  }

각각의 메소드에 해당하는 어노테이션 @GetMapping, @PostMapping, @DeleteMapping을 구현한 메소드에 매핑시켜준다.

get() 메소드는 서비스단에서 앞서 구현한 getAll() 함수를 호출해 모든 TODO 리스트를 가져온다.

create() 메소드는 @RequestBody 어노테이션으로 Task 형의 post body를 가져와 save() 메소드를 호출한다.

delete() 메소드는 @PathVariable 어노테이션으로 매핑된 url parameter인 id를 가지고 해당 id에 대한 delete() 메소드를 수행한다. 삭제 성공 여부에 따라 200, 404 응답을 리턴해준다.

Taking It for a Ride

이제 구현한 모든 HTTP 메소드를 호출해보자

  • GET
curl -w "\n" -X GET http://localhost:8080/task 

[]
  • POST
curl -w "\n" -X POST \
-H "Content-Type: application/json" \
-d '{"description": "write code"}' http://localhost:8080/task

curl -w "\n" -X POST \
-H "Content-Type: application/json" \
-d '{"description": "test"}' http://localhost:8080/task 


added task with description write code
added task with description test
  • DELETE
curl -w "\n" -X DELETE http://localhost:8080/task/1

curl -w "\n" -X DELETE http://localhost:8080/task/10

Task with id 1 deleted
Task with id 10 not found

마지막으로 GET

curl -w "\n" -X GET http://localhost:8080/task

[{"id":2,"description":"test"}]
profile
주니어 개발쟈🤦‍♂️
post-custom-banner

2개의 댓글

comment-user-thumbnail
2021년 7월 29일

화이팅

1개의 답글