Руководство по REST архитектуре

0
1723

Руководство по REST архитектуре

Часть 1. Что такое REST

REST, как и многое в мире IT, — это акроним, сокращение от английского Representational State Transfer — передача состояния представления. Это архитектурный стиль взаимодействия компонентов распределенной системы в компьютерной сети. Проще говоря, REST определяет стиль взаимодействия (обмена данными) между разными компонентами системы, каждая из которых может физически располагаться в разных местах. Данный архитектурный стиль представляет собой согласованный набор ограничений, учитываемых при проектировании распределенной системы. Эти ограничения иногда называют принципами REST. Их немного, всего 6 штук. О них мы поговорим чуть позже.

Приложения, построенные с учетом REST, т.е. не нарушающие накладываемые REST ограничения, называют RESTful.

История возникновения REST

Термин REST ввел Рой Филдинг, один из создателей протокола HTTP, в своей докторской диссертации «Архитектурные стили и дизайн сетевых программных архитектур» («Architectural Styles and the Design of Network-based Software Architectures») в 2000 году. Можно сказать, что термин REST еще молодой, хотя его концепция лежит в самой основе всемирной паутины. Мы не будем погружаться глубоко в историю возникновения данного термина. Если хочешь окунуться в первоисточники, загляни в диссертацию Филдинга.

REST ограничения и принципы

Как было сказано выше, REST определяет, как компоненты распределенной системы должны взаимодействовать друг с другом. В общем случае этот происходит посредством запросов-ответов. Компоненту, которая отправляет запрос называют клиентом; компоненту, которая обрабатывает запрос и отправляет клиенту ответ, называют сервером. Запросы и ответы, чаще всего, отправляются по протоколу HTTP (англ. HyperText Transfer Protocol — «протокол передачи гипертекста»). Как правило сервер — это некое веб-приложение. Клиентом же может быть не то чтобы что угодно, но довольно многое. Например, мобильное приложение, которое запрашивает у сервера данные. Либо браузер, который отправляет запросы с веб-страницы на сервер для загрузки данных. Приложение А может запрашивать данные у приложения Б. Тогда А является клиентом по отношению к Б, а Б — сервером по отношению к А. Одновременно с этим, А может обрабатывать запросы от В, Г, Д и т.д. В таком случае, приложение А является одновременно и сервером, и клиентом. Все зависит от контекста. Однозначно одно: компонента которая шлет запрос — это клиент. Компонента, которая принимает, обрабатывает и отвечает на запрос — сервер. Однако не каждая система, чьи компоненты обмениваются данными посредством запросов-ответов, является REST (или же RESTful) системой. Чтобы система считалась RESTful, она должна “вписываться” в шесть REST ограничений:

1. Приведение архитектуры к модели клиент-сервер

В основе данного ограничения лежит разграничение потребностей. Необходимо отделять потребности клиентского интерфейса от потребностей сервера, хранящего данные. Данное ограничение повышает переносимость клиентского кода на другие платформы, а упрощение серверной части улучшает масштабируемость системы. Само разграничение на “клиент” и “сервер” позволяет им развиваться независимо друг от друга.

2. Отсутствие состояния

Архитектура REST требует соблюдения следующего условия. В период между запросами серверу не нужно хранить информацию о состоянии клиента и наоборот. Все запросы от клиента должны быть составлены так, чтобы сервер получил всю необходимую информацию для выполнения запроса. Таким образом и сервер, и клиент могут «понимать» любое принятое сообщение, не опираясь при этом на предыдущие сообщения.

3. Кэширование

Клиенты могут выполнять кэширование ответов сервера. У тех, в свою очередь, должно быть явное или неявное обозначение как кэшируемых или некэшируемых, чтобы клиенты в ответ на последующие запросы не получали устаревшие или неверные данные. Правильное использование кэширования помогает полностью или частично устранить некоторые клиент-серверные взаимодействия, ещё больше повышая производительность и расширяемость системы.

4. Единообразие интерфейса

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

5. Слои

Под слоями подразумевается иерархическая структура сетей. Иногда клиент может общаться напрямую с сервером, а иногда — просто с промежуточным узлом. Применение промежуточных серверов способно повысить масштабируемость за счёт балансировки нагрузки и распределённого кэширования. Приведем пример. Представим себе некоторое мобильное приложение, которое пользуется популярностью во всем мире. Его неотъемлемая часть — загрузка картинок. Так как пользователей — миллионы человек, один сервер не смог бы выдержать такой большой нагрузки. Разграничение системы на слои решит эту проблему. Клиент запросит картинку у промежуточного узла, промежуточный узел запросит картинку у сервера, который наименее загружен в данный момент, и вернет картинку клиенту. Если здесь на каждом уровне иерархии правильно применить кэширование, то можно добиться хорошей масштабируемости системы.

6. Код по требованию (необязательное ограничение)

Данное ограничение подразумевает, что клиент может расширять свою функциональность, за счет загрузки кода с сервера в виде апплетов или сценариев.

Преимущества, которые дает REST

У приложений, которые соблюдают все вышеперечисленные ограничения, есть такие преимущества: надёжность (не нужно сохранять информацию о состоянии клиента, которая может быть утеряна);

  • производительность (за счёт использования кэша);
  • масштабируемость;
  • прозрачность системы взаимодействия;
  • простота интерфейсов;
  • портативность компонентов;
  • лёгкость внесения изменений;
  • способность эволюционировать, приспосабливаясь к новым требованиям.

Коммуникация между клиентом и сервером

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

Чтобы все было (стало) понятно, будем разбирать клиент-серверную коммуникацию на примере некоторого RESTful приложения. Допустим, мы разрабатываем веб приложение, которое способно хранить информацию о клиентах и их заказах. Т.е. наша система способна манипулировать некоторыми сущностями: создавать их, редактировать, удалять, выдавать информацию о них. Этими сущностями будут:

  • clients — клиенты;
  • orders — заказы клиентов;
  • items — товары.

В REST архитектуре клиенты отправляют на сервер запросы для получения или модификации данных, а сервера отправляют клиентам ответы на их запросы.

Запросы

Клиентские запросы практически всегда сделаны по протоколу HTTP. В общем, HTTP запросы состоят из нескольких составляющих:

  • HTTP метод;
  • заголовок;
  • URI;
  • тело запроса.

Ниже мы рассмотрим более подробно каждую составляющую часть.

URI и Ресурсы

Данные, которые получают или изменяют клиенты посредством запросов, называют ресурсами. Основа клиент-серверного взаимодействия — манипуляция над ресурсами. Ресурсы в REST — это все, чему можно дать имя. Это в каком то смысле как классы в Java. В Java мы можем создать класс для чего угодно. Так и в REST — ресурсом может быть что угодно: пользователь, документ, отчет, заказ. Все это может быть как абстракцией некоторой сущности, так и чем-то конкретным, например, файлом — картинкой, видео, анимацией, PDF файлом. В рамках нашего примера у нас есть 3 ресурса:

  • clients — клиенты;
  • orders — заказы клиентов;
  • items — товары.

Клиенты отправляют запросы на так называемые эндпоинты, или же конечные точки (end point). Если говорить очень просто, эндпоинт — это что-то вроде адреса в сети. Если углубляться в суть, можно сказать, что эндпоинт — это URI: последовательность символов, идентифицирующая абстрактный или физический ресурс. Uniform Resource Identifier — унифицированный идентификатор ресурса. Иногда конечную точку, или URI называют путем (path) — путем до ресурса. В рамках этой статьи мы будем использовать термин URI. У каждого конкретного ресурса должен быть уникальный URI. Ответственность за то, чтобы у каждого ресурса всегда был свой URI лежит на плечах разработчика сервера. В нашем примере разработчики это мы, поэтому будем делать это так, как умеем. Подобно тому, как в реляционной базе данных часто принято первичным ключом задавать некоторый числовой ID, в REST у каждого ресурса есть свой ID. Часто бывает так, что ID ресурса в REST совпадает с ID записи в базе данных, в которой хранится информация о данном ресурсе. URI в REST принято начинать с множественной формы существительного, описывающего некоторый ресурс. Например, со слова clients. Далее через слэш указывают ID — идентификатор некоторого конкретного клиента. Примеры:

  • /clients — URI всех имеющихся клиентов;
  • /clients/23 — URI конкретного клиента, а именно клиента с ID=23;
  • /clients/4 — URI конкретного клиента, а именно клиента с ID=23.

Но и это еще не все. Мы можем продолжить URI, добавив к нему заказы:

  • /clients/4/orders — URI всех заказов клиента №4;
  • /clients/1/orders/12 — URI заказа №12 клиента №1.

Если мы продолжим эту цепочку и добавим еще и товары, получим:

  • /clients/1/orders/12/items — URI списка всех товаров в заказе №12 сделанного клиентом №1.

С уровнями вложенности главное — делать URI интуитивно понятными.

HTTP метод

Метод HTTP (англ. HTTP Method) — последовательность из любых символов, кроме управляющих и разделителей, которая указывает на основную операцию над ресурсом. Существует несколько общепринятых методов HTTP. Перечислим те из них, которые наиболее часто используются в RESTful сервисах:

  • GET — служит для получения информации о конкретном ресурсе (через ID) либо о коллекции ресурсов;
  • POST — служит для создания нового ресурса;
  • PUT — служит для изменения ресурса (через ID);
  • DELETE — служит для удаления ресурса (через ID).

Заголовки

В запросах, как собственно и в ответах, присутствуют HTTP заголовки. В них отправляется дополнительная информация о запросе (либо ответе). Заголовки представляют собой пары ключ-значение. Список наиболее распространенных заголовков можешь почитать на странице в Википедии. Применительно к REST клиенты часто могут слать в запросе к серверу заголовок Accept. Он нужен, чтобы дать серверу понять, в каком формате клиент ожидает получить от него ответ. Различные варианты форматов представлены в так называемом списке MIME-типов. MIME (англ. Multipurpose Internet Mail Extensions — многоцелевые расширения интернет-почты) — спецификация для кодирования информации и форматирования сообщений таким образом, чтобы их можно было пересылать по интернету. Каждый MIME тип состоит из двух частей, разделяемых слэшем — из типа и подтипа. Примеры MIME-типов для разных видов файлов:

  • text — text/plain, text/css, text/html;
  • image — image/png, image/jpeg, image/gif;
  • audio — audio/wav, audio/mpeg;
  • video — video/mp4, video/ogg;
  • application — application/json, application/pdf, application/xml, application/octet-stream.

Итого, у запроса может присутствовать заголовок:

Accept:application/json

Данный заголовок говорит серверу, что клиент ожидает получить ответ в JSON формате.

Тело запроса

Пересылаемое клиентом сообщение на сервер. Есть у запроса тело или нет, зависит от типа HTTP запроса. Например, запросы GET и DELETE как правило не содержат никакого тела запроса. А вот PUT и POST могут содержать: тут все дело в функциональном назначении типа запроса. Ведь для получения данных и удаления по id (который передается в URL) не нужно слать на сервер дополнительные данные. А вот для создания нового ресурса (запрос POST) нужно этот ресурс передать. Также как и для модификации существующего ресурса. В REST для передачи тела запроса чаще всего используют форматы XML или JSON. Наиболее часто встречается JSON формат. Предположим, мы хотим отправить на сервер запрос, а в нем — создать новый ресурс. Если ты не забыл, в качестве примера мы рассматривали приложение, которое управляет заказами клиентов. Допустим, мы хотим создать нового клиента. В нашем случае мы храним следующую информацию о клиентах: Имя Email Номер телефона Тогда телом такого запроса может быть следующий JSON:

{
  "name" : "Amigo",
  "email" : "amigo@jr.com",
  "phone" : "+7 (191) 746-43-23"
}

Собираем запросы воедино

Итак, мы рассмотрели с тобой из чего может состоять клиентский запрос. Приведем теперь несколько примеров запросов с описанием

Руководство по REST архитектуре
Руководство по REST архитектуре

Ответы

Скажем пару слов об ответах сервера. Ответ как правило состоит из следующих частей:

  • код ответа;
  • заголовки;
  • тело ответа.

В целом заголовки ответов мало чем отличаются от заголовков запросов. К тому же, некоторые заголовки используются и в ответах, и в запросах. С телом ответа тоже, думаю, все понятно. В теле часто возвращается информация которую запросил клиент. Может возвращаться в том же формате JSON информация на GET запросы. А вот последняя часть, немного более интересна.

Коды HTTP ответов

Рассмотрим подробнее коды HTTP ответов. Приведем цитату из Википедии: Код состояния HTTP (англ. HTTP status code) — часть первой строки ответа сервера при запросах по протоколу HTTP. Он представляет собой целое число из трёх десятичных цифр. Первая цифра указывает на класс состояния. За кодом ответа обычно следует отделенная пробелом поясняющая фраза на английском языке, которая разъясняет человеку причину именно такого ответа. Примеры:

  • 201 Created;
  • 401 Unauthorized;
  • 507 Insufficient Storage.

Клиент узнаёт по коду ответа о результатах его запроса и определяет, какие действия ему предпринимать дальше. Коды ответов подразделяются на несколько групп:

  • 1ХХ — информационные;
  • 2ХХ — информируют о случаях успешного принятия и обработки запроса клиента;
  • 3ХХ — сообщают клиенту, что для успешного выполнения операции необходимо сделать другой запрос, как правило по другому URI;
  • 4ХХ — ошибка клиента. Например, неправильно составленный запрос или же широко известный код 404 Not Found, которая может возникнуть, когда клиент запрашивает несуществующий ресурс;
  • 5ХХ — ошибка сервера. Возвращается клиенту в случае неудачного выполнения операции по вине сервера.

Создание RESTful сервиса на Spring Boot

Создание проекта

В данном разделе мы создадим небольшое RESTful приложение на Spring Boot. В нашем приложении будут реализованы CRUD (Create, Read, Update, Delete) операции над клиентами из примера выше. Для начала создадим новое Spring Boot приложение через меню File -> New -> Project… В открывшимся окне выбираем Spring Initializr и указываем Project SDK:

Руководство по REST архитектуре

Нажимаем кнопку Next. В следующем окне указываем тип проекта Maven, указываем Group и Artifact:

Руководство по REST архитектуре

Нажимаем кнопку Next. В следующем окне нам необходимо выбрать нужные для проекта компоненты Spring Framework. Нам будет достаточно Spring Web:

Руководство по REST архитектуре

Нажимаем кнопку Next. Далее осталось указать только наименование проекта и его расположение в файловой системе:

Руководство по REST архитектуре

Нажимаем кнопку Finish. Проект создан, теперь мы можем увидеть его структуру:

Руководство по REST архитектуре

IDEA сгенерировала за нас дескриптор развертывания системы сборки 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.javarush.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.javarush.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.javarush.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 и не пуст, мы возвращаем c помощью класса 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 архитектуре

Запуск и тестирование

Чтобы запустить наше приложение, достаточно запустить метод main в классе RestExampleApplication.

А для того, чтобы тестировать RESTful веб сервисы, нужно скачать новое ПО )

Дело в том, что GET запросы довольно просто отправлять из обычного браузера, а вот для POST, PUT и DELETE обычным браузером не обойтись.

Не переживай: чтобы отправлять любые HTTP запросы, можно воспользоваться программой Postman. Скачать её можно отсюда.

После скачивания и установки, приступаем к тестированию нашего приложения.

Для этого открываем программу и создаем новый запрос:

Руководство по REST архитектуре

Нажимаем кнопку New в левом верхнем углу.

Далее выбираем Request:

Руководство по REST архитектуре

Далее задаем ему имя и сохраняем его.

Попробуем теперь отправить POST запрос на сервер и создать первого клиента:

Руководство по REST архитектуре

Создаем таким образом несколько клиентов. Затем меняем тип запроса на GET и отправляем его на сервер:

Руководство по REST архитектуре

Общие итоги

Поздравляю: мы рассмотрели довольно тему REST. Весь материал получился объемным, но, надеемся, полезным для тебя:

  1. Мы узнали, что такое REST.
  2. Познакомились с историей возникновения REST.
  3. Поговорили об ограничениях и принципах данного архитектурного стиля:
    • приведение архитектуры к модели клиент-сервер;
    • отсутствие состояния;
    • кэширование;
    • единообразие интерфейса;
    • слои;
    • код по требованию (необязательное ограничение).
  4. Разобрали преимущества которые дает REST
  5. Подробно рассмотрели, как сервер и клиент взаимодействуют друг с другом по HTTP протоколу.
  6. Поближе познакомились с запросами и ответами. Разобрали их составные части.
  7. Наконец, мы перешли к практике и написали свое небольшое RESTful приложение на Spring Boot. И даже научились его тестировать с помощью программы Postman.

источник