Premiers pas avec Spring Boot
Créer votre premier microservice
10-15 min
Premiers pas avec Spring Boot
Dans ce chapitre, nous allons créer notre premier microservice avec Spring Boot. Nous allons apprendre à configurer le projet, structurer le code et implémenter une API REST simple.
Configuration du projet
Création du projet avec Spring Initializr
-
Dépendances essentielles
<dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Starter Data JPA --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- H2 Database --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
-
Configuration de base
# application.yml server: port: 8080 spring: application: name: product-service datasource: url: jdbc:h2:mem:productdb driver-class-name: org.h2.Driver username: sa password: jpa: database-platform: org.hibernate.dialect.H2Dialect hibernate: ddl-auto: update h2: console: enabled: true path: /h2-console
Structure du projet
src/main/java/com/example/productservice/
├── ProductServiceApplication.java
├── controller/
│ └── ProductController.java
├── model/
│ └── Product.java
├── repository/
│ └── ProductRepository.java
├── service/
│ └── ProductService.java
└── dto/
├── ProductDTO.java
└── CreateProductRequest.java
Implémentation
1. Modèle de données
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private BigDecimal price;
private Integer stock;
}
2. DTO (Data Transfer Objects)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProductDTO {
private Long id;
private String name;
private String description;
private BigDecimal price;
private Integer stock;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateProductRequest {
private String name;
private String description;
private BigDecimal price;
private Integer stock;
}
3. Repository
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByPriceLessThan(BigDecimal price);
Optional<Product> findByName(String name);
}
4. Service
@Service
@Slf4j
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
public List<ProductDTO> getAllProducts() {
return productRepository.findAll()
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public ProductDTO getProductById(Long id) {
return productRepository.findById(id)
.map(this::convertToDTO)
.orElseThrow(() -> new ProductNotFoundException(id));
}
public ProductDTO createProduct(CreateProductRequest request) {
Product product = new Product();
product.setName(request.getName());
product.setDescription(request.getDescription());
product.setPrice(request.getPrice());
product.setStock(request.getStock());
return convertToDTO(productRepository.save(product));
}
public ProductDTO updateProduct(Long id, CreateProductRequest request) {
Product product = productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));
product.setName(request.getName());
product.setDescription(request.getDescription());
product.setPrice(request.getPrice());
product.setStock(request.getStock());
return convertToDTO(productRepository.save(product));
}
public void deleteProduct(Long id) {
if (!productRepository.existsById(id)) {
throw new ProductNotFoundException(id);
}
productRepository.deleteById(id);
}
private ProductDTO convertToDTO(Product product) {
return new ProductDTO(
product.getId(),
product.getName(),
product.getDescription(),
product.getPrice(),
product.getStock()
);
}
}
5. Controller
@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
@GetMapping
public ResponseEntity<List<ProductDTO>> getAllProducts() {
return ResponseEntity.ok(productService.getAllProducts());
}
@GetMapping("/{id}")
public ResponseEntity<ProductDTO> getProductById(@PathVariable Long id) {
return ResponseEntity.ok(productService.getProductById(id));
}
@PostMapping
public ResponseEntity<ProductDTO> createProduct(@RequestBody CreateProductRequest request) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(productService.createProduct(request));
}
@PutMapping("/{id}")
public ResponseEntity<ProductDTO> updateProduct(
@PathVariable Long id,
@RequestBody CreateProductRequest request) {
return ResponseEntity.ok(productService.updateProduct(id, request));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
return ResponseEntity.noContent().build();
}
}
6. Gestion des exceptions
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<ErrorResponse> handleProductNotFoundException(ProductNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"Une erreur inattendue s'est produite"
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
Tests
Test unitaire du service
@ExtendWith(MockitoExtension.class)
class ProductServiceTest {
@Mock
private ProductRepository productRepository;
@InjectMocks
private ProductService productService;
@Test
void createProduct_ShouldReturnProductDTO() {
// Arrange
CreateProductRequest request = new CreateProductRequest(
"Test Product",
"Description",
new BigDecimal("10.00"),
100
);
Product product = new Product(1L, "Test Product", "Description",
new BigDecimal("10.00"), 100);
when(productRepository.save(any(Product.class))).thenReturn(product);
// Act
ProductDTO result = productService.createProduct(request);
// Assert
assertNotNull(result);
assertEquals("Test Product", result.getName());
assertEquals(new BigDecimal("10.00"), result.getPrice());
}
}
Test d’intégration
@SpringBootTest
@AutoConfigureMockMvc
class ProductControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
void createProduct_ShouldReturnCreatedProduct() throws Exception {
// Arrange
CreateProductRequest request = new CreateProductRequest(
"Test Product",
"Description",
new BigDecimal("10.00"),
100
);
// Act & Assert
mockMvc.perform(post("/api/products")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("Test Product"))
.andExpect(jsonPath("$.price").value("10.00"));
}
}
Exercices pratiques
-
Création d’un service de commandes
- Implémentez un service REST pour gérer les commandes
- Ajoutez la validation des données
- Implémentez la gestion des erreurs
-
Ajout de fonctionnalités
- Implémentez la pagination des résultats
- Ajoutez le tri des produits
- Implémentez la recherche par critères
-
Tests
- Ajoutez des tests unitaires pour le service
- Implémentez des tests d’intégration
- Ajoutez des tests de performance
Conclusion
Dans ce chapitre, nous avons créé notre premier microservice avec Spring Boot. Nous avons appris à :
- Configurer un projet Spring Boot
- Structurer le code selon les bonnes pratiques
- Implémenter une API REST
- Gérer les exceptions
- Écrire des tests
Dans le prochain chapitre, nous explorerons les différentes stratégies de communication entre microservices.
Commentaires
Les commentaires sont alimentés par GitHub Discussions
Connectez-vous avec GitHub pour participer à la discussion