Chapitre 1 — Qu’est-ce que l’architecture logicielle#

L’architecture logicielle est l’une des disciplines les plus mal comprises du génie logiciel. Tout le monde en parle, peu de personnes s’accordent sur sa définition, et encore moins sur qui est censé la pratiquer. Ce chapitre pose les bases conceptuelles sur lesquelles repose le reste du livre.

Définition — décisions structurantes vs décisions tactiques#

Une définition courante, attribuée à Ralph Johnson, est celle-ci : « l’architecture, c’est ce qui est difficile à changer plus tard ». Cette formulation, lapidaire, capture quelque chose d’essentiel. Elle distingue deux catégories de décisions dans un projet logiciel.

Les décisions structurantes sont celles dont le coût de révision est élevé. Choisir une base de données relationnelle plutôt que documentaire, adopter une architecture orientée événements, organiser le code en modules indépendants déployables séparément — ce sont des décisions qui conditionnent des dizaines d’autres décisions. Les modifier exige une refonte partielle ou totale du système.

Les décisions tactiques sont les choix d’implémentation du quotidien : le nom d’une variable, l’algorithme de tri utilisé dans une fonction, le format d’une réponse JSON interne. Ces décisions peuvent être révisées localement, sans impact systémique.

La frontière entre les deux n’est pas fixe. Elle dépend du contexte, de la taille du système, et de l’étape du projet. Un choix de framework web est structurant dans un système de 500 000 lignes de code ; il est presque tactique dans un prototype de 3 000 lignes.

Martin Fowler propose une définition complémentaire : l’architecture est l’ensemble des décisions partagées par les membres expérimentés de l’équipe, celles qu’ils auraient voulu connaître au début du projet. Cette définition met en avant la dimension sociale : l’architecture n’est pas un artefact produit par une seule personne, c’est une compréhension partagée.

Distinction clé

Une décision est architecturale si elle affecte plusieurs composants du système, si elle est difficile à inverser, ou si elle conditionne les attributs de qualité du système (performance, scalabilité, sécurité). Les décisions purement locales à un module sont des décisions de conception, pas d’architecture.

L’IEEE 42010 définit formellement l’architecture comme « l’organisation fondamentale d’un système, incarnée dans ses composants, leurs relations entre eux et avec l’environnement, et les principes guidant sa conception et son évolution ». Cette définition institutionnelle est utile pour les contextes formels, mais la formulation de Johnson reste plus opérationnelle au quotidien.

Le rôle de l’architecte — facilitateur vs décideur#

Le titre « architecte logiciel » recouvre des réalités très différentes selon les organisations. Dans certaines entreprises, l’architecte est une figure centralisatrice qui valide toutes les décisions techniques. Dans d’d’autres, le rôle est distribué au sein des équipes. Les deux extrêmes ont leurs problèmes.

L’architecte-oracle — celui qui décide seul — crée un goulot d’étranglement. Les équipes attendent ses décisions, perdent en autonomie, et développent une dépendance qui fragilise le système si la personne part. L’architecte devient un point de défaillance unique organisationnel.

L’absence totale d’architecture formelle conduit à ce que l’on appelle l’architecture émergente non contrôlée : chaque équipe fait ses propres choix, les systèmes sont incohérents, les interfaces entre composants sont négociées au cas par cas, et la maintenance devient un cauchemar.

La position la plus efficace est celle du facilitateur technique. L’architecte :

  • Définit les contraintes et les principes directeurs, pas l’implémentation détaillée

  • Rend les bonnes décisions faciles à prendre (en fournissant des templates, des ADR, des exemples)

  • Identifie les risques architecturaux avant qu’ils ne deviennent des problèmes

  • Communique les décisions et leur justification à toutes les parties prenantes

  • Fait des trade-offs explicites plutôt que de les laisser implicites

La compétence de communication est souvent sous-estimée. Un architecte passe plus de temps à expliquer, convaincre et documenter qu’à écrire du code. Il doit traduire des contraintes techniques en langage métier pour les décideurs, et des exigences métier en contraintes techniques pour les développeurs.

Trade-offs, pas de solutions parfaites

L’architecte ne cherche pas la solution optimale — elle n’existe pas. Il cherche le meilleur compromis étant donné les contraintes actuelles : budget, délais, compétences de l’équipe, besoins métier. Un bon architecte documente pourquoi une option a été rejetée autant que pourquoi une option a été choisie.

Architecture Decision Records (ADR)#

Les Architecture Decision Records sont des documents courts qui capturent une décision architecturale significative, son contexte, et ses conséquences. Ils répondent à une question simple : dans six mois, quand quelqu’un se demandera pourquoi le système est conçu de cette façon, où trouvera-t-il la réponse ?

Le format standard d’un ADR, proposé par Michael Nygard, comprend :

  • Titre : court, au format « ADR-NNN : Titre de la décision »

  • Statut : proposed / accepted / deprecated / superseded

  • Contexte : la situation qui a rendu cette décision nécessaire, les forces en présence

  • Décision : ce qui a été décidé, formulé à la voix active

  • Conséquences : les effets positifs et négatifs, les engagements pris

Le format MADR (Markdown ADR) enrichit ce schéma avec des options considérées et les critères de décision. Il est particulièrement adapté aux équipes qui veulent tracer explicitement le raisonnement.

Exemple concret — ADR pour le choix d’une base de données dans un système e-commerce :

# ADR-007 : Utilisation de PostgreSQL pour le catalogue produits

## Statut
Accepted

## Contexte
Le catalogue produits contient des données structurées (prix, stock, dimensions)
mais aussi des attributs variables selon la catégorie (vêtements : taille/couleur,
électronique : fréquence/connectique). Les requêtes combinant filtres structurés
et attributs dynamiques sont fréquentes.

## Décision
Utiliser PostgreSQL avec le type JSONB pour les attributs variables.
Ne pas utiliser MongoDB : l'équipe n'a pas de compétences opérationnelles,
et les transactions ACID sont requises pour la mise à jour de stock.

## Conséquences
+ Transactions ACID sur l'ensemble des données
+ Requêtes hybrides SQL + JSONB performantes avec index GIN
- Nécessite une formation à l'indexation JSONB
- Migration future vers un modèle purement relationnel complexe si JSONB devient dominant

Les ADR sont versionnés avec le code. Quand une décision est révisée, l’ADR original est marqué « superseded by ADR-NNN », et un nouvel ADR documente la nouvelle décision. L’historique des décisions est ainsi préservé.

Outils ADR

L’outil CLI adr-tools (GitHub : npryce/adr-tools) automatise la création et la numérotation des ADR. Pour les projets utilisant Architecture as Code, Structurizr supporte les ADR nativement. Dans les équipes sans outillage dédié, un dossier docs/decisions/ avec des fichiers Markdown numérotés suffit amplement.

Les vues architecturales — le modèle 4+1#

Un système complexe ne peut pas être représenté par un seul diagramme. Philippe Kruchten a proposé en 1995 le modèle 4+1 vues, qui organise la documentation architecturale autour de cinq perspectives complémentaires.

Vue logique : décrit les fonctionnalités offertes aux utilisateurs finaux. Elle est exprimée en termes de classes, packages, modules, leurs responsabilités et leurs relations. C’est la vue la plus proche du domaine métier.

Vue processus : décrit les processus concurrents, leur communication, et la distribution des responsabilités entre threads ou processus. Elle répond aux questions de performance, disponibilité et tolérance aux pannes.

Vue développement (ou vue d’implémentation) : décrit l’organisation du code source — modules, bibliothèques, composants buildables. Elle concerne directement les développeurs et leur workflow.

Vue physique (ou vue de déploiement) : décrit le mapping des composants logiciels sur l’infrastructure physique ou virtuelle — serveurs, conteneurs, réseaux. Elle intéresse les équipes d’exploitation.

Vue des scénarios (+1) : quelques cas d’utilisation significatifs qui traversent les quatre vues précédentes. Ces scénarios servent de liant — ils permettent de vérifier que les quatre vues sont cohérentes entre elles.

Quand utiliser 4+1 ?

Le modèle 4+1 est adapté aux systèmes de grande taille avec des parties prenantes diverses. Pour les projets de taille intermédiaire, le modèle C4 (section suivante) offre une approche plus légère et plus accessible aux équipes modernes.

Le modèle C4 — niveaux de zoom#

Le modèle C4, développé par Simon Brown, propose une approche hiérarchique de la documentation architecturale : Context, Container, Component, Code. Chaque niveau est un zoom sur le précédent.

Niveau 1 — Contexte (Context) : Vue grand angle. Le système est représenté comme une boîte noire, entouré de ses utilisateurs (personnes) et des systèmes externes avec lesquels il interagit. Cette vue est compréhensible par des non-techniciens.

Niveau 2 — Conteneurs (Container) : Les conteneurs sont les blocs déployables indépendamment : applications web, API, bases de données, files de messages, services mobiles. Ce niveau répond à la question « comment les responsabilités techniques sont-elles distribuées ? ».

Niveau 3 — Composants (Component) : Zoom sur un conteneur spécifique, montrant ses composants internes — contrôleurs, services, repositories, et leurs dépendances. Ce niveau intéresse principalement les développeurs travaillant sur ce conteneur.

Niveau 4 — Code : Diagrammes de classes UML pour les composants les plus critiques. Ce niveau est rarement documenté de façon statique car les IDE le génèrent à la demande.

La force du C4 est sa progressivité. Une équipe peut commencer avec un diagramme de contexte dessiné en dix minutes sur un tableau blanc, et l’affiner progressivement sans jamais produire de documentation exhaustive.

Notation C4

La notation C4 est intentionnellement simple : rectangles pour les éléments, flèches pour les relations, avec des étiquettes décrivant la nature de la relation. La couleur est optionnelle mais utile pour distinguer les éléments internes des éléments externes. Les outils officiels incluent Structurizr (Simon Brown), PlantUML avec l’extension C4, et Mermaid pour les contextes légers.

Attributs de qualité — ISO 25010#

Les attributs de qualité (ou exigences non fonctionnelles) définissent comment le système doit se comporter, par opposition aux exigences fonctionnelles qui définissent ce que le système doit faire.

La norme ISO 25010 organise les attributs de qualité en huit caractéristiques principales :

Caractéristique

Description

Adéquation fonctionnelle

Le système fait ce qu’il est censé faire

Performance

Temps de réponse, débit, utilisation des ressources

Compatibilité

Coexistence et interopérabilité avec d’autres systèmes

Utilisabilité

Facilité d’apprentissage et d’utilisation

Fiabilité

Disponibilité, tolérance aux pannes, récupérabilité

Sécurité

Confidentialité, intégrité, non-répudiation

Maintenabilité

Analysabilité, modifiabilité, testabilité

Portabilité

Adaptabilité, installabilité, remplaçabilité

La distinction entre exigences fonctionnelles et non-fonctionnelles est importante mais souvent mal comprise. « L’utilisateur peut passer une commande » est une exigence fonctionnelle. « La commande doit être traitée en moins de 200ms pour 95% des requêtes » est une exigence non fonctionnelle de performance. « Le système doit rester disponible en cas de panne d’un datacenter » est une exigence de fiabilité.

Les attributs de qualité sont en tension permanente. Augmenter la sécurité (chiffrement de bout en bout) dégrade la performance. Augmenter la disponibilité (réplication multi-région) augmente les coûts et peut compromettre la cohérence des données. Augmenter la maintenabilité (abstractions supplémentaires) peut augmenter la complexité.

Scénarios de qualité

Les attributs de qualité ne doivent pas rester vagues. « Le système doit être performant » est inutilisable. « Sous une charge de 1000 requêtes par seconde, 95% des requêtes de consultation de produit doivent être traitées en moins de 150ms » est un scénario de qualité actionnable. La méthode ATAM (Architecture Tradeoff Analysis Method) formalise la collecte et l’analyse de ces scénarios.

Pourquoi l’architecture échoue#

Comprendre les échecs architecturaux est aussi important que comprendre les bonnes pratiques. Les pathologies récurrentes sont bien documentées.

L’over-engineering consiste à concevoir pour des problèmes que le système n’a pas et n’aura peut-être jamais. Un système de blog conçu avec une architecture microservices, un bus d’événements et trois niveaux de cache est un exemple classique. Le coût opérationnel et cognitif dépasse largement la valeur ajoutée. YAGNI — You Ain’t Gonna Need It — reste une heuristique valide.

L’under-engineering est l’inverse : ignorer les attributs de qualité jusqu’à ce qu’ils deviennent des crises. Un système e-commerce conçu sans réflexion sur la scalabilité qui s’effondre au premier pic de trafic illustre ce problème. La dette technique accumulée par manque de réflexion architecturale initiale est souvent plus coûteuse à rembourser que l’investissement initial évité.

Le Big Design Up Front (BDUF) est l’approche consistant à concevoir exhaustivement l’architecture avant d’écrire une ligne de code. Elle échoue car les exigences évoluent, les technologies évoluent, et les décisions prises sans retour d’expérience sont souvent incorrectes. Les méthodes agiles ont réagi à ce problème, parfois en allant trop loin dans l’autre sens.

La loi de Conway est peut-être le facteur d’échec le plus sous-estimé. Formulée par Melvin Conway en 1967 : « Les organisations qui conçoivent des systèmes sont contraintes de produire des designs qui sont des copies de leur structure de communication. » Une entreprise avec quatre équipes séparées produira une architecture à quatre composants faiblement couplés — indépendamment de ce que dicte la conception optimale.

L’inverse Conway maneuver consiste à inverser la relation : concevoir d’abord l’architecture souhaitée, puis organiser les équipes de façon à ce que leur structure de communication produise naturellement cette architecture. Les Team Topologies (Matthew Skelton et Manuel Pais) formalisent cette approche.

Complexité et loi de Conway

La loi de Conway n’est pas une fatalité, mais elle est souvent ignorée. Quand une équipe se plaint que l’intégration entre deux services est « toujours compliquée », vérifiez d’abord si deux équipes différentes possèdent ces services et si elles se parlent rarement. La solution n’est pas toujours technique.

Visualisations#

Diagramme C4 — Niveau Contexte#

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

fig, ax = plt.subplots(figsize=(12, 8))
ax.set_xlim(0, 12)
ax.set_ylim(0, 8)
ax.axis('off')
ax.set_facecolor('#f8f9fa')
fig.patch.set_facecolor('#f8f9fa')

def draw_person(ax, x, y, label, sublabel="", color="#1168BD"):
    circle = plt.Circle((x, y + 0.5), 0.35, color=color, zorder=3)
    ax.add_patch(circle)
    ax.plot([x, x], [y + 0.15, y - 0.5], color=color, linewidth=2, zorder=3)
    ax.plot([x - 0.4, x + 0.4], [y - 0.1, y - 0.1], color=color, linewidth=2, zorder=3)
    ax.plot([x, x - 0.3], [y - 0.5, y - 0.9], color=color, linewidth=2, zorder=3)
    ax.plot([x, x + 0.3], [y - 0.5, y - 0.9], color=color, linewidth=2, zorder=3)
    ax.text(x, y - 1.1, label, ha='center', va='top', fontsize=9, fontweight='bold', color='#333333')
    if sublabel:
        ax.text(x, y - 1.45, sublabel, ha='center', va='top', fontsize=7.5, color='#666666', style='italic')

def draw_system_box(ax, x, y, w, h, label, sublabel="", color="#1168BD", text_color="white", style="round,pad=0.1"):
    bbox = mpatches.FancyBboxPatch((x - w/2, y - h/2), w, h,
                                    boxstyle=style,
                                    facecolor=color, edgecolor='white',
                                    linewidth=1.5, zorder=2)
    ax.add_patch(bbox)
    ax.text(x, y + 0.1, label, ha='center', va='center',
            fontsize=9, fontweight='bold', color=text_color, zorder=3)
    if sublabel:
        ax.text(x, y - 0.35, sublabel, ha='center', va='center',
                fontsize=7.5, color=text_color, alpha=0.85, style='italic', zorder=3)

def draw_arrow(ax, x1, y1, x2, y2, label="", color="#555555"):
    ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
                arrowprops=dict(arrowstyle="-|>", color=color, lw=1.5))
    mx, my = (x1 + x2) / 2, (y1 + y2) / 2
    if label:
        ax.text(mx, my + 0.15, label, ha='center', va='bottom',
                fontsize=7.5, color=color, style='italic')

# Système central
draw_system_box(ax, 6, 4, 2.8, 1.4, "Boutique en ligne",
                "[Système]\nPlateforme e-commerce", color="#1168BD")

# Utilisateurs
draw_person(ax, 1.2, 5.5, "Client", "[Personne]\nAchète des produits")
draw_person(ax, 1.2, 2.5, "Administrateur", "[Personne]\nGère le catalogue")

# Systèmes externes
draw_system_box(ax, 10.2, 5.8, 2.2, 1.0, "Stripe",
                "[Externe]\nPaiement en ligne", color="#6B7280")
draw_system_box(ax, 10.2, 4.0, 2.2, 1.0, "Mailchimp",
                "[Externe]\nEmails transactionnels", color="#6B7280")
draw_system_box(ax, 10.2, 2.2, 2.2, 1.0, "Entrepôt WMS",
                "[Externe]\nGestion des stocks", color="#6B7280")

# Flèches
draw_arrow(ax, 2.0, 5.8, 4.6, 4.4, "Navigue, commande", "#1168BD")
draw_arrow(ax, 2.0, 2.8, 4.6, 3.7, "Gère produits/commandes", "#1168BD")
draw_arrow(ax, 7.4, 4.5, 9.1, 5.6, "Traite paiements", "#6B7280")
draw_arrow(ax, 7.4, 4.2, 9.1, 4.0, "Envoie emails", "#6B7280")
draw_arrow(ax, 7.4, 3.7, 9.1, 2.5, "Sync stock", "#6B7280")

ax.set_title("Diagramme C4 — Niveau Contexte : Boutique en ligne",
             fontsize=13, fontweight='bold', pad=15, color='#222222')

plt.savefig("c4_context.png", dpi=120, bbox_inches='tight')
plt.show()
_images/f297cde275c882c0d382c849476f026a836c90209b7fe2ff5e2005ee272b49d7.png

Visualisation d’un ADR en tableau#

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

fig, ax = plt.subplots(figsize=(13, 6))
ax.axis('off')

titre = "ADR-007 : Utilisation de PostgreSQL pour le catalogue produits"
sections = [
    ("Statut", "accepted", "#2d6a4f", "white"),
    ("Contexte",
     "Données structurées (prix, stock) + attributs variables\n"
     "selon catégorie (taille/couleur pour vêtements, GHz pour\n"
     "électronique). Requêtes hybrides fréquentes.",
     "#f8f9fa", "#222222"),
    ("Décision",
     "PostgreSQL avec JSONB pour attributs variables.\n"
     "MongoDB écarté : pas de compétences opé. + ACID requis\n"
     "pour la mise à jour de stock concurrent.",
     "#e8f4f8", "#222222"),
    ("Conséquences +",
     "✓  Transactions ACID sur toutes les données\n"
     "✓  Index GIN sur JSONB : requêtes hybrides <10ms\n"
     "✓  Équipe déjà formée SQL",
     "#d4edda", "#155724"),
    ("Conséquences −",
     "✗  Formation indexation JSONB nécessaire (~2j)\n"
     "✗  Migration vers modèle pur relationnel complexe\n"
     "   si JSONB > 60% des données",
     "#f8d7da", "#721c24"),
]

ax.text(0.5, 0.96, titre, ha='center', va='top', transform=ax.transAxes,
        fontsize=12, fontweight='bold', color='#1168BD')

y_positions = [0.80, 0.63, 0.43, 0.23, 0.04]
heights = [0.10, 0.14, 0.14, 0.14, 0.14]

for i, ((label, content, bg, fg), y_pos, h) in enumerate(zip(sections, y_positions, heights)):
    rect = mpatches.FancyBboxPatch((0.02, y_pos - h/2), 0.96, h,
                                    boxstyle="round,pad=0.01",
                                    facecolor=bg, edgecolor='#cccccc',
                                    linewidth=1, transform=ax.transAxes)
    ax.add_patch(rect)

    label_rect = mpatches.FancyBboxPatch((0.02, y_pos - h/2), 0.13, h,
                                          boxstyle="round,pad=0.01",
                                          facecolor='#1168BD' if label == "Statut" else '#dee2e6',
                                          edgecolor='none',
                                          transform=ax.transAxes)
    ax.add_patch(label_rect)

    label_color = 'white' if label == "Statut" else '#333333'
    ax.text(0.085, y_pos, label, ha='center', va='center',
            transform=ax.transAxes, fontsize=9, fontweight='bold', color=label_color)

    ax.text(0.17, y_pos, content, ha='left', va='center',
            transform=ax.transAxes, fontsize=8.5, color=fg,
            linespacing=1.5)

plt.savefig("adr_tableau.png", dpi=120, bbox_inches='tight')
plt.show()
_images/1ae6c80eb4c9f150a59891e64e0a6cf264ad986387569137e818170cfd829452.png

Graphe de Conway — organisation et architecture#

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import networkx as nx
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

fig, axes = plt.subplots(1, 2, figsize=(14, 6))

def draw_conway(ax, teams, components, comm_edges, dep_edges, title):
    G_teams = nx.Graph()
    G_teams.add_nodes_from(teams)
    G_teams.add_edges_from(comm_edges)

    G_comp = nx.DiGraph()
    G_comp.add_nodes_from(components)
    G_comp.add_edges_from(dep_edges)

    n_t = len(teams)
    n_c = len(components)
    pos_teams = {t: (i * 2.5, 3.0) for i, t in enumerate(teams)}
    pos_comp  = {c: (i * 2.5, 0.5) for i, c in enumerate(components)}

    # Couche équipes
    nx.draw_networkx_nodes(G_teams, pos_teams, ax=ax,
                           node_color='#1168BD', node_size=900, alpha=0.9)
    nx.draw_networkx_labels(G_teams, pos_teams, ax=ax,
                            font_size=8, font_color='white', font_weight='bold')
    nx.draw_networkx_edges(G_teams, pos_teams, ax=ax,
                           edge_color='#1168BD', width=2, alpha=0.6)

    # Couche composants
    nx.draw_networkx_nodes(G_comp, pos_comp, ax=ax,
                           node_color='#e07b39', node_size=900, alpha=0.9)
    nx.draw_networkx_labels(G_comp, pos_comp, ax=ax,
                            font_size=8, font_color='white', font_weight='bold')
    nx.draw_networkx_edges(G_comp, pos_comp, ax=ax,
                           edge_color='#e07b39', width=1.5, alpha=0.6,
                           arrows=True, arrowsize=15,
                           connectionstyle='arc3,rad=0.1')

    # Liens Conway (miroir organisation → architecture)
    for t, c in zip(teams, components):
        if t in pos_teams and c in pos_comp:
            x1, y1 = pos_teams[t]
            x2, y2 = pos_comp[c]
            ax.annotate("", xy=(x2, y2 + 0.25), xytext=(x1, y1 - 0.25),
                        arrowprops=dict(arrowstyle="-|>", color='#666666',
                                        lw=1, linestyle='dashed', alpha=0.5))

    ax.set_xlim(-1, (max(n_t, n_c) - 1) * 2.5 + 1)
    ax.set_ylim(-0.5, 4.5)
    ax.axis('off')
    ax.set_title(title, fontsize=11, fontweight='bold', pad=10)

    team_patch = mpatches.Patch(color='#1168BD', label='Équipes (communication)')
    comp_patch = mpatches.Patch(color='#e07b39', label='Composants (dépendances)')
    ax.legend(handles=[team_patch, comp_patch], loc='lower left', fontsize=8)

# Cas 1 : organisation silotée → couplage fort
draw_conway(
    axes[0],
    teams=["Frontend", "Backend", "Data"],
    components=["UI", "API", "DB"],
    comm_edges=[],
    dep_edges=[("UI", "API"), ("API", "DB"), ("UI", "DB")],
    title="Org. silotée → couplage fort"
)

# Cas 2 : organisation produit → découplage
draw_conway(
    axes[1],
    teams=["Équipe\nCatalogue", "Équipe\nCommandes", "Équipe\nLivraison"],
    components=["Service\nCatalogue", "Service\nCommandes", "Service\nLivraison"],
    comm_edges=[("Équipe\nCatalogue", "Équipe\nCommandes")],
    dep_edges=[("Service\nCommandes", "Service\nCatalogue"),
               ("Service\nLivraison", "Service\nCommandes")],
    title="Org. produit → découplage (Inverse Conway)"
)

fig.suptitle("Loi de Conway — l'organisation reflète l'architecture",
             fontsize=13, fontweight='bold', y=1.02)
plt.savefig("conway.png", dpi=120, bbox_inches='tight')
plt.show()
_images/5695780767e6d5dc5ed4d90fc38745b12e2a175921bbb4605191b732f77efff9.png

Radar des attributs de qualité ISO 25010#

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

categories = [
    'Performance', 'Fiabilité', 'Sécurité', 'Maintenabilité',
    'Utilisabilité', 'Portabilité', 'Compatibilité', 'Ad. fonctionnelle'
]
N = len(categories)
angles = np.linspace(0, 2 * np.pi, N, endpoint=False).tolist()
angles += angles[:1]

# Profils de deux systèmes fictifs
systemes = {
    "Banque en ligne": [8, 9, 10, 6, 7, 4, 7, 9],
    "Outil data interne": [7, 5, 6, 9, 6, 7, 8, 7],
}
couleurs = ['#1168BD', '#e07b39']

fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))

for (nom, valeurs), couleur in zip(systemes.items(), couleurs):
    valeurs_fermees = valeurs + valeurs[:1]
    ax.plot(angles, valeurs_fermees, 'o-', linewidth=2, color=couleur, label=nom)
    ax.fill(angles, valeurs_fermees, alpha=0.15, color=couleur)

ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories, size=10)
ax.set_ylim(0, 10)
ax.set_yticks([2, 4, 6, 8, 10])
ax.set_yticklabels(['2', '4', '6', '8', '10'], size=8, color='grey')
ax.grid(color='grey', linestyle='--', linewidth=0.5, alpha=0.6)

ax.set_title("Attributs de qualité ISO 25010\n(score /10 selon le contexte système)",
             size=12, fontweight='bold', pad=20)
ax.legend(loc='upper right', bbox_to_anchor=(1.35, 1.15), fontsize=10)

plt.savefig("radar_qualite.png", dpi=120, bbox_inches='tight')
plt.show()
_images/b2e49f6cf917e8eb299153976f37e678e416ebf8a0501848d3915f89cf427575.png

Résumé#

L’architecture logicielle est l’art de prendre des décisions structurantes en contexte d’incertitude. Ce chapitre a posé les fondements conceptuels :

Définition : L’architecture englobe les décisions difficiles à changer, celles qui conditionnent les attributs de qualité et les capacités d’évolution du système. La frontière avec la conception tactique est contextuelle, pas absolue.

Rôle de l’architecte : Entre l’oracle décideur et l’absence totale de gouvernance, la position efficace est celle du facilitateur — définir des contraintes, rendre les bonnes décisions faciles, documenter les trade-offs, communiquer vers toutes les parties prenantes.

ADR : Les Architecture Decision Records capturent le contexte et la justification des décisions architecturales. Versionnés avec le code, ils constituent la mémoire collective de l’architecture. Le format MADR enrichit le schéma de base en traçant explicitement les options considérées.

Vues architecturales : Le modèle 4+1 (Kruchten) et le modèle C4 (Brown) offrent deux approches complémentaires pour représenter un système depuis différentes perspectives. Le C4 est plus accessible aux équipes modernes grâce à sa progressivité.

Attributs de qualité : L’ISO 25010 structure huit caractéristiques de qualité. Les attributs de qualité sont en tension permanente — toute décision architecturale est un compromis. Les formaliser comme des scénarios mesurables les rend actionnables.

Pathologies : L’over-engineering, l’under-engineering, le BDUF, et la loi de Conway sont les causes récurrentes d’échec architectural. La loi de Conway est particulièrement insidieuse car elle est invisible : l’architecture reflète toujours la structure de communication de l’organisation qui la produit.

Points clés à retenir

  • Une décision est architecturale si elle est difficile à inverser ou si elle conditionne les attributs de qualité

  • L’architecte est un facilitateur, pas un oracle — il rend les bonnes décisions faciles

  • Les ADR documentent le « pourquoi », pas seulement le « quoi »

  • Tout attribut de qualité est en tension avec d’autres — il n’existe pas de solution optimale, seulement des compromis éclairés

  • L’organisation produit l’architecture qu’elle mérite (Conway) — concevoir l’organisation et l’architecture ensemble