Skip to content

RESTful API 생성

Ktor를 사용하여 Kotlin에서 RESTful API를 생성하는 방법

코드 예제: tutorial-server-restful-api

사용된 플러그인:

Routing
Routing은 서버 애플리케이션에서 들어오는 요청을 처리하기 위한 핵심 플러그인입니다.
,
Static Content
스타일시트, 스크립트, 이미지 등과 같은 정적 콘텐츠를 제공하는 방법을 알아봅니다.
,
Content Negotiation
ContentNegotiation 플러그인은 클라이언트와 서버 간의 미디어 유형 협상과 특정 형식의 콘텐츠 직렬화/역직렬화라는 두 가지 주요 목적을 수행합니다.
, kotlinx.serialization

이 튜토리얼에서는 JSON 파일을 생성하는 RESTful API 예제를 통해 Kotlin과 Ktor를 사용하여 백엔드 서비스를 구축하는 방법을 설명합니다.

이전 튜토리얼
작업 관리자 애플리케이션을 구축하며 Ktor를 사용한 Kotlin의 라우팅 기초, 요청 처리 및 파라미터에 대해 알아봅니다.
에서는 유효성 검사, 에러 처리 및 유닛 테스트의 기초를 소개했습니다. 이번 튜토리얼에서는 이러한 주제를 확장하여 작업을 관리하는 RESTful 서비스를 만들어 보겠습니다.

다음 내용을 배우게 됩니다:

  • JSON 직렬화(serialization)를 사용하는 RESTful 서비스 생성하기.
  • Content Negotiation (콘텐츠 협상)
    ContentNegotiation 플러그인은 클라이언트와 서버 간의 미디어 유형 협상과 특정 형식의 콘텐츠 직렬화/역직렬화라는 두 가지 주요 목적을 수행합니다.
    프로세스 이해하기.
  • Ktor 내에서 REST API를 위한 라우트 정의하기.

사전 준비 사항

이 튜토리얼은 독립적으로 진행할 수 있지만,

요청을 처리하고 응답을 생성하는 방법
작업 관리자 애플리케이션을 구축하며 Ktor를 사용한 Kotlin의 라우팅 기초, 요청 처리 및 파라미터에 대해 알아봅니다.
을 배우기 위해 이전 튜토리얼을 먼저 완료하는 것을 강력히 권장합니다.

IntelliJ IDEA를 설치할 것을 권장하지만, 원하는 다른 IDE를 사용할 수도 있습니다.

Hello RESTful Task Manager

이 튜토리얼에서는 기존의 Task Manager(작업 관리자)를 RESTful 서비스로 다시 작성해 보겠습니다. 이를 위해 여러 Ktor

플러그인
플러그인은 직렬화, 콘텐츠 인코딩, 압축 등과 같은 공통 기능을 제공합니다.
을 사용합니다.

기존 프로젝트에 수동으로 추가할 수도 있지만, 새 프로젝트를 생성한 다음 이전 튜토리얼의 코드를 점진적으로 추가하는 것이 더 간단합니다. 진행하면서 모든 코드를 다시 반복할 것이므로 이전 프로젝트를 가지고 있을 필요는 없습니다.

  1. Ktor Project Generator로 이동합니다.

  2. Project artifact 필드에 프로젝트 아티팩트 이름으로 com.example.ktor-rest-task-app를 입력합니다. Ktor Project Generator에서 프로젝트 아티팩트 이름 지정

  3. Plugins 섹션에서 Add 버튼을 클릭하여 다음 플러그인들을 검색하고 추가합니다:

    1. Content Negotiation
    2. kotlinx.serialization
    3. Static Content

    Ktor Project Generator에서 플러그인 추가하기 플러그인을 추가하면 프로젝트 설정 아래에 모든 플러그인이 나열되는 것을 볼 수 있습니다. Ktor Project Generator의 플러그인 목록

  4. Download 버튼을 클릭하여 Ktor 프로젝트를 생성하고 다운로드합니다.

  1. IntelliJ IDEA에서 Ktor 프로젝트 열기, 탐색 및 실행 튜토리얼에서 설명한 대로 IntelliJ IDEA에서 프로젝트를 엽니다.

  2. src/main/kotlin으로 이동하여 model이라는 하위 패키지를 생성합니다.

  3. model 패키지 안에 새 Task.kt 파일을 생성합니다.

  4. Task.kt 파일을 열고 우선순위를 나타내는 enum과 작업을 나타내는 class를 추가합니다:

    kotlin

    이전 튜토리얼에서는 확장 함수를 사용하여 Task를 HTML로 변환했습니다. 이번에는 Task 클래스에 kotlinx.serialization 라이브러리의 Serializable 타입 어노테이션이 추가되었습니다.

  5. Routing.kt 파일을 열고 기존 코드를 아래 구현으로 교체합니다:

    kotlin

    이전 튜토리얼과 마찬가지로 URL /tasks에 대한 GET 요청 라우트를 만들었습니다. 이번에는 작업 목록을 수동으로 변환하는 대신 목록 자체를 반환하고 있습니다.

  6. IntelliJ IDEA에서 실행 버튼(intelliJ IDEA 실행 아이콘)을 클릭하여 애플리케이션을 시작합니다.

  7. 브라우저에서 http://0.0.0.0:8080/tasks로 이동합니다. 아래와 같이 JSON 형식의 작업 목록을 볼 수 있습니다:

  8. 브라우저 화면에 표시된 JSON 데이터

    상당히 많은 작업이 우리 대신 수행되고 있습니다. 정확히 무슨 일이 일어나고 있는 걸까요?

Content Negotiation(콘텐츠 협상) 이해하기

브라우저를 통한 Content Negotiation

프로젝트를 생성할 때

Content Negotiation
ContentNegotiation 플러그인은 클라이언트와 서버 간의 미디어 유형 협상과 특정 형식의 콘텐츠 직렬화/역직렬화라는 두 가지 주요 목적을 수행합니다.
플러그인을 포함했습니다. 이 플러그인은 클라이언트가 렌더링할 수 있는 콘텐츠 유형을 확인하고, 이를 현재 서비스가 제공할 수 있는 콘텐츠 유형과 매칭합니다. 그래서 Content Negotiation(콘텐츠 협상)이라는 용어를 사용합니다.

HTTP에서 클라이언트는 Accept 헤더를 통해 자신이 렌더링할 수 있는 콘텐츠 유형을 알립니다. 이 헤더의 값은 하나 이상의 콘텐츠 유형입니다. 위의 경우 브라우저에 내장된 개발자 도구를 사용하여 이 헤더의 값을 확인할 수 있습니다.

다음 예시를 살펴보세요:

*/*가 포함되어 있음에 유의하세요. 이 헤더는 HTML, XML 또는 이미지를 허용하지만, 그 외의 다른 모든 콘텐츠 유형도 허용하겠다는 신호입니다.

Content Negotiation 플러그인은 브라우저에 데이터를 다시 보낼 형식을 찾아야 합니다. 프로젝트의 생성된 코드 내부를 보면 src/main/kotlin 안에 Serialization.kt라는 파일이 있으며, 여기에는 다음 내용이 포함되어 있습니다:

kotlin

이 코드는 ContentNegotiation 플러그인을 설치하고 kotlinx.serialization 플러그인을 구성합니다. 이렇게 하면 클라이언트가 요청을 보낼 때 서버가 JSON으로 직렬화된 객체를 다시 보낼 수 있습니다.

브라우저의 요청의 경우, ContentNegotiation 플러그인은 JSON만 반환할 수 있다는 것을 알고 있고, 브라우저는 수신된 모든 것을 표시하려고 시도합니다. 따라서 요청이 성공합니다.

    프로덕션 환경에서는 일반적으로 JSON을 브라우저에 직접 표시하지 않습니다. 대신 브라우저에서 실행되는 JavaScript 코드가 요청을 수행한 다음 반환된 데이터를 SPA(Single Page Application)의 일부로 표시합니다. 일반적으로 이러한 종류의 애플리케이션은 React, Angular 또는 Vue.js와 같은 프레임워크를 사용하여 작성됩니다.

  1. 이를 시뮬레이션하기 위해 src/main/resources/static 내부의 index.html 페이지를 열고 기본 콘텐츠를 다음으로 교체합니다:

    html

    이 페이지는 HTML 폼과 빈 테이블을 포함하고 있습니다. 폼을 제출하면 JavaScript 이벤트 핸들러가 Accept 헤더를 application/json으로 설정하여 /tasks 엔드포인트로 요청을 보냅니다. 반환된 데이터는 역직렬화되어 HTML 테이블에 추가됩니다.

  2. IntelliJ IDEA에서 재실행 버튼(intelliJ IDEA 재실행 아이콘)을 클릭하여 애플리케이션을 다시 시작합니다.

  3. URL http://0.0.0.0:8080/static/index.html로 이동합니다. View The Tasks 버튼을 클릭하여 데이터를 가져올 수 있습니다:

    HTML 테이블로 표시된 작업들과 버튼을 보여주는 브라우저 창

GET 라우트 추가하기

이제 콘텐츠 협상 프로세스에 익숙해졌으니,

이전 튜토리얼
작업 관리자 애플리케이션을 구축하며 Ktor를 사용한 Kotlin의 라우팅 기초, 요청 처리 및 파라미터에 대해 알아봅니다.
의 기능을 이 프로젝트로 옮겨 보겠습니다.

작업 레포지토리 재사용

작업 레포지토리는 수정 없이 재사용할 수 있으므로, 이를 먼저 수행하겠습니다.

  1. model 패키지 안에 새 TaskRepository.kt 파일을 생성합니다.

  2. TaskRepository.kt를 열고 아래 코드를 추가합니다:

    kotlin

GET 요청 라우트 재사용

레포지토리를 만들었으므로 GET 요청을 위한 라우트를 구현할 수 있습니다. 작업을 HTML로 변환하는 것을 더 이상 걱정할 필요가 없으므로 이전 코드를 단순화할 수 있습니다:

  1. src/main/kotlin에 있는 Routing.kt 파일로 이동합니다.

  2. Application.configureRouting() 함수 내부의 /tasks 라우트 코드를 다음 구현으로 업데이트합니다:

    kotlin

    이제 서버는 다음과 같은 GET 요청에 응답할 수 있습니다:

    • /tasks는 레포지토리의 모든 작업을 반환합니다.
    • /tasks/byName/{taskName}은 지정된 taskName으로 필터링된 작업을 반환합니다.
    • /tasks/byPriority/{priority}는 지정된 priority로 필터링된 작업들을 반환합니다.
  3. IntelliJ IDEA에서 재실행 버튼(intelliJ IDEA 재실행 아이콘)을 클릭하여 애플리케이션을 다시 시작합니다.

기능 테스트하기

    브라우저에서 이러한 라우트를 테스트할 수 있습니다. 예를 들어, http://0.0.0.0:8080/tasks/byPriority/Medium으로 이동하면 Medium 우선순위를 가진 모든 작업이 JSON 형식으로 표시되는 것을 볼 수 있습니다:

    브라우저 창에서 JSON 형식으로 표시된 Medium 우선순위 작업들

    이러한 요청은 일반적으로 JavaScript에서 오기 때문에 더 세밀한 테스트가 바람직합니다. 이를 위해 Postman과 같은 전문 도구를 사용할 수 있습니다.

  1. Postman에서 URL이 http://0.0.0.0:8080/tasks/byPriority/Medium인 새 GET 요청을 만듭니다.

  2. Headers 패널에서 Accept 헤더의 값을 application/json으로 설정합니다.

  3. Send를 클릭하여 요청을 보내고 응답 뷰어에서 응답을 확인합니다.

    Postman에서 JSON 형식의 Medium 우선순위 작업을 보여주는 GET 요청

    IntelliJ IDEA Ultimate에서는 HTTP 요청 파일에서 동일한 단계를 수행할 수 있습니다.

  1. 프로젝트 루트 디렉토리에 새 REST Task Manager.http 파일을 생성합니다.

  2. REST Task Manager.http 파일을 열고 다음 GET 요청을 추가합니다:

    http
  3. IntelliJ IDEA 내에서 요청을 보내려면 옆에 있는 거터 아이콘(intelliJ IDEA 거터 아이콘)을 클릭합니다.

  4. 이것은 Services 도구 창에서 열리고 실행됩니다:

    HTTP 파일에서 JSON 형식의 Medium 우선순위 작업을 보여주는 GET 요청

NOTE

라우트를 테스트하는 또 다른 방법은 Kotlin Notebook 내에서 khttp 라이브러리를 사용하는 것입니다.

POST 요청을 위한 라우트 추가하기

이전 튜토리얼에서는 HTML 폼을 통해 작업을 생성했습니다. 하지만 이제 RESTful 서비스를 구축하고 있으므로 더 이상 그렇게 할 필요가 없습니다. 대신 대부분의 번거로운 작업을 대신 해줄 kotlinx.serialization 프레임워크를 활용할 것입니다.

  1. src/main/kotlin 내부의 Routing.kt 파일을 엽니다.

  2. 다음과 같이 Application.configureRouting() 함수에 새 POST 라우트를 추가합니다:

    kotlin

    다음의 새 import 문들을 추가합니다:

    kotlin

    POST 요청이 /tasks로 전송되면, kotlinx.serialization 프레임워크가 요청 본문을 Task 객체로 변환하는 데 사용됩니다. 성공하면 작업이 레포지토리에 추가됩니다. 역직렬화 프로세스가 실패하면 서버는 SerializationException을 처리하고, 작업이 중복된 경우 IllegalStateException을 처리합니다.

  3. 애플리케이션을 다시 시작합니다.

  4. Postman에서 이 기능을 테스트하려면 URL http://0.0.0.0:8080/tasks로 새 POST 요청을 만듭니다.

  5. Body 패널에 새 작업을 나타내는 다음 JSON 문서를 추가합니다:

    json
    새 작업을 추가하기 위한 Postman의 POST 요청
  6. Send을 클릭하여 요청을 보냅니다.

  7. http://0.0.0.0:8080/tasks로 GET 요청을 보내 작업이 추가되었는지 확인할 수 있습니다.

  8. IntelliJ IDEA Ultimate 내에서 HTTP 요청 파일에 다음 내용을 추가하여 동일한 단계를 수행할 수 있습니다:

    http

삭제 기능 지원 추가하기

서비스에 기본 작업들을 거의 다 추가했습니다. 이러한 작업들은 종종 CRUD(Create, Read, Update, and Delete) 작업으로 요약됩니다. 이제 삭제(Delete) 작업을 구현하겠습니다.

  1. TaskRepository.kt 파일에서 TaskRepository 객체 내에 이름을 기반으로 작업을 제거하는 다음 메서드를 추가합니다:

    kotlin
  2. Routing.kt 파일을 열고 DELETE 요청을 처리할 엔드포인트를 routing() 함수에 추가합니다:

    kotlin
  3. 애플리케이션을 다시 시작합니다.

  4. HTTP 요청 파일에 다음 DELETE 요청을 추가합니다:

    http
  5. IntelliJ IDEA 내에서 DELETE 요청을 보내려면 옆에 있는 거터 아이콘(intelliJ IDEA 거터 아이콘)을 클릭합니다.

  6. Services 도구 창에서 응답을 확인할 수 있습니다:

    HTTP 요청 파일에서의 DELETE 요청

Ktor Client로 유닛 테스트 생성하기

지금까지는 애플리케이션을 수동으로 테스트했지만, 이미 눈치채셨겠지만 이 방식은 시간이 많이 걸리고 규모를 확장하기 어렵습니다. 대신 내장된 client 객체를 사용하여 JSON을 가져오고 역직렬화하는

JUnit 테스트
특수 테스트 엔진을 사용하여 서버 애플리케이션을 테스트하는 방법을 알아봅니다.
를 구현할 수 있습니다.

  1. src/test/kotlin 안에 있는 ServerTest.kt 파일을 엽니다.

  2. ServerTest.kt 파일의 내용을 다음으로 교체합니다:

    kotlin

    서버에서 했던 것과 동일한 방식으로 PluginsContentNegotiationkotlinx.serialization 플러그인을 설치해야 함에 유의하세요.

  3. build.gradle.kts 파일에 다음 종속성을 추가합니다:

    kotlin

JsonPath로 유닛 테스트 생성하기

Ktor 클라이언트나 유사한 라이브러리로 서비스를 테스트하는 것은 편리하지만, 품질 보증(QA) 관점에서는 단점이 있습니다. JSON을 직접 처리하지 않는 서버는 JSON 구조에 대한 가정이 확실하다고 확신할 수 없습니다.

예를 들어 다음과 같은 가정들입니다:

  • 실제로는 object가 사용되는데 값이 array에 저장되고 있다고 가정하는 경우.
  • 속성이 실제로는 strings인데 numbers로 저장되고 있다고 가정하는 경우.
  • 멤버가 선언된 순서대로 직렬화되지 않는데 순서대로 된다고 가정하는 경우.

서비스를 여러 클라이언트가 사용할 예정이라면 JSON 구조에 대한 확신을 갖는 것이 중요합니다. 이를 위해 Ktor Client를 사용하여 서버에서 텍스트를 가져온 다음 JSONPath 라이브러리를 사용하여 이 콘텐츠를 분석합니다.

  1. build.gradle.kts 파일의 dependencies 블록에 JSONPath 라이브러리를 추가합니다:

    kotlin
  2. src/test/kotlin 폴더로 이동하여 새 ApplicationJsonPathTest.kt 파일을 생성합니다.

  3. ApplicationJsonPathTest.kt 파일을 열고 다음 내용을 추가합니다:

    kotlin

    JsonPath 쿼리는 다음과 같이 작동합니다:

    • $[*].name은 "문서를 배열로 처리하고 각 항목의 name 속성 값을 반환하라"는 의미입니다.
    • $[?(@.priority == '$priority')].name은 "우선순위가 제공된 값과 동일한 배열의 모든 항목에 대해 name 속성 값을 반환하라"는 의미입니다.

    이와 같은 쿼리를 사용하여 반환된 JSON에 대한 이해가 올바른지 확인할 수 있습니다. 코드 리팩토링이나 서비스 재배포를 수행할 때, 현재 프레임워크에서의 역직렬화에 문제가 없더라도 직렬화 과정에서의 모든 변경 사항을 식별할 수 있습니다. 이를 통해 공개적으로 사용 가능한 API를 자신 있게 다시 배포할 수 있습니다.

다음 단계

축하합니다! 이제 Task Manager 애플리케이션을 위한 RESTful API 서비스를 성공적으로 완성했으며, Ktor Client와 JsonPath를 사용한 유닛 테스트의 세부 사항을 배웠습니다.

다음 튜토리얼
Kotlin과 Ktor, Thymeleaf 템플릿을 사용하여 웹사이트를 구축하는 방법을 알아봅니다.
로 넘어가서 작성한 API 서비스를 재사용하여 웹 애플리케이션을 구축하는 방법을 배워보세요.