«1. Обзор

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

Приложения, работающие с микросервисной архитектурой, призваны упростить разработку, развертывание и обслуживание. Декомпозированный характер приложения позволяет разработчикам сосредоточиться на одной проблеме за раз. Улучшения могут быть введены без воздействия на другие части системы.

С другой стороны, когда мы используем микросервисный подход, возникают другие проблемы:

    Экстернализация конфигурации, чтобы она была гибкой и не требовала перестроения сервиса при изменении Обнаружение сервисов Скрытие сложности сервисов, развернутых на разных хостах ~~ ~ В этой статье мы создадим пять микрослужб: сервер конфигурации, сервер обнаружения, сервер шлюза, службу книг и, наконец, службу оценки. Эти пять микросервисов образуют надежное базовое приложение для начала разработки облака и решения вышеупомянутых проблем.

2. Сервер конфигурации

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

Чтобы решить эту проблему, мы объединим всю нашу конфигурацию в один репозиторий Git и подключим его к одному приложению, которое управляет конфигурацией всех наших приложений. Мы собираемся настроить очень простую реализацию.

Чтобы узнать больше деталей и увидеть более сложный пример, ознакомьтесь с нашей статьей Spring Cloud Configuration.

2.1. Настройка

Перейдите на https://start.spring.io и выберите Maven и Spring Boot 2.2.x.

Установите для артефакта значение «config». В разделе зависимостей найдите «config server» и добавьте этот модуль. Затем нажмите кнопку «Создать», и мы сможем загрузить zip-файл с предварительно настроенным проектом внутри и готовым к работе.

Кроме того, мы можем сгенерировать проект Spring Boot и добавить некоторые зависимости в файл POM вручную.

Эти зависимости будут общими для всех проектов:

Давайте добавим зависимость для сервера конфигурации:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.0</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies> 

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR4</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Для справки, мы можем найти последнюю версию на Maven Central (spring-cloud -зависимости, тест, конфиг-сервер).

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>

2.2. Spring Config

Чтобы включить сервер конфигурации, мы должны добавить некоторые аннотации к основному классу приложения:

@EnableConfigServer превратит наше приложение в сервер конфигурации.

@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {...}

2.3. Свойства

Давайте добавим application.properties в src/main/resources:

Наиболее важной настройкой для сервера конфигурации является параметр git.uri. В настоящее время установлен относительный путь к файлу, который обычно разрешается в c:\\Users\\{имя пользователя}\\ в Windows или /Users/{имя пользователя}/ в *nix. Это свойство указывает на репозиторий Git, в котором хранятся файлы свойств для всех других приложений. При необходимости можно установить абсолютный путь к файлу.

server.port=8081
spring.application.name=config

spring.cloud.config.server.git.uri=file://${user.home}/application-config

Совет: На машине с Windows предварите значение «file:///», на *nix используйте «file://».

2.4. Репозиторий Git

Перейдите к папке, указанной в spring.cloud.config.server.git.uri, и добавьте папку application-config. компакт-диск в эту папку и введите git init. Это инициализирует репозиторий Git, где мы можем хранить файлы и отслеживать их изменения.

2.5. Run

Запустим сервер конфигурации и убедимся, что он работает. В командной строке введите mvn spring-boot:run. Это запустит сервер.

Мы должны увидеть этот вывод, указывающий на то, что сервер запущен:

2.6. Конфигурация начальной загрузки

Tomcat started on port(s): 8081 (http)

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

«Это процесс начальной загрузки, и каждое из этих приложений будет иметь файл с именем bootstrap.properties. Он будет содержать такие же свойства, как и application.properties, но с одной изюминкой:

Родительский Spring ApplicationContext сначала загружает bootstrap.properties. Это очень важно, чтобы Config Server мог начать управлять свойствами в application.properties. Именно этот специальный ApplicationContext также будет расшифровывать любые зашифрованные свойства приложения.

Разумно хранить эти файлы свойств отдельно. bootstrap.properties предназначен для подготовки сервера конфигурации, а application.properties — для свойств, специфичных для нашего приложения. Однако технически можно поместить свойства приложения в bootstrap.properties.

Наконец, поскольку Config Server управляет свойствами нашего приложения, может возникнуть вопрос, зачем вообще нужен файл application.properties? Ответ заключается в том, что они по-прежнему пригодятся в качестве значений по умолчанию, которых, возможно, нет в Config Server.

3. Обнаружение

Теперь, когда мы позаботились о конфигурации, нам нужен способ, чтобы все наши серверы могли найти друг друга. Мы решим эту проблему, настроив сервер обнаружения Eureka. Поскольку наши приложения могут работать с любой комбинацией IP/порта, нам нужен центральный реестр адресов, который может служить для поиска адресов приложений.

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

Чтобы узнать больше деталей и увидеть более сложную реализацию обнаружения, ознакомьтесь со статьей Spring Cloud Eureka.

3.1. Настройка

Снова мы перейдем к start.spring.io. Установите артефакт на «обнаружение». Найдите «eureka server» и добавьте эту зависимость. Найдите «config client» и добавьте эту зависимость. Наконец, сгенерируйте проект.

Кроме того, мы можем создать проект Spring Boot, скопировать содержимое POM с сервера конфигурации и поменять местами эти зависимости:

Для справки, мы найдем пакеты на Maven Central (config-client , эврика-сервер).

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>

3.2. Spring Config

Давайте добавим конфигурацию Java в основной класс:

@EnableEurekaServer настроит этот сервер как сервер обнаружения, используя Netflix Eureka. Spring Boot автоматически обнаружит зависимость конфигурации от пути к классам и найдет конфигурацию на сервере конфигурации.

@SpringBootApplication
@EnableEurekaServer
public class DiscoveryApplication {...}

3.3. Свойства

Теперь мы добавим два файла свойств:

Во-первых, мы добавим bootstrap.properties в src/main/resources:

Эти свойства позволят серверу обнаружения запрашивать сервер конфигурации при запуске.

spring.cloud.config.name=discovery
spring.cloud.config.uri=http://localhost:8081

Во-вторых, мы добавляем в наш Git-репозиторий discovery.properties

Имя файла должно соответствовать свойству spring.application.name.

spring.application.name=discovery
server.port=8082

eureka.instance.hostname=localhost

eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

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

В рабочей среде у нас было бы более одного из них для обеспечения избыточности в случае сбоя, и этот параметр был бы истинным.

Давайте закоммитим файл в репозиторий Git. В противном случае файл не будет обнаружен.

3.4. Добавьте зависимость к серверу конфигурации

Добавьте эту зависимость в файл POM сервера конфигурации:

Для справки, мы можем найти пакет на Maven Central (eureka-client).

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

Добавьте эти свойства в файл application.properties в src/main/resources сервера конфигурации:

3.5. Выполнить

eureka.client.region = default
eureka.client.registryFetchIntervalSeconds = 5
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/

Запустите сервер обнаружения с помощью той же команды mvn spring-boot:run. Вывод из командной строки должен включать:

Остановите и перезапустите службу конфигурации. Если все в порядке, вывод должен выглядеть так:

Fetching config from server at: http://localhost:8081
...
Tomcat started on port(s): 8082 (http)

4. Шлюз

DiscoveryClient_CONFIG/10.1.10.235:config:8081: registering service...
Tomcat started on port(s): 8081 (http)
DiscoveryClient_CONFIG/10.1.10.235:config:8081 - registration status: 204

Теперь, когда мы решили проблемы с конфигурацией и обнаружением, у нас все еще есть проблема с доступом клиентов ко всем нашим приложениям.

«Если мы оставим все в распределенной системе, то нам придется управлять сложными заголовками CORS, чтобы разрешить кросс-происхождение запросов на клиентах. Мы можем решить эту проблему, создав сервер шлюза. Это будет действовать как обратный прокси-сервер, перенаправляющий запросы от клиентов на наши внутренние серверы.

Сервер шлюза — отличное приложение в микросервисной архитектуре, поскольку он позволяет отправлять все ответы с одного хоста. Это устранит необходимость в CORS и даст нам удобное место для решения распространенных проблем, таких как аутентификация.

4.1. Настройка

К настоящему времени мы знаем упражнение. Перейдите на https://start.spring.io. Установите артефакт на «шлюз». Найдите «zuul» и добавьте эту зависимость. Найдите «config client» и добавьте эту зависимость. Найдите «eureka discovery» и добавьте эту зависимость. Наконец, сгенерируйте этот проект.

В качестве альтернативы мы могли бы создать приложение Spring Boot со следующими зависимостями:

Для справки, мы можем найти пакет на Maven Central (config-client, eureka-client, zuul).

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>

4.2. Spring Config

Добавим конфигурацию в основной класс:

4.3. Свойства

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class GatewayApplication {...}

Теперь мы добавим два файла свойств:

bootstrap.properties в src/main/resources:

gateway.properties в нашем репозитории Git

spring.cloud.config.name=gateway
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true

eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/

Свойство zuul.routes позволяет нам определить приложение для маршрутизации определенных запросов на основе сопоставления URL-адресов ant. Наше свойство указывает Zuul направлять любой запрос, поступающий в /book-service/**, в приложение с spring.application.name book-service. Затем Zuul выполнит поиск хоста на сервере обнаружения, используя имя приложения, и перенаправит запрос на этот сервер.

spring.application.name=gateway
server.port=8080

eureka.client.region = default
eureka.client.registryFetchIntervalSeconds = 5

zuul.routes.book-service.path=/book-service/**
zuul.routes.book-service.sensitive-headers=Set-Cookie,Authorization
hystrix.command.book-service.execution.isolation.thread.timeoutInMilliseconds=600000

zuul.routes.rating-service.path=/rating-service/**
zuul.routes.rating-service.sensitive-headers=Set-Cookie,Authorization
hystrix.command.rating-service.execution.isolation.thread.timeoutInMilliseconds=600000

zuul.routes.discovery.path=/discovery/**
zuul.routes.discovery.sensitive-headers=Set-Cookie,Authorization
zuul.routes.discovery.url=http://localhost:8082
hystrix.command.discovery.execution.isolation.thread.timeoutInMilliseconds=600000

Не забудьте зафиксировать изменения в репозитории!

4.4. Запустите

Запустите приложения конфигурации и обнаружения и подождите, пока приложение конфигурации не зарегистрируется на сервере обнаружения. Если они уже запущены, нам не нужно их перезапускать. После этого запустите сервер шлюза. Сервер шлюза должен запускаться на порту 8080 и регистрироваться на сервере обнаружения. Вывод из консоли должен содержать:

Одна ошибка, которую легко сделать, состоит в том, чтобы запустить сервер до того, как сервер конфигурации зарегистрируется в Eureka. В этом случае мы увидим журнал с такими выводами:

Fetching config from server at: http://10.1.10.235:8081/
...
DiscoveryClient_GATEWAY/10.1.10.235:gateway:8080: registering service...
DiscoveryClient_GATEWAY/10.1.10.235:gateway:8080 - registration status: 204
Tomcat started on port(s): 8080 (http)

Это URL-адрес и порт по умолчанию для сервера конфигурации, указывающие на то, что у нашей службы обнаружения не было адреса, когда был сделан запрос конфигурации. Просто подождите несколько секунд и повторите попытку, как только сервер конфигурации зарегистрируется в Eureka, проблема будет решена.

Fetching config from server at: http://localhost:8888

5. Book Service

В микросервисной архитектуре мы можем создавать столько приложений, сколько необходимо для достижения бизнес-целей. Часто инженеры делят свои услуги по областям. Мы будем следовать этому шаблону и создадим книжный сервис для обработки всех операций с книгами в нашем приложении.

5.1. Настройка

Еще раз. Перейдите на https://start.spring.io. Установите артефакт на «book-service». Найдите «web» и добавьте эту зависимость. Найдите «config client» и добавьте эту зависимость. Найдите «eureka discovery» и добавьте эту зависимость. Создайте этот проект.

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

Для справки, мы можем найти пакет на Maven Central (config-client, eureka-client, web).

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

5.2. Spring Config

Давайте изменим наш основной класс:

Мы также добавили контроллер REST и поле, заданное нашим файлом свойств, чтобы возвращать значение, которое мы установим во время настройки.

@SpringBootApplication
@EnableEurekaClient
@RestController
@RequestMapping("/books")
public class BookServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(BookServiceApplication.class, args);
    }

    private List<Book> bookList = Arrays.asList(
        new Book(1L, "Baeldung goes to the market", "Tim Schimandle"),
        new Book(2L, "Baeldung goes to the park", "Slavisa")
    );

    @GetMapping("")
    public List<Book> findAllBooks() {
        return bookList;
    }

    @GetMapping("/{bookId}")
    public Book findBook(@PathVariable Long bookId) {
        return bookList.stream().filter(b -> b.getId().equals(bookId)).findFirst().orElse(null);
    }
}

Теперь добавим книгу POJO:

5.3. Свойства

public class Book {
    private Long id;
    private String author;
    private String title;

    // standard getters and setters
}

Теперь нам просто нужно добавить два файла свойств:

bootstrap.properties в src/main/resources:

book-service.properties в наш репозиторий Git:

spring.cloud.config.name=book-service
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true

eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/

Зафиксируем изменения в репозитории.

spring.application.name=book-service
server.port=8083

eureka.client.region = default
eureka.client.registryFetchIntervalSeconds = 5
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/

5.4. Запустить

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

«

DiscoveryClient_BOOK-SERVICE/10.1.10.235:book-service:8083: registering service...
DiscoveryClient_BOOK-SERVICE/10.1.10.235:book-service:8083 - registration status: 204
Tomcat started on port(s): 8083 (http)

«Как только он будет запущен, мы можем использовать наш браузер для доступа к конечной точке, которую мы только что создали. Перейдите к http://localhost:8080/book-service/books, и мы получим объект JSON с двумя книгами, которые мы добавили в наш контроллер. Обратите внимание, что мы не обращаемся к сервису книг напрямую через порт 8083, а идем через сервер шлюза.

6. Служба оценки

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

6.1. Настройка

Еще раз. Перейдите на https://start.spring.io. Установите для артефакта значение «rating-service». Найдите «web» и добавьте эту зависимость. Найдите «config client» и добавьте эту зависимость. Найдите «eureka discovery» и добавьте эту зависимость. Затем создайте этот проект.

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

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Для справки, мы можем найти пакет на Maven Central (config-client, eureka-client, web).

6.2. Spring Config

Давайте изменим наш основной класс:

@SpringBootApplication
@EnableEurekaClient
@RestController
@RequestMapping("/ratings")
public class RatingServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(RatingServiceApplication.class, args);
    }

    private List<Rating> ratingList = Arrays.asList(
        new Rating(1L, 1L, 2),
        new Rating(2L, 1L, 3),
        new Rating(3L, 2L, 4),
        new Rating(4L, 2L, 5)
    );

    @GetMapping("")
    public List<Rating> findRatingsByBookId(@RequestParam Long bookId) {
        return bookId == null || bookId.equals(0L) ? Collections.EMPTY_LIST : ratingList.stream().filter(r -> r.getBookId().equals(bookId)).collect(Collectors.toList());
    }

    @GetMapping("/all")
    public List<Rating> findAllRatings() {
        return ratingList;
    }
}

Мы также добавили контроллер REST и поле, заданное нашим файлом свойств, чтобы возвращать значение, которое мы установим во время настройки.

Добавим рейтинг POJO:

public class Rating {
    private Long id;
    private Long bookId;
    private int stars;

    //standard getters and setters
}

6.3. Свойства

Теперь нам просто нужно добавить два файла свойств:

bootstrap.properties в src/main/resources:

spring.cloud.config.name=rating-service
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true

eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/

rating-service.properties в наш репозиторий Git:

spring.application.name=rating-service
server.port=8084

eureka.client.region = default
eureka.client.registryFetchIntervalSeconds = 5
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/

Зафиксируем изменения в репозитории.

6.4. Запустить

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

DiscoveryClient_RATING-SERVICE/10.1.10.235:rating-service:8083: registering service...
DiscoveryClient_RATING-SERVICE/10.1.10.235:rating-service:8083 - registration status: 204
Tomcat started on port(s): 8084 (http)

После того, как он запущен, мы можем использовать наш браузер для доступа к конечной точке, которую мы только что создали. Перейдите по адресу http://localhost:8080/rating-service/ratings/all, и мы получим JSON со всеми нашими оценками. Обратите внимание, что мы не обращаемся к службе рейтинга напрямую через порт 8084, а проходим через шлюзовой сервер.

7. Заключение

Теперь мы можем соединить различные части Spring Cloud в работающее микросервисное приложение. Это формирует основу, которую мы можем использовать для создания более сложных приложений.

Как всегда, мы можем найти этот исходный код на GitHub.