Pourquoi CI/CD pour Spring Boot avec GitHub Actions ?
L'automatisation du processus de construction, de test et de déploiement (CI/CD) est essentielle pour les développeurs Spring Boot. Il permet de détecter rapidement les erreurs dans le code et de les corriger avant qu'ils ne soient intégrés au projet principal. Cela augmente la qualité du code et accélère le développement logiciel.
Un cas d'usage concret est un équipe qui travaille sur un microservice déployé en production chaque jour. Avec une automatisation de CI/CD, ils peuvent être certains que chaque commit est fonctionnel avant qu'il ne soit déployé, réduisant ainsi les retards et la maintenance.
Prerequis
- Connaissance avancée des concepts de Spring Boot.
- Un compte GitHub avec un repository existant ou prêt à être créé.
- Java JDK 11 ou ultérieur installé sur votre système (https://openjdk.java.net/install/)
- Node.js installé pour les outils frontend (https://nodejs.org/en/download/)
- Maven ou Gradle pour le build du projet.
Concepts fondamentaux
Workflow
Un workflow est une série d'étapes définies par GitHub Actions qui s'exécutent dans un environnement spécifique. Chaque workflow peut être déclenché par des événements spécifiques, comme la création d'une pull request ou le push sur une branche.
## .github/workflows/ci.yml
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
- name: Build and test with Maven
run: mvn clean install
Actions et Services
Les actions sont des étapes individuelles dans un workflow. Par exemple, l'action actions/checkout est utilisée pour obtenir le code du dépôt.
- name: Checkout code
uses: actions/checkout@v2
Les services permettent de démarrer des services comme une base de données ou un serveur d'application avant l'exécution des étapes du workflow.
services:
db:
image: postgres
env:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
Environnements
GitHub Actions vous permet de configurer différents environnements pour votre workflow, comme ubuntu-latest, windows-latest ou macos-latest.
runs-on: ubuntu-latest
Mise en pratique : projet fil rouge
Créons un simple application Spring Boot qui expose une API REST pour gérer les utilisateurs.
Étape 1 : Créer le projet
- Créez un nouveau dépôt GitHub et nommez-le
spring-boot-ci-cd. - Clonez le dépôt sur votre machine locale.
- Initialisez un nouveau projet Spring Boot avec Spring Initializr (https://start.spring.io/).
mvn archetype:generate -DgroupId=com.example -DartifactId=spring-boot-ci-cd -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
Étape 2 : Ajouter des dépendances
Ajoutez les dépendances nécessaires pour un projet Spring Boot avec une API REST.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
Étape 3 : Créer le modèle et le service
Créez un modèle User et un service UserService.
// src/main/java/com/example/demo/User.java
package com.example.demo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
}
java
// src/main/java/com/example/demo/UserService.java
package com.example.demo;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class UserService {
private final AtomicLong counter = new AtomicLong();
private final List<User> users = new ArrayList<>();
public User createUser(User user) {
user.setId(counter.incrementAndGet());
users.add(user);
return user;
}
public List<User> getAllUsers() {
return users;
}
public Optional<User> getUserById(Long id) {
return users.stream().filter(u -> u.getId().equals(id)).findFirst();
}
}
Étape 4 : Créer le contrôleur REST
Créez un contrôleur pour gérer les requêtes HTTP.
// src/main/java/com/example/demo/UserController.java
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}
@GetMapping("/{id}")
public Optional<User> getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
}
Étape 5 : Ajouter des tests unitaires
Ajoutez des tests unitaires pour le service UserService.
// src/test/java/com/example/demo/UserServiceTest.java
package com.example.demo;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
public class UserServiceTest {
private final UserService userService = new UserService();
@Test
public void testCreateUser() {
User user = new User(null, "John Doe");
User createdUser = userService.createUser(user);
assertNotNull(createdUser.getId());
assertEquals("John Doe", createdUser.getName());
}
@Test
public void testGetAllUsers() {
List<User> users = userService.getAllUsers();
assertTrue(users.isEmpty());
User user = new User(null, "Jane Doe");
userService.createUser(user);
users = userService.getAllUsers();
assertEquals(1, users.size());
}
}
Étape 6 : Configurer le workflow GitHub Actions
Créez un fichier .github/workflows/ci.yml pour configurer le workflow CI.
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
- name: Build and test with Maven
run: mvn clean install
Erreurs frequentes et debugging
Erreur 1 : mvn clean install échoue avec "Could not resolve dependencies"
Cause: Les dépendances nécessaires ne sont pas résolues.
Solution: Vérifiez que les versions des dépendances sont correctes et que le pom.xml est bien configuré.
<!-- src/main/resources/application.properties -->
server.port=8080
Erreur 2 : mvn clean install échoue avec "Failed to execute goal on project spring-boot-ci-cd: Execution default-test of goal org.apache.maven.plugins:maven-surefire-plugin:2.22.2:test failed"
Cause: Les tests unitaires échouent.
Solution: Vérifiez les tests unitaires et corrigez-les si nécessaire.
// src/test/java/com/example/demo/UserServiceTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
public class UserServiceTest {
private final UserService userService = new UserService();
@Test
public void testCreateUser() {
User user = new User(null, "John Doe");
User createdUser = userService.createUser(user);
assertNotNull(createdUser.getId());
assertEquals("John Doe", createdUser.getName());
}
@Test
public void testGetAllUsers() {
List<User> users = userService.getAllUsers();
assertTrue(users.isEmpty());
User user = new User(null, "Jane Doe");
userService.createUser(user);
users = userService.getAllUsers();
assertEquals(1, users.size());
}
}
Erreur 3 : GitHub Actions échoue avec "Error: Process completed with exit code 2"
Cause: Une erreur est survenue pendant l'exécution du workflow.
Solution: Vérifiez les journaux d'erreur pour identifier la cause de l'échec et corrigez le problème.
## Afficher les journaux d'erreur GitHub Actions
gh run view <RUN_ID> --log
Pour aller plus loin
Déploiement automatique sur Heroku : Automatisez le déploiement de votre application Spring Boot sur Heroku en utilisant la fonctionnalité de déploiement automatisé d'Heroku.
- Liens utiles :
Tests E2E avec Selenium : Ajoutez des tests E2E pour vérifier le comportement de votre application en utilisant Selenium et une base de données in-memory comme H2.
Analyse de code statique avec SonarQube : Intégrez SonarQube dans votre pipeline pour analyser la qualité du code et détecter les anomalies.
Défi pratique :
- Ajoutez un service d'authentification JWT à votre application Spring Boot.
- Configurez le workflow GitHub Actions pour déployer votre application sur une infrastructure cloud comme AWS ou Azure.
- Ajoutez des tests d'intégration pour vérifier la communication entre différents microservices de votre architecture.