В мире разработки программного обеспечения создание эффективных и масштабируемых веб-API является ключевой задачей для большинства проектов. Использование правильного инструментария и языка программирования может существенно облегчить этот процесс. В этой статье мы рассмотрим, как использование языка программирования Kotlin с фреймворком Spring Boot упрощает создание REST API, обеспечивая при этом высокую производительность и надежность.

Почему Kotlin и Spring Boot?

Kotlin, разработанный компанией JetBrains, и Spring Boot, один из наиболее популярных фреймворков для создания веб-приложений на языке Java, образуют мощное сочетание для разработки REST API. Kotlin предоставляет чистый и современный синтаксис, что упрощает разработку и делает код более читаемым. Spring Boot, с другой стороны, предоставляет множество готовых компонентов и упрощает конфигурацию, что позволяет сосредоточиться на бизнес-логике приложения.

Преимущества использования Kotlin и Spring Boot для разработки REST API:

  1. Простота и скорость разработки: Kotlin позволяет писать более компактный код по сравнению с Java, что уменьшает количество ошибок и ускоряет разработку. Spring Boot, в свою очередь, предоставляет множество готовых компонентов, таких как автоматическая конфигурация, встроенные серверы приложений и механизмы управления зависимостями, что значительно упрощает создание новых проектов.

  2. Интеграция с существующим кодом на Java: Kotlin полностью совместим с Java, что позволяет использовать существующие библиотеки и код на Java без изменений. Это особенно важно для проектов, которые уже используют Java, так как разработчики могут постепенно переходить на Kotlin, не изменяя существующего кода.

  3. Богатый экосистем фреймворка Spring: Spring Boot предоставляет широкий выбор инструментов для создания веб-приложений, включая модули для работы с базами данных, безопасностью, микросервисной архитектурой и многое другое. Это позволяет разработчикам быстро создавать и масштабировать веб-API любой сложности.

Пример создания REST API с использованием Kotlin и Spring Boot

Давайте рассмотрим пример простого веб-API для управления задачами (TODO list) с использованием Kotlin и Spring Boot .

Переходим на сайт https://start.spring.io/ и конфигурируем приложение. Нам понадобятся Spring Web, Spring Data JPA, H2 Database, Spring Boot Devtools

конфигурация Spring приложения
конфигурация Spring приложения

Открываем наше сконфигурированное приложение в редакторе кода (я использую IntelliJ IDEA) и приступаем.

Создадим data class (src/main/kotlin/.../repository/model/Task.kt)

@Entity
@Table(name = "task")
data class Task(
  @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
  val id: Long?, 
  @Column(name = "name", unique = true, nullable = false, length = 100)
  val name: String
)

Создадим repository, который унаследуем от JpaRepository (src/main/kotlin/.../repository/TaskRepository.kt)

@Repository
interface TaskRepository : JpaRepository<Task, Long>

Создадим класс TaskNotFoundException для обработки исключений, когда наша задача не будет найдена (src/main/kotlin/.../exception/TaskNotFoundException.kt)

class TaskNotFoundException(httpStatus: HttpStatus, message: String): Exception ()

Создадим слой Service, в котором опишем логику нашего приложения (src/main/kotlin/.../service/TaskService.kt)

@Service
class TaskService(private val taskRepository: TaskRepository) {
    // Получаем список всех задач
    fun getAllTasks(): List<Task> = taskRepository.findAll()

    //Получаем задачу по taskId
    fun getTaskById(taskId: Long): Optional<Task> = taskRepository.findById(taskId)
        ?: throw TaskNotFoundException(HttpStatus.NOT_FOUND, "Задача с $taskId не найдена")
    
    //Создаем задачу
    fun createTask(task: Task): Task = taskRepository.save(task)

    //Обновляем задачу, если она найдена, если нет выбрасываем исключение
    fun updateTask(taskId: Long, task: Task): Task {
        return if (taskRepository.existsById(taskId)) {
            taskRepository.save(
                Task(
                    id = task.id,
                    name = task.name
                )
            )
        } else throw TaskNotFoundException(HttpStatus.NOT_FOUND, "Задача с $taskId не найдена")
    }

    //Удаляем задачу
    fun deleteTaskById(taskId: Long) {
        return if (taskRepository.existsById(taskId)) {
            taskRepository.deleteById(taskId)
        } else throw TaskNotFoundException(HttpStatus.NOT_FOUND, "Задача с $taskId не найдена")
    }
}

Создадим контроллер (src/main/kotlin/.../controller/TaskController.kt). Думаю здесь уже понятно, комментарии излишни

@RestController
class EmployeeController(private val taskService: TaskService) {

    @GetMapping("/tasks")
    fun getAllTasks(): List<Task> = taskService.getAllTasks()

    @GetMapping("/tasks/{id}")
    fun getTaskById(@PathVariable("id") taskId: Long): Optional<Task> =
        taskService.getTaskById(taskId)

    @PostMapping("/tasks")
    fun createEmployee(@RequestBody payload: Task): Task = taskService.createTask(payload)

    @PutMapping("/tasks/{id}")
    fun updateTaskById(@PathVariable("id") taskId: Long, @RequestBody payload: Task): Task =
        taskService.updateTask(taskId, payload)

    @DeleteMapping("/tasks/{id}")
    fun deleteTask(@PathVariable("id") taskId: Long) = taskService.deleteTaskById(taskId)

}

В этом примере мы определили простой REST контроллер с помощью Spring Boot и Kotlin. Контроллер обрабатывает запросы для получения списка задач, получение конкретной задачи, добавления новой задачи, обновление задачи и удаления задачи по идентификатору.

Заключение

Kotlin и Spring Boot предоставляют мощный инструментарий для разработки REST API. Их сочетание обеспечивает простоту и скорость разработки, интеграцию с существующим кодом на Java и широкие возможности для масштабирования. Если вы рассматриваете создание нового веб-API, обязательно рассмотрите Kotlin и Spring Boot как один из вариантов разработки.

ЗЫ: Строго не судите. Первая статья для многочисленной аудитории. Жду критику/пожелания/предложения.

Спасибо за внимание.

Комментарии (11)


  1. Kinski
    30.05.2024 07:44

    А насколько хорошо data class работает в качестве entity? Есть подозрение, что неизменяемый id поломает вам работу при createTask, если конечно Вы не генерируете id на вызывающей стороне.

    Но с общей мыслью согласен - котлин очень сахарный относительно джавы, да и со спрингом дружит отлично, так что всех агитирую новые проекты начинать на котлине)


    1. kloun_za_2rub
      30.05.2024 07:44

      Разве id как-то меняется при создании? Там же внутри просто распарс на sql запрос идет, а возвращаться вряд ли будет тот же объект

      UPD: возможно тут имелось ввиду, что Long без "?", не увидел этого


      1. ApoFis_93 Автор
        30.05.2024 07:44

        id non nullable поле и генерируется на вызывающей стороне.

        Возвращаться будет объект, который создали.


      1. Kinski
        30.05.2024 07:44

        ID может меняться при создании. Когда с условного фронта приходит набор полей для сохранения в БД (в виде объекта Task), то никаким ид (читай первичный ключ из БД) там не пахнет. И этот ID генерируется в БД, а затем присваивается в поле Task.id.

        Ну и если посмотреть, как работает JPA и Hibernate под капотом, то там генерация запроса и основывается на наличии\отсутствии id поля. Если поля нет - то генерируется insert, иначе генерируется update.


    1. ApoFis_93 Автор
      30.05.2024 07:44

      можно сделать так

          @Id @GeneratedValue(strategy = GenerationType.UUID)
      val id: UUID?

      Тогда в запросе передавать id не надо. Генерироваться будет автоматически.
      Спасибо. Дополнил.


  1. Fancryer
    30.05.2024 07:44
    +1

    Зачем мешать Optional и исключения? Почему нельзя вернуть None и обработать в дальнейшем? Optional же как раз подразумевает наличие или отсутствие значения нужного типа. Ну а если нужно вернуть либо значение, либо исключение, почему нельзя использовать, например, Either из Arrow?


    1. ApoFis_93 Автор
      30.05.2024 07:44

      Согласен, можно и так.


    1. ApoFis_93 Автор
      30.05.2024 07:44

      можно переписать таким образом

      fun getTaskById(taskId: Long): Task = taskRepository.findById(taskId)
              .orElseThrow { TaskNotFoundException(HttpStatus.NOT_FOUND, "Задача с $taskId не найдена") }


      1. Ksnz
        30.05.2024 07:44
        +2

        А ещё лучше так

        fun getTaskById(taskId: Long): Task = taskRepository.findByIdOrNull(taskId) 
                ?: throw TaskNotFoundException(HttpStatus.NOT_FOUND, "Задача с $taskId не найдена")
        

        Благо в Spring есть Kotlin extension findByIdOrNull для CrudRepository а работа с Optional в котлин коде считается моветоном :)

        P.S. Ещё  DELETE по спецификации REST должен быть идемпотентный, так что нет смысла проверять на наличие в репозитории, правильнее будет просто молча вызывать deleteById


        1. ApoFis_93 Автор
          30.05.2024 07:44

          Понял, спасибо


  1. DonAlPAtino
    30.05.2024 07:44

    А DTO нет, как же без DTO?

    Или в Котлине оно не нужно?