Найти

Вы здесь:

Git проекта: https://gitverse.ru/cherepakhin/companies

Задание:

  1. Создать новый пустой Spring (5) Java-проект Web-приложения с поддержкой Maven, Java 11.
    Проект должен поддерживать подключение к PostgreSQL (12 и выше) и механизм миграций на базе Liquibase.
    Необходимые настройки проекта (в том числе и параметры подключения к БД) должны храниться в профиле.
    При запуске проекта должны автоматически формироваться необходимые структуры данных в БД, а так же
    должны сразу же автоматически добавляться сами объекты тестовых данных (не менее 50 организаций, которые описаны в п.3). Должно поддерживаться открытие и запуск проекта в IntelliJ Idea.
  2. В собранном web-приложении должна быть реализована REST-архитектура, предоставляющая доступ без авторизации к следующим web-методам:
    1. Метод получения списка объектов данных (описаны в п.3) в формате JSON с возможностью получения как всего списка данных, так и списка данных, отфильтрованного по переданной в этот метод поисковой строке.
    2. Метод получения одного объекта данных (описан в п.3) в формате JSON по его уникальному идентификатору.
  3. Описание одного объекта данных для использования при реализации п.1, 2:
    Объект данных представляет собой сведения по одной организации, в которые входят:
    3.1 Полное наименование организации
    3.2 Краткое наименования организации
    3.3 ИНН
    3.4 ОГРН
    3.5 Почтовый адрес
    3.6 Юридический адрес
    3.5 Сведения о генеральном директоре, включая Фамилию, Имя, Отчество, дату рождения
    3.6 Сведения о филиалах (может быть несколько), включая наименование, почтовый адрес,
    сведения о руководителе (Фамилия, Имя отчество, дата рождения)
  4. Все классы, методы и свойства должны иметь комментарии в формате Javadoc.
  5. Необходимо создать файл с описанием:
    5.1 Необходимых для запуска web-приложения настроек в профиле
    5.2 REST API web-приложения
  6. Показать работу с БД разными способами (sql native запросы, qbe-запросы)
  7. flyway database migration
  8. Интеграционно протестировать
  9. Кэши для REST
  10. Docker
  11. log
  12. Профили для dev и prod
  13. Использован Thymeleaf

Установка версии java

в Linux:

export JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64

в Windows:
(выполнять из cmd.exe)

C:>echo %JAVA_HOME%
C:\po\jdk-21

Z:\prog\java\companies> export JAVA_HOME=c:\po\open-jdk11 ?????

Проверка:

$ ./mvnw -version

Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: /home/vasi/.m2/wrapper/dists/apache-maven-3.6.3-bin/1iopthnavndlasol9gbrbg6bf2/apache-maven-3.6.3
Java version: 11.0.19, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ru_RU, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-150-generic", arch: "amd64", family: "unix"

Использование базы данных

Используется программа psql из дистрибутива PostgreSQL.

Создание базы:

psql
# create database companies;

Для подключения к БД используются user/password из переменных операционной системы PG_USER, PG_PASSWORD. По умолчанию значения обеих переменных 'postgres'.

Подключение через psql:

psql --dbname=companies

Подключение через psql с бД на хосте 192.168.1.20:

psql -U vasi -d companies -h 192.168.1.20

Backup:

pg_dump -U vasi -W companies > companies.backup

Restore:

psql -U vasi -d companies < companies.backup

После восстановления предоставить права:

GRANT ALL PRIVILEGES ON DATABASE "companies" to vasi;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO vasi;

Для версионирования БД используется flyway. Автообновление БД отключено. В application.yaml установлен флаг:

flyway:
    enabled: false

Миграции в src/resources/migration. При изменении структуры вручную выполнить:

./mvnw flyway:migrate -Dflyway.user=postgres -Dflyway.password=postgres -Dflyway.url=jdbc:postgresql://127.0.0.1:5432/companies

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

Прогон всех тестов (включая интеграционные):

./mvnw test
Tests run: 15, Failures: 0, Errors: 0, Skipped: 0

Прогон только интеграционных тестов:

./mvnw test -Dtest=*_IntegrationTest
Tests run: 9, Failures: 0, Errors: 0, Skipped: 0

Прогон только unit тестов:

$ ./mvnw test -Dtest=\!*_IntegrationTest
Tests run: 6, Failures: 0, Errors: 0, Skipped: 0

Из Windows:

./mvnw test -Dtest=!*_IntegrationTest
.\mvnw.cmd test -Dtest=!*_IntegrationTest

Прогон конкретного тестового класса:

./mvnw test -Dtest=CompanyServiceImplTest -DfailIfNoTests=false

NOTE: No tests were executed! -DfailIfNoTests=false to ignore this error

Прогон конкретного метода конкретного тестового класса:

./mvnw test -Dtest="CompanyServiceImplTest#getAll"
./mvnw test -Dtest="EmployeeServiceImpl_IntegrationTest#findByLastNameLikeOrderByN"
./mvnw test -Dtest=CompanyServiceImplTest#getAll

Разные варианты прогона тестов:

./mvnw test -Dtest=*ImplTest -DfailIfNoTests=false
./mvnw test -Dtest=*IntegrationTest -DfailIfNoTests=false

Прогон теста в Windows:

// из cmd.exe
companies>echo %JAVA_HOME%
C:\po\jdk-21
// из PowerShell
companies>.\mvnw.cmd test -Dtest=*_IntegrationTest
companies>.\mvnw.cmd test -Dtest=!*_IntegrationTest
companies>.\mvnw.cmd test

Сборка без тестов:

./mvnw package -DskipTests

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

Использован jacoco. Генерация отчета:

$./mvnw test
$./mvnw jacoco:report

Отчет будет в папке target/site/jacoco/index.html

jacoco_report

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

jacoco_class

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

Запуск проекта

$./mvnw clean spring-boot:run

Для удобства есть скрипт run.sh, который запускает проект через Maven wrapper с нужным профилем:

./run.sh          # профиль dev (по умолчанию)
./run.sh prod     # профиль prod
./run.sh --help   # справка

Скрипт выполняет ./mvnw spring-boot:run -Dspring-boot.run.profiles=<profile>. Приложение будет доступно на http://localhost:8080/api.

Интерфейс

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

Стили общие для всех страниц: src/main/resources/static/css/app.css.

Web-интерфейс выполнен в стиле Gmail: шапка с логотипом, поисковой строкой и кнопкой Compose, список с карточками, аватарами и действиями, карточка просмотра и формы редактирования.

Поиск по спискам выполняется через параметр q: http://localhost:8080/api/companies?q=foo.

Логирование

Сделана настройка вывода протоколв работы через logback в папку logs. Настройки в файле resources/logback-spring.xml

Конфигурации (spring profiles)

Конфигурация для разработки расположена в application-dev.yml, для production версии в application-prod.yml. С какой конфигурацией запускать указано в application.yml

spring:
  profiles:
    active: dev

Профиль можно указать при запуске:

$java -jar target/companies-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod

Validate DTO

http POST :8080/api/employee/validate < src/test/test_jsons/empl_err1.json
HTTP/1.1 400
Connection: close
Content-Type: application/json
Date: Mon, 23 Oct 2023 07:24:22 GMT
Transfer-Encoding: chunked

{
    "errors": [
        "field: firstname, error: Firstname empty"
    ],
    "message": "Errors in input dto: EmployeeDto{n=null, firstname='', lastname='LASTNAME_31', fathername='FATHERNAME_31', birthday='2023/01/01'}",
    "status": "BAD_REQUEST"
}

Ручная проверка REST

Проверка ответа при ошибке:

$ http :8080/api/company/1000

HTTP/1.1 502
Connection: keep-alive
Content-Length: 25
Content-Type: text/plain;charset=UTF-8

Company not found id=1000

app.log:

ERROR [http-nio-8080-exec-1] ru.perm.v.companies.rest.CompanyRest: Company not found id=1000

ResponseEntity

Показаны разные способы возврата ошибки.

1) Простой:

CompanyRest.deleteById(...)

@DeleteMapping("/{id}")
public ResponseEntity<Long> deleteById(@PathVariable Long id) {
    try {
        companyService.deleteById(id);
        return ResponseEntity.ok(id);
    } catch (Exception e) {
        log.error(e.getMessage());
        return new ResponseEntity(e.getMessage(), HttpStatus.BAD_GATEWAY);
    }
}

2) И через ApiError:

EmployeeRest.update():

if (errors.size() > 0) {
    ApiError apiError =
            new ApiError(HttpStatus.BAD_REQUEST, String.format("Errors in input dto: %s", dto), errors);
    return new ResponseEntity(
            apiError, new HttpHeaders(), apiError.getStatus());
}

CI/CD

Скрипт Jenkinsfile для сборки для Jenkins. Вставлены некоторые команды для отладки и понятия работы Jenkinsfile:

pipeline {
    agent any
    options {internal
        durabilityHint 'MAX_SURVIVABILITY'
    }
    stages {
        stage('git clone') {
            steps {
                sh 'pwd'
                sh 'rm -rf compaines'
                sh 'git clone https://github.com/cherepakhin/companies.git'
                sh 'ls'
            }
        }
        stage('unit tests') {
            steps {
                sh 'ls'
                sh 'cd companies;chmod +x mvnw;ls -al;pwd'
                sh 'pwd;ls -al;cd companies;./mvnw test -Dtest=!*_IntegrationTest'
                sh 'ls'
            }
        }
        stage('deploy to nexus') {
            steps {
                sh 'ls'
                sh 'cd companies;ls;./mvnw -Dmaven.test.skip=true deploy'
                sh 'ls'
            }
        }
    }
}

Примечание: каждый sh в stage начинает работу от рабочего каталога проекта Jenkins (см. stage('unit tests')):

sh 'ls'
sh 'cd companies;chmod +x mvnw;ls -al;pwd'
sh 'pwd;ls -al;cd companies;./mvnw test -Dtest=!*_IntegrationTest'
sh 'ls'

jenkins_pipeline

Jenkins user: vasi
Jenkins password: pass

Deploy:

./mvnw deploy

Примечания

Для гибкой работы с СУБД используется QueryDSL

Ссылки

Разное

keyboard shortcuts:
Alt-F12 - переход в терминал

Разные варианты LIKE https://www.baeldung.com/spring-jpa-like-queries

Неожиданно стал СИЛЬНО ТОРМОЗИТЬ запуск тестов в Idea. Удалил папку .idea.

Просмотр и установка переменных в Windows из PowerShell:

C:>$env:PG_USER="vasi"
C:>ls env:
C:>> ls env:PG_USER

Name                           Value
----                           -----
PG_USER                        vasi

Перестройка миграций flyway(при ошибках "Flyway: Found non-empty schema(s) "public" without schema history table! Use baseline() - on Empty database") установить в application.yaml:

spring:
   flyway:
      baseline-on-migrate = true