Вы здесь:

Оглавление:

Цель

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

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

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

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

Swagger
Spring Actuator

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.

 

Статический анализатор 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

5. Kotlin классы по умолчанию final. Для Entity это проблема. Можно применить plugin "allopen":

plugins {
  kotlin("plugin.allopen")
}
...

allopen {
  annotation("javax.persistence.Entity")
}

 

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

./gradlew test

Возможно , пригодятся параметры продолжения тестирования, если текущий тест упал:

$ gradle --continue ...
$ mvn install -Dmaven.test.failure.ignore=true
$ mvn --fail-at-end test
$ mvn -fae 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

Возможно , пригодятся параметры продолжения тестирования, если текущий тест упал:

$ gradle --continue ...
$ mvn install -Dmaven.test.failure.ignore=true
$ mvn --fail-at-end test
$ mvn -fae test

Включено протоколирование тестов 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

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

./gradlew bootRun

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

Создание:

./gradlew bootJar

(bootJar не bootRun!!!)

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

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

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

или так:

cd shop_kotlin/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<MavenPublication>("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

Работа с 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

(8980 - основной порт, 8988 - spring actuator)

$ docker container ls
CONTAINER ID   IMAGE                COMMAND              CREATED          STATUS          PORTS                    NAMES
6544af7b0adb   shop_kotlin:0.1.18
 12 seconds ago   Up 11 seconds   0.0.0.0:8980 - 8980/tcp

Проверка:

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

HTTP/1.1 200
Connection: keep-alive
Content-Length: 3
Content-Type: text/plain<span class="pl-k">;</span>charset=UTF-8
Date: Thu, 30 Nov 2023 15:36:25 GMT
Keep-Alive: timeout=60
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/

swagger

 

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
        },
....

actuator

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

$ http http://127.0.0.1:8988/shop_kotlin/api/actuator/metrics/jvm.memory.used
....
"baseUnit": "bytes",
    "description": "The amount of used memory",
    "measurements": [
        {
            "statistic": "VALUE",
            "value": 147665416.0
        }
    ],
    "name": "jvm.memory.used"
....

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

 

Prometheus

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

scrape_configs:
- job_name: 'spring boot scrape'
  metrics_path: '/shop_kotlin/api/actuator/prometheus'
  scrape_interval: 5s
  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..."

 

Grafana

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

grafana

grafana

Запуск grafana:

vasi@v$ sudo service grafana-server start

Остановка grafana:

vasi@v$ 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<GroupProductDTO> {
    ...
}

Использован 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, добавлен параметр в 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")
 

 

Deploy to NEXUS repository

Возможен с использованием Jenkins (описано выше) или ручной deploy в Nexus с личного компьютера.

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

$ ./gradlew publish

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

url = uri("https://v.perm.ru:8082/repository/ru.perm.v/")

Для установки переменных доступа к Nexus repository выполнить в shell:

$ export NEXUS_CRED_USR=admin
$ export NEXUS_CRED_PSW=pass

nexus

Секция publishing в build.gradle.kts:

publishing {
    repositories {
        maven {
            url = uri("https://v.perm.ru:8082/repository/ru.perm.v/")
            isAllowInsecureProtocol = true
            //  publish в nexus "./gradlew publish" из ноута и Jenkins проходит
            // export NEXUS_CRED_USR=admin
            // 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

 

Логирование

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

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

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

Пример работы плагина Idea EasyCode:

Main window Virtual Machine Manager

 

Использование "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)
....

 

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, т.к. проект сделан только для демонстрации 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
  • Тесты проходят (из idea и из shell)
  • 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."

 

Ссылки: