Статьи

Быстрое создание прототипов веб-приложений с помощью Spring Boot и MongoDB

Вернувшись в один из моих предыдущих проектов, меня попросили составить небольшое заявление на случай непредвиденных обстоятельств. График был плотный и простота. Внутренним стандартом кодирования является PHP, поэтому попытка создать классический стек Java EE была бы настоящей проблемой. И, если честно, совершенно негабаритный. Так что тогда? Я рискнул и попробовал Spring. Я использовал его раньше, но в старых версиях, спрятанных в техническом стеке программного обеспечения портала, которое меня мучило в это время.

Моей целью было сделать что-то, что WebOps может просто поставить на сервер с установленной Java и запустить его. Не нужно возиться с десятками конфигураций XML и тонкой настройкой памяти. Так же просто, как java -jar application.jar .
Это был идеальный вызов для «Spring Boot». Этот весенний проект предназначен для того, чтобы упростить для вас, разработчика, скорость и избавить от необходимости загружать конфигурацию и шаблонное кодирование.

Еще одна вещь, о которой мечтал мой проект, — это ориентированное на документы хранилище данных. Я имею в виду, что основной целью приложения было предложить цифровую версию реальной бумажной формы. Так зачем создавать реляционный беспорядок, если мы можем представить документ как документ ?! Я использовал MongoDB в нескольких небольших проектах раньше, поэтому я решил пойти с ним.

Какое отношение это имеет к этой статье? Что ж, я покажу вам, как быстро вы можете собрать все кусочки, необходимые для веб-приложения. Spring Boot сделает многое довольно простым и сделает код минимальным. И в конце у вас будет файл JAR, который является исполняемым и может быть развернут, просто поместив его на сервер. Ваши WebOps будут любить вас за это.

Давайте представим, что мы собираемся создать следующее большое веб-приложение для администрирования продукта. Поскольку это следующая большая вещь, ей нужно громкое имя: Productr (по этой причине я программист, а не занимаюсь продажами или маркетингом…).
Productr сделает удивительные вещи, и эта статья покажет вам его ранние стадии, которые:

  • предоставление простого интерфейса REST для запроса всех доступных продуктов
  • загрузка этих продуктов из MongoDB
  • обеспечение готового к производству средства мониторинга
  • отображение всех продуктов с помощью пользовательского интерфейса JavaScript

Все, что вам нужно для начала, это:

  • Java 8
  • специалист
  • Ваш любимый IDE (IntelliJ, Eclipse, vi, edlin, бабочка …)
  • Браузер (хорошо, или Internet Explorer / MS Edge, но кто бы действительно этого хотел ?!)

И для нетерпеливых, код также доступен на GitHub .

Давайте начнем

Создайте файл pom.xml со следующим содержимым:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="UTF-8"?>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.0.RELEASE</version>
    </parent>
 
    <modelVersion>4.0.0</modelVersion>
    <groupId>net.h0lg.tutorials.rapid</groupId>
    <artifactId>rapid-resting</artifactId>
    <version>1.0</version>
 
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
 
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

В этих нескольких строках много чего уже происходит. Наиболее важным является определенный родительский проект. Это принесет нам много полезных и необходимых зависимостей, таких как ведение журнала, среда выполнения Tomcat и многое другое. Благодаря модульности Spring все можно переконфигурировать с помощью pom.xml или внедрения зависимостей. Для быстрого начала работы по умолчанию все в порядке. (Соглашение по конфигурации, кто-нибудь?)

Теперь создайте обязательную структуру папок Maven:

1
mkdir -p src/main/java src/main/resources src/test/java src/test/resources

И мы поселились.

Запустить двигатели

Давай приступим к работе. Мы хотим предложить интерфейс REST, чтобы получить доступ к нашему огромному количеству продуктов. Итак, начнем с создания коллекции REST, доступной в / api / products . Для этого нам нужно сделать несколько вещей:

  1. Наша «модель данных», содержащая всю информацию о наших невероятных продуктах, должна быть создана
  2. Нам нужен контроллер, предлагающий метод, который делает все необходимое для ответа на запрос GET
  3. Создайте основную точку входа для нашего приложения

Модель данных довольно проста и выполняется быстро. Просто создайте пакет с именем demo.model и класс с именем Product в нем. Класс Product очень прост:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package demo.model;
 
import java.io.Serializable;
 
/**
 * Our very important and sophisticated data model
 */
public class Product implements Serializable {
 
    String productId;
    String name;
    String vendor;
 
    public String getProductId() {
        return productId;
    }
 
    public void setProductId(String productId) {
        this.productId = productId;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getVendor() {
        return vendor;
    }
 
    public void setVendor(String vendor) {
        this.vendor = vendor;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
 
        Product product = (Product) o;
 
        if (getProductId() != null ? !getProductId().equals(product.getProductId()) : product.getProductId() != null)
            return false;
        if (getName() != null ? !getName().equals(product.getName()) : product.getName() != null) return false;
        return !(getVendor() != null ? !getVendor().equals(product.getVendor()) : product.getVendor() != null);
 
    }
 
    @Override
    public int hashCode() {
        int result = getProductId() != null ? getProductId().hashCode() : 0;
        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
        result = 31 * result + (getVendor() != null ? getVendor().hashCode() : 0);
        return result;
    }
}

Наш продукт обладает невероятным количеством 3 свойств: буквенно-цифровой идентификатор продукта, имя и поставщик (просто имя, чтобы упростить задачу). Он является сериализуемым, и методы получения, установки и методы equals () и hashCode () реализуются с помощью генерации кода в моей среде IDE.

Хорошо, поэтому создание контроллера с методом, чтобы предложить слушателю GET это сейчас. Вернитесь в свою любимую среду IDE и создайте пакет demo.controller и класс с именем ProductsController со следующим содержимым:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package demo.controller;
 
import demo.model.Product;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 * This controller provides the REST methods
 */
@RestController
@RequestMapping(value = "/", method = RequestMethod.GET)
public class ProductsController {
 
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public List getProducts() {
        List products = new ArrayList();
 
        return products;
    }
 
}

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

Последнее, что не хватает, это точка входа для нашего приложения. Просто создайте класс с именем Productr в демонстрационном пакете и дайте ему следующее содержимое:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package demo;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
/**
 * This is the entry point of our application
 */
@SpringBootApplication
public class ProductrApplication {
 
    public static void main (String... opts) {
        SpringApplication.run(ProductrApplication.class, opts);
    }
 
}

Spring Boot экономит нам много нажатий клавиш. @SpringBootApplication в любом случае делает несколько вещей, которые нам нужны для каждого веб-приложения. Эта аннотация является сокращением для следующих:

  • @Configuration
  • @EnableAutoConfiguration
  • @ComponentScan

Теперь пришло время запустить наше приложение в первый раз. Благодаря подключаемому модулю Spring Boot, который мы настроили в нашем pom.xml, запускать приложение так же просто, как: mvn spring-boot:run . Просто запустите эту команду в корневом каталоге вашего проекта. Вы предпочитаете ленивый способ point-n-click, предоставляемый вашей IDE? Хорошо, просто укажите вашей любимой IDE для запуска ProductrApplication .

Как только он будет запущен, используйте браузер, клиент REST (вы должны проверить Postman , мне нравится этот инструмент) или инструмент командной строки, такой как curl . Адрес, который вы ищете: http: // localhost: 8080 / api / products / . Итак, с curl команда выглядит так:

1
curl http://localhost:8080/api/products/

Данные пожалуйста

Хорошо, возвращение пустого списка не так уж блестяще, не так ли? Итак, давайте введем данные. Во многих проектах классическая реляционная база данных, как правило, излишня (и болезненна, если вам нужно ее использовать и масштабировать). Это может быть одной из причин шумихи вокруг баз данных NoSQL. Один (на мой взгляд, хороший) пример — MongoDB.

Начать и запустить MongoDB довольно легко. В Linux вы можете использовать менеджер пакетов для его установки. Например, для Debian / Ubuntu просто выполните: sudo apt-get install mongodb .

Для Mac самый простой способ — это homebrew : brew install mongodb и следуйте инструкциям в разделе «Предостережения».

Пользователи Windows должны пойти с установщиком MongoDB (и toi toi toi).

Хорошо, мы только что отсортировали наше хранилище данных. Пора его использовать. Существует один конкретный проект Spring, связанный с данными — Spring Data . И по стечению обстоятельств нас ожидает только подпроект под названием Spring Data MongoDB . Более того, Spring Boot предоставляет пакет зависимостей, позволяющий быстро набрать скорость. Неудивительно, что следующих нескольких строк в разделе <dependencies> файла pom.xml достаточно, чтобы добавить все, что нам нужно:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

Теперь создайте новый пакет с именем demo.domain и вставьте новый интерфейс с именем ProductRepository . Spring предоставляет довольно удобный способ избавиться от написания кода, который обычно необходим для взаимодействия с источником данных. Большинство основных запросов генерируются Spring Data — все, что вам нужно, это определить интерфейс. Пара методов запросов доступны даже без указания заголовков методов. Одним из примеров является метод findAll() , который возвращает все записи в коллекции.
Но давайте посмотрим на это в действии, а не будем говорить об этом. Сделанный на заказ интерфейс ProductRepository должен выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
package demo.domain;
 
import demo.model.Product;
import org.springframework.data.mongodb.repository.MongoRepository;
 
/**
 * This interface lets Spring generate a whole Repository implementation for
 * Products.
 */
public interface ProductRepository extends MongoRepository {
 
}

Затем создайте класс с именем ProductService в том же пакете. Цель этого класса — предоставить некоторые полезные методы для запроса продуктов. На данный момент код так прост:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package demo.domain;
 
import demo.model.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import java.util.List;
 
/**
 * This is a little service class we will let Spring inject later.
 */
@Service
public class ProductService {
 
    @Autowired
    private ProductRepository repository;
 
    public List getProducts() {
        return repository.findAll();
    }
 
}

Посмотрите, как мы можем использовать repository.findAll() даже не определяя его в интерфейсе? Довольно гладко, не правда ли? Особенно, если вы спешите и хотите быстро встать на ноги.

Хорошо, пока мы подготовили основу для доступа к данным. Я думаю, что пришло время соединить это вместе. Для этого просто вернитесь к нашему классу demo.controller.ProductsController и немного измените его. Все, что нам нужно сделать, это внедрить наш блестящий новый сервис ProductService и вызвать его getProducts() . После этого класс будет выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package demo.controller;
 
import demo.domain.ProductService;
import demo.model.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 * This controller provides the REST methods
 */
@RestController
@RequestMapping("/api/products/")
public class ProductsController {
 
    // Let Spring DI inject the service for us
    @Autowired
    private ProductService productService;
 
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public List getProducts() {
        // Ask the data store for a list of products
        return productService.getProducts();
    }
 
}

Вот и все. Запустите MongoDB (если он еще не запущен), снова запустите наше приложение (вспомните mvn spring-boot:run thingy ?!) и запустите еще один GET-запрос по адресу http: // localhost: 8080 / api / products / :

1
2
$ curl http://localhost:8080/api/products/
[]

Подождите, все еще пустой список? Да, или вы помните, что мы что-то вносили в базу данных? Давайте изменим это с помощью следующей команды:

1
mongo localhost/test --eval "db.product.insert({productId: 'a1234', name: 'Our First Product', vendor: 'ACME'})"

Это добавляет один продукт под названием «Наш первый продукт» в нашу базу данных. Хорошо, так что же теперь возвращает наш сервис? Этот:

1
2
$ curl http://localhost:8080/api/products/
[{"productId":"5657654426ed9d921affc3c0","name":"Our First Product","vendor":"ACME"}]

Легко, не так ли ?!

Ищете немного больше данных, но нет времени, чтобы создать их самостоятельно? Хорошо, уже почти Рождество, так что возьмите мой маленький тестовый выбор:

1
curl https://gist.githubusercontent.com/daincredibleholg/f8667a26ce2f17776903/raw/ed9b4c8ec6c9c455dc063e833af2418648928ba6/quick-web-app-product-example.json | mongoimport -d test -c product --jsonArray

Основные требования у вас под рукой

В сегодняшние беспокойные дни и с распространением культуры «микросервиса» становится все труднее следить за тем, что действительно работает на ваших серверах или в облачных средах. Так что почти во всех средах, над которыми я работал в течение последних лет, мониторинг был большой вещью. Одним из распространенных способов является предоставление конечных точек проверки работоспособности. Можно найти все, от простых конечных точек пинга до показателей состояния, возвращая подробный обзор релевантных для бизнеса показателей. Все это в большинстве случаев является приключением типа «копируй и вставляй» и включает в себя множество типового кода. Вот что мы должны сделать — просто добавьте следующую зависимость в ваш pom.xml:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

и перезапустите сервис. Давайте посмотрим, что произойдет, если мы запросим http: // localhost: 8080 / health:

1
2
{"status":"UP","diskSpace":{"status":"UP","total":499088621568,"free":83261571072,"threshold":10485760},"mongo":{"status":"UP","version":"3.0.7"}}

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

Показать его мне

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

Спасибо Санте, что есть действительно активное сообщество веб-интерфейса, работающее над множеством удобных и простых в использовании фреймворков и библиотек. Один довольно популярный пример — Bootstrap . Он прост в использовании, и все необходимые фрагменты предоставляются через открытые CDN.

Мы хотим иметь краткий обзор наших продуктов, поэтому было бы неплохо посмотреть таблицу. Bootstrap Table поможет нам в этом. Он построен поверх Bootstrap, а также доступен через CDN. В каком мире мы живем …

Но подождите, где поместить наш HTML-файл? Spring Boot облегчает, опять же. Просто создайте папку с именем src / main / resources / static и создайте новый файл HTML с именем index.html со следующим содержимым:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
 
    <title>Productr</title>
 
    <!-- Import Bootstrap CSS from CDNs -->
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.9.1/bootstrap-table.min.css">
</head>
<body>
<nav class="navbar navbar-inverse">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">Productr</a>
        </div>
        <div id="navbar" class="collapse navbar-collapse">
            <ul class="nav navbar-nav">
                <li class="active"><a href="#">Home</a></li>
                <li><a href="#about">About</a></li>
                <li><a href="#contact">Contact</a></li>
            </ul>
        </div><!--/.nav-collapse -->
    </div>
</nav>
    <div class="container">
        <table data-toggle="table" data-url="/api/products/">
            <thead>
            <tr>
                <th data-field="productId">Product Reference</th>
                <th data-field="name">Name</th>
                <th data-field="vendor">Vendor</th>
            </tr>
            </thead>
        </table>
    </div>
 
 
<!-- Import Bootstrap, Bootstrap Table and JQuery JS from CDNs -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.9.1/bootstrap-table.min.js"></script>
</body>
</html>

Этот файл не очень сложный. Это всего лишь HTML-файл, который включает минимизированные CSS-файлы из CDN. Если вы видите ссылку типа //maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css в первый раз, это не плохая ошибка, что протокол (http или https) отсутствует. Ресурс, на который ссылаются таким образом, будет загружен по тому же протоколу, с которым загружена главная страница. Скажем, если вы используете http: // localhost: 8080 / , он будет использовать http: для загрузки файлов CSS.

Блок <body> содержит панель навигации (с использованием тега <nav> HTML5) и таблицу. Интересной частью этого определения таблицы является предоставленный атрибут data-url . Это интерпретируется Bootstrap Table для загрузки данных. Наше определение указывает на нашу ранее созданную конечную точку REST. Какая часть наших объектов JSON используется, в каком столбце определяется с помощью атрибутов поля данных в определениях <th> . Можете ли вы найти соответствующие имена атрибутов?

И последнее, но не менее важное: мы загружаем необходимые библиотеки JavaScript. Для всех функций JavaScript, связанных с Bootstrap, требуется JQuery, поэтому это первая загружаемая библиотека. Далее следуют основные файлы JavaScript Bootstrap и таблицы Bootstrap. Каждый из этих файлов библиотеки загружается в свернутой версии, чтобы свести время загрузки к минимуму.

Куда идти сейчас

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

Я думаю, что одним из наиболее важных шагов, необходимых для следующего, помимо добавления отсутствующих тестов, является обеспечение безопасности. Ознакомьтесь с Spring Security и его подпроектами Spring Security OAuth . Больше заинтересованы в «классических» веб-страницах? Посмотрите Spring MVC и узнайте, как легко интегрировать довольно сложные шаблоны (например, следуя этому руководству ).

Надеюсь, вам понравилась эта статья так же, как мне понравилось ее создание. Я желаю всем вам счастливого Рождества, и если тот или другой хочет связаться, вы можете найти меня, например, в Twitter , G + и LinkedIn .