Зміст курсу
Spring Boot Backend
Spring Boot Backend
Basic Principles of REST
The core principles of REST form the foundation for creating effective and easily scalable web services. In Spring Boot, they are frequently used to implement APIs.
Let’s explore what these principles are, why they are important, and examine examples of their application in Spring Boot.
Core Principles of REST
REST (Representational State Transfer) is an architectural style based on six key principles that help developers build simple, flexible, and scalable APIs. These principles describe how systems should interact to remain adaptable and maintainable.
Client-Server Architecture
A REST API should separate responsibilities between the client and the server. The client is responsible for the user interface and making requests, while the server handles data storage and request processing.
The REST API ensures a clear separation between the client-side and server-side of the application, allowing them to evolve independently.
The client-side could be a web browser, mobile app, or any other client application, while the server-side can be implemented in any programming language.
Stateless
Each request from the client to the server must include all the information needed to process that request. The server should not retain any state between requests, ensuring each request is isolated from others.
For example, imagine we have an application that returns a list of products in different languages. The client must include the language information in each request so that the server knows which language to use for the response. The server does not store language information between requests. Let’s implement this example in code.
Main
@RestController @RequestMapping("/products") public class ProductController { @GetMapping public List<Product> getProducts(@RequestParam("lang") String language) { // Check the language requested by the client if ("en".equals(language)) { return productService.getProductsInEnglish(); } else if ("es".equals(language)) { return productService.getProductsInSpanish(); } else { return productService.getProductsInDefaultLanguage(); } } }
This code represents a REST controller that handles HTTP GET
requests at the /products
endpoint. The getProducts()
method takes a lang
parameter, indicating the language in which the client wants to receive the data.
Based on this parameter, the method returns a list of products in the specified language or in the default language.
Uniform Interface
To make a REST API easy to use, it should have a simple and organized structure. This means that all endpoints need to follow some basic guidelines. Here are the key principles:
Use nouns to represent resources instead of verbs. For example, instead of using GET /createProduct
, it’s better to use POST /products
, where products is the resource.
GET /products
— Retrieves a list of products;
POST /products
— Creates a new product;
PUT /products/{id}
— Updates information about a specific product, where {id}
is the unique identifier of the product;
DELETE /products/{id}
— Deletes the product with the specified identifier.
Let’s describe a Spring REST controller that manages products by implementing various HTTP methods for creating, retrieving, updating, and deleting product data, while following best practices for a user-friendly API structure.
Main
@RestController @RequestMapping("/products") public class ProductController { // Example of applying the client-server architecture principle private final ProductService productService; // Constructor injection ensures productService is provided by Spring public ProductController(ProductService productService) { this.productService = productService; } // Uniform interface principle: GET request to retrieve all products @GetMapping public List<Product> getAllProducts() { // Calls the service to return a list of all products return productService.getAllProducts(); } // Uniform interface principle: POST request to create a new product @PostMapping public Product createProduct(@RequestBody Product product) { // Calls the service to create a new product based on the provided request body return productService.createProduct(product); } // Uniform interface principle: GET request to retrieve a product by its ID @GetMapping("/{id}") public Product getProductById(@PathVariable Long id) { // Calls the service to find and return the product with the given ID return productService.getProductById(id); } // Uniform interface principle: PUT request to update a product by its ID @PutMapping("/{id}") public Product updateProduct(@PathVariable Long id, @RequestBody Product product) { // Calls the service to update the product details based on the provided request body and ID return productService.updateProduct(id, product); } // Uniform interface principle: DELETE request to remove a product by its ID @DeleteMapping("/{id}") public void deleteProduct(@PathVariable Long id) { // Calls the service to delete the product with the specified ID productService.deleteProduct(id); } }
The @RequestMapping("/products")
annotation specifies that the base URL /products
will be automatically prefixed to all routes within this controller.
In this example, the controller handles operations related to the Product
entity (Product class), so the base URL is /products
. This approach avoids repeating /products
for each method. Instead, we simply specify the HTTP methods (GET, POST, PUT, DELETE), which will be applied to this base URL.
GET /products
— Retrieves a list of products;
POST /products
— Creates a new product.
We can have multiple endpoints with the same URL but different HTTP methods. For instance, GET /products
and POST /products
share the same URL, but they use different HTTP methods. The client will specify the HTTP method in the request header, indicating which action to perform.
Caching
To enhance performance, the server can instruct the client when to cache data. This reduces the server load and speeds up request processing.
This means that when the method is called with the same parameters, the result will be retrieved from the cache rather than re-executing the method. This can improve performance by reducing the load on the service.
In Spring Boot, caching responses can be managed using annotations or HTTP headers. Here’s an example of data caching:
Main
@Cacheable("products") @GetMapping public List<Product> getAllProducts() { return productService.getAllProducts(); }
In this example, the @Cacheable
annotation is used to cache the result of the getAllProducts()
method.
The @Cacheable("products")
annotation indicates that the result of the getAllProducts()
method will be stored in the cache under the name products
. When the method is called again with the same parameters, Spring will look for the result in the products
cache rather than executing the method again.
Layered System
The layered system principle in REST API means that the client doesn't interact with just one server; instead, it works through several levels. Let’s explain this using the three-tier architecture in Spring Boot.
When the client sends a request, it goes through all three levels: from the controller to the service, then to the repository, and back. This separation helps keep the system organized and makes it easier to maintain the code.
Code on Demand
Although less commonly used, a REST API can return executable code to the client for execution on their side. This principle is rarely applied and requires additional security measures.
For example, an API might return JavaScript code that runs in the client's browser to process data or perform other tasks. This can be useful for dynamically loading functionality based on requests, such as form handling or client-side data validation.
Summary
The fundamental principles of REST are essential guidelines for creating flexible and maintainable APIs. In Spring Boot, these principles are implemented using annotations such as @RestController
, @Cacheable
which facilitate the development of well-structured and efficient systems for interacting with clients.
Дякуємо за ваш відгук!