Избранное сообщение

Фетісов В. С. Комп’ютерні технології в тестуванні. Навчально-методичний посібник. 2-ге видання, перероблене та доповнене / Мои публикации

В 10-х годах я принимал участие в программе Европейского Союза Tempus "Освітні вимірювання, адаптовані до стандартів ЄС". В рамк...

Благодаря Интернету количество писателей и поэтов увеличивается в геометрической прогрессии. Поголовье читателей начинает заметно отставать.

воскресенье, 19 ноября 2017 г.

Реактивное программирование со Spring Boot 2. Часть 2 / Хабрахабр / Программирование на Java


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

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

Для этого нам понадобятся:

  • База данных MongoDB. Дело в том, что далеко не все СУБД поддерживают асинхронную работу. MongoDB в этом смысле приятно отличается и полностью поддерживает нужный нам функционал.
  • Драйвер для асинхронной работы с базой данных. Обычный драйвер так не умеет.
  • Spring Boot 2. На данный момент Spring Boot находится в стадии разработки, но должен выйти 18 декабря. Он нам сильно облегчит работу и сэкономит много времени.
  • Gradle.Будет использован в качестве системы сборки.
  • Java 8. Да, это не последняя версия на данный момент, но для наших целей вполне подойдет.
  • Lombok. Поможет сократить код.

Все настройки и финальный проект можно посмотреть на github.

Приступим

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

 compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive')
 compile('org.springframework.boot:spring-boot-starter-webflux')
 compileOnly('org.projectlombok:lombok')
 testCompile('org.springframework.boot:spring-boot-starter-test')
 testCompile(‘io.projectreactor:reactor-test')


Итак, у нас все готово. Для начала создадим пользователя нашего приложения:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document
public class User {

    @Id
    private String id;
    private String firstName;
    private String lastName;
}

Напоминаю, что для успешной компиляции и работы в IntelliJ Idea необходимо выставить галочку в пункте enable annotation processing либо написать самостоятельно все сеттеры, гетеры и конструкторы.

Далее создадим репозиторий с нашими пользователями:

import org.faoxis.habrreactivemongo.domain.User;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;

public interface UserRepository extends ReactiveMongoRepository<User, String> {
}

Здесь следует заметить, что мы наследуемся от особого интерфейса для работы в реактивном режиме. Если заглянуть в интерфейс ReactiveMongoRepository, то можно увидеть, что нам возвращаются объекты, обернутые в уже знакомые нам классы Mono и Flux. Это значит, что при каком-либо обращении в БД, мы не получаем сразу же результат. Вместо этого мы получаем поток данных, из которого можно получить данные по мере готовности.

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

public interface UserService {

    Flux<User> get();
    Mono<User> save(User user);
}

Наш сервис достаточно прост, поэтому сразу создаем несколько полезных методов. И тут же реализуем их:

@Service
@AllArgsConstructor
public class UserServiceImpl implements UserService {

    private UserRepository userRepository;

    @Override
    public Flux<User> get() {
        return userRepository.findAll();
    }

    @Override
    public Mono<User> save(User user) {
        return userRepository.save(user);
    }
}

Здесь стоит заметить, что внедрение зависимости UserRepository происходит через конструктор с помощью аннотации AllArgsConstructor. На всякий случай напомню, что с некоторой версии Spring 4 можно осуществлять автоматическое внедрение зависимостей через конструктор без аннотации Autowire.

И, наконец, сделаем контроллер:

import lombok.AllArgsConstructor;
import org.faoxis.habrreactivemongo.domain.User;
import org.faoxis.habrreactivemongo.service.UserService;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/users")
@AllArgsConstructor
public class UserController {

    private UserService userService;

    @PostMapping
    public Mono<User> post(@RequestBody User user) {
        return userService.save(user);
    }

    @GetMapping
    public Flux<User> get() {
        return userService.get();
    }
}

Запустим наше приложение. Все должно работать. Теперь сделаем POST запрос на localhost:8080/users со следующим содержимым:

{
 "firstName": "Peter",
 "lastName": "Griffin"
}

В ответ мы получим такой же объект, но с присвоенным ему id:
{
    "id": "5a0bf0fdc48fd53478638c9e",
    "firstName": "Peter",
    "lastName": "Griffin"
}

Отлично! Давайте сохраним еще пару пользователей и попробуем посмотреть, что у нас уже есть в БД. У меня такой результат GET запроса на localhost:8080/users:

[
    {
        "id": "5a0bf0fdc48fd53478638c9e",
        "firstName": "Peter",
        "lastName": "Griffin"
    },
    {
        "id": "5a0bf192c48fd53478638c9f",
        "firstName": "Lois",
        "lastName": "Griffin"
    },
    {
        "id": "5a0bf19ac48fd53478638ca0",
        "firstName": "Mag",
        "lastName": "Griffin"
    }
]

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

Чтобы продемонстрировать это, создадим еще один обработчик URL метода в нашем контроллере:

    @GetMapping("{lastName}")
    public Flux<User> getByLastName(@PathVariable(name = "lastName") String lastName) {
        return userService.getByLastName(lastName);
    }

Здесь все просто. Мы получаем фамилию пользователя и выводим всех людей с такой фамилией. 

Конечно, такую логику можно возложить на базу данных, но здесь я хотел бы обратить внимание, как это будет выглядит в сервисе:

    @Override
    public Flux<User> getByLastName(final String lastName) {
        return userRepository
                .findAll()
                .filter(user -> user.getLastName().equals(lastName));
    }

Обратите внимание, что есть разница в работе с данными и потоками данных. Обычно мы осуществляем какие-то действия над данными непосредственно. Здесь ситуация другая. Мы говорим, что следует сделать в потоке данных.

Попробуем сделать GET запрос по URL localhost:8080/users/Griffin. У меня такой результат:

[
    {
        "id": "5a0bf0fdc48fd53478638c9e",
        "firstName": "Peter",
        "lastName": "Griffin"
    },
    {
        "id": "5a0bf192c48fd53478638c9f",
        "firstName": "Lois",
        "lastName": "Griffin"
    },
    {
        "id": "5a0bf19ac48fd53478638ca0",
        "firstName": "Mag",
        "lastName": "Griffin"
    }
]

В этой статье мы рассмотрели, как построить асинхронный сервис с новым фреймворком WebFlux и сервером Netty (он идет из коробки по умолчанию). Также мы убедились, как легко этого достичь со Spring Boot 2. Если у вас на проекте микросервисная архитектура, то скорее всего вы легко сможете при желании перевести свои приложения на WebFlux с выходом Spring Boot 2(если, конечно, в этом есть потребность). 

P.S. Есть несколько идей по продолжению. Если вам интересен материал, и вы не против моей подачи, то в следующих частях мы можем рассмотреть, например, асинхронное взаимодействие между приложениями. Спасибо за внимание!


Смотри также:

Комментариев нет:

Отправить комментарий