Architecture et technologies clés d'une application de livraison de repas

Ce document synthétise les principes techniques et l'architecture d'une application web de type "livraison de repas". Il couvre les aspects back-end, front-end et l'intégration de services tiers, en s'appuyant sur le framework Spring Boot.

Vue d'ensemble du projet

L'application se compose de deux parties principales :

  • L'interface d'administration : destinée au personnel du restaurant pour gérer les catégories, les plats, les menus, les commandes et les employés.
  • L'application client (sous forme de mini-programme) : permet aux consommateurs de parcourir les plats, de passer commande et de effectuer des paiements.

Proxy inverse avec Nginx

Le proxy inverse agit comme un intermédiaire centralisé pour les requêtes client. Il peut répartir la charge entre plusieurs serveurs d'application.

Un exemple de configuration de base :


server {
    listen 8080;
    server_name monsite.com;

    location / {
        proxy_pass http://backend_app:8090;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Pour la répartition de charge, on définit un groupe de serveurs en amont :


http {
    upstream mes_serveurs_app {
        server 10.0.0.1:8080 weight=5;
        server 10.0.0.2:8080;
        server 10.0.0.3:8080 backup;
    }

    server {
        listen 80;
        location / {
            proxy_pass http://mes_serveurs_app;
        }
    }
}

Documentation d'API avec Swagger et Apifox

La génération automatique de documentation d'API est essentielle. Swagger (basé sur OpenAPI) et Apifox permettent de concevoir, tester et documenter les endpoints REST. Knife4j améliore l'interface de Swagger UI pour Spring Boot.

Architecture modulaire et patterns

L'application utilise une architecture Maven multi-modules. La gestion des dépendances est centrael.

Héritage et dependency management :



<parent>
    <groupId>com.maventech</groupId>
    <artifactId>parent-project</artifactId>
    <version>1.0.0</version>
</parent>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>3.1.0</version>
        </dependency>
    </dependencies>
</dependencyManagement>


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

Les objets de transfert de données (DTO) et les objets de valeur (VO) séparent le modèle de domaine des besoins de l'API.

Gestion globale des exceptions

Un gestionnaire centralisé capture les exceptions et transforme les erreurs en réponses JSON cohérentes.


@RestControllerAdvice
public class GestionnaireExceptions {

    @ExceptionHandler(EntiteNonTrouveeException.class)
    public ResponseEntity<ReponseApi> gererNonTrouve(EntiteNonTrouveeException ex) {
        ReponseApi reponse = new ReponseApi(HttpStatus.NOT_FOUND.value(), ex.getMessage());
        return new ResponseEntity<>(reponse, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ReponseApi> gererExceptionGenerique(Exception ex) {
        ReponseApi reponse = new ReponseApi(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Erreur interne");
        return new ResponseEntity<>(reponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Système de journalisation

Spring Boot intègre SLF4J avec Logback. La configuration se fait dans application.yml :


logging:
  level:
    root: INFO
    com.monapp: DEBUG
    org.springframework.web: WARN
  pattern:
    console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/app.log

Protocole HTTP et authentification JWT

JWT est utilisé pour l'authentification stateless. Un token est généré à la connexion et vérifié par un intercepteur.


@Component
public class IntercepteurAuthJWT implements HandlerInterceptor {

    @Autowired
    private ServiceTokenJWT serviceToken;

    @Override
    public boolean preHandle(HttpServletRequest requete, HttpServletResponse reponse, Object handler) {
        String enteteAuth = requete.getHeader("Authorization");
        if (enteteAuth != null && enteteAuth.startsWith("Bearer ")) {
            String token = enteteAuth.substring(7);
            try {
                Claims reclamations = serviceToken.validerToken(token);
                // Injecter l'ID utilisateur dans le contexte local du thread
                ContexteUtilisateur.definirId(reclamations.get("userId", Long.class));
                return true;
            } catch (Exception e) {
                reponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return false;
            }
        }
        reponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        return false;
    }
}

Le ContexteUtilisateur utilise ThreadLocal pour isoler les données par thread de requête.

Programmation orientée aspect (AOP)

AOP permet de factoriser les préoccupations transversales. Exemple de journalisation des appels de méthode :


@Aspect
@Component
public class AspectJournalisation {

    private static final Logger journal = LoggerFactory.getLogger(AspectJournalisation.class);

    @Around("execution(* com.monapp.service.*.*(..))")
    public Object journaliserAppel(ProceedingJoinPoint pointConjoint) throws Throwable {
        String signature = pointConjoint.getSignature().toShortString();
        journal.info("Entrée dans : {}", signature);
        long debut = System.currentTimeMillis();
        try {
            Object resultat = pointConjoint.proceed();
            long duree = System.currentTimeMillis() - debut;
            journal.info("Sortie de : {} ({} ms)", signature, duree);
            return resultat;
        } catch (Throwable t) {
            journal.error("Exception dans {} : {}", signature, t.getMessage());
            throw t;
        }
    }
}

Intégration Redis et Spring Cache

Redis sert de cache et de stockage rapide pour les données fréquemment accédées. La configuration du RedisTemplate :


@Configuration
public class ConfigurationRedis {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory usineConnexion) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(usineConnexion);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

Spring Cache simplifie la mise en cache déclarative :


@Service
public class ServicePlat {

    @Cacheable(value = "plats", key = "#idCategorie")
    public List<Plat> obtenirPlatsParCategorie(Long idCategorie) {
        // Logique d'accès aux données
        return depotPlat.trouverParCategorie(idCategorie);
    }

    @CacheEvict(value = "plats", key = "#plat.categorie.id")
    public void mettreAJourPlat(Plat plat) {
        depotPlat.sauvegarder(plat);
    }
}

Tâches planifiées avec Spring Task

L'exécution périodique de tâches est gérée par les expressions cron.


@Component
public class TachesPlanifiees {

    @Scheduled(cron = "0 0 2 * * ?") // Tous les jours à 2h du matin
    public void nettoyerAnciennesDonnees() {
        // Logique de nettoyage
        journal.info("Nettoyage des données anciennes");
    }

    @Scheduled(fixedRate = 300000) // Toutes les 5 minutes
    public void verifierStatutSysteme() {
        // Vérification périodique
    }
}

Communication en temps réel avec WebSocket

WebSocket établit une connexion persistante pour une communication bidirectionnelle, idéale pour les notifications push (ex: état d'une commande).


@ServerEndpoint("/ws/statut-commande/{idClient}")
@Component
public class PointFinalStatutCommande {

    private static final Map<String, Session> sessionsParClient = new ConcurrentHashMap<>();

    @OnOpen
    public void ouvrirConnexion(Session session, @PathParam("idClient") String idClient) {
        sessionsParClient.put(idClient, session);
    }

    @OnMessage
    public void recevoirMessage(String message, @PathParam("idClient") String idClient) {
        // Traitement d'un message client si nécessaire
    }

    @OnClose
    public void fermerConnexion(@PathParam("idClient") String idClient) {
        sessionsParClient.remove(idClient);
    }

    public static void envoyerMiseAJour(String idClient, String statutJson) {
        Session session = sessionsParClient.get(idClient);
        if (session != null && session.isOpen()) {
            try {
                session.getBasicRemote().sendText(statutJson);
            } catch (IOException e) {
                // Gestion d'erreur
            }
        }
    }
}

Visualisation de données avec ECharts

Apache ECharts est une bibliothèque JavaScript pour créer des graphiques interactifs (barres, lignes, camemberts). Les données sont généralement préparées côté serveur au format JSON.

Traitement de fichiers Excel avec POI

Apache POI permet de lire et écrire des fichiers Microsoft Office.


public class GestionExcel {

    public void exporterRapport(List<DonneeCommande> donnees, OutputStream fluxSortie) {
        try (XSSFWorkbook classeur = new XSSFWorkbook()) {
            XSSFSheet feuille = classeur.createSheet("Commandes");
            // Créer l'en-tête
            XSSFRow ligneEntete = feuille.createRow(0);
            ligneEntete.createCell(0).setCellValue("ID Commande");
            ligneEntete.createCell(1).setCellValue("Client");
            ligneEntete.createCell(2).setCellValue("Montant");

            int numeroLigne = 1;
            for (DonneeCommande donnee : donnees) {
                XSSFRow ligne = feuille.createRow(numeroLigne++);
                ligne.createCell(0).setCellValue(donnee.getIdCommande());
                ligne.createCell(1).setCellValue(donnee.getNomClient());
                ligne.createCell(2).setCellValue(donnee.getMontantTotal());
            }
            classeur.write(fluxSortie);
        } catch (IOException e) {
            throw new RuntimeException("Erreur lors de la génération du fichier Excel", e);
        }
    }
}

Intégration avec des services externes (ex: WeChat Login)

L'authentification via WeChat suit un flux standardisé utilisant des codes d'autorisation temporaires et des appels HTTP à l'API WeChat via HttpClient ou RestTemplate.

Étiquettes: nginx Spring Boot Redis WebSocket JWT

Publié le 21 juillet à 16h31