Создание системы мониторинга цен на авиабилеты: пошаговое руководство [Часть 1] Создание системы мониторинга цен на авиабилеты: пошаговое руководство [Часть 2]

Содержание

Создание системы мониторинга цен на авиабилеты: пошаговое руководство [Часть 3] - 1

Пишем уровень Controller’ов. SubscriptionController

Последний этап — написание REST API, через который будем общаться с приложением. Для этого будет использоваться Spring boot starter web. В SubscriptionController будет 4 метода для CRUD операций. Для контроллеров есть набор аннотаций, при помощи которых это работает:
  • @Controller — используется для добавления в ApplicationContext;
  • @RequestMapping(“path”) — определяет, что в этом классе будут REST методы, также path — путь, на котором будет начинаться запрос;
  • @PostMapping — для POST запросов, используется для создания;
  • @GetMapping — для GET запросов, используется для чтения;
  • @PutMapping — для PUT запросов, используется для редактирования;
  • @DeleteMapping — для DELETE запросов, используется для удаления;
  • @PathVariable — значит переменная будет задана в URI запроса;
  • @RequestBody — значит, что данные будут лежать в теле запроса;
  • @Valid — значит, что будет проходить валидация данных, если они не соответствуют, то будет ошибка.
SubscriptionController
import com.github.romankh3.flightsmonitoring.rest.dto.SubscriptionCreateDto;
import com.github.romankh3.flightsmonitoring.rest.dto.SubscriptionDto;
import com.github.romankh3.flightsmonitoring.rest.dto.SubscriptionUpdateDto;
import com.github.romankh3.flightsmonitoring.service.SubscriptionService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
* {@link Controller} to handle Subscriptions.
*/
@Api(value = "Operations with Subscriptions", tags = "Subscription Controller")
@RequestMapping(SubscriptionController.SUBSCRIPTION_CONTROLLER_EP)
@Controller
public class SubscriptionController {

   public static final String SUBSCRIPTION_CONTROLLER_EP = "/subscription";

   @Autowired
   private SubscriptionService subscriptionService;

   @ApiOperation("Create new subscription based on SubscriptionDto")
   @PostMapping
   public @ResponseBody
   SubscriptionDto create(@RequestBody @Valid SubscriptionCreateDto dto) {
       return subscriptionService.create(dto);
   }

   @ApiOperation("Finds all subscriptions based on email")
   @GetMapping("/{email}")
   public @ResponseBody
   List<SubscriptionDto> findByEmail(@PathVariable final String email) {
       return subscriptionService.findByEmail(email);
   }

   @ApiOperation("Updates subscription based on it ID")
   @PutMapping("/{id}")
   public SubscriptionDto update(@PathVariable final Long id,
           @RequestBody @Valid SubscriptionUpdateDto dto) {
       return subscriptionService.update(id, dto);
   }

   @ApiOperation("Deletes subscription based on it ID")
   @DeleteMapping("/{id}")
   public void delete(@PathVariable final Long id) {
       subscriptionService.delete(id);
   }
}
Также определим какие новые DTO классы добавились:
  • SubscriptionCreateDto

    import com.fasterxml.jackson.annotation.JsonFormat;
    import com.github.romankh3.flightsmonitoring.repository.entity.Subscription;
    import io.swagger.annotations.ApiModelProperty;
    import java.time.LocalDate;
    import javax.validation.constraints.Email;
    import javax.validation.constraints.NotNull;
    import lombok.Data;
    
    /**
    * Data transfer object to create a {@link Subscription} object.
    */
    @Data
    public class SubscriptionCreateDto {
    
       @NotNull
       @Email
       @ApiModelProperty(value = "Subscriber's email", example = "test@test.com")
       private String email;
    
       @NotNull
       @ApiModelProperty(value = "Country Code", example = "UA")
       private String country;
    
       @NotNull
       @ApiModelProperty(value = "Currency Code", example = "UAH")
       private String currency;
    
       @NotNull
       @ApiModelProperty(value = "Locale", example = "ru-RU")
       private String locale;
    
       @NotNull
       @ApiModelProperty(value = "Code of the origin place", example = "HRK-sky")
       private String originPlace;
    
       @NotNull
       @ApiModelProperty(value = "Code of the destination place", example = "KBP-sky")
       private String destinationPlace;
    
       @NotNull
       @JsonFormat(pattern = "yyyy-MM-dd")
       @ApiModelProperty(value = "Date front", example = "2019-12-18")
       private LocalDate outboundPartialDate;
    
       @JsonFormat(pattern = "yyyy-MM-dd")
       @ApiModelProperty(value = "Date back", example = "2019-12-25")
       private LocalDate inboundPartialDate;
    }
  • SubscriptionUpdateDto

    import com.fasterxml.jackson.annotation.JsonFormat;
    import com.github.romankh3.flightsmonitoring.repository.entity.Subscription;
    import io.swagger.annotations.ApiModelProperty;
    import java.time.LocalDate;
    import javax.validation.constraints.Email;
    import javax.validation.constraints.NotNull;
    import lombok.Data;
    
    /**
    * Dto for updating {@link Subscription} object.
    */
    @Data
    public class SubscriptionUpdateDto {
    
       @NotNull
       @Email
       @ApiModelProperty(value = "Subscriber's email", example = "test@test.com")
       private String email;
    
       @NotNull
       @ApiModelProperty(value = "Country Code", example = "UA")
       private String country;
    
       @NotNull
       @ApiModelProperty(value = "Currency Code", example = "UAH")
       private String currency;
    
       @NotNull
       @ApiModelProperty(value = "Locale", example = "ru-RU")
       private String locale;
    
       @NotNull
       @ApiModelProperty(value = "Code of the origin place", example = "HRK-sky")
       private String originPlace;
    
       @NotNull
       @ApiModelProperty(value = "Code of the destination place", example = "KBP-sky")
       private String destinationPlace;
    
       @NotNull
       @JsonFormat(pattern = "yyyy-MM-dd")
       @ApiModelProperty(value = "Date front", example = "2019-12-18")
       private LocalDate outboundPartialDate;
    
       @JsonFormat(pattern = "yyyy-MM-dd")
       @ApiModelProperty(value = "Date back", example = "2019-12-25")
       private LocalDate inboundPartialDate;
    }
  • SubscriptionDto

    import com.fasterxml.jackson.annotation.JsonFormat;
    import com.github.romankh3.flightsmonitoring.client.dto.FlightPricesDto;
    import io.swagger.annotations.ApiModelProperty;
    import java.time.LocalDate;
    import javax.validation.constraints.Email;
    import javax.validation.constraints.NotNull;
    import lombok.Data;
    
    /**
    * Data transfer object to see all the data related to subscription.
    */
    @Data
    public class SubscriptionDto {
    
       private Long id;
    
       @NotNull
       @Email
       @ApiModelProperty(value = "Subscriber's email", example = "test@test.com")
       private String email;
    
       @NotNull
       @ApiModelProperty(value = "Country Code", example = "UA")
       private String country;
    
       @NotNull
       @ApiModelProperty(value = "Currency Code", example = "UAH")
       private String currency;
    
       @NotNull
       @ApiModelProperty(value = "Locale", example = "ru-RU")
       private String locale;
    
       @NotNull
       @ApiModelProperty(value = "Code of the origin place", example = "HRK-sky")
       private String originPlace;
    
       @NotNull
       @ApiModelProperty(value = "Code of the destination place", example = "KBP-sky")
       private String destinationPlace;
    
       @NotNull
       @JsonFormat(pattern = "yyyy-MM-dd")
       @ApiModelProperty(value = "Date front", example = "2019-12-18")
       private LocalDate outboundPartialDate;
    
       @JsonFormat(pattern = "yyyy-MM-dd")
       @ApiModelProperty(value = "Date back", example = "2019-12-25")
       private LocalDate inboundPartialDate;
    
       @ApiModelProperty(value = "Min price based on all these data", example = "100")
       private Integer minPrice;
    
       @ApiModelProperty(value = "Response which contains all the need info about min price flight")
       private FlightPricesDto flightPricesDto;
    }
В DTO есть аннотации из Bean Validation API(статья на хабре), такие как:
  • @Email — проверяет на правильность написания электронной почты;
  • @NotNull — проверяет, что переменная не должна быть пустой.
И это всё… Полностью проект можно посмотреть на GitHub flights-monitoring. Более того, он сейчас развернут на Heroku и его можно посмотреть здесь. P.S. не удивляйтесь, что долго грузится первый запрос. Это аккаунт бесплатный, и поэтому после 30 минут бездействия хероку останавливает приложение и при запросе он его обратно развертывает. Поэтому и данные не сохраняются, так как при остановке H2 убивает все данные.

Тестирование приложения

В статье я не буду описывать тестирование. Тем не менее, тесты на проекте есть, и их можно посмотреть и изучить самостоятельно. Более того, если будут какие-то вопросы, можно задавать их здесь в комментариях: я с удовольствием отвечу на них.

Планы на развитие

Проект уже есть и я планирую заниматься им дальше. В ближайшем будущем хочу сделать следующее:
  • написать отдельно как библиотеку клиент для Skyscanner Flight Search, чтоб можно было использовать это как зависимость не только в этом проекте, но и в других;
  • перевести на PostgreSQL, чтоб не терялись данные на Heroku при остановке приложения. Это сделает его более стабильным;
  • создать контроллеры для данных клиента, которые поддерживаются им. Например для Places, Currencies, Countries;
  • расширить функционал поиска. Там еще есть много опций;
  • жду предложения от сообщества.

Выводы

Написать веб-приложение с REST API на основании Spring Boot и экосистемы в целом — это не так сложно, как может показаться. Да, есть нюансы, которые нужно знать. Чтоб прояснить всё, что связано со спрингом, я рекомендую к прочтению Spring in Action 5th edition. После этой книги станет яснее, что здесь происходит. P.S. ее можно найти в вк бесплатно. Создание системы мониторинга цен на авиабилеты: пошаговое руководство [Часть 3] - 2

Полезные ссылки

Все данные, которыми я пользовался при написании проекта и просто полезные ссылки для понимания того, что хорошо бы изучить:
  1. Ссылка на проект: flights-monitoring.
  2. Ссылка на развернутый проект на хероку.
  3. Sptring Initializr — быстрый способ сформировать проект с нужными конфигурациями, зависимостями. Ссылка на сформированный проект.
  4. Skyscanner Flight Search — открытый API для получения данных о полетах.
  5. Хабр: Введение в Spring Boot: создание простого REST API на Java.
  6. JavaRush: Введение в Jackson Framework.
  7. JavaRush: Project Lombok, или объявляем войну бойлерплейту.
  8. Rapidapi: Skyscanner Flight Search API(Java).
  9. Wiki: POJO.
  10. Spring Boot With H2 database.
  11. Решение проблем с Gmail для отправки писем.
  12. Spring Boot - how to send email via SMTP.
  13. Scheduling tasks.
  14. Setting Up Swagger 2 with a Spring REST API.
Смотрите также мои другие статьи: