Développement webConseils

Architecture logicielle : paradigmes et bonnes pratiques

Aurélien Debord
Architecture logicielle : paradigmes et bonnes pratiques

L'architecture logicielle est le squelette de toute application. C'est elle qui détermine comment les composants interagissent, comment le code évolue avec le temps, et combien il coûtera à maintenir dans deux ans. Mal choisie, elle devient un frein.

Qu'est-ce que l'architecture logicielle ?

L'architecture logicielle décrit la façon dont un système est organisé : comment le code est découpé, comment les composants communiquent, quels principes guident les choix de conception, et surtout quels compromis on accepte au passage.

Ça ne se limite pas au fonctionnel (ce que fait l'application). Ça couvre aussi tout ce qui se mesure une fois l'application en production : performance, sécurité, évolutivité et maintenabilité. Un logiciel peut très bien faire ce qu'il est censé faire et rester impossible à maintenir six mois plus tard.

L'architecture monolithique

En quoi ça consiste ?

L'architecture monolithique est l'approche traditionnelle du développement logiciel. L'ensemble de l'application est développé, déployé et maintenu comme une seule unité. Tous les composants (interface utilisateur, logique métier, accès aux données) sont regroupés dans un même projet.

# Exemple d'une application monolithique Flask
from flask import Flask, render_template
from models import User, Order
from services import PaymentService, EmailService
 
app = Flask(__name__)
 
@app.route('/order', methods=['POST'])
def create_order():
    # Tout est dans le même processus
    user = User.get_current()
    order = Order.create(user)
    PaymentService.process(order)
    EmailService.send_confirmation(user, order)
    return render_template('order_success.html')

Avantages

Un seul projet à gérer, un seul déploiement, et tout le code au même endroit quand il faut debugger. Pas de latence réseau entre les composants non plus, et moins d'infrastructure à mettre en place, donc un coût initial réduit.

Limitations

Impossible de scaler uniquement une partie de l'application. Un changement mineur oblige à redéployer l'ensemble, et comme tout est fortement couplé, une modification peut avoir des effets de bord imprévus. Avec le temps, le code devient difficile à maintenir.

Quand c'est le bon choix

Pour une startup en phase de validation ou pour un MVP qui doit sortir vite, le monolithe est presque toujours la bonne réponse. Tant qu'on cherche encore le bon produit, on n'a pas besoin d'une infrastructure distribuée ; on a besoin de boucles de feedback courtes. Les applications internes d'entreprise avec une petite équipe de dev sont dans la même logique.

L'architecture microservices

En quoi ça consiste ?

L'architecture microservices décompose l'application en services indépendants, chacun responsable d'une fonctionnalité métier spécifique. Ces services communiquent entre eux via des API, généralement REST ou via des messages asynchrones.

# Exemple de composition Docker pour une architecture microservices
version: '3.8'
services:
  user-service:
    image: myapp/user-service
    ports:
      - "8001:8000"
 
  order-service:
    image: myapp/order-service
    ports:
      - "8002:8000"
    depends_on:
      - user-service
 
  payment-service:
    image: myapp/payment-service
    ports:
      - "8003:8000"
 
  notification-service:
    image: myapp/notification-service
    ports:
      - "8004:8000"

Avantages

  • Chaque service peut être scalé indépendamment
  • La défaillance de l'un n'impacte pas les autres
  • Chaque service peut utiliser sa propre stack
  • Les déploiements sont indépendants, donc les mises à jour sont plus fréquentes et moins risquées
  • Une équipe peut gérer son propre service

Limitations

Le monitoring, le logging et le debugging deviennent distribués, donc plus lourds. Les appels entre services ajoutent de la latence, les transactions distribuées rendent la cohérence des données complexe à garantir, et il faut plus de ressources d'infrastructure.

Quand c'est le bon choix

Les microservices prennent tout leur sens sur des plateformes à fort trafic (e-commerce, SaaS, réseaux sociaux) et dans les organisations où plusieurs équipes travaillent en parallèle sur des pans différents du produit. Netflix, Amazon ou Spotify les utilisent pour cette raison. En revanche, partir en microservices à trois développeurs et sans trafic, c'est presque toujours une erreur : on hérite de la complexité sans les bénéfices.

L'architecture hexagonale (Ports & Adapters)

En quoi ça consiste ?

L'architecture hexagonale, aussi appelée "Ports et Adaptateurs", place le domaine métier au centre de l'application. Le code métier est isolé des préoccupations techniques (base de données, frameworks, interfaces) grâce à des interfaces (ports) et des implémentations (adaptateurs).

# Architecture hexagonale en Python
 
# Le port (interface) - côté domaine
from abc import ABC, abstractmethod
 
class OrderRepository(ABC):
    @abstractmethod
    def save(self, order: Order) -> None:
        pass
 
    @abstractmethod
    def find_by_id(self, order_id: str) -> Order:
        pass
 
# Le domaine métier - au centre de l'hexagone
class OrderService:
    def __init__(self, repository: OrderRepository):
        self.repository = repository
 
    def create_order(self, items: list) -> Order:
        order = Order(items)
        order.validate()  # Logique métier pure
        self.repository.save(order)
        return order
 
# L'adaptateur - côté infrastructure
class PostgresOrderRepository(OrderRepository):
    def save(self, order: Order) -> None:
        # Implémentation spécifique PostgreSQL
        self.db.execute("INSERT INTO orders ...")
 
    def find_by_id(self, order_id: str) -> Order:
        result = self.db.execute("SELECT * FROM orders WHERE id = %s", order_id)
        return Order.from_dict(result)

Avantages

Le domaine métier peut être testé sans dépendances externes, et vous changez de base de données sans toucher au métier. La séparation entre logique métier et technique est nette, et ajouter un nouvel adaptateur ne demande presque rien.

Limitations

  • Il faut une bonne compréhension des principes SOLID pour s'y mettre
  • Plus de code à écrire, entre les interfaces et les implémentations
  • Vite excessif sur des projets simples

Quand c'est le bon choix

Dès que la logique métier est centrale et amenée à évoluer, l'hexagonale vaut son investissement. C'est typiquement le cas des logiciels sur-mesure d'entreprise avec des règles métier qui changent en fonction du contrat, du client ou de la réglementation. Si vous pratiquez le Domain-Driven Design ou que les tests unitaires sont une priorité affichée, vous y viendrez naturellement.

L'architecture en couches (Layered)

En quoi ça consiste ?

L'architecture en couches organise l'application en strates horizontales, chacune ayant une responsabilité précise. Typiquement, on retrouve trois ou quatre couches : présentation (interface utilisateur), logique métier (services), accès aux données (repositories) et parfois une couche d'infrastructure. Chaque couche ne communique qu'avec les couches adjacentes, créant ainsi une hiérarchie claire.

  1. Couche présentation : controllers, views
  2. Couche métier : services, use cases
  3. Couche données : repositories, DAOs
  4. Couche infrastructure : base de données, APIs externes

Avantages

  • Chaque développeur sait où placer son code
  • Les couches peuvent être réutilisées dans d'autres projets
  • Les modifications restent localisées dans une couche spécifique
  • Les nouveaux développeurs comprennent vite la structure

Limitations

Les changements traversent souvent toutes les couches, et une modification métier peut nécessiter des ajustements à tous les niveaux. Les appels successifs entre couches peuvent aussi impacter les performances.

Quand c'est le bon choix

Pour une application CRUD classique ou un projet où l'équipe est structurée par compétence technique (frontend, backend, DBA), la séparation en couches colle à la réalité de l'organisation. C'est aussi l'architecture la plus enseignée, donc la plus naturelle pour des équipes juniors qui démarrent.

L'architecture Event-Driven

En quoi ça consiste ?

L'architecture orientée événements (Event-Driven Architecture ou EDA) repose sur la production, la détection et la réaction à des événements. Au lieu d'appels synchrones entre composants, les services émettent des événements que d'autres services peuvent consommer de manière asynchrone.

# Exemple simplifié d'architecture event-driven
 
# Producteur d'événements
class OrderService:
    def __init__(self, event_bus):
        self.event_bus = event_bus
 
    def create_order(self, order_data):
        order = Order.create(order_data)
        # Émet un événement au lieu d'appeler directement les autres services
        self.event_bus.publish("order.created", {
            "order_id": order.id,
            "user_id": order.user_id,
            "total": order.total
        })
        return order
 
# Consommateur d'événements
class NotificationService:
    @event_handler("order.created")
    def on_order_created(self, event):
        user = User.find(event["user_id"])
        self.send_email(user.email, "Votre commande a été créée !")
 
class InventoryService:
    @event_handler("order.created")
    def on_order_created(self, event):
        self.reserve_stock(event["order_id"])

Avantages

Les services ne se connaissent pas directement, ce qui les découple fortement. Les consommateurs peuvent être scalés indépendamment, les événements peuvent être rejoués en cas de défaillance, et vous ajoutez de nouveaux consommateurs sans modifier le producteur.

Limitations

  • Tracer un flux à travers plusieurs événements est difficile
  • Pas de garantie de cohérence immédiate des données (cohérence éventuelle)
  • Il faut un message broker (RabbitMQ, Kafka, etc.)

Quand c'est le bon choix

Les systèmes temps réel (trading, IoT, notifications) sont des candidats naturels. Même logique pour les workflows où une action en déclenche d'autres en cascade : commande validée → stock réservé → facture générée → email envoyé. Tant que les étapes sont indépendantes, mettre un bus d'événements au milieu simplifie beaucoup de choses.

Comment choisir la bonne architecture ?

Le choix dépend de votre contexte :

CritèreMonolithiqueMicroservicesHexagonaleEvent-Driven
Taille de l'équipePetite (< 10)Grande (10+)MoyenneMoyenne à grande
Complexité métierFaibleVariableÉlevéeVariable
Budget initialLimitéImportantModéréImportant
Besoin de scalabilitéFaibleÉlevéModéréÉlevé
Time-to-marketCourtLongMoyenLong
Temps réelNonPossibleNonOui

Quelques principes qui valent pour tous les projets :

  1. Commencez simple. Un monolithe bien structuré peut évoluer vers des microservices si le besoin arrive. L'inverse, beaucoup moins. Tant que la complexité n'est pas justifiée par un problème réel, elle est juste un coût.

  2. Pensez à votre équipe. Une architecture sophistiquée plaquée sur une équipe qui ne la maîtrise pas va générer plus de bugs que de bénéfices. L'architecture doit correspondre aux compétences disponibles, pas à l'inverse.

  3. Combinez les approches. Rien n'interdit d'avoir une architecture hexagonale à l'intérieur d'un monolithe, ou de mélanger event-driven et microservices. Les catégories de ce type d'article sont plus propres que la réalité des projets.

  4. Prévoyez l'évolution. Si vous partez en monolithique, identifiez dès le départ les frontières fonctionnelles qui pourraient un jour devenir des services séparés. Ça ne coûte presque rien à l'écriture et ça facilite beaucoup la suite.

Pour aller plus loin

Dans tous les cas, le bon réflexe est de garder une trace écrite des choix d'architecture et de leurs raisons. Ça évite d'avoir à deviner, deux ans plus tard, pourquoi le service des commandes communique en asynchrone avec le service des paiements.

Envie de vous lancer ?

Du cadrage au prototype, jusqu'à l'intégration IA.

Nous accompagnons vos projets de logiciels métier de bout en bout.

Développer mon projet