REST란?
Representational State Transfer 줄임말로, 서버에 존재하는 데이터들을 이름으로 구분하고, 데이터를 주고 받는 행위들을 의미한다.
REST API를 구성하는건 다음과 같다.
- url
- url만 봐도 어떤 자원에 접근하는지 명시된다.
- ex) /devices : 서버에 존재하는 devices들에 접근한다.
- ex) /devices/1 : 서버에 존재하는 devices들중 id 1번에 해당하는 것에 접근한다.
- ex) /devices/1/value : 1번 id의 device의 value에 접근한다.
- url만 봐도 어떤 자원에 접근하는지 명시된다.
- method
- GET : 데이터를 가져온다.
- POST : 데이터를 생성한다.
- PATCH : 데이터를 수정한다.
- PUT : 데이터가 있으면 수정, 없으면 생성
- DELETE: 데이터 삭제
- ...(더 있지만 생략)
Controller 설계
현재 프로젝트에선 모든 method에 대해서 API가 필요 없어서 다음과 같이 코드를 작성한다.
package com.studuy.study.device
import org.springframework.http.MediaType
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
class DeviceContorller(
private val deviceService: DeviceService
) {
@GetMapping("/v1/devices")
fun getDevices(): Iterable<Device> {
return deviceService.getAllDevice()
}
@PostMapping("/v1/devices")
fun createDevice(): Device {
return deviceService.createDevice()
}
@GetMapping("/v1/devices/{deviceId}")
fun getDevice(@PathVariable("deviceId") deviceId: Long): Device {
return deviceService.getDevice(deviceId)
}
@PatchMapping("/v1/devices/{deviceId}/value")
fun updateDeviceValue(
@PathVariable("deviceId") deviceId: Long,
@RequestBody @Validated value: String
): Device {
return deviceService.updateDeviceValue(deviceId, value)
}
@PatchMapping("/v1/devices/{deviceId}/command")
fun updateDeviceCommand(
@PathVariable("deviceId") deviceId: Long,
@RequestBody @Validated command: String
): Device {
return deviceService.updateDeviceCommand(deviceId, command)
}
}
- 설명
- @RestController
- 해당 class가 Rest API를 controll하는 역할이라는 걸 명시. @Controller는 Viewer와 매핑되는 다른 어노테이션이니 주의하자
- @RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
- 해당 클래스로 들어오고 나가는 데이터들이 json형식을 따르게 해준다.
- 요청 헤더에 “Contents-type: application/json”이 없으면 에러가 난다.
- @GetMapping
- 해당 url에 맞는 요청이 들어왔을 때 해당 함수를 실행한다.
- @PathVariable
- url에 {}로 표현되는 url경로상의 변수를 받아와 준다.
- @RequestBody
- 요청에 body를 가져와 변수에 대입해준다.
- @Validated
- 변수에 들어간 데이터가 유요한 형식인지 확인해 준다.
- @RestController
이제 controller까지 다 만들었으니 API는 모두 완성 되었다. 한번 서버를 돌려보자.
itellij에선 바로 실행할 수 있지만... 그래도 terminal 유저를 위해...
cd ~/{your workspace}
./gradlew build && java -jar build/libs/study-0.0.1-SNAPSHOT.jar
API가 잘 만들어 졌는지 확인하기 위해 curl로 요청을 보내면 잘 작동하는걸 확인 할 수 있다.
curl -X POST <http://localhost:8080/v1/devices>
curl -X PATCH <http://localhost:8080/v1/devices/1/command> -d "test command" -H "Content-Type: application/json"
이를 문서화 하기 위해 swagger라는게 존재하는데 이를 사용하기 위해 다음과 같이 build.gradle.kts를 수정한다.
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
val kotlinVersion = "1.6.10"
id("org.springframework.boot") version "2.6.3"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
id("org.asciidoctor.convert") version "1.5.8"
kotlin("jvm") version kotlinVersion
kotlin("plugin.spring") version kotlinVersion
kotlin("plugin.jpa") version kotlinVersion
}
group = "com.studuy"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
}
repositories {
mavenCentral()
}
val snippetsDir by extra { file("build/generated-snippets") }
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-mustache")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
//추가
implementation("org.springdoc:springdoc-openapi-ui:1.6.4")
runtimeOnly("com.h2database:h2")
runtimeOnly("org.springframework.boot:spring-boot-devtools")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
allOpen {
annotation("javax.persistence.Entity")
annotation("javax.persistence.MappedSuperclass")
annotation("javax.persistence.Embeddable")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.test {
outputs.dir(snippetsDir)
}
tasks.asciidoctor {
inputs.dir(snippetsDir)
dependsOn(tasks.test)
}
그리고 main/resources에 application.yaml을 만들고 다음과 같이 작성한다.
springdoc:
swagger-ui:
path: /api
- 설명
- swagger-ui 의 base url을 /api로 설정하였다.
마지막으로 Controller를 다음과 같이 수정한다.
package com.studuy.study.device
import io.swagger.v3.oas.annotations.Operation
import org.springframework.http.MediaType
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
class DeviceContorller(
private val deviceService: DeviceService
) {
@Operation(summary = "Device list를 주는 API")
@GetMapping("/v1/devices")
fun getDevices(): Iterable<Device> {
return deviceService.getAllDevice()
}
@Operation(summary = "Device를 생성하는 API")
@PostMapping("/v1/devices")
fun createDevice(): Device {
return deviceService.createDevice()
}
@Operation(summary = "Device data를 주는 API")
@GetMapping("/v1/devices/{deviceId}")
fun getDevice(@PathVariable("deviceId") deviceId: Long): Device {
return deviceService.getDevice(deviceId)
}
@Operation(summary = "Device의 value를 수정하는 API")
@PatchMapping("/v1/devices/{deviceId}/value")
fun updateDeviceValue(
@PathVariable("deviceId") deviceId: Long,
@RequestBody @Validated value: String
): Device {
return deviceService.updateDeviceValue(deviceId, value)
}
@Operation(summary = "Device의 command를 수정하는 API")
@PatchMapping("/v1/devices/{deviceId}/command")
fun updateDeviceCommand(
@PathVariable("deviceId") deviceId: Long,
@RequestBody @Validated command: String
): Device {
return deviceService.updateDeviceCommand(deviceId, command)
}
}
- 설명
- @Operation
- 각 함수가 API operation이라고 명시, swagger에 등록된다.
- @Operation
다 수정한뒤 서버를 다시 빌드하고 실행시킨뒤 브라우저에서 localhost:8080/api로 들어가면 다음과 같은 화면이 보인다.
다음 포스팅에선 aws로 배포하는 법에 대해 포스팅할 예정이다.
728x90