Разработка InnerLoop с использованием Java — SpringBoot

1. Обзор

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

Что вы узнаете

В этой лабораторной работе вы изучите методы разработки с использованием контейнеров в GCP, в том числе:

  • Настройка и требования
  • Создание нового стартового Java-приложения
  • Обзор процесса разработки
  • Разработка простого REST-сервиса с операциями CRUD.
  • Уборка

2. Настройка и требования

Настройка среды для самостоятельного обучения

  1. Войдите в консоль Google Cloud и создайте новый проект или используйте существующий. Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Название проекта — это отображаемое имя участников данного проекта. Это строка символов, не используемая API Google, и вы можете изменить её в любое время.
  • Идентификатор проекта должен быть уникальным для всех проектов Google Cloud и неизменяемым (его нельзя изменить после установки). Консоль Cloud автоматически генерирует уникальную строку; обычно вам неважно, какая она. В большинстве практических заданий вам потребуется указать идентификатор проекта (обычно он обозначается как PROJECT_ID ), поэтому, если он вам не нравится, сгенерируйте другой случайный идентификатор или попробуйте свой собственный и посмотрите, доступен ли он. Затем он "замораживается" после создания проекта.
  • Существует третье значение — номер проекта , который используется некоторыми API. Подробнее обо всех трех значениях можно узнать в документации .
  1. Далее вам потребуется включить оплату в консоли Cloud, чтобы использовать ресурсы/API Cloud. Выполнение этого практического задания не должно стоить дорого, если вообще что-либо. Чтобы отключить ресурсы и избежать дополнительных расходов после завершения этого урока, следуйте инструкциям по «очистке», приведенным в конце практического задания. Новые пользователи Google Cloud имеют право на бесплатную пробную версию стоимостью 300 долларов США .

Запустить редактор Cloudshell

Данная лабораторная работа разработана и протестирована для использования с редактором Google Cloud Shell. Для доступа к редактору,

  1. Получите доступ к своему проекту Google по адресу https://console.cloud.google.com .
  2. В правом верхнем углу нажмите на значок редактора облачной оболочки.

8560cc8d45e8c112.png

  1. В нижней части вашего окна откроется новое окно.
  2. Нажмите кнопку «Открыть редактор».

9e504cb98a6a8005.png

  1. В начале редактора справа будет изображен исследователь, а в центре — сам редактор.
  2. В нижней части экрана также должна быть доступна панель терминала.
  3. Если терминал НЕ открыт, используйте комбинацию клавиш `Ctrl+`, чтобы открыть новое окно терминала.

Настройте gcloud

В Cloud Shell укажите идентификатор проекта и регион, в который вы хотите развернуть приложение. Сохраните их как переменные PROJECT_ID и REGION .

export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')

Получите исходный код

Исходный код для этой лабораторной работы находится в репозитории container-developer-workshop на GitHub в GoogleCloudPlatform. Клонируйте его с помощью приведенной ниже команды, а затем перейдите в нужную директорию.

git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git
cd container-developer-workshop/labs/spring-boot

Обеспечьте наличие инфраструктуры, используемой в этой лаборатории.

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

./setup.sh

3. Создание нового стартового Java-приложения

В этом разделе вы создадите новое Java-приложение Spring Boot с нуля, используя пример приложения, предоставленный spring.io.

Клонируйте демонстрационное приложение

  1. Создайте стартовое приложение
curl  https://start.spring.io/starter.zip -d dependencies=web -d type=maven-project -d javaVersion=11 -d packageName=com.example.springboot -o sample-app.zip
  1. Распакуйте приложение.
unzip sample-app.zip -d sample-app
  1. Перейдите в каталог sample-app и откройте папку в рабочей области Cloud Shell IDE.
cd sample-app && cloudshell workspace .

Добавьте spring-boot-devtools и Jib.

Чтобы включить инструменты разработчика Spring Boot, найдите и откройте файл pom.xml в обозревателе вашего редактора. Затем вставьте следующий код после строки описания, которая выглядит так: <description>Демонстрационный проект для Spring Boot</description>

  1. Добавьте spring-boot-devtools в pom.xml

Откройте файл pom.xml в корне проекта. Добавьте следующую конфигурацию после записи Description .

pom.xml

  <!--  Spring profiles-->
  <profiles>
    <profile>
      <id>sync</id>
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
        </dependency>
      </dependencies>
    </profile>
  </profiles>
  1. Включите jib-maven-plugin в pom.xml

Jib — это инструмент контейнеризации Java с открытым исходным кодом от Google, который позволяет Java-разработчикам создавать контейнеры, используя знакомые им инструменты Java. Jib — это быстрый и простой конструктор образов контейнеров, который обрабатывает все этапы упаковки вашего приложения в образ контейнера. Он не требует написания Dockerfile или установки Docker и напрямую интегрирован в Maven и Gradle.

Прокрутите вниз файл pom.xml и обновите раздел Build , добавив плагин Jib. После завершения раздел Build должен соответствовать следующему содержимому.

pom.xml

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <!--  Jib Plugin-->
      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>3.2.0</version>
      </plugin>
       <!--  Maven Resources Plugin-->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>3.1.0</version>
      </plugin>
    </plugins>
  </build>

При изменении файла сборки выберите Always .

447a90338f51931f.png

Создать манифесты

Skaffold предоставляет интегрированные инструменты для упрощения разработки контейнеров. На этом этапе вы инициализируете Skaffold, который автоматически создаст базовые YAML-файлы Kubernetes. Процесс пытается идентифицировать каталоги с определениями образов контейнеров, подобно Dockerfile, а затем создает манифест развертывания и службы для каждого из них.

Выполните указанную ниже команду, чтобы начать процесс.

  1. Выполните следующую команду в терминале.
skaffold init --generate-manifests
  1. При появлении запроса:
  • Используйте стрелки, чтобы переместить курсор к Jib Maven Plugin
  • Нажмите пробел, чтобы выбрать нужный вариант.
  • Нажмите Enter, чтобы продолжить.
  1. Введите 8080 для порта.
  2. Введите y для сохранения конфигурации.

В рабочую область добавлены два файла: skaffold.yaml и deployment.yaml

Обновить название приложения

Значения по умолчанию, указанные в конфигурации, в настоящее время не соответствуют названию вашего приложения. Обновите файлы, чтобы они ссылались на название вашего приложения, а не на значения по умолчанию.

  1. Измените записи в конфигурации Skaffold.
  • Откройте skaffold.yaml
  • Выберите имя изображения, которое в данный момент задано как pom-xml-image
  • Щелкните правой кнопкой мыши и выберите «Изменить все вхождения».
  • Введите новое имя как demo-app
  1. Измените записи в конфигурации Kubernetes.
  • Откройте файл deployment.yaml
  • Выберите имя изображения, которое в данный момент задано как pom-xml-image
  • Щелкните правой кнопкой мыши и выберите «Изменить все вхождения».
  • Введите новое имя как demo-app

Включить горячую синхронизацию

Для оптимизации процесса горячей перезагрузки вы будете использовать функцию синхронизации, предоставляемую Jib. На этом шаге вы настроите Skaffold для использования этой функции в процессе сборки.

Обратите внимание, что профиль "sync", который вы настраиваете в конфигурации skaffold, использует профиль "sync" Spring, который вы настроили на предыдущем шаге, где вы включили поддержку spring-dev-tools.

  1. Обновить конфигурацию Skaffold

В файле skaffold.yaml замените весь раздел build следующим описанием. Не изменяйте другие разделы файла.

skaffold.yaml

build:
  artifacts:
  - image: demo-app
    jib:
      project: com.example:demo
      type: maven
      args: 
      - --no-transfer-progress
      - -Psync
      fromImage: gcr.io/distroless/java:debug
    sync:
      auto: true

Добавить маршрут по умолчанию

Создайте файл с именем HelloController.java в папке /src/main/java/com/example/springboot/

Вставьте следующее содержимое в файл, чтобы создать HTTP-маршрут по умолчанию.

HelloController.java

package com.example.springboot;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Value;

@RestController
public class HelloController {

    @Value("${target:local}")
    String target;

    @GetMapping("/") 
    public String hello()
    {
        return String.format("Hello from your %s environment!", target);
    }
}

4. Обзор процесса разработки.

В этом разделе вы шаг за шагом изучите основные процессы с помощью плагина Cloud Code, а также проверите конфигурацию и настройку вашего стартового приложения.

Cloud Code интегрируется со Skaffold для оптимизации процесса разработки. При развертывании в GKE на следующих этапах Cloud Code и Skaffold автоматически создадут образ контейнера, загрузят его в реестр контейнеров, а затем развернут ваше приложение в GKE. Это происходит в фоновом режиме, абстрагируя детали от процесса разработки. Cloud Code также улучшает процесс разработки, предоставляя традиционные возможности отладки и синхронизации для разработки на основе контейнеров.

Развертывание в Kubernetes

  1. В нижней части окна редактора Cloud Shell выберите Cloud Code.

fdc797a769040839.png

  1. В появившейся вверху панели выберите «Отладка в Kubernetes». Если появится запрос, выберите «Да», чтобы использовать текущий контекст Kubernetes.

cfce0d11ef307087.png

  1. При первом запуске команды в верхней части экрана появится запрос, спрашивающий, хотите ли вы использовать текущий контекст Kubernetes. Выберите «Да», чтобы принять запрос и использовать текущий контекст.

817ee33b5b412ff8.png

  1. Далее появится запрос о том, какой реестр контейнеров использовать. Нажмите Enter, чтобы принять предоставленное значение по умолчанию.

eb4469aed97a25f6.png

  1. Чтобы просмотреть ход выполнения и уведомления, выберите вкладку «Вывод» в нижней панели.

f95b620569ba96c5.png

  1. Выберите «Kubernetes: Запуск/Отладка — Подробная информация» в раскрывающемся списке каналов справа, чтобы просмотреть дополнительные сведения и журналы, транслируемые в режиме реального времени из контейнеров.

94acdcdda6d2108.png

  1. Вернитесь к упрощенному представлению, выбрав в раскрывающемся списке «Kubernetes: Run/Debug».
  2. После завершения сборки и тестирования на вкладке «Вывод» отображается сообщение: Resource deployment/demo-app status completed successfully , а также указан URL-адрес: «Перенаправленный URL-адрес из службы демонстрационного приложения: http://localhost:8080».
  3. В терминале Cloud Code наведите курсор на URL-адрес в выводе (http://localhost:8080), а затем во всплывающей подсказке выберите «Открыть предварительный просмотр веб-страницы».

Ответ будет следующим:

Hello from your local environment!

Используйте точки останова

  1. Откройте приложение HelloController.java, расположенное по адресу /src/main/java/com/example/springboot/HelloController.java
  2. Найдите оператор return для корневого пути, который выглядит следующим образом: return String.format("Hello from your %s environment!", target);
  3. Установите точку останова на этой строке, щелкнув по пустому месту слева от номера строки. Красный индикатор покажет, что точка останова установлена.
  4. Перезагрузите браузер и обратите внимание, что отладчик останавливает процесс в точке останова и позволяет исследовать переменные и состояние приложения, работающего удаленно в GKE.
  5. Прокрутите вниз в раздел переменных, пока не найдете переменную "Цель".
  6. Текущее значение следует рассматривать как "локальное".
  7. Дважды щелкните по имени переменной "target", и во всплывающем окне измените значение на другое, например, "Cloud".
  8. Нажмите кнопку «Продолжить» на панели управления отладкой.
  9. Проверьте ответ в браузере, где теперь отображается обновленное значение, которое вы только что ввели.

Горячая перезарядка

  1. Измените оператор так, чтобы он возвращал другое значение, например, "Привет от %s Code".
  2. Файл автоматически сохраняется и синхронизируется с удалёнными контейнерами в GKE.
  3. Обновите страницу в браузере, чтобы увидеть обновленные результаты.
  4. Остановите сеанс отладки, нажав на красный квадрат на панели инструментов отладки. a13d42d726213e6c.png

5. Разработка простого REST-сервиса с операциями CRUD.

На этом этапе ваше приложение полностью настроено для контейнерной разработки, и вы прошли базовый рабочий процесс разработки с помощью Cloud Code. В следующих разделах вы попрактикуетесь в применении полученных знаний, добавив конечные точки REST-сервиса, подключающиеся к управляемой базе данных в Google Cloud.

Настройка зависимостей

Код приложения использует базу данных для сохранения данных REST-сервиса. Убедитесь в наличии зависимостей, добавив следующее в файл pom.xl.

  1. Откройте файл pom.xml и добавьте следующее в раздел dependencies файла config.xml.

pom.xml

    <!--  Database dependencies-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.flywaydb</groupId>
      <artifactId>flyway-core</artifactId>
    </dependency>

Напишите код для REST-сервиса

Quote.java

Создайте файл с именем Quote.java в папке /src/main/java/com/example/springboot/ и скопируйте в него приведенный ниже код. Он определяет модель Entity для объекта Quote, используемого в приложении.

package com.example.springboot;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Objects;

@Entity
@Table(name = "quotes")
public class Quote
{
    @Id
    @Column(name = "id")
    private Integer id;

    @Column(name="quote")
    private String quote;

    @Column(name="author")
    private String author;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getQuote() {
        return quote;
    }

    public void setQuote(String quote) {
        this.quote = quote;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
      if (o == null || getClass() != o.getClass()) {
        return false;
      }
        Quote quote1 = (Quote) o;
        return Objects.equals(id, quote1.id) &&
                Objects.equals(quote, quote1.quote) &&
                Objects.equals(author, quote1.author);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, quote, author);
    }
}

QuoteRepository.java

Создайте файл с именем QuoteRepository.java в папке src/main/java/com/example/springboot и скопируйте в него следующий код.

package com.example.springboot;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface QuoteRepository extends JpaRepository<Quote,Integer> {

    @Query( nativeQuery = true, value =
            "SELECT id,quote,author FROM quotes ORDER BY RANDOM() LIMIT 1")
    Quote findRandomQuote();
}

Этот код использует JPA для сохранения данных. Класс расширяет интерфейс Spring JPARepository и позволяет создавать собственный код. В коде вы добавили пользовательский метод findRandomQuote .

QuoteController.java

Для предоставления доступа к конечной точке сервиса этот функционал будет реализован в классе QuoteController .

Создайте файл с именем QuoteController.java в папке src/main/java/com/example/springboot и скопируйте в него следующее содержимое.

package com.example.springboot;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class QuoteController {

    private final QuoteRepository quoteRepository;

    public QuoteController(QuoteRepository quoteRepository) {
        this.quoteRepository = quoteRepository;
    }

    @GetMapping("/random-quote") 
    public Quote randomQuote()
    {
        return quoteRepository.findRandomQuote();  
    }

    @GetMapping("/quotes") 
    public ResponseEntity<List<Quote>> allQuotes()
    {
        try {
            List<Quote> quotes = new ArrayList<Quote>();
            
            quoteRepository.findAll().forEach(quotes::add);

            if (quotes.size()==0 || quotes.isEmpty()) 
                return new ResponseEntity<List<Quote>>(HttpStatus.NO_CONTENT);
                
            return new ResponseEntity<List<Quote>>(quotes, HttpStatus.OK);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<List<Quote>>(HttpStatus.INTERNAL_SERVER_ERROR);
        }        
    }

    @PostMapping("/quotes")
    public ResponseEntity<Quote> createQuote(@RequestBody Quote quote) {
        try {
            Quote saved = quoteRepository.save(quote);
            return new ResponseEntity<Quote>(saved, HttpStatus.CREATED);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<Quote>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }     

    @PutMapping("/quotes/{id}")
    public ResponseEntity<Quote> updateQuote(@PathVariable("id") Integer id, @RequestBody Quote quote) {
        try {
            Optional<Quote> existingQuote = quoteRepository.findById(id);
            
            if(existingQuote.isPresent()){
                Quote updatedQuote = existingQuote.get();
                updatedQuote.setAuthor(quote.getAuthor());
                updatedQuote.setQuote(quote.getQuote());

                return new ResponseEntity<Quote>(updatedQuote, HttpStatus.OK);
            } else {
                return new ResponseEntity<Quote>(HttpStatus.NOT_FOUND);
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<Quote>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }     

    @DeleteMapping("/quotes/{id}")
    public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
        try {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }    
}

Добавить настройки базы данных

application.yaml

Добавьте конфигурацию для базы данных бэкэнда, к которой обращается сервис. Отредактируйте (или создайте, если она отсутствует) файл application.yaml в папке src/main/resources и добавьте параметризованную конфигурацию Spring для бэкэнда.

target: local

spring:
  config:
    activate:
      on-profile: cloud-dev
  datasource:
    url: 'jdbc:postgresql://${DB_HOST:127.0.0.1}/${DB_NAME:quote_db}'
    username: '${DB_USER:user}'
    password: '${DB_PASS:password}'
  jpa:
    properties:
      hibernate:
        jdbc:
          lob:
            non_contextual_creation: true
        dialect: org.hibernate.dialect.PostgreSQLDialect
    hibernate:
      ddl-auto: update

Добавить миграцию базы данных

Создайте папку по адресу src/main/resources/db/migration/

Создайте SQL-файл: V1__create_quotes_table.sql

Вставьте следующее содержимое в файл.

V1__create_quotes_table.sql

CREATE TABLE quotes(
   id INTEGER PRIMARY KEY,
   quote VARCHAR(1024),
   author VARCHAR(256)
);

INSERT INTO quotes (id,quote,author) VALUES (1,'Never, never, never give up','Winston Churchill');
INSERT INTO quotes (id,quote,author) VALUES (2,'While there''s life, there''s hope','Marcus Tullius Cicero');
INSERT INTO quotes (id,quote,author) VALUES (3,'Failure is success in progress','Anonymous');
INSERT INTO quotes (id,quote,author) VALUES (4,'Success demands singleness of purpose','Vincent Lombardi');
INSERT INTO quotes (id,quote,author) VALUES (5,'The shortest answer is doing','Lord Herbert');

Конфигурация Kubernetes

Следующие дополнения к файлу deployment.yaml позволяют приложению подключаться к экземплярам CloudSQL.

  • TARGET — задает переменную, указывающую среду, в которой выполняется приложение.
  • SPRING_PROFILES_ACTIVE — показывает активный профиль Spring, который будет настроен для cloud-dev
  • DB_HOST — частный IP-адрес базы данных, который был указан при создании экземпляра базы данных или при нажатии на SQL в навигационном меню консоли Google Cloud — пожалуйста, измените это значение!
  • DB_USER и DB_PASS — параметры, заданные в конфигурации экземпляра CloudSQL и хранящиеся в качестве секрета в GCP.

Обновите файл deployment.yaml, добавив в него содержимое, указанное ниже.

deployment.yaml

apiVersion: v1
kind: Service
metadata:
  name: demo-app
  labels:
    app: demo-app
spec:
  ports:
  - port: 8080
    protocol: TCP
  clusterIP: None
  selector:
    app: demo-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-app
  labels:
    app: demo-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo-app
  template:
    metadata:
      labels:
        app: demo-app
    spec:
      containers:
      - name: demo-app
        image: demo-app
        env:
          - name: PORT
            value: "8080"
          - name: TARGET
            value: "Local Dev - CloudSQL Database - K8s Cluster"
          - name: SPRING_PROFILES_ACTIVE
            value: cloud-dev
          - name: DB_HOST
            value: ${DB_INSTANCE_IP}   
          - name: DB_PORT
            value: "5432"  
          - name: DB_USER
            valueFrom:
              secretKeyRef:
                name: gke-cloud-sql-secrets
                key: username
          - name: DB_PASS
            valueFrom:
              secretKeyRef:
                name: gke-cloud-sql-secrets
                key: password
          - name: DB_NAME
            valueFrom:
              secretKeyRef:
                name: gke-cloud-sql-secrets
                key: database 

Замените значение DB_HOST адресом вашей базы данных.

export DB_INSTANCE_IP=$(gcloud sql instances describe quote-db-instance \
    --format=json | jq \
    --raw-output ".ipAddresses[].ipAddress")

envsubst < deployment.yaml > deployment.new && mv deployment.new deployment.yaml

Развертывание и проверка приложения

  1. В нижней части окна редактора Cloud Shell выберите Cloud Code, а затем в верхней части экрана выберите Debug on Kubernetes.
  2. После завершения сборки и тестирования на вкладке «Вывод» отображается сообщение: Resource deployment/demo-app status completed successfully , а также указан URL-адрес: «Перенаправленный URL-адрес из службы демонстрационного приложения: http://localhost:8080».
  3. Просмотреть случайные цитаты

В терминале CloudShell выполните приведенную ниже команду несколько раз, обращаясь к конечной точке random-quote. Обратите внимание, что при повторном вызове возвращаются разные котировки.

curl -v 127.0.0.1:8080/random-quote
  1. Добавить цитату

Создайте новое коммерческое предложение с идентификатором id=6, используя указанную ниже команду, и понаблюдайте за тем, как запрос будет возвращен.

curl -v -H 'Content-Type: application/json' -d '{"id":"6","author":"Henry David Thoreau","quote":"Go confidently in the direction of your dreams! Live the life you have imagined"}' -X POST 127.0.0.1:8080/quotes
  1. Удалить цитату

Теперь удалите только что добавленную цитату с помощью метода delete и посмотрите на код ответа HTTP/1.1 204 .

curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. Ошибка сервера

Повторный запуск последнего запроса после удаления записи может привести к ошибке.

curl -v -X DELETE 127.0.0.1:8080/quotes/6

Обратите внимание, что в ответе возвращается HTTP:500 Internal Server Error .

Отладка приложения

В предыдущем разделе вы обнаружили ошибку в приложении при попытке удалить запись, которой не было в базе данных. В этом разделе вы установите точку останова, чтобы найти проблему. Ошибка произошла в операции DELETE, поэтому вы будете работать с классом QuoteController.

  1. Откройте файл src.main.java.com.example.springboot.QuoteController.java
  2. Найдите метод deleteQuote()
  3. Найдите строку, в которой выполняется удаление элемента из базы данных: quoteRepository.deleteById(id);
  4. Установите точку останова на этой строке, щелкнув по пустому месту слева от номера строки.
  5. Появится красный индикатор, указывающий на то, что точка останова установлена.
  6. Повторите команду delete .
curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. Чтобы вернуться в режим отладки, нажмите на значок в левой колонке.
  2. Обратите внимание на строку отладки, которая останавливается в классе QuoteController.
  3. В отладчике нажмите значок " step over ". b814d39b2e5f3d9e.png и обратите внимание, что будет выброшено исключение.
  4. Обратите внимание, что RuntimeException was caught. Оно возвращает клиенту ошибку Internal Server Error HTTP 500, что нежелательно.
   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> DELETE /quotes/6 HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500
< Content-Length: 0
< Date: 
<
* Connection #0 to host 127.0.0.1 left intact

Обновите код

Код некорректен, и блок обработки исключений следует переписать таким образом, чтобы он перехватывал исключение EmptyResultDataAccessException и отправлял обратно код состояния HTTP 404 "Не найдено".

Исправьте ошибку.

  1. Не отключая сеанс отладки, завершите запрос, нажав кнопку «Продолжить» на панели управления отладкой.
  2. Далее добавьте в код следующий блок:
       } catch (EmptyResultDataAccessException e){
            return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
        }

Метод должен выглядеть следующим образом.

    public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
        try {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } catch(EmptyResultDataAccessException e){
            return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
  1. Повторно выполните команду удаления.
curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. Пройдите пошаговый отладчик и понаблюдайте за тем, как перехватывается исключение EmptyResultDataAccessException и как вызывающей стороне возвращается HTTP-запрос 404 Not Found.
   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> DELETE /quotes/6 HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 404
< Content-Length: 0
< Date: 
<
* Connection #0 to host 127.0.0.1 left intact
  1. Остановите сеанс отладки, нажав на красный квадрат на панели инструментов отладки. a13d42d726213e6c.png

6. Уборка

Поздравляем! В этой лабораторной работе вы создали с нуля новое Java-приложение и настроили его для эффективной работы с контейнерами. Затем вы развернули и отладили свое приложение в удаленном кластере GKE, следуя тому же процессу разработки, что и в традиционных стеках приложений.

После завершения лабораторной работы необходимо провести уборку:

  1. Удалите файлы, использованные в лабораторной работе.
cd ~ && rm -rf container-developer-workshop
  1. Удалите проект, чтобы удалить всю связанную с ним инфраструктуру и ресурсы.