Cách sử dụng Khung Nền Web Kotlin Ktor để Xây dựng Dịch Vụ RESTful API - Net79 Club Game Bài Uy Tín Nhất 2024

| Apr 21, 2025 min read

29 tháng Rio88 Game Bài Twin 9 năm 2023 Máy tính

Trong hai bài viết trước “Cách sử dụng Spring Boot và Kotlin để xây dựng dịch vụ RESTful API?” và “Cách sử dụng bộ công cụ HTTP Kotlin http4k để xây dựng dịch vụ RESTful API?”, chúng tôi đã giới thiệu cách sử dụng Spring Boot và http4k trong Kotlin để phát triển các API RESTful. Bài viết này sẽ tập trung vào việc làm thế nào để sử dụng khung nền web chính thức của Kotlin, Ktor, để phát triển API RESTful.

Bài viết sẽ lấy ví dụ về việc phát triển các API thêm, xóa, sửa, tra cứu cho đối tượng User nhằm học hỏi cách sử dụng Ktor. Dự án mẫu sử dụng Gradle để quản lý, cấu trúc dự án vẫn theo mô hình MVC ba lớp phổ biến nhất trong ngành; để làm nổi bật trọng tâm, bài viết không đề cập đến cơ sở dữ liệu và tầng DAO, thay vào đó sử dụng một danh sách List trong tầng Service làm nơi lưu trữ dữ liệu; để gần với tình huống thực tế hơn, ví dụ dự án này sử dụng Kodein để thực hiện tiêm phụ thuộc.

Toàn bộ bài viết được chia thành ba phần: giới thiệu cấu trúc dự án, phân tích sơ lược mã nguồn dự án, và kiểm thử API. Mong rằng sau khi đọc bài viết này, bạn sẽ có cái nhìn tổng quan về cách sử dụng Ktor để phát triển API.

Trước khi bắt đầu, hãy liệt kê các phiên bản phần mềm hoặc khung nền mà bài viết sử dụng:

Gradle: 8.3
Kotlin: 1.9.10
JDK: Amazon Corretto 17.0.8
Ktor: 2.3.4

1. Giới thiệu Cấu Trúc Dự Án

Dự án sử dụng Gradle để quản lý, cấu trúc dự án như sau:

ktor-restful-service-demo
|--- src/main/
|  |--- resources/
|  |  |--- application.conf
|  |  \--- logback.xml
|  \--- kotlin/
|     \--- com.example.demo/
|       |--- route/
|       |  \--- UserRoute.kt
|       |--- service/
|       |  |--- UserService.kt
|       |--- code/
|       |  \--- ErrorCodes.kt
|       |--- model/
|       |  |--- ErrorResponse.kt
|       |  \--- User.kt
|       |--- plugin/
|       |  |--- Routing.kt
|       |  \--- Serialization.kt
|       |--- conf/
|       |  \--- KodeinConf.kt
|       \--- DemoApplication.kt
...
|--- gradle/
|--- gradlew
\--- build.gradle.kts

Như có thể thấy, thư mục gốc chứa tệp cấu hình Gradle build.gradle.kts, lệnh Gradle gradlew và thư mục Gradle Wrapper gradle; tiếp theo là thư mục cấu hình src/main/resources và thư mục mã nguồn src/main/kotlin. Thư mục src/main/resources có hai tệp: application.conflogback.xml, lần lượt là tệp cấu hình cho máy chủ Ktor và tệp cấu hình ghi nhật ký Logback. Sau đây là một số thư mục con bên dưới src/main/kotlin:

  • route Tương tự như tầng Controller của các khung nền khác, dùng để cấu hình định tuyến Ktor.
  • service Tầng Service, tất cả logic kinh doanh chính được viết ở đây.
  • code Thư mục chứa lớp liệt kê ErrorCodes.kt, ví dụ dự án này sử dụng loại liệt kê này để lưu trữ mọi thông tin phản hồi lỗi.
  • model Thư mục chứa các lớp mô hình dữ liệu.
  • plugin Thư mục chứa các plugin Ktor, dùng để cấu hình đường dẫn gốc và phương thức chuỗi hóa.
  • conf Thư mục chứa các lớp cấu hình, ví dụ cấu hình khung tiêm phụ thuộc Kodein KodeinConf.kt nằm tại đây.

Ngoài những gói này, src/main/kotlin còn có một tệp DemoApplication.kt, là điểm nhập của chương trình.

2. Phân Tích Mã Nguồn Dự Án

Phần trước đã giới thiệu cấu trúc thư mục và ý nghĩa của các gói trong ví dụ dự án, bây giờ chúng ta sẽ phân tích sơ lược tệp cấu hình Gradle và mã nguồn trong các gói.

2.1 Tệp cấu hình Gradle

Ví dụ dự án sử dụng Gradle để quản lý, nội dung tệp cấu hình build.gradle.kts như sau:

// build.gradle.kts
plugins {
  kotlin("jvm") version "1.9.10"
  id("io.ktor.plugin") version "2.3.4"
}
application {
  mainClass.set("com.example.demo.DemoApplicationKt")
}
repositories {
  mavenCentral()
}
dependencies {
  implementation("io.ktor:ktor-server-core")
  implementation("io.ktor:ktor-server-netty")
  implementation("io.ktor:ktor-server-content-negotiation")
  implementation("io.ktor:ktor-serialization-jackson")
  implementation("org.kodein.di:kodein-di:7.20.2")
  implementation("ch.qos.logback:logback-classic:1.4.11")
}

Như có thể thấy, tệp này chỉ định phiên bản Kotlin là 1.9.10, phiên bản Ktor là 2.3.4; điểm nhập chương trình là DemoApplication.kt; kho chứa là Maven Repository, các phụ thuộc bao gồm io.ktor:ktor-server-core (thành phần cốt lõi của Ktor), io.ktor:ktor-server-netty (động cơ máy chủ Netty được sử dụng), io.ktor:ktor-server-content-negotiation (dùng để chuyển đổi chuỗi hóa và ngược chuỗi hóa giữa các đối tượng Kotlin và định dạng JSON), io.ktor:ktor-serialization-jackson (thực hiện chuỗi hóa JSON bằng Jackson), org.kodein.di:kodein-di:7.20.2 (gói tiêm phụ thuộc Kodein), và ch.qos.logback:logback-classic:1.4.11 (gói in ra nhật ký Logback).

2.2 Mã nguồn trong gói plugin

Gói plugin có hai tệp: Routing.ktSerialization.kt, lần lượt dùng để cấu hình đường dẫn gốc và phương thức chuỗi hóa. Mã nguồn của Routing.kt như sau:

// src/main/kotlin/com/example/demo/plugin/Routing.kt
package com.example.demo.plugin
import com.example.demo.route.userRouting
import io.ktor.server.application.*
import io.ktor.server.routing.*
fun Application.configureRouting() {
  routing {
    userRouting()
  }
}

Như có thể thấy, đoạn mã trên chịu trách nhiệm cấu hình đường dẫn gốc của dự án, dự án này chỉ cấu hình một đường dẫn: userRouting(), nằm trong gói route, là quy tắc định tuyến cho User, chúng ta sẽ xem xét mã nguồn chi tiết sau. Mã nguồn của Serialization.kt như sau:

// src/main/kotlin/com/example/demo/plugin/Serialization.kt
package [m88vin - cổng game quốc tế](/post/muslim-funeral-reading/)  com.example.demo.plugin
import io.ktor.serialization.jackson.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
fun Application.configureSerialization() {
  install(ContentNegotiation) {
    jackson()
  }
}

Như có thể thấy, đoạn mã trên thiết lập Jackson làm phương thức chuỗi hóa và ngược chuỗi hóa nội dung.

2.3 Mã nguồn trong gói route

Route tương đương với Controller, chịu trách nhiệm nhận yêu cầu, gọi Service để xử lý, cuối cùng trả về phản hồi. Gói route của ví dụ dự án này chỉ có một tệp UserRoute.kt, mã nguồn của nó như sau:

// src/main/kotlin/com/example/demo/route/UserRoute.kt
package com.example.demo.route
import com.example.demo.code.ErrorCodes
import com.example.demo.conf.kodein
import com.example.demo.model.User
import com.example.demo.service.UserService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.kodein.di.instance
fun Route.userRouting() {
  val userService: UserService by kodein.instance()
  route("/users") {
    // liệt kê tất cả
    get {
      val users = userService.listAll()
      call.respond(users)
    }
    // lấy User theo ID
    get(Regex("/(?<id>\\d+)")) {
      val id = call.parameters["id"]!!.toLong()
      val user = userService.getById(id) ?: return@get call.respond(
          ErrorCodes.USER_NOT_FOUND.status,
          ErrorCodes.USER_NOT_FOUND.toErrorResponse()
      )
      call.respond(user)
    }
    // cập nhật
    patch {
      val user = call.receive<User>()
      userService.getById(user.id) ?: return@patch call.respond(
          ErrorCodes.USER_NOT_FOUND.status,
          ErrorCodes.USER_NOT_FOUND.toErrorResponse()
      )
      userService.update(user)
      call.respond(HttpStatusCode.NoContent)
    }
    // lưu
    post {
      val user = call.receive<User>()
      userService.getById(user.id)?.let {
        return@post call.respond(
            ErrorCodes.USER_ALREADY_EXISTS.status,
            ErrorCodes.USER_ALREADY_EXISTS.toErrorResponse()
        )
      }
      userService.save(user)
      call.respond(HttpStatusCode.Created)
    }
    // xóa theo ID
    delete(Regex("/(?<id>\\d+)")) {
      val id = call.parameters["id"]!!.toLong()
      userService.getById(id) ?: return@delete call.respond(
          ErrorCodes.USER_NOT_FOUND.status,
          ErrorCodes.USER_NOT_FOUND.toErrorResponse()
      )
      userService.deleteById(id)
      call.respond(HttpStatusCode.NoContent)
    }
  }
}

Như có thể thấy, đoạn mã trên userRouting là hàm mở rộng của Route, sử dụng cách tiêm Kodein để lấy được thể hiện của UserService; có năm API, lần lượt là: lấy tất cả User, lấy một User duy nhất, cập nhật User, tạo mới User, và xóa User; bên trong đều gọi UserService để thực hiện, đối với thông tin phản hồi lỗi, đều sử dụng loại liệt kê thống nhất ErrorCodes.kt.

Tiếp tục phần còn lại nếu cần thiết…