Back-end

Spring Boot microservices : architecture & déploiement

Spring-Boot Microservices Java Devops Docker Rest-Api Spring-Data-Jpa Kubernetes Architecture Spring-Cloud Scalabilite Backend-Java Feign Deploiement
Spring Boot microservices : architecture & déploiement

Concevez une architecture microservices robuste avec Spring Boot : REST controllers, Spring Data JPA, Docker, orchestration et communication inter-services.

Architecture microservices

Les microservices divisent une application monolithique en services indépendants. Spring Boot simplifie cette approche :

Principes clés : Chaque service est responsable d'un domaine métier unique, peut être déployé indépendamment et communique via des APIs.

Structure typique d'un projet Spring Boot microservices :

user-service/
├── src/main/java/com/app/
│   ├── UserApplication.java
│   ├── controller/
│   │   └── UserController.java
│   ├── service/
│   │   └── UserService.java
│   ├── repository/
│   │   └── UserRepository.java
│   └── model/
│       └── User.java
├── application.yml
└── pom.xml

Configuration Spring Boot pour microservices :

# application.yml
spring:
  application:
    name: user-service
  jpa:
    hibernate:
      ddl-auto: update
  datasource:
    url: jdbc:mysql://localhost:3306/users_db
    username: root
    password: password

server:
  port: 8081
  servlet:
    context-path: /api

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

REST Controllers

Les contrôleurs gèrent les requêtes HTTP et retournent du JSON :

// UserController.java
package com.app.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.app.model.User;
import com.app.service.UserService;
import java.util.List;

@RestController
@RequestMapping("/api/users")
@CrossOrigin("*")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        return ResponseEntity.ok(userService.findAll());
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        return userService.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User created = userService.save(user);
        return new ResponseEntity<>(created, HttpStatus.CREATED);
    }

    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
        user.setId(id);
        return ResponseEntity.ok(userService.save(user));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteById(id);
        return ResponseEntity.noContent().build();
    }
}
Note : @CrossOrigin permet les requêtes cross-origin (CORS) depuis le frontend.

Spring Data JPA

Spring Data JPA fournit une abstraction pour l'accès aux données :

// User.java (Entity)
package com.app.model;

import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;

@Entity
@Table(name = "users")
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String name;

    @Column(name = "created_at")
    private LocalDateTime createdAt = LocalDateTime.now();
}

// UserRepository.java
package com.app.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.app.model.User;
import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
    List<User> findByNameContainingIgnoreCase(String name);
}

// UserService.java
package com.app.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.app.model.User;
import com.app.repository.UserRepository;
import java.util.List;
import java.util.Optional;

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public List<User> findAll() {
        return userRepository.findAll();
    }

    public Optional<User> findById(Long id) {
        return userRepository.findById(id);
    }

    public User save(User user) {
        return userRepository.save(user);
    }

    public void deleteById(Long id) {
        userRepository.deleteById(id);
    }
}

Conteneurisation avec Docker

Packagez vos services Spring Boot en conteneurs Docker :

# Dockerfile
FROM openjdk:17-jdk-slim

WORKDIR /app

COPY target/user-service-1.0.0.jar app.jar

EXPOSE 8081

ENTRYPOINT ["java", "-jar", "app.jar"]

Créez une pipeline de construction :

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

Build et run du conteneur :

# Builder l'image
mvn clean package
docker build -t user-service:1.0.0 .

# Lancer le conteneur
docker run -p 8081:8081 \
  -e SPRING_DATASOURCE_URL=jdbc:mysql://host.docker.internal:3306/users_db \
  user-service:1.0.0

Communication inter-services

Les microservices communiquent via HTTP ou asynchrone. Utilisez RestTemplate ou WebClient :

// OrderService - appel synchrone à UserService
package com.app.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestTemplate;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Autowired
    private RestTemplate restTemplate;

    public void createOrder(Long userId, Order order) {
        // Vérifier que l'utilisateur existe
        String userUrl = "http://USER-SERVICE/api/users/" + userId;
        User user = restTemplate.getForObject(userUrl, User.class);

        if (user == null) {
            throw new RuntimeException("Utilisateur non trouvé");
        }

        // Créer la commande
        order.setUserId(userId);
        // Sauvegarder...
    }
}

// Configuration RestTemplate
@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

Pour une communication asynchrone, utilisez RabbitMQ ou Kafka :

// RabbitMQ Producer (UserService)
@Component
public class UserEventPublisher {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void publishUserCreated(User user) {
        rabbitTemplate.convertAndSend("user-exchange", "user.created", user);
    }
}

// RabbitMQ Consumer (NotificationService)
@Component
public class UserEventListener {
    @RabbitListener(queues = "user-queue")
    public void onUserCreated(User user) {
        // Envoyer email de bienvenue
        sendWelcomeEmail(user.getEmail());
    }
}
À retenir : Les appels synchrones sont simples mais couplent les services. Préférez l'asynchrone pour la scalabilité.

Questions fréquentes

Adoptez un monorepo multi-module Maven/Gradle avec un parent POM centralisant les versions (Spring Boot BOM). Chaque microservice est un module indépendant avec son propre `application.yml` et ses dépendances. Utilisez Spring Cloud pour les patterns distribués : Eureka (service discovery), Spring Cloud Gateway (API gateway), Config Server (configuration centralisée). Les services communiquent via REST (OpenFeign) ou messaging asynchrone (Kafka, RabbitMQ). Containerisez chaque service avec un `Dockerfile` multi-stage pour minimiser la taille des images.

Resilience4j est la bibliothèque de référence pour Spring Boot 3+ (remplace Hystrix). Annotez vos clients Feign avec `@CircuitBreaker(name="service", fallbackMethod="fallback")` pour ouvrir le circuit après des erreurs répétées. `@Retry(name="service", maxAttempts=3)` gère les retry avec backoff exponentiel. `@RateLimiter` protège contre les appels excessifs. `@TimeLimiter` évite les timeouts bloquants. Configurez dans `application.yml` avec `resilience4j.circuitbreaker.instances.service.slidingWindowSize=10`.

Créez un Deployment Kubernetes par microservice avec une stratégie `RollingUpdate` (maxSurge: 1, maxUnavailable: 0) pour zéro downtime. Définissez des `readinessProbe` pointant vers `/actuator/health/readiness` et des `livenessProbe` vers `/actuator/health/liveness` (Spring Boot Actuator 2.3+). Les ConfigMaps et Secrets Kubernetes remplacent avantageusement Spring Cloud Config pour les petites infrastructures. Helm charts facilitent le packaging et le versioning des déploiements. Utilisez Skaffold pour le développement local avec hot-reload sur Kubernetes.

Les transactions ACID distribuées (XA/2PC) sont à éviter dans les microservices — elles créent des couplages forts et des points de défaillance. Préférez le pattern Saga : chorégraphie (événements Kafka/RabbitMQ) pour les workflows simples ou orchestration (Axon Framework, Temporal) pour les workflows complexes. Le pattern Outbox garantit la cohérence entre la base de données et le broker de messages : persiste l'événement dans une table `outbox` de la même transaction SQL, puis un poller asynchone (Debezium CDC) le publie dans Kafka.

Partager