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

1. Обзор

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

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

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

  • Настройка и требования
  • Создание нового начального приложения Java
  • Прогулка по процессу разработки
  • Разработка простой службы отдыха CRUD
  • Очистка

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

Самостоятельная настройка среды

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

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Имя проекта — это отображаемое имя для участников этого проекта. Это строка символов, не используемая API Google, и вы можете обновить ее в любое время.
  • Идентификатор проекта должен быть уникальным для всех проектов Google Cloud и неизменяемым (нельзя изменить после его установки). Cloud Console автоматически генерирует уникальную строку; обычно тебя не волнует, что это такое. В большинстве лабораторий кода вам потребуется указать идентификатор проекта (обычно он обозначается как PROJECT_ID ), поэтому, если он вам не нравится, создайте другой случайный идентификатор или попробуйте свой собственный и посмотрите, доступен ли он. Затем он «замораживается» после создания проекта.
  • Существует третье значение — номер проекта , который используют некоторые API. Подробнее обо всех трех этих значениях читайте в документации .
  1. Далее вам необходимо включить выставление счетов в Cloud Console, чтобы использовать облачные ресурсы/API. Прохождение этой лаборатории кода не должно стоить много, если вообще стоит. Чтобы отключить ресурсы и не платить за выставление счетов за пределами этого руководства, следуйте инструкциям по «очистке», которые можно найти в конце лаборатории кода. Новые пользователи 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)')

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

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

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. Перейдите в каталог примера приложения и откройте папку в рабочей области Cloud Shell IDE.
cd sample-app && cloudshell workspace .

Добавьте Spring-Boot-devtools и Jib

Чтобы включить Spring Boot DevTools, найдите и откройте 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 в pom.xml

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

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

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 предоставляет интегрированные инструменты для упрощения разработки контейнеров. На этом этапе вы инициализируете скаффолд, который автоматически создаст базовые файлы 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 для использования этой функции в процессе сборки.

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

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

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

скаффолд.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-маршрут по умолчанию.

Приветконтроллер.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: Run/Debug — Detailed» в раскрывающемся списке каналов справа, чтобы просмотреть дополнительные сведения и журналы, транслируемые в реальном времени из контейнеров.

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 String.format("Hello from your %s environment!", target);
  3. Добавьте точку останова в эту строку, щелкнув пустое место слева от номера строки. Красный индикатор покажет, что точка останова установлена.
  4. Перезагрузите браузер и обратите внимание, что отладчик останавливает процесс в точке останова и позволяет вам исследовать переменное состояние песка приложения, которое работает удаленно в GKE.
  5. Нажимайте вниз в раздел переменных, пока не найдете переменную «Цель».
  6. Наблюдайте за текущим значением как «локальное».
  7. Дважды щелкните имя переменной «цель» и во всплывающем окне измените значение на другое, например «Облако».
  8. Нажмите кнопку «Продолжить» на панели управления отладкой.
  9. Просмотрите ответ в своем браузере, который теперь показывает обновленное значение, которое вы только что ввели.

Горячая перезагрузка

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

5. Разработка простой службы отдыха CRUD

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

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

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

  1. Откройте файл pom.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>

Код остальной службы

Цитата.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 .

ЦитатаController.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');

Конфигурация Кубернетеса

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

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

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

развертывание.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, затем выберите «Отладка в Kubernetes» вверху экрана.
  2. Когда сборка и тесты завершены, на вкладке «Вывод» отображается сообщение: Resource deployment/demo-app status completed successfully » и отображается URL-адрес: «Перенаправленный URL-адрес из демонстрационного приложения службы: http://localhost:8080».
  3. Просмотр случайных цитат

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

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

Создайте новую цитату с идентификатором = 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. Это возвращает клиенту внутреннюю ошибку сервера 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. Удалите проект, чтобы удалить всю связанную инфраструктуру и ресурсы.