Вы здесь:

Оглавление:

Цель

Статический анализатор Idea Analize
Статический анализатор SonarCube
JPA Entity классы с Kotlin

Unit тестирование
Интеграционное тестирование в проекте
Покрытие тестами
Spring profiles
CORS filter
Тестовый запуск

Создание запускаемого файла и его запуск
Publishing SpringBoot "FAT" jar

Интеграционное тестирование
Примеры тестов httpie
DataJpa tests
RestAssured tests
Нагрузочное тестирование

Swagger
Spring Actuator

Micrometer
Prometheus
Пример просмотра использования CPU в Prometheus
Запуск prometheus в docker

Docker
Grafana
Кеширование
Сборка Jenkins
Nexus

Просмотр ресурсов с помощью Java Mission Control
Логирование

Использование "ChatGPT-EasyCode" в Idea
Использование "ChatGPT-EasyCode" в VSCode

Частный параметр конфигурации в application.yaml
Переопределение значения переменных application.yaml

TODO
Примечания
Ссылки

Цель

Cоздать небольшое приложение на Kotlin с использованием Spring Boot. Справочник товаров со следующими типами:

  • Настольные компьютеры
  • Ноутбуки
  • Мониторы
  • Жесткие диски

Каждый товар имеет следующие свойства:

  • номер серии
  • производитель
  • цена
  • количество единиц продукции на складе

Дополнительные свойства:

  • Настольные компьютеры имеют форм-фактор: десктопы, неттопы, моноблоки
  • Ноутбуки подразделяются по размеру: 13, 14, 15, 17 дюймовые
  • Мониторы имеют диагональ
  • Жесткие диски имеют объем

Необходимо реализовать back-end приложение, которое имеет RESTful HTTP методы выполняющие:

  • Добавление товара
  • Редактирование товара
  • Просмотр всех существующих товаров по типу
  • Просмотр товара по идентификатору

В качестве базы данных использовать in memory database, например H2.

Результат: https://github.com/cherepakhin/shop_kotlin.

Статический анализатор Idea Analize

Проверка кода. Вызывается из контекстного меню Analize - Inspect Code.

Наверх

Статический анализатор SonarCube

https://github.com/cherepakhin/shop_kotlin/blob/dev/doc/sonarqube/use_sonarcube.md

JPA Entity классы с Kotlin

Источник: https://habr.com/ru/companies/haulmont/articles/572574/
Примеры из источника: https://github.com/Klimenkoob/spring-kotlin-hibernate

Рекомендации:

  1. Data class not recommended for JPA Entity . Warning in Idea. Почему не использовать Data-классы? Потому что они финальны сами по себе, имеют по всем полям определенные equals, hashCode и toString. А это недопустимо в связке с Hibernate.
  2. Явно помечать ключевым словом open все Entity. Согласно спецификации JPA, все классы и свойства, связанные с JPA, не должны быть final. В отличие от Java, в Kotlin классы, свойства и методы по умолчанию final. Поэтому их нужно явно помечать ключевым словом open.
  3. Явно определять hashCode(), equals(), toString(). При определении внимание на lazy поля (как и в Java).
  4. Добавить nullable = false
    @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "client_id", nullable = false)
    lateinit var client: Client
    
  1. Kotlin классы по умолчанию final. Для Entity это проблема. Можно применить plugin "allopen":
    plugins {
      kotlin("plugin.allopen")
    }
    ...
    
    allopen {
      annotation("javax.persistence.Entity")
    }
    

Unit тестирование

./gradlew test

Примеры отбора тестов:

./gradlew test --tests '*EntityTest'
./gradlew test --tests '*Rest*'
./gradlew test --tests ProductDTOTest
./gradlew test --tests '*TestIntegration'
./gradlew test --tests '*MockMvcTest'

Интеграционное тестирование в проекте

Вообще, интеграционные тесты должны быть в отдельном проекте (см. https://github.com/cherepakhin/shop_kotlin_restassured_test). В этом проекте оставил только тесты работы с базой данных. Имена интеграционных тестов должны заканчиваться ..TestIntegration. Прогнать все, кроме интеграционных (включены только *Test, исключены *TestIntegration):

./gradlew clean test --tests *Test

Только интеграционные:

./gradlew clean test --tests *TestIntegration*
./gradlew clean test --tests *TestIntegration

Другие примеры:

gradle test --tests org.gradle.SomeTest.someSpecificFeature
gradle test --tests *SomeTest.someSpecificFeature
gradle test --tests *SomeSpecificTest
gradle test --tests all.in.specific.package*
gradle test --tests *IntegTest
gradle test --tests *IntegTest*ui*
gradle test --tests *IntegTest.singleMethod
gradle someTestTask --tests *UiTest someOtherTestTask --tests *WebTest*ui

Включено протоколирование тестов build.gradle.kts:

...
tasks.withType<Test> {
    ...
    // Show test log
    testLogging {
        events("passed", "skipped", "failed")
    }
    ...
}

Вывод в консоль:

...
ProductServiceImplMockTest > getByGroupProductN() PASSED
ProductServiceIntegrationTest > existByN() PASSED
ProductServiceIntegrationTest > notExistByN() PASSED
ProductServiceIntegrationTest > checkSortByName_ByDslFilterByName() PASSED
...

с events("standardOut", "started", "passed", "skipped", "failed") логируется вывод в консоль.

Покрытие тестами

Использован jacoco. Отчет формируется при прогоне тестов

./gradlew test jacocoTestReport

Отчет будет в папке build/reports/jacoco/test/html. В отчете НЕТ информации о результатах тестирования, только протестирован участок кода или нет.

jacoco

Пример отчета по конкретному классу:

jacoco_example

jacoco_class

Красным или желтым выделены непротестированные участки кода, зеленым протестировано.

Spring profiles

Настроены два Spring профиля: application-prod.yml и application-dev.yml. В этом же файле указан профиль по умолчанию:

spring:
  profiles:
    active: dev
  application:
    name: shop_kotlin

Запуск с указанием профиля:

java -D"spring.profiles.active=dev" -jar app.jar

или установить env переменную:

SPRING_PROFILES_ACTIVE = dev

Запуск с gradle:

./gradlew bootRun --args='--spring.profiles.active=dev'

или

SPRING_PROFILES_ACTIVE=dev ./gradlew clean bootRun

CORS filter

При выполнении запросов может быть получена ошибка: "Ошибка: "Access to XMLHttpRequest at 'http://localhost:8080/api/hello' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.". Доходчиво описано в https://sysout.ru/nastrojka-cors-v-spring-security/. Суть в двух словах: по умолчанию Spring не позволяет делать кроссдоменные запросы. CORS не нужно отключать, а наборот включить и настроить. Другими словами, нужно настроить разрешения в приложении откуда разрешать запросы. Примеры заголовков:

Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: http://localhost:9999

Необходимые настройки сделаны в https://github.com/cherepakhin/shop_kotlin/blob/dev/src/main/kotlin/ru/perm/v/shopkotlin/config/CorsFilter.kt

Тестовый запуск

./gradlew bootRun

Создание запускаемого файла и его запуск

Создание:

./gradlew bootJar

(bootJar не bootRun!!!)

Собранный файл будет в папке ./build/libs/

запуск с RAM 256Мб:

shop_kotlin/$ java -Xmx256M -jar build/libs/shop_kotlin-0.1.20.jar

или так:

shop_kotlin/$ cd build/libs
shop_kotlin/build/libs$ java -Xmx256M -jar shop_kotlin-0.1.20.jar

Publishing SpringBoot "FAT" jar

Настройка:

publishing {
  repositories {
      maven {
          url = uri("https://v.perm.ru:8082/repository/ru.perm.v/")
          isAllowInsecureProtocol = true
          //  for publish to nexus "./gradlew publish"
          // export NEXUS_CRED_USR=admin
          // echo $NEXUS_CRED_USR
          credentials {
              username = System.getenv("NEXUS_CRED_USR")
              password = System.getenv("NEXUS_CRED_PSW")
          }
      }
  }
  publications {
    create("maven"){
      artifact(tasks["bootJar"]) // build and publish bootJar
    }
}

https://stackoverflow.com/questions/64062905/unable-to-publish-jar-to-gitlab-package-registry-with-gradle

Примеры тестов httpie

Echo запрос для простой проверки работоспособности (:8980/shop_kotlin/ базовый путь проекта):

$ http :8980/shop_kotlin/api/echo/aaa

HTTP/1.1 200
Connection: keep-alive
Content-Length: 3
Content-Type: text/plain;charset=UTF-8
Date: Thu, 15 Jun 2023 07:30:38 GMT
Keep-Alive: timeout=60

aaa

Поиск по имени Product:

$ http :8980/shop_kotlin/api/group_product/find?name='Comp'

[
    {
        "haveChilds": true,
        "id": 2,
        "name": "Computers",
        "parentId": 1
    },
    {
        "haveChilds": false,
        "id": 3,
        "name": "Desktop Computers",
        "parentId": 2
    }

POST запрос на изменение Product:

$ http POST :8980/shop_kotlin/api/product/ < ./src/test/json_test/product.json

Интеграционное тестирование

Два варианта тестирования - cо Spring @DataJpaTest и через RestAssured (это bdd тестирование). Совершенно разные тесты, для совершенно разных целей. DataJpaTest на уровне БД, RestAssured - сквозное тестирование от rest до БД.

DataJPA test

Тестируется работа с базой данных с использованием @DataJpaTest. Находятся в пакете https://github.com/cherepakhin/shop_kotlin/blob/dev/src/test/kotlin/ru/perm/v/shopkotlin/datajpatest.

RestAssured

Для этих тестов сделан отдельный проект https://github.com/cherepakhin/shop_kotlin_reastassured_test

Результат тестов:

allure_result_test

Docker

Файл Dockerfile лежит в корне проекта:

FROM eclipse-temurin:11
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

Создан скрипт /docker_build.sh. для создания docker образа.

Работа с Docker:

# create docker image
$./docker_build.sh

# run app
$ docker run -p 8080:8980 shop_kotlin/app

$ docker ps
CONTAINER ID   IMAGE             COMMAND                CREATED          STATUS          PORTS                    NAMES
c70ef82f3a54   shop_kotlin/app   "java -jar /app.jar"   43 seconds ago   Up 42 seconds   0.0.0.0:8080->8980/tcp   inspiring_germain

# simple test
$ http :8080/api/group_product/find?name='Comp'

$ docker ps -a
# 2d1325e1222a   shop_kotlin:0.24.0105    "/cnb/process/web" ...

# stop docker app
$ docker stop 2d1

# rm container shop_kotlin 2d1325e1222a (id from docker ps -a)
$ docker container rm 2d1

# rm image
$ docker image ls
shop_kotlin 0.24.0105   94560d28efb3   ...

$ docker image rm 945
Untagged: shop_kotlin:0.24.0105

# clear ALL images
$ docker image prune -a

# clear all
$ docker system prune -af

 

ИЛИ средствами gradle:

Создание docker image:

$ ./gradlew bootBuildImage

...

    [creator]     Saving docker.io/library/shop_kotlin:0.1.18...
    [creator]     *** Images (f80a4e623a3d):
    [creator]           docker.io/library/shop_kotlin:0.1.18
Successfully built image 'docker.io/library/shop_kotlin:0.1.18'
...

В файле META-INF/MANIFEST.MF указать Main-Class

Main-Class: ru.perm.v.shopkotlin.ShopKotlinApplication

Запуск docker image:

$ docker run -p 8980:8980 -p 8988:8988  docker.io/library/shop_kotlin:0.1.18

(из контейнера docker открыты порты: 8980 - основной порт, 8988 - spring actuator)

$ docker container ls

Проверка:

$ http http://127.0.0.1:8980/shop_kotlin/api/echo/aaa

HTTP/1.1 200
Connection: keep-alive
Content-Length: 3
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers

aaa

Swagger

Swagger доступен по адресу http://127.0.0.1:8980/shop_kotlin/api/swagger-ui/

Spring Actuator

Spring Actuator предназначен для получения информации о работающем приложении - статус приложения (жив/нет), использовании памяти, cpu и т.п.. Подключен по адресу http://127.0.0.1:8988/shop_kotlin/api/actuator

порт указан в application.yaml:

management:
  server:
    port: 8988

Использование из командной строки:

$ http http://127.0.0.1:8988/shop_kotlin/api/actuator

{
    "_links": {
        "beans": {
            "href": "http://127.0.0.1:8988/shop_kotlin/api/actuator/beans",
            "templated": false
        },
        "caches": {
            "href": "http://127.0.0.1:8988/shop_kotlin/api/actuator/caches",
            "templated": false
        },
        "caches-cache": {
            "href": "http://127.0.0.1:8988/shop_kotlin/api/actuator/caches/{cache}",
            "templated": true
        },
....

Пример "Сколько памяти используется СЕЙЧАС?":

$ http http://127.0.0.1:8988/shop_kotlin/api/actuator/metrics/jvm.memory.used
{
    "availableTags": [
        {
            "tag": "area",
            "values": [
                "heap",
                "nonheap"
            ]
        },
        {
            "tag": "application",
            "values": [
                "shop_kotlin"
            ]
        },
        {
            "tag": "id",
            "values": [
                "G1 Survivor Space",
                "Compressed Class Space",
                "CodeCache",
                "G1 Old Gen",
                "Metaspace",
                "G1 Eden Space"
            ]
        }
    ],
    "baseUnit": "bytes",
    "description": "The amount of used memory",
    "measurements": [
        {
            "statistic": "VALUE",
            "value": 169351144.0
        }
    ],
    "name": "jvm.memory.used"
}

(Нагрузочные тесты показали следующие результаты: 64M - приложение выпадает с OutOfMemory, со 128M - тесты проходят)

Micrometer

Micrometer - это , наверное, базовый инструмент для исследования работающего приложения и снятия характеристик. Входит в пакет spring-boot-starter-actuator:

implementation 'org.springframework.boot:spring-boot-starter-actuator'

Пример запроса:

$ http http://127.0.0.1:8988/vacancy/api/actuator/metrics/http.server.requests

Ответ:

{
  "availableTags": [
    {
      "tag": "exception",
      "values": [
        "HttpMediaTypeNotSupportedException",
        "HttpMessageNotReadableException",
        "MethodArgumentTypeMismatchException",
        "None"
      ]
    },
    {
      "tag": "method",
      "values": [
        "PUT",
        "GET"
      ]
    },
    {
      "tag": "application",
      "values": [
        "vacancy"
      ]
    },
    {
      "tag": "uri",
      "values": [
        "/company/find",
        "/company/{n}",
        "/company/sortByColumn/{column}"
      ]
    },
    {
      "tag": "outcome",
      "values": [
        "CLIENT_ERROR",
        "SUCCESS"
      ]
    },
    {
      "tag": "status",
      "values": [
        "400",
        "415",
        "200"
      ]
    }
  ],
  "baseUnit": "seconds",
  "description": null,
  "measurements": [
    {
      "statistic": "COUNT",
      "value": 212.0
    },
    {
      "statistic": "TOTAL_TIME",
      "value": 2.386371417
    },
    {
      "statistic": "MAX",
      "value": 0.0
    }
  ],
  "name": "http.server.requests"
}


Prometheus

Prometheus - это сервис (программа) работающая на одной из машин. Задача Prometheus опрашивать, снимать и хранить характеристики с узлов, заданных в конфигурации. На моем сервере запущен Prometheus. Prometheus ОПРАШИВАЕТ приложение, согласно заданию (ниже yaml), и собирает метрики (metrics_path+targets). Пример задания для опроса в файле doc/prometheus/prometheus.yml. Содержимое doc/prometheus/prometheus.yml поместить в настройки prometheus. Пример задания:

scrape_configs:
- job_name: "shop_kotlin(1.20)"
  scrape_interval: 5s
  metrics_path: "/shop_kotlin/api/actuator/prometheus"
  static_configs:
    - targets: ["192.168.1.20:8988"]

Опрашивать сервис '192.168.1.20:8988/shop_kotlin/api/actuator/prometheus' каждые 5 сек.

Для просмотра получаемых prometheus-ом метрик выполнить:

$ http http://127.0.0.1:8788/api/actuator/prometheus

(Использован httpie)

Ответ:

# HELP jvm_threads_daemon_threads The current number of live daemon threads
# TYPE jvm_threads_daemon_threads gauge
jvm_threads_daemon_threads 13.0
# HELP hikaricp_connections Total connections
...

Подключение к Prometheus из браузера: http://192.168.1.20:9090/targets

http://192.168.1.20:9090/graph

Основной экран: 192.168.1.20 - адрес хоста с prometheus http://192.168.1.20:9090/targets

Основной экран

Меню: Status/Targets Статус

Приложение остановлено:

Приложение остановлено

Приложение запущено:

Приложение остановлено

Пример просмотра использования CPU в Prometheus

Prometheus запущен на 192.168.1.20:9090

Перейти на http://192.168.1.20:9090 В меню "Graph":

  1. В "Expression" ввести system_cpu_usage
  2. Перейти на Graph
  3. Отрегулировать период (н.п. 15 мин.)

Итоговый запрос: http://192.168.1.20:9090/graph?g0.range_input=15m&g0.expr=system_cpu_usage&g0.tab=0

prometheus_system_cpu_usage

Запуск prometheus в docker.

docker run -d -p 9090:9090 -v "/$(pwd)/for_prometheus/prometheus.yml":/etc/prometheus/prometheus.yml prom/prometheus

(внимание на кавычки)

Просмотр состояния prometheus, запущенного в docker:

Вычисление ID container

$ docker ps
CONTAINER ID   IMAGE             COMMAND                  CREATED         STATUS         PORTS                    NAMES
e081bb1f500c   prom/prometheus   "/bin/prometheus --c…"   4 minutes ago   Up 4 minutes   0.0.0.0:9090->9090/tcp   reverent_newton

Просмотр логов контейнера prometheus (e08 id контейнера)

$ docker logs e08 --follow

ts=2023-09-11T13:13:17.850Z caller=main.go:1009 level=info msg="Server is ready to receive web requests."
ts=2023-09-11T13:13:17.850Z caller=manager.go:1009 level=info component="rule manager" msg="Starting rule manager..."
...

(follow - не отключаться)

Grafana

Graphana отображает метрики, собранные Prometheus. На домашнем сервере развернута Grafana. На картинках ниже отображено использование CPU двух приложений, расположенных на двух разных хостах в упрощенном и полном формате:

grafana

grafana

Запуск grafana:

$ sudo service grafana-server start

Остановка grafana:

$ sudo service grafana-server stop

Пример запроса

user(pass): admin/admin

Нагрузочное тестирование

Экономия средств при росте производительности — горячая тема в индустрии. Подробнее о нагрузочном тестировании и метриках в этом проекте https://github.com/cherepakhin/shop_kotlin_yandex_tank_test

Кеширование

Кеширование сделано для RestController:

@GetMapping("/")
@Cacheable("allGroupProductDTO")
@ApiOperation("Get all groups of product")
fun all(): List {
    ...
}

Использован org.springframework.cache. Ручная проверка работы кеш:

# Очистка кэша
$ http :8780/api/group_product/clear_cache
# Для проверки, несколько раз сделать get запрос. 
# При этом в лог будет только один запрос к репозиторию.
$ http :8780/api/group_product/

Сборка Jenkins

Сборка происходит в Jenkins, развернутом на домашнем сервере. Pipeline для Jenkins описан в файле ./Jenkinsfile

jenkins

Установка и настройка домашнего Jenkins описана в https://v.perm.ru/main/index.php/50-organizatsiya-sobstvennogo-ci-cd

Для ограничения памяти при сборке Gradle, добавлен параметр org.gradle.jvmargs=-Xmx512M в gradle.properties:

org.gradle.jvmargs=-Xmx512M

Nexus

В проекте используется внешняя зависимость из домашнего NEXUS репозитория https://github.com/cherepakhin/shop_kotlin_extdto:

implementation("ru.perm.v:shop_kotlin_extdto:0.0.3")

Эта библиотека используется в нескольких проектах (DTO для обмена между сервисами).

Deploy to NEXUS repository

Возможен с использованием Jenkins (в Jenkisfile "stage('Publish to Nexus')") или ручной deploy в Nexus с личного компьютера.

Для deploy выполнить:

$ ./gradlew publish

Путь к репозиторию установлен в build.gradle.kts:

...
publishing {
    repositories {
        maven {
            url = uri("http://v.perm.ru:8081/repository/ru.perm.v/")
...

Для установки переменных доступа к Nexus repository выполнить в shell (переменные будут использованы в build.gradle.kts, см.ниже):

$ export NEXUS_CRED_USR=admin
$ export NEXUS_CRED_PSW=pass

Результат deploy:

nexus

Секция publishing в build.gradle.kts полностью:

...
publishing {
 repositories {
        maven {
            url = uri("https://v.perm.ru:8082/repository/ru.perm.v/")
            isAllowInsecureProtocol = true
            // example export variables in shell
            // export NEXUS_CRED_USR=admin
            // verify:
            // echo $NEXUS_CRED_USR
            credentials {
                username = System.getenv("NEXUS_CRED_USR")
                password = System.getenv("NEXUS_CRED_PSW")
            }
        }
    }
    publications {
        register("mavenJava", MavenPublication::class) {
            groupId
            artifactId
            version
            from(components["java"])
        }
}

Просмотр ресурсов с помощью Java Mission Control

Java Mission Control

jmc-8.3.1_linux-x64/JDK Mission Control$ jmc

jmc_monitor.png

Логирование

Настройка сделана в application.yaml:

logging:
  level:
    root: info
  file:
    path: log/

Использование "ChatGPT-EasyCode" в Idea

Пример работы плагина Idea EasyCode для автодополнения кода : demo ChatGPT-EasyCode

Серым выделена подсказка EasyCode. "TAB" - принять предложение.

Использование "ChatGPT-EasyCode" в VSCode

Генерация тестов Сгенерировать тесты ChatGPT-EasyCode

Рефакторинг Рефакторинг ChatGPT-EasyCode

Вещь!

А отечественный GigaCode, в течении нескольких недель, отвечаЛ унылым

и в конце концов какие-то шестеренки провернулись: gigacode
В IntelliJ IDEA 2023.3.2 (Community Edition):
gigacode

Частный параметр конфигурации в application.yaml

Бывает нужно добавить СВОЙ параметр конигурации в application.yaml для удобства тестирования, разработки, развертывания. Пример описан в проекте https://github.com/cherepakhin/camel_rest в разделе "Дополнительно". В application.yaml добавлен ЧАСТНЫЙ параметр конфигурации "myconfig".
Описание:

@ConfigurationProperties("myconfig")
@ConstructorBinding
data class MyConfig(val testDirectory: String)

Объявление в application.yaml:

myconfig:
    testDirectory: file:~/temp/testarea

Использование:

@RestController
class ParamCtrl {

    @Autowired
    lateinit var myConfig: MyConfig

    @GetMapping("/test_directory")
    fun getTestDirectory(): String? {
        logger.info("GET param test_directory=${myConfig.testDirectory}")
        return myConfig.testDirectory
    }
}

Переопределение значения переменных application.yaml

Можно задать значения переменных application.yaml через специальную переменную SPRING_APPLICATION_JSON. Пример замены порта приложения на 8960 (в application.yaml server: port=8980):

// экспорт переменной
$ export SPRING_APPLICATION_JSON='{"server":{"port":8960}}'
// проверка
$ java -jar build/libs/spring_config_k-0.0.1-SNAPSHOT.jar
....
INFO 23327 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  :
    Tomcat initialized with port(s): 8960 (http)
....

Пример: https://github.com/cherepakhin/spring_config_k

TODO

  • cache on rest (сделано)
  • RestAssured tests (сделано) (https://github.com/cherepakhin/shop_kotlin_reastassured_test)
  • by lazy (сделано)
  • Docker (сделано)
  • Swagger (сделано)
  • Actuator (сделано)
  • flyway (использована БД в памяти url: jdbc:h2:mem:easybotdb). Миграции не требуются.
  • webmvc тесты (ProductRestMockMvcTest)
  • тесты service слоя с СУБД @DataJpaTest (сделано)
  • вертикальные БД (?)
  • Различные SQL запросы через native Sql и Spring JPA(order by, group by) (сделано)
  • Pagination
  • Grafana (сделано)
  • ElasticSearch
  • Авторизация

Примечания:


В программе не используются слои controller и service, т.к. проект сделан только для демонстрации связки Spring Boot и Kotlin, и не планируется какая-либо бизнес-логика. Конвертация в DTO сделана в REST контроллерах.


Поля в entity классах д.б. обозначены "open" (н.п. open val name: String = ""). В Kotlin используется следующий подход - можно наследовать от суперклассов и переопределять их свойства и функции только в том случае, если они снабжены префиксом "open".


Имя для "id" поля в entity классах названы "n". Причины:

  • В некоторых БД "id" ключевое слово
  • В interface repository есть отдельные методы repository.getById(), repository.findById(). Имеется ввиду поиск по primary key. Это путает.

Правила использования модификатора lateinit:

  • используется только совместно с ключевым словом var;
  • свойство может быть объявлено только внутри тела класса (не в основном конструкторе);
  • тип свойства не может быть нулевым и примитивным;
  • у свойства не должно быть пользовательских геттеров и сеттеров;
  • с версии Kotlin 1.2 можно применять к свойствам верхнего уровня и локальным переменным.
  • сообщает что инициализация будет как-то сделана (в примере ниже аннотациями @Mock, @InjectMocks). @Mock mockProductService будут инжектированы в @InjectMocks productRest. Пример:
    internal class ProductRestTest {
        @Mock
        private lateinit var mockProductService: ProductService
    
        @InjectMocks
        private lateinit var productRest: ProductRest
        ...
    }
    

Ключевое слово byLazy

val catName: String by lazy { getName() }

свойство объявлено как VAL, но присвоение будет выполнено при первом использовании и потом уже не будет инициализироваться.

Ссылки о lazy и lateinit:


Статические члены и компаньон объекты в Kotlin:

Как правило объекты-компаньоны используются для объявления переменных и функций, к которым требуется обращаться без создания экземпляра класса. Либо для объявления констант. По сути они своего рода замена статическим членам класса (в отличие от Java, в Kotlin нет статики).

  • Как бы static:
    class Someclass {
      ...
      object AsStaticObject {
        fun create() {
          ...
        }
      }
    
    }
    ...
    // использование
    val someClass = SomeClass.AsStaticObject.create()
    
  • Companion Object (вложенный объект. Метод create() принадлежит родительскому классу):
    class SomeClass {
    
      companion object {
        fun create()
      }
    }
    ...
    val someClass = SomeClass.create()
    

Различия между val и const val в Kotlin

val MY_VAL = 1
const val MY_CONST_VAL = 2
  • Обе эти переменные немодифицируемые
  • MY_VAL станет приватной переменной, для доступа к которой будет создан геттер
  • MY_CONST_VAL будет "заинлайнена", то есть компилятор заменит все полученные значения этой переменной на само значение
  • Kotlin const, var, and val Keywords

Ошибка "ERROR: Cannot resume build because FlowNode 19"

Ответ тут https://community.jenkins.io/t/error-cannot-resume-build-because-flownode-19/9477/3

В двух словах: добавить в Jenkinsfile "максмальную выживаемость":

pipeline {
    agent any
    options {
        durabilityHint 'MAX_SURVIVABILITY'
    }
...
}

По мелочи:

  • В Windows на сетевом диске компилировалось с проблемами query-dsl. После переноса на диск C: проблемы ушли.

  • Если появилась ошибка: "./gradlew: строка 2: $'\r': команда не найдена", то выполнить:

  • Похоже, при расшаривании папки для Windows и работе в Windows изменился символ конца строки.

  • Смена версии gradlew: в ./gragle/wrapper/gradle-wrapper.properties
    distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
  • Jetty рекомендуют, как более оптимизированный контейнер. Для замены сделать:
    dependencies {
      implementation("org.springframework.boot:spring-boot-starter-web") {
      exclude("org.springframework.boot:spring-boot-starter-tomcat")
      }
      implementation("org.springframework.boot:spring-boot-starter-jetty") // jetty uses less memory
      ...
    }
    
  • Описание Data class: https://kotlinlang.ru/docs/reference/data-classes.html. "Классы данных не могут быть абстрактными, open, sealed или inner. Компилятор автоматически формирует следующие члены данного класса из свойств, объявленных в основном конструкторе: equals()/hashCode(), toString() в форме "User(name=John, age=42)", компонентные функции componentN(), которые соответствуют свойствам, в соответствии с порядком их объявления. Основной конструктор должен иметь как минимум один параметр. Все параметры основного конструктора должны быть отмечены, как val или var."

Ссылки: