Статьи

Создание облачных собственных приложений для Spring – часть 1

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

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

Spring Cloud — один из проектов Spring, основанный на Boot и быстро развивающийся за последний год . Общая цель проекта Spring Cloud — позволить вам создавать облачные нативные приложения с помощью Spring Boot. Вот хорошая диаграмма (любезно предоставленная слайдами Дейва Сайера и Спенсера Гибба), которая объясняет, как Spring Cloud вписывается в общую архитектуру Spring.весна-ю-дерево

Вот некоторые из функций, включенных в Spring Cloud:

  • Распределенная / версионная конфигурация
  • Служба регистрации и обнаружения
  • Маршрутизация
  • Сервисные звонки
  • Балансировки нагрузки
  • Автоматические выключатели
  • Глобальные замки
  • Лидерские выборы и кластерное государство
  • Распределенный обмен сообщениями

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

Одним из наиболее интересных проектов под эгидой Spring Cloud является Spring Cloud Netflix . Spring Cloud Netflix использует ряд проектов Netflix OSSпредоставить некоторые из функций, перечисленных выше. Есть ряд причин, по которым я считаю проект Spring Cloud Netflix полезным. Прежде всего, Netflix стал детищем того, почему микросервисы являются хорошим способом создания облачных приложений. Одна из причин этого заключается в том, что они открыли большое количество кода, который они написали для запуска одного из самых больших и надежных приложений для микросервисов под зонтиком Netflix OSS. Это означает, что код от Netflix, как доказывают, работает в реальном случае использования, и мне всегда нравится использовать код, который, я знаю, работает. Чтобы упростить использование проектов Netflix, команда Spring взяла некоторые из этих проектов и превратила их в «стартовые», которые можно просто включить в приложение Spring Boot, как если бы вы хотели использовать JPA или AMQP.Некоторые из проектов Spring Cloud Netflix настолько просты в использовании, что им просто нужно добавить пару аннотаций в приложение Spring Boot, реализация будет действительно приятной и чистой. Некоторые проекты Netflix OSS, используемые в Spring Cloud Netflix, включают

  • Эврика — для открытия сервиса
  • Hystrix — для всех ваших потребностей автоматического выключателя
  • Feign — позволяет определять декларативные REST-клиенты
  • Лента  — балансировка нагрузки на стороне клиента
  • Zuul — для маршрутизации и фильтрации

Если вы хотите узнать больше о Spring Cloud, вы можете прослушать несколько хороших записей сессий.  Вот один от Джоша Лонга  и  другой от Спенсера Гибба .

Начать работу с Spring Cloud относительно легко, особенно если вы уже знакомы с Spring Boot. Если вы перейдете на start.spring.io, вы попадете на страницу, которая в основном загрузит ваше приложение Spring Boot, просто заполнив форму. Команда Spring интегрировала проекты Spring Cloud в этот инструмент, что позволяет вам использовать их в своем приложении Spring Boot, если вы выберете. В этом посте и в нескольких последующих постах мы создадим базовое приложение для микросервисов, используя Spring Boot и Spring Cloud. Один из моих интересов вне технологий — гонки на препятствияхИтак, в духе этого интереса, давайте создадим веб-приложение, в котором перечислены некоторые предстоящие гонки с препятствиями и участники этих гонок. Будет три «службы», которые составляют приложение, одна, создающая список рас, одна, которая производит участников этих гонок, и одна, которая обслуживает клиентов (браузеры) интерфейсный код. Давайте начнем создавать три службы.

Создание Сервиса Рас

Сначала перейдите на start.spring.io и заполните форму, как на картинке ниже. Единственный флажок, который вам нужно будет пометить, — «Веб».

Снимок экрана 2015-09-09 в 10.41.47

 

Затем нажмите кнопку «Создать», чтобы загрузить ZIP-файл, содержащий исходный код проекта Spring Boot. Затем вы можете импортировать этот проект в вашу любимую IDE, мне нравится использовать STS , но вы можете использовать обычную Eclipse или любую другую Java IDE, если она поддерживает Maven. Там будет один исходный файл в пакете com.ryanjbaxter.spring.cloud.ocr.races называемых OcrRacesApplication.java . Откройте его и скопируйте в него приведенный ниже код.

package com.ryanjbaxter.spring.cloud.ocr.races;

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

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class OcrRacesApplication implements CommandLineRunner {

    private static List<Race> races = new ArrayList<Race>();

    public static void main(String[] args) {
        SpringApplication.run(OcrRacesApplication.class, args);
    }

    @Override
    public void run(String... arg0) throws Exception {
        races.add(new Race("Spartan Beast", "123", "MA", "Boston"));
        races.add(new Race("Tough Mudder RI", "456", "RI", "Providence"));
    }

    @RequestMapping("/")
    public List<Race> getRaces() {
        return races;
    }
}

class Race {
    private String name;
    private String id;
    private String state;
    private String city;

    public Race(String name, String id, String state, String city) {
        super();
        this.name = name;
        this.id = id;
        this.state = state;
        this.city = city;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
}

Этот код довольно простой, он создает одну конечную точку REST, которая возвращает все расы. Сейчас расы просто хранятся в Списке в классе, это просто базовый пример, очевидно, есть более сложные способы сделать это. Если вы используете STS, вы можете легко запустить это приложение, выбрав «Выполнить» -> «Выполнить как» -> Приложение Spring Boot. Если вы предпочитаете, вы также можете запустить приложение через Maven из командной строки в корне проекта, запустив

$ mvn spring-boot:run

Приложение будет запускаться на локальном хосте с использованием порта 8080, поэтому, если вы откроете браузер и перейдете по адресу http: // localhost: 8080 /, вы должны увидеть список JSON, возвращенный с подробностями гонки.

Мы собираемся запускать множество сервисов одновременно на одной машине, и они не могут работать на одном и том же порту, поэтому давайте настроим порт, на котором будет работать сервис гонок. В каталоге приложения src / main / resources будет файл с именем application.properties . Здесь вы можете установить различные свойства вашего приложения Spring. Я предпочитаю использовать файлы YAML (меньше печатать), поэтому переименуйте этот файл в application.yml . Затем откройте файл и добавьте в него следующие две строки.

server:
  port: 8282

Теперь, если вы перезапустите свое приложение, оно должно запуститься на порту 8282.

Создание сервиса участников

Следующим сервисом, который мы хотим создать, является наш сервис участников гонки. Снова вернитесь к start.spring.io и заполните форму, как показано на рисунке ниже.

Снимок экрана 2015-09-09 в 11.18.56

Нажмите кнопку «Создать», чтобы загрузить код для своего проекта и импортировать его в свою среду IDE. Опять же , там будет один исходный файл в пакете com.ryanjbaxter.spring.cloud.ocr.participants называемых OcrParticipantsApplication.java . Откройте этот файл и скопируйте в него код ниже.

package com.ryanjbaxter.spring.cloud.ocr.participants;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class OcrParticipantsApplication implements CommandLineRunner {

    private static List<Participant> participants = new ArrayList<Participant>();

    public static void main(String[] args) {
        SpringApplication.run(OcrParticipantsApplication.class, args);
    }

    @Override
    public void run(String... arg0) throws Exception {
        participants.add(new Participant("Ryan", "Baxter", "MA", "S", Arrays.asList("123", "456")));
        participants.add(new Participant("Stephanie", "Baxter", "MA", "S", Arrays.asList("456")));        
    }

    @RequestMapping("/")
    public List<Participant> getParticipants() {
        return participants;
    }

    @RequestMapping("/races/{id}")
    public List<Participant> getParticipants(@PathVariable String id) {
        return participants.stream().filter(p -> p.getRaces().contains(id)).collect(Collectors.toList());
    }
}

class Participant {
    private String firstName;
    private String lastName;
    private String homeState;
    private String shirtSize;
    private List<String> races;
    public Participant(String firstName, String lastName, String homeState,
            String shirtSize, List<String> races) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
        this.homeState = homeState;
        this.shirtSize = shirtSize;
        this.races = races;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public String getHomeState() {
        return homeState;
    }
    public void setHomeState(String homeState) {
        this.homeState = homeState;
    }
    public String getShirtSize() {
        return shirtSize;
    }
    public void setShirtSize(String shirtSize) {
        this.shirtSize = shirtSize;
    }
    public List<String> getRaces() {
        return races;
    }
    public void setRaces(List<String> races) {
        this.races = races;
    }

}

Этот класс похож на тот же класс в гоночном сервисе, за исключением того, что здесь мы работаем с участниками. Опять же, мы не хотим, чтобы это приложение запускалось на порту 8080, поэтому в src / main / resources переименуйте application.properties в application.yml и добавьте эти две строки.

server:
  port: 8181

Если вы запустите это приложение и перейдете по адресу http: // localhost: 8181 /, вы увидите всех участников. Кроме того, если вы перейдете по адресу http: // localhost: 8181 / races / 123, вы увидите только участников, которые будут участвовать в гонке с идентификатором 123.

Создание веб-службы

Последний сервис, который мы собираемся создать, — это сервис, который обслуживает код браузера на стороне клиента. Наше веб-приложение будет построено с использованием Angular.js . Опять же, мы создадим новый проект из start.spring.io . Заполните форму, следуя снимку экрана ниже.Снимок экрана 2015-09-09 в 2.59.50 PM

Откройте переименование application.properties в application.yml и добавьте следующие две строки.

server:
  port: 8080

В src / main / resources / static создайте каталоги скриптов / контроллеров и представлений . В скриптах / контроллерах создайте новый файл с именем main.js и добавьте следующий код.

angular.module('ocrApp')
  .controller('MainCtrl', function ($scope, $http) {     });

В каталоге scripts создайте новый файл с именем app.js и добавьте следующий код.

angular
  .module('ocrApp', [
    'ngAnimate',
    'ngCookies',
    'ngResource',
    'ngRoute',
    'ngSanitize',
    'ngTouch'
  ])
  .config(function ($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controller: 'MainCtrl'
      })
      .otherwise({
        redirectTo: '/'
      });
  });

 

В каталоге views создайте файл с именем main.html и добавьте следующий код.

<h1>hello world</h1>

В статическом каталоге создайте новый файл с именем index.html и добавьте следующий код.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="../../favicon.ico">

    <title>OCR Races</title>

    <!-- Bootstrap core CSS -->
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">

    <!-- Custom styles for this template -->
    <link href="http://getbootstrap.com/examples/jumbotron-narrow/jumbotron-narrow.css" rel="stylesheet">

    <!-- Just for debugging purposes. Don't actually copy these 2 lines! -->
    <!--[if lt IE 9]><script src="../../assets/js/ie8-responsive-file-warning.js"></script><![endif]-->
    <script src="http://getbootstrap.com/assets/js/ie-emulation-modes-warning.js"></script>

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>

  <body data-pinterest-extension-installed="cr1.38.4" class=" hasGoogleVoiceExt" ng-app="ocrApp">

    <div class="container">
      <div class="header clearfix">
        <nav>
        </nav>
        <h3 class="text-muted">OCR Races</h3>
      </div>

      <div ng-view=""></div>

      <footer class="footer">
        <p>© Company 2014</p>
      </footer>

    </div> <!-- /container -->


    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <script src="http://getbootstrap.com/assets/js/ie10-viewport-bug-workaround.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular.js"></script>
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular-animate.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular-cookies.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular-resource.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular-route.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular-sanitize.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular-touch.js"></script>
    <script src="scripts/app.js"></script>
    <script src="scripts/controllers/main.js"></script>


</body></html>

Если вы запустите это приложение и перейдете по адресу http: // localhost: 8080, вы увидите простую страницу, на которой просто привет мир.

Позвонив в нашу службу гонок

Теперь пришло время попытаться использовать некоторые из сервисов, которые мы создали в нашем интерфейсе. Первое, что мы хотим сделать, это перечислить все расы. В сервисе веб-приложений откройте main.js и добавьте следующий код.

angular.module('ocrApp')
  .controller('MainCtrl', function ($scope, $http) {
      $http({
          method: 'GET',
          url: 'http://localhost:8282/races'
      }).then(function(response) {
          $scope.races = response.data;
      }, function(response) {
          console.error('Error requesting races');
      });
  });

Здесь все, что мы делаем, это вызываем наш сервис рас, чтобы получить список рас и присваиваем его переменной в нашей области видимости. Запустите приложение службы гонок и службу веб-приложений и перейдите по адресу http: // localhost: 8080 . Если вы откроете консоль браузера, вы увидите следующую ошибку.

Блокировка перекрестного запроса: та же политика происхождения запрещает чтение удаленного ресурса по адресу http: // localhost: 8282 / races. (Причина: отсутствует заголовок CORS «Access-Control-Allow-Origin»).

Если вы веб-разработчик, вы, вероятно, очень хорошо знакомы с этой ошибкой. Все современные браузеры предотвращают запросы AJAX к другим доменам, если только сервер в этом домене не разрешил запросы от вашего домена, это называется политикой того же происхождения . В этом случае мы делаем запрос с localhost: 8080 на localhost: 8282, а сервер на localhost: 8282 не сказал, что разрешает запросы, поступающие с localhost: 8080. Мы могли бы включить CORS (совместное использование ресурсов из разных источников) в нашем сервисе гонок, поэтому мы можем отправлять запросы на него с localhost: 8080, но это становится довольно грязным. Что происходит при развертывании в производство или тестирование? Это дополнительные домены, которые мы также должны включить. Поскольку теоретически мы можем говорить со многими, многими микросервисами из кода на стороне клиента, нам придется делать это для каждого сервиса. Кроме того, в приложении для микросервисов нередко случается, что сервисы развиваются и меняются со временем, поэтому, хотя сервис Races находится по определенному URL-адресу сегодня, это может быть не так в будущем. Короче говоря, жесткое кодирование URL-адреса службы в нашем клиентском коде и включение CORS просто не приведет к его сокращению.

К счастью, у Spring Cloud есть очень чистое и надежное решение, доступное для нас. Чтобы решить проблему жесткого кодирования URL-адресов в нашем клиентском коде или в любом другом месте нашего приложения, мы захотим использовать обнаружение служб. Обнаружение служб позволяет службам запрашивать централизованное расположение для получения полного списка доступных служб и URL-адресов, по которым эти службы доступны. Чтобы решить междоменную проблему, было бы хорошо, если бы у нас был простой обратный прокси-сервер в том же домене, что и наше веб-приложение, которое использовало сервис обнаружения сервисов для маршрутизации запросов к нужному сервису. Для этого мы можем использовать два проекта, которые являются частью Spring Cloud Netflix.Проект Spring Cloud Netflix Eureka позволит нам легко настроить службу обнаружения служб для нашего приложения, а Spring Cloud Netflix Zuul настроит обратный прокси-сервер, который интегрируется с Eureka для вызова служб. В следующем посте мы рассмотрим, как интегрировать эти два проекта Spring Cloud в наше приложение для решения нашей междоменной проблемы.

Читать часть 2