JavaRush /Java блог /Random UA /Огляд REST. Частина 3: створення RESTful сервісу на Sprin...

Огляд REST. Частина 3: створення RESTful сервісу на Spring Boot

Стаття з групи Random UA
Це остання частина аналізу REST. У попередніх частинах: Огляд REST.  Частина 3: створення RESTful сервісу на Spring Boot - 1

Створення проекту

У даному розділі ми створимо невеликий RESTful додаток на Spring Boot. У нашому додатку буде реалізовано CRUD (Create, Read, Update, Delete) операції над клієнтами з прикладу з попередньої частини розбору. Для початку створимо новий Spring Boot додаток через меню File -> New -> Project... У вікні, що відкрилося, вибираємо Spring Initializr і вказуємо Project SDK: Огляд REST.  Частина 3: створення RESTful сервісу на Spring Boot - 2Натискаємо кнопку Next. У наступному вікні вказуємо тип проекту Maven, вказуємо Group та Artifact: Огляд REST.  Частина 3: створення RESTful сервісу на Spring Boot - 3Натискаємо кнопку Next. У наступному вікні необхідно вибрати потрібні для проекту компоненти Spring Framework. Нам буде достатньо Spring Web: Огляд REST.  Частина 3: створення RESTful сервісу на Spring Boot - 4Натискаємо кнопку Next. Далі залишилося вказати лише найменування проекту та його розташування у файловій системі: Огляд REST.  Частина 3: створення RESTful сервісу на Spring Boot - 5Натискаємо кнопку Finish. Проект створений, тепер ми можемо побачити його структуру: Огляд REST.  Частина 3: створення RESTful сервісу на Spring Boot - 6IDEA згенерувала за нас дескриптор розгортання системи складання Maven - pom.xml та головний клас програми: RestExampleApplication. Наведемо їх код:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>2.2.2.RELEASE</version>
       <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.codegym.lectures</groupId>
   <artifactId>rest_example</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>rest_example</name>
   <description>REST example project</description>

   <properties>
       <java.version>1.8</java.version>
   </properties>

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

       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
           <exclusions>
               <exclusion>
                   <groupId>org.junit.vintage</groupId>
                   <artifactId>junit-vintage-engine</artifactId>
               </exclusion>
           </exclusions>
       </dependency>
   </dependencies>

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

</project>
RestExampleApplication:
@SpringBootApplication
public class RestExampleApplication {

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

}

Створення REST функціоналу

Наш додаток керує клієнтами. Тому насамперед нам необхідно створити сутність клієнта. Це буде клас POJO. Створимо пакет modelусередині пакета com.codegym.lectures.rest_example. Усередині пакету modelстворимо клас Client:
public class Client {

   private Integer id;
   private String name;
   private String email;
   private String phone;

   public Integer getId() {
       return id;
   }

   public void setId(Integer id) {
       this.id = id;
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public String getEmail() {
       return email;
   }

   public void setEmail(String email) {
       this.email = email;
   }

   public String getPhone() {
       return phone;
   }

   public void setPhone(String phone) {
       this.phone = phone;
   }
}
У сервісі буде реалізовано CRUD операції над клієнтом. Наступним кроком ми маємо створити сервіс, у якому будуть реалізовані ці операції. У пакеті com.codegym.lectures.rest_exampleстворимо пакет service, усередині якого створимо інтерфейс ClientService. Наведемо код інтерфейсу з коментарями:
public interface ClientService {

   /**
    * Создает нового клиента
    * @param client - клиент для создания
    */
   void create(Client client);

   /**
    * Повертає список всех имеющихся клиентов
    * @return список клиентов
    */
   List<client> readAll();

   /**
    * Повертає клиента по его ID
    * @param id - ID клиента
    * @return - об'єкт клиента с заданным ID
    */
   Client read(int id);

   /**
    * Обновляет клиента с заданным ID,
    * в соответствии с переданным клиентом
    * @param client - клиент в соответсвии с которым нужно обновить данные
    * @param id - id клиента которого нужно обновить
    * @return - true если данные были обновлены, иначе false
    */
   boolean update(Client client, int id);

   /**
    * Удаляет клиента с заданным ID
    * @param id - id клиента, которого нужно удалить
    * @return - true если клиент был удален, иначе false
    */
   boolean delete(int id);
}
Далі нам потрібно створити реалізацію цього інтерфейсу. Зараз у ролі сховища клієнтів виступатиме Map<Integer, Client>. Ключем картки буде ID клієнта, а значенням - сам клієнт. Зроблено це у тому, ніж перевантажувати приклад специфікою роботи з БД. Однак, у майбутньому ми зможемо написати іншу реалізацію інтерфейсу, в якій можна буде підключити реальну базу даних. У пакеті serviceстворимо реалізацію інтерфейсу ClientService:
@Service
public class ClientServiceImpl implements ClientService {

   // Хранабоще клиентов
   private static final Map<Integer, Client> CLIENT_REPOSITORY_MAP = new HashMap<>();

   // Переменная для генерации ID клиента
   private static final AtomicInteger CLIENT_ID_HOLDER = new AtomicInteger();

   @Override
   public void create(Client client) {
       final int clientId = CLIENT_ID_HOLDER.incrementAndGet();
       client.setId(clientId);
       CLIENT_REPOSITORY_MAP.put(clientId, client);
   }

   @Override
   public List<Client> readAll() {
       return new ArrayList<>(CLIENT_REPOSITORY_MAP.values());
   }

   @Override
   public Client read(int id) {
       return CLIENT_REPOSITORY_MAP.get(id);
   }

   @Override
   public boolean update(Client client, int id) {
       if (CLIENT_REPOSITORY_MAP.containsKey(id)) {
           client.setId(id);
           CLIENT_REPOSITORY_MAP.put(id, client);
           return true;
       }

       return false;
   }

   @Override
   public boolean delete(int id) {
       return CLIENT_REPOSITORY_MAP.remove(id) != null;
   }
}
Анотація @Serviceкаже спрингу, що цей клас є сервісом. Це спеціальний тип класів, у якому реалізується деяка бізнес-логіка програми. Згодом, завдяки цій інструкції Spring надаватиме нам екземпляр даного класу в місцях, де це потрібно за допомогою Dependency Injection. Тепер настав час для створення контролера. Спеціального класу, де ми реалізуємо логіку обробки клієнтських запитів на ендпоінти (URI). Щоб було зрозуміліше, створюватимемо цей клас частинами. Спочатку створимо сам клас і впровадимо в нього залежність від ClientService:
@RestController
public class ClientController {

   private final ClientService clientService;

   @Autowired
   public ClientController(ClientService clientService) {
       this.clientService = clientService;
   }
}
Пояснимо анотації: @RestController - каже спрингу, що цей клас є REST контролером. Тобто. в даному класі буде реалізована логіка обробки клієнтських запитів @Autowired - каже спрингу, що тут необхідно запровадити залежність. У конструктор ми передаємо інтерфейс ClientService. Реалізацію цього сервісу ми позначабо інструкцією @Serviceраніше, і тепер спринг зможе передати екземпляр цієї реалізації в конструктор контролера. Далі ми покроково реалізовуватимемо кожен метод контролера, для обробки CRUD операцій. Почнемо з операції Create. Для цього напишемо метод create:
@PostMapping(value = "/clients")
public ResponseEntity<?> create(@RequestBody Client client) {
   clientService.create(client);
   return new ResponseEntity<>(HttpStatus.CREATED);
}
Розберемо даний метод: @PostMapping(value = "/clients")- Тут ми позначаємо, що даний метод обробляє запити POST на адресау /clients Метод повертає ResponseEntity<?>. ResponseEntity- Спеціальний клас для повернення відповідей. За допомогою нього ми зможемо надалі повернути клієнту HTTP статус код. Метод приймає параметр @RequestBody Client client, значення цього параметра підставляється із тіла запиту. Про це свідчить інструкція @RequestBody. Усередині тіла методу ми викликаємо метод create у раніше створеного сервісу та передаємо йому прийнятого у параметрах контролера клієнта. Після цього повертаємо статус 201 Created, створивши новий об'єкт ResponseEntityі передавши в нього необхідне значення енума HttpStatus. Далі реалізуємо операцію Read: Для початку реалізуємо операцію отримання списку всіх наявних клієнтів:
@GetMapping(value = "/clients")
public ResponseEntity<List<Client>> read() {
   final List<Client> clients = clientService.readAll();

   return clients != null &&  !clients.isEmpty()
           ? new ResponseEntity<>(clients, HttpStatus.OK)
           : new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Приступимо до розбору: @GetMapping(value = "/clients")— все аналогічно до анотації @PostMapping, тільки тепер ми обробляємо GET запити. Цього разу ми повертаємо ResponseEntity<List<Client>>тільки цього разу, крім HTTP статусу, ми повернемо ще й тіло відповіді, яким буде список клієнтів. У REST контролерах спрингу всі POJO об'єкти, а також колекції POJO об'єктів, які повертаються як тіло відповідей, автоматично серіалізуються в JSON, якщо явно не вказано інше. Нас це цілком влаштовує. Всередині методу за допомогою нашого сервісу ми отримуємо список усіх клієнтів. Далі, якщо список не null і не порожній, ми повертаємо за допомогою класу ResponseEntityсам список клієнтів і HTTP статус 200 OK. Інакше ми повертаємо просто статус HTTP 404 Not Found. Далі реалізуємо можливість отримувати клієнта за його id:
@GetMapping(value = "/clients/{id}")
public ResponseEntity<Client> read(@PathVariable(name = "id") int id) {
   final Client client = clientService.read(id);

   return client != null
           ? new ResponseEntity<>(client, HttpStatus.OK)
           : new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
З нового, у нас тут з'явилася змінна дорога. Змінна, визначена в URI. value = "/clients/{id}". Ми вказали її у фігурних дужках. А в параметрах методу приймаємо її як змінну int, за допомогою анотації @PathVariable(name = "id"). Даний метод прийматиме запити на uri виду /clients/{id}, де замість {id}може бути будь-яке чисельне значення. Дане значення, згодом, передається змінною int id- параметр методу. У тілі ми отримуємо об'єкт Clientза допомогою нашого сервісу та прийнятого id. І далі, за аналогією зі списком, повертаємо статус 200 OK і сам об'єкт Client, або просто статус 404 Not Found, якщо клієнта з таким idне виявилося в системі. Залишилося реалізувати дві операції - Update та Delete. Наведемо код цих методів:
@PutMapping(value = "/clients/{id}")
public ResponseEntity<?> update(@PathVariable(name = "id") int id, @RequestBody Client client) {
   final boolean updated = clientService.update(client, id);

   return updated
           ? new ResponseEntity<>(HttpStatus.OK)
           : new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}

@DeleteMapping(value = "/clients/{id}")
public ResponseEntity<?> delete(@PathVariable(name = "id") int id) {
   final boolean deleted = clientService.delete(id);

   return deleted
           ? new ResponseEntity<>(HttpStatus.OK)
           : new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
Чогось суттєво нового в даних методах немає, тому докладний опис пропустимо. Єдине, що варто сказати: метод updateобробляє PUT запити (анотація @PutMapping), а метод deleteобробляє DELETE запити (анотація DeleteMapping). Наведемо повний код контролера:
@RestController
public class ClientController {

   private final ClientService clientService;

   @Autowired
   public ClientController(ClientService clientService) {
       this.clientService = clientService;
   }

   @PostMapping(value = "/clients")
   public ResponseEntity<?> create(@RequestBody Client client) {
       clientService.create(client);
       return new ResponseEntity<>(HttpStatus.CREATED);
   }

   @GetMapping(value = "/clients")
   public ResponseEntity<List<Client>> read() {
       final List<client> clients = clientService.readAll();

       return clients != null &&  !clients.isEmpty()
               ? new ResponseEntity<>(clients, HttpStatus.OK)
               : new ResponseEntity<>(HttpStatus.NOT_FOUND);
   }

   @GetMapping(value = "/clients/{id}")
   public ResponseEntity<Client> read(@PathVariable(name = "id") int id) {
       final Client client = clientService.read(id);

       return client != null
               ? new ResponseEntity<>(client, HttpStatus.OK)
               : new ResponseEntity<>(HttpStatus.NOT_FOUND);
   }

   @PutMapping(value = "/clients/{id}")
   public ResponseEntity<?> update(@PathVariable(name = "id") int id, @RequestBody Client client) {
       final boolean updated = clientService.update(client, id);

       return updated
               ? new ResponseEntity<>(HttpStatus.OK)
               : new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
   }

   @DeleteMapping(value = "/clients/{id}")
   public ResponseEntity<?> delete(@PathVariable(name = "id") int id) {
       final boolean deleted = clientService.delete(id);

       return deleted
               ? new ResponseEntity<>(HttpStatus.OK)
               : new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
   }
}
У результаті структура нашого проекту виглядає так: Огляд REST.  Частина 3: створення RESTful сервісу на Spring Boot - 7

Запуск та тестування

Щоб запустити нашу програму, достатньо запустити метод mainу класі RestExampleApplication. А для того, щоб тестувати RESTful веб сервіси, потрібно завантажити нове ПЗ) Справа в тому, що GET запити досить просто відправляти зі звичайного браузера, а ось для POST, PUT і DELETE звичайним браузером не обійтися. Не хвилюйся: щоб надсилати будь-які HTTP запити, можна скористатися програмою Postman. Завантажити її можна звідси . Після скачування та встановлення, приступаємо до тестування нашої програми. Для цього відкриваємо програму та створюємо новий запит: Огляд REST.  Частина 3: створення RESTful сервісу на Spring Boot - 9Натискаємо кнопку New у лівому верхньому кутку. Далі вибираємо Request: Огляд REST.  Частина 3: створення RESTful сервісу на Spring Boot - 10Далі задаємо йому ім'я та зберігаємо його. Спробуємо тепер відправити запит на сервер POST і створити першого клієнта: Огляд REST.  Частина 3: створення RESTful сервісу на Spring Boot - 11Створюємо в такий спосіб кілька клієнтів. Потім змінюємо тип запиту на GET і відправляємо його на сервер: Огляд REST.  Частина 3: створення RESTful сервісу на Spring Boot - 12

Загальні підсумки

Вітаю: ми розглянули досить тему REST. Весь матеріал вийшов об'ємним, але, сподіваємось, корисним для тебе:
  1. Ми довідалися, що таке REST.

  2. Ознайомабося з історією виникнення REST.

  3. Поговорабо про обмеження та принципи даного архітектурного стилю:

    • приведення архітектури до моделі клієнт-сервер;
    • відсутність стану;
    • кешування;
    • однаковість інтерфейсу;
    • шари;
    • код на вимогу (необов'язкове обмеження).
  4. Розібрали переваги, які дає REST

  5. Детально розглянули, як сервер та клієнт взаємодіють один з одним за протоколом HTTP.

  6. Ближче познайомабося із запитами та відповідями. Розібрали їхні складові.

  7. Нарешті, ми перейшли до практики та написали свій невеликий RESTful додаток на Spring Boot. І навіть навчабося тестувати його за допомогою програми Postman.

Фуух. Вийшло об'ємно, але, проте є чим зайнятися, як домашнє завдання.

Домашнє завдання

Спробуй зробити таке:
  1. Дотримуючись опису вище, створи самостійно Spring Boot проект і реалізуй у ньому ту ж логіку, що й у лекції. Повтори всі 1 у 1.
  2. Запусти. додаток.
  3. Скачай та налаштування Postman (або будь-який інший інструмент для відправки запитів, хоч curl).
  4. Протестуй запити POST та GET так само, як було зазначено у лекції.
  5. Протестуй запити PUT та DELETE самостійно.
Частина 1: що таке REST Частина 2: комунікація між клієнтом та сервером
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ