null

Kotlin для начинающих. Kotlin и веб-разработка

Kotlin - лаконичный язык

Одна из ключевых особенностей языка Kotlin - значительное сокращение шаблонного кода (boilerplate code). Если, к примеру при создании простого класса в Java мы явно объявляем поля, конструктор и геттеры этого класса, то в Kotlin все намного проще. Рассмотрим класс, который обозначает абстрактного Клиента. Класс имеет следующие поля: Name, Email и Company. Вот как выглядит код, написанный на языке Java:

public class Customer{

	String name;
	String email;
	String company;
	
	//constructor to initialise fields
	public Customer(String name, String email, String company) {
		this.name = name;
		this.email = email;
		this.company = company;
	}
	 
	//getter method for name
	public String getName() {
		return name;
	}
	 
	//getter method for email
	public String getEmail() {
		return email;
	}
	 
	//getter method for company
	public String getCompany() {
		return company;
	}
}

А теперь посмотрим на этот же класс в Kotlin:

class Customer(val name: String, val email: String, val company: String)

Как можно заметить в коде на Kotlin отсутствует множество шаблонного кода, предназначенного лишь для того, чтобы реализовать базовые действия над полями класса. Все эти действия в Kotlin работают из коробки, хотя и не указываются явно как в Java. Как итог, код становится лаконичнее, не теряя при этом своей функциональности.

Kotlin - безопасный язык

Нулевые ссылки (null references) являются наиболее распространенной причиной исключений (exception) в приложениях на Java. Нам всем знакомо досадное исключение NullPointerException (NPE). Держа в уме эту проблему, разработчики Kotlin сделали многое, чтобы ее решить.

В Kotlin по умолчанию переменной не может быть присвоено нулевое значение — это первая линия защиты от NPE. Используя оператор nullable '?', переменная может получить нулевое значение, но для доступа к атрибутам переменной необходимо использовать оператор null-safety '?'. Если переменная имеет нулевое значение, она будет рассматриваться как строка. Единственный способ получить NPE в Kotlin - использовать оператор not-null '!!', который вызовет исключение KotlinNullPointerException.

Вот несколько примеров:

//Нулевая ссылка
var a: String = "abc" 
a = null //ошибка компиляции 
//вывод: Null cannot be a value of a non-null type String 


//Оператор nullable ? 
var a: String? = "abc" 
a = null //ok 
print(a) 
//вывод: null 
  

var a: String? = "abc" 
a = null //ok 
print(a.length) //ошибка компиляции 
//вывод: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String? 
  

//Оператор null-safety ?. 
var a: String? = "abc" 
a = null  
print(a?.length) //ok 
//вывод: null 

  
//Оператор not-null !! 
var a: String? = "abc" 
a = null  
print(a!!.length)  
/*вывод: Exception in thread "main" kotlin.KotlinNullPointerException 
at …………
*/

Kotlin - функционально совместимый язык

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

Kotlin и Spring

Spring официально поддерживает Kotlin, начиная со Spring Framework 5.0. Интересной особенностью является то, что разработчики Spring пытаются сделать свой фреймворк null-safe в части, поддерживающей Kotlin, и стремятся позволить разработчикам исправлять ошибки с нулевыми значениями еще на этапе компиляции и не допускать NullPointerExceptions, которые могут возникнуть во время выполнения.

Для того, чтобы начать разрабатывать в связке Spring + Kotlin, рекомендуется зайти на сайт https://start.spring.io. На этом сайте можно инициализировать новый проект Spring. Важно при этом выбрать язык Kotlin. Также необходимо выбрать инструмент сборки проекта. Доступны два инструмента - Gradle и Maven. Обычно для проектов на Kotlin рекомендуется использовать Gradle, так как он хорошо поддерживает Kotlin DSL - язык по умолчанию при генерации проекта с Kotlin. Однако можно использовать и Maven, если он вам более удобен.

Пример реализации контроллера на Kotlin

Часто веб-приложения построены по архитектуре, в которой применяется понятие Контроллера (например Model-View-Controller, MVC).

Давайте рассмотрим, как можно реализовать типовой контроллер в связке Spring + Kotlin.

Предположим, мы создали простенькую view (blog.html):

<html>
  <head></head>
    <body>
        <h1>{{title}}</h1>
     </body>
</html>

Теперь напишем работающий с этой view контроллер:

import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.ui.set
import org.springframework.web.bind.annotation.GetMapping

@Controller
class HtmlController {

  @GetMapping("/blog")
  fun showBlogSite(model: Model): String {
    model["title"] = "Blog"
    return "blog"
  }
}

И так, когда мы попытаемся получить доступ к пути /blog с помощью GET-запроса, сработает наш контроллер. Сначала он добавит атрибут title со значением «Blog» к модели, а затем возвратит нашу view «blog.html», заполненную в согласии с этой моделью.

Бывает, что вместо того, чтобы возвратить view целиком или частично, необходимо просто вернуть сериализованные данные. В таком случае используется REST-контроллер. Рассмотрим пример:

@RestController
@RequestMapping("/api/article")
class ArticleController(private val repository: ArticleRepository) {

  @GetMapping("/")
  fun findAll() = repository.findAllByOrderByAddedAtDesc()

  @GetMapping("/{id}")
  fun findOne(@PathVariable id: UUID) =
      repository.findById(id) ?: ResponseStatusException(HttpStatus.NOT_FOUND, "This article does not exist")

}

В этом примере, мы создали контроллер «ArticleController». Контроллер взаимодействует с репозиторием данных «ArticleRepository» (доступ передается через конструктор контроллера).

Репозиторий напрямую связан с хранилищем данных (это может быть, например реляционная база данных или NoSQL источник). Если мы попытаемся получить доступ к пути «/» с помощью GET запроса, контроллер возвратит нам из репозитория все объекты типа «Article», упорядоченные по дате добавления в порядке убывания. Так как мы явно не указали тип возвращаемых данных, метод «findAll» возвратит данные из репозитория, в том виде (и того типа) в котором они там хранятся.

Во второй конечной точке (endpoint) с адресом «GET /{id}», используется elvis-оператор «?:». Это означает что, если метод репозитория «findById» возвратит значение null, контроллер выбросит исключение типа «ReponseStatusException», иначе – будет возвращен найденный по «id» объект. В языке Java отсутствует оператор elvis, поэтому, для реализации того же функционала, в нем порою приходится писать существенно больший объем кода.

Тестирование кода

Для тестирования кода рекомендуется использовать @MockMvcTest и библиотеку SpringMockK, которая похожа на Mockito, но лучше подходит для Kotlin:

@MockMvcTest
class HttpControllersTests(@Autowired val mockMvc: MockMvc) {

  @MockkBean
  private lateinit var articleRepository: ArticleRepository

  @Test
  fun `List articles`() {
    val user = User("testUser", "John", "Doe")
    val spring5Article = Article("Spring Framework 5.0 goes GA", "Dear Spring community ...", "Lorem ipsum", user)
    val spring43Article = Article("Spring Framework 4.3 goes GA", "Dear Spring community ...", "Lorem ipsum", user)
    every { articleRepository.findAllByOrderByAddedAtDesc() } returns listOf(spring5Article, spring43Article)
    mockMvc.perform(get("/api/article/").accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk)
        .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
        .andExpect(jsonPath("\$.[0].author.login").value(user.login))
        .andExpect(jsonPath("\$.[0].slug").value(spring5Article.slug))
        .andExpect(jsonPath("\$.[1].author.login").value(user.login))
        .andExpect(jsonPath("\$.[1].slug").value(spring43Article.slug))
  }
}

В примере выше мы создали одного тестового пользователя и два сымитированных объекта вида «Статья» (mocked Article entities). Затем задали, что для каждого (every) запроса репозитория на поиск всех «статей» должен возвращаться список (list) из этих сымитированных статей. Дальше с помощью MockMVC мы составили имитирующий (mocked) HTTP-запрос. Мы указали MockMVC выполнить GET-запрос на '/api/article' и ожидать содержимое в формате JSON. Тест будет считаться пройденным, если полученный на сымитированный вызов ответ будет «OK», содержимое будет в формате JSON, и в нем будут содержаться данные наших сымитированные «статей», такие как: логин автора и название статьи.

 

Конец статьи

===============================================================

Данная статья является отредактированным переводом статьи "Kotlin in Web Development" автора Kristijan Zdelarec.