267 lines
14 KiB
TeX
267 lines
14 KiB
TeX
\chapter{Elements d'architecture}
|
||
|
||
\begin{quote}
|
||
If you think good architecture is expensive, try bad architecture
|
||
|
||
--- Brian Foote and Joseph Yoder
|
||
\end{quote}
|
||
|
||
Au delà des principes dont il est question plus haut, c'est dans les
|
||
ressources proposées et les cas démontrés que l'on comprend leur
|
||
intérêt: plus que de la définition d'une architecture adéquate, c'est
|
||
surtout dans la facilité de maintenance d'une application que ces
|
||
principes s'identifient.
|
||
|
||
Une bonne architecture va rendre le système facile à lire, facile à
|
||
développer, facile à maintenir et facile à déployer. L'objectif ultime
|
||
étant de minimiser le coût de maintenance et de maximiser la
|
||
productivité des développeurs. Un des autres objectifs d'une bonne
|
||
architecture consiste également à se garder le plus d'options possibles,
|
||
et à se concentrer sur les détails (le type de base de données, la
|
||
conception concrète, le plus tard possible, tout en
|
||
conservant la politique principale en ligne de mire. Cela permet de
|
||
délayer les choix techniques à «~plus tard~», ce qui permet également de
|
||
concrétiser ces choix en ayant le plus d'informations possibles
|
||
\cite[pp.137-141]{clean_architecture}
|
||
|
||
Derrière une bonne architecture, il y a aussi un investissement quant
|
||
aux ressources qui seront nécessaires à faire évoluer l'application: ne
|
||
pas investir dès qu'on le peut va juste lentement remplir la case de la
|
||
dette technique.
|
||
|
||
Une architecture ouverte et pouvant être étendue n'a d'intérêt que si le
|
||
développement est suivi et que les gestionnaires (et architectes)
|
||
s'engagent à économiser du temps et de la qualité lorsque des
|
||
changements seront demandés pour l'évolution du projet.
|
||
|
||
\section{Politiques et règles métiers}
|
||
|
||
\section{Considérations sur les frameworks}
|
||
|
||
\begin{quote}
|
||
Frameworks are tools to be used, not architectures to be conformed to.
|
||
Your architecture should tell readers about the system, not about the
|
||
frameworks you used in your system. If you are building a health care
|
||
system, then when new programmers look at the source repository, their
|
||
first impression should be, «~oh, this is a health care system~». Those
|
||
new programmers should be able to learn all the use cases of the system,
|
||
yet still not know how the system is delivered.
|
||
|
||
--- Robert C. Martin Clean Architecture
|
||
\end{quote}
|
||
|
||
Le point soulevé ci-dessous est qu'un framework n'est qu'un outil, et
|
||
pas une obligation de structuration. L'idée est que le framework doit se
|
||
conformer à la définition de l'application, et non l'inverse. Dans le
|
||
cadre de l'utilisation de Django, c'est un point critique à prendre en
|
||
considération: une fois que vous aurez fait ce choix, vous aurez
|
||
extrêmement difficile à faire machine arrière:
|
||
|
||
\begin{itemize}
|
||
\item
|
||
Votre modèle métier sera largement couplé avec le type de base de
|
||
données (relationnelle, indépendamment
|
||
\item
|
||
Votre couche de présentation sera surtout disponible au travers d'un
|
||
navigateur
|
||
\item
|
||
Les droits d'accès et permissions seront en grosse partie gérés par le
|
||
frameworks
|
||
\item
|
||
La sécurité dépendra de votre habilité à suivre les versions
|
||
\item
|
||
Et les fonctionnalités complémentaires (que vous n'aurez pas voulu/eu
|
||
le temps de développer) dépendront de la bonne volonté de la
|
||
communauté
|
||
\end{itemize}
|
||
|
||
Le point à comprendre ici n'est pas que "Django, c'est mal", mais qu'une
|
||
fois que vous aurez défini la politique, les règles métiers, les données
|
||
critiques et entités, et que vous aurez fait le choix de développer en
|
||
âme et conscience votre nouvelle création en utilisant Django, vous
|
||
serez bon gré mal gré, contraint de continuer avec. Cette décision ne
|
||
sera pas irrévocable, mais difficile à contourner.
|
||
|
||
\begin{quote}
|
||
At some point in their history most DevOps organizations were hobbled by
|
||
tightly-coupled, monolithic architectures that while extremely
|
||
successfull at helping them achieve product/market fit - put them at
|
||
risk of organizational failure once they had to operate at scale (e.g.
|
||
eBay's monolithic C++ application in 2001, Amazon's monolithic OBIDOS
|
||
application in 2001, Twitter's monolithic Rails front-end in 2009, and
|
||
LinkedIn's monolithic Leo application in 2011). In each of these cases,
|
||
they were able to re-architect their systems and set the stage not only
|
||
to survice, but also to thrise and win in the marketplace.
|
||
|
||
\cite[182]{devops_handbook}
|
||
\end{quote}
|
||
|
||
Ceci dit, Django compense ses contraintes en proposant énormément de
|
||
flexibilité et de fonctionnalités \textbf{out-of-the-box}, c'est-à-dire
|
||
que vous pourrez sans doute avancer vite et bien jusqu'à un point de
|
||
rupture, puis revoir la conception et réinvestir à ce moment-là, mais en
|
||
toute connaissance de cause.
|
||
|
||
\begin{quote}
|
||
When any of the external parts of the system become obsolete, such as
|
||
the database, or the web framework, you can replace those obsolete
|
||
elements with a minimum of fuss.
|
||
|
||
--- Robert C. Martin Clean Architecture
|
||
\end{quote}
|
||
|
||
Avec Django, la difficulté à se passer du framework va consister à
|
||
basculer vers «~autre chose~» et a remplacer chacune des tentacules qui
|
||
aura pousser partout dans l'application.
|
||
|
||
A noter que les services et les «~architectures orientées services~» ne
|
||
sont jamais qu'une définition d'implémentation des frontières, dans la
|
||
mesure où un service n'est jamais qu'une fonction appelée au travers
|
||
d'un protocole (rest, soap, \ldots\hspace{0pt}). Une application
|
||
monolotihique sera tout aussi fonctionnelle qu'une application découpée
|
||
en microservices. \cite[p. 243]{clean_architecture}
|
||
|
||
\section{Inversion de dépendances}
|
||
|
||
Dans la partie SOLID, nous avons évoqué plusieurs principes de
|
||
développement. Django est un framework qui évolue, et qui a pu présenter
|
||
certains problèmes liés à l'un de ces principes.
|
||
|
||
Les link:release
|
||
notes{[}\url{https://docs.djangoproject.com/en/2.0/releases/2.0/}{]} de
|
||
Django 2.0 date de décembre 2017; parmi ces notes, l'une d'elles cite
|
||
l'abandon du support d'link:Oracle
|
||
11.2{[}\url{https://docs.djangoproject.com/en/2.0/releases/2.0/\#dropped-support-for-oracle-11-2}{]}.
|
||
En substance, cela signifie que le framework se chargeait lui-même de
|
||
construire certaines parties de requêtes, qui deviennent non
|
||
fonctionnelles dès lors que l'on met le framework ou le moteur de base
|
||
de données à jour. Réécrit, cela signifie que:
|
||
|
||
\begin{enumerate}
|
||
\def\labelenumi{\arabic{enumi}.}
|
||
\item
|
||
Si vos données sont stockées dans un moteur géré par Oracle 11.2, vous
|
||
serez limité à une version 1.11 de Django
|
||
\item
|
||
Tandis que si votre moteur est géré par une version ultérieure, le
|
||
framework pourra être mis à jour.
|
||
\end{enumerate}
|
||
|
||
Nous sommes dans un cas concret d'inversion de dépendances ratée: le
|
||
framework (et encore moins vos politiques et règles métiers) ne
|
||
devraient pas avoir connaissance du moteur de base de données. Pire, vos
|
||
politiques et données métiers ne devraient pas avoir connaissance
|
||
\textbf{de la version} du moteur de base de données.
|
||
|
||
En conclusion, le choix d'une version d'un moteur technique (\textbf{la
|
||
base de données}) a une incidence directe sur les fonctionnalités mises
|
||
à disposition par votre application, ce qui va à l'encontre des 12
|
||
facteurs (et des principes de développement).
|
||
|
||
Ce point sera rediscuté par la suite, notamment au niveau de l'épinglage
|
||
des versions, de la reproduction des environnements et de
|
||
l'interdépendance entre des choix techniques et fonctionnels.
|
||
|
||
\section{Conclusions}
|
||
|
||
\begin{quote}
|
||
La perfection est atteinte, non pas lorsqu'il n'y a plus rien à ajouter, mais lorsqu'il n'y a plus rien à retirer.
|
||
|
||
-- Antoine de Saint-Exupéry
|
||
\end{quote}
|
||
|
||
Il est impossible de se projeter dans le futur d'une application: il est impossible d'imaginer les modifications qui seront demandées par les utilisateurs, de se projeter dans l'évolution d'un langage, dans les nécessités d'intégration de certaines librairies ou dans le support-même de certaines fonctionnalités par les navigateurs Web.
|
||
Ce sont des choses qui viennent avec l'expérience (ou avec la tordure d'esprit \footnote{Si, ça existe}).
|
||
Cela rejoint le fameux "YAGNI\index{YAGNI}" dont nous parlions plus tôt: il est inutile de vouloir développer absolument toutes les fonctionnalités qui pourraient un jour pouvoir être utilisées ou souhaitées, car cela complexifiera autant le code, que les déploiement, l'utilisabilité ou la compréhension que les utilisateurs pourront avoir de votre application. \cite{rework}
|
||
|
||
Il est impossible d'imaginer ou de se projeter dans tous les éléments qui pourraient devoir être modifiés après que votre développement ait été livrée.
|
||
En ayant connaissance de toutes les choses qui pourraient être modifiées par la suite, l’idée est de pousser le développement jusqu’au point où une décision pourrait devoir être faite.
|
||
A ce stade, l’architecture nécessitera des modifications, mais aura déjà intégré le fait que cette possibilité existe.
|
||
Nous n’allons donc pas jusqu’au point où le service doit être créé (même s’il peut ne pas être nécessaire), ni à l’extrême au fait d’ignorer qu’un service pourrait être nécessaire, mais nous aboutissons à une forme de compromis.
|
||
Une forme d'application de la philosophie de René Descartes, où le fait de seulement envisager une possibilité ouvre un maximum de portes.
|
||
|
||
Avec cette approche, les composants seront déjà découplés au mieux.
|
||
|
||
En terme de découpe, les composants peuvent l'être aux niveaux suivants:
|
||
|
||
\begin{itemize}
|
||
\item
|
||
\textbf{Code source}
|
||
\item
|
||
\textbf{Déploiement}, au travers de dll, jar, linked libraries, \ldots{} voire
|
||
au travers de threads ou de processus locaux.
|
||
\item
|
||
\textbf{Mise à disposition de services}
|
||
\end{itemize}
|
||
|
||
Cette section se base sur deux ressources principales \cite{maintainable_software} \cite{clean_code}, qui répartissent un ensemble de conseils parmi quatre niveaux de composants:
|
||
|
||
\begin{itemize}
|
||
\item
|
||
Les méthodes et fonctions
|
||
\item
|
||
Les classes
|
||
\item
|
||
Les composants
|
||
\item
|
||
Et des conseils plus généraux.
|
||
\end{itemize}
|
||
|
||
\subsection{Au niveau des méthodes et fonctions}
|
||
|
||
\begin{itemize}
|
||
\item
|
||
\textbf{Gardez vos méthodes/fonctions courtes}. Pas plus de 15 lignes, en comptant les commentaires.
|
||
Des exceptions sont possibles, mais dans une certaine mesure uniquement (pas plus de 6.9\% de plus de 60 lignes; pas plus de 22.3\% de plus de 30 lignes, au plus 43.7\% de plus de 15 lignes et au moins 56.3\% en dessous de 15 lignes).
|
||
Oui, c'est dur à tenir, mais faisable.
|
||
\item
|
||
\textbf{Conserver une complexité de McCabe en dessous de 5}, c'est-à-dire avec quatre branches au maximum.
|
||
A nouveau, si une méthode présente une complexité cyclomatique de 15, la séparer en 3 fonctions ayant chacune une complexité de 5 conservera la complexité globale à 15, mais rendra le code de chacune de ces méthodes plus lisible, plus maintenable.
|
||
\item
|
||
\textbf{N'écrivez votre code qu'une seule fois: évitez les duplications, copie, etc.}: imaginez qu'un bug soit découvert dans une fonction; il devra alors être corrigé dans toutes les fonctions qui auront été copiées/collées.
|
||
\item
|
||
\textbf{Conservez de petites interfaces et signatures de fonctions/méthodes}. Quatre paramètres, pas plus.
|
||
Au besoin, refactorisez certains paramètres dans une classe ou une structure, qui sera plus facile à tester.
|
||
\end{itemize}
|
||
|
||
\subsection{Au niveau des classes}
|
||
|
||
|
||
\begin{itemize}
|
||
\item
|
||
\textbf{Privilégiez un couplage faible entre vos classes}.
|
||
Ceci n'est pas toujours possible, mais dans la mesure du possible, éclatez vos classes en fonction de leur domaine de compétences respectif.
|
||
L'implémentation du service \texttt{UserNotificationsService} ne doit pas forcément se trouver embarqué dans une classe \texttt{UserService}.
|
||
De même, pensez à passer par une interface (commune à plusieurs classes), afin d'ajouter une couche d'abstraction.
|
||
La classe appellante n'aura alors que les méthodes offertes par l'interface comme points d'entrée.
|
||
\end{itemize}
|
||
|
||
\subsection{Au niveau des composants}
|
||
|
||
|
||
\begin{itemize}
|
||
\item
|
||
\textbf{Tout comme pour les classes, il faut conserver un couplage faible au niveau des composants} également.
|
||
Une manière d'arriver à ce résultat est de conserver un nombre de points d'entrée restreint, et d'éviter qu'il ne soit possible de contacter trop facilement des couches séparées de l'architecture.
|
||
Pour une architecture n-tiers par exemple, la couche d'abstraction à la base de données ne peut être connue que des services; sans cela, au bout de quelques semaines, n'importe quelle couche de présentation risque de contacter directement la base de données, "\emph{juste parce qu'elle en a la possibilité}".
|
||
Vous pourriez également passer par des interfaces, afin de réduire le nombre de points d'entrée connus par un composant externe (qui ne connaîtra par exemple que \texttt{IFileTransfer} avec ses méthodes \texttt{put} et \texttt{get}, et non pas les détailsd'implémentation complet d'une classe \texttt{FtpFileTransfer} ou \texttt{SshFileTransfer}).
|
||
\item
|
||
\textbf{Conserver un bon balancement au niveau des composants}: évitez qu'un composant \textbf{A} ne soit un énorme mastodonte, alors que le
|
||
composant juste à côté ne soit capable que d'une action.
|
||
De cette manière, les nouvelles fonctionnalités seront mieux réparties parmi les différents systèmes, et les responsabilités seront plus faciles à
|
||
gérer.
|
||
Un conseil est d'avoir un nombre de composants compris entre 6 et 12 (idéalement, 12), et que chacun de ces composants soit approximativement de même taille.
|
||
\end{itemize}
|
||
|
||
\subsection{De manière générale}
|
||
|
||
\begin{itemize}
|
||
\item
|
||
\textbf{Conserver une densité de code faible}: il n'est évidemment pas possible d'implémenter n'importe quelle nouvelle fonctionnalité en
|
||
moins de 20 lignes de code; l'idée ici est que la réécriture du projet ne prenne pas plus de 20 hommes/mois.
|
||
Pour cela, il faut (activement) passer du temps à réduire la taille du code existant: soit en faisantdu refactoring (intensif), soit en utilisant des librairies existantes, soit en explosant un système existant en plusieurs sous-systèmes communiquant entre eux.
|
||
Mais surtout, en évitant de copier/coller bêtement du code existant.
|
||
\item
|
||
\textbf{Automatiser les tests}, en ajoutant un environnement d'intégration continue dès le début du projet et en faisant vérifier par des outils automatiques tous les points ci-dessus.
|
||
\end{itemize}
|
||
|