Réécriture de l'Architecture

This commit is contained in:
Gregory Trullemans 2023-04-21 13:26:38 +02:00
parent 1461ee2fa1
commit 6fa6fa3f7d
1 changed files with 86 additions and 113 deletions

View File

@ -1,5 +1,3 @@
\chapter{Eléments d'architecture}
\begin{quote}
@ -11,30 +9,23 @@
Les principes SOLID, introduit par Robert C. Martin dans les années 2000 pour orienter le développement de modules, sont les suivants :
\begin{enumerate}
\item
\textbf{SRP} - Single responsibility principle - Principe de Responsabilité Unique
\item
\textbf{OCP} - Open-closed principle
\item
\textbf{LSP} - Liskov Substitution
\item
\textbf{ISP} - Interface ségrégation principle
\item
\textbf{DIP} - Dependency Inversion Principle
\item \textbf{SRP} - Single responsibility principle - Principe de Responsabilité Unique
\item \textbf{OCP} - Open-closed principle
\item \textbf{LSP} - Liskov Substitution
\item \textbf{ISP} - Interface ségrégation principle
\item \textbf{DIP} - Dependency Inversion Principle
\end{enumerate}
Des équivalents à ces directives existent au niveau des composants, puis au niveau architectural :
\begin{enumerate}
\item
Reuse/release équivalence principle,
\item
\textbf{CCP} - Common Closure Principle,
\item
\textbf{CRP} - Common Reuse Principle.
\item Reuse/release équivalence principle,
\item \textbf{CCP} - Common Closure Principle,
\item \textbf{CRP} - Common Reuse Principle.
\end{enumerate}
\includegraphics{images/arch-comp-modules.png}
\section{Modules}
\subsection{Single Responsility Principle}
@ -98,8 +89,8 @@ Une méthode \texttt{render} permet également de proposer (très grossièrement
\end{listing}
Lorsque nous devrons ajouter un nouveau rendu (Atom, OpenXML, ...), il sera nécessaire de modifier la classe \texttt{Document}.
Ceci n'est:
Lorsque nous devrons ajouter un nouveau rendu (Atom, OpenXML, \ldots), il sera nécessaire de modifier la classe \texttt{Document}.
Ceci n' :
\begin{enumerate}
\item Ni intuitif : \emph{ce n'est pas le document qui doit savoir dans quels formats il peut être converti}
@ -143,7 +134,7 @@ En suivant le principe de responsabilité unique, une bonne pratique consiste à
A présent, lorsque nous devrons ajouter un nouveau format de prise en charge, il nous suffira de modifier la classe \texttt{DocumentRenderer}, sans que la classe \texttt{Document} ne soit impactée.
En même temps, le jour où une instance de type \texttt{Document} sera liée à un champ \texttt{author}, rien ne dit que le rendu devra en tenir compte; nous modifierons donc notre classe pour y ajouter le nouveau champ sans que cela n'impacte nos différentes manières d'effectuer un rendu.
Un autre exemple consiterait à faire communiquer une méthode avec une base de données: ce ne sera pas à cette méthode à gérer l'inscription d'une exception à un emplacement spécifique (emplacement sur un disque, ...): cette action doit être prise en compte par une autre classe (ou un autre concept ou composant), qui s'occupera de définir elle-même l'emplacement où l'évènement sera enregistré, que ce soit dans une base de données, une instance Graylog ou un fichier.
Un autre exemple consiterait à faire communiquer une méthode avec une base de données : ce ne sera pas à cette méthode à gérer l'inscription d'une exception à un emplacement spécifique (emplacement sur un disque, \ldots) : cette action doit être prise en compte par une autre classe (ou un autre concept ou composant), qui s'occupera de définir elle-même l'emplacement où l'évènement sera enregistré, que ce soit dans une base de données, une instance Graylog ou un fichier.
Cette manière de structurer le code permet de centraliser la configuration d'un type d'évènement à un seul endroit, ce qui augmente ainsi la testabilité globale du projet.
L'équivalent du principe de responsabilité unique au niveau des composants sera le \texttt{Common Closure Principle} \index{CCP}.
@ -160,7 +151,8 @@ L'objectif est de rendre le système facile à étendre, en limitant l'impact qu
Reprendre notre exemple de modélisation de documents parle de lui-même :
\begin{enumerate}
\item Des données que nous avons converties dans un format spécifique pourraient à présent devoir être présentées dans une page web.
\item
Des données que nous avons converties dans un format spécifique pourraient à présent devoir être présentées dans une page web.
\item Et demain, ce sera dans un document PDF.
\item Et après demain, dans un tableur Excel.
\end{enumerate}
@ -335,7 +327,7 @@ Petit exemple pratique: si nous définissons une méthode \texttt{make\_some\_no
def make_some_noise(self):
print("Roaaar!")
\end{minted}
\caption{Un lion et un canard sont sur un bateau...}
\caption{Un lion et un canard sont sur un bateau\ldots}
\end{listing}
Le principe de substitution de Liskov suggère qu'une classe doit toujours pouvoir être considérée comme une instance de sa classe parente, et \textbf{doit pouvoir s'y substituer}.
@ -365,7 +357,7 @@ Pour revenir à nos exemples de rendus de documents, nous aurions pu faire héri
import markdown
return markdown.markdown(document.content)
\end{minted}
\caption{Le convertisseur Markdown hérite d'un convertisseur XML...}
\caption{Le convertisseur Markdown hérite d'un convertisseur XML\ldots}
\end{listing}
Si nous décidons à un moment d'ajouter une méthode d'entête au niveau de notre classe de rendu XML, notre rendu en Markdown héritera irrémédiablement de cette même méthode :
@ -397,7 +389,7 @@ Si nous décidons à un moment d'ajouter une méthode d'entête au niveau de not
\caption{\ldots~ et il a mal à l'entête}
\end{listing}
Le code ci-dessus ne porte pas à conséquence \footnote{Pas immédiatement, en tout cas...}, mais dès que nous invoquerons la méthode \texttt{header()} sur une instance de type \texttt{MarkdownRenderer}, nous obtiendrons un bloc de déclaration XML (\texttt{\textless{}?xml\ version\ =\ "1.0"?\textgreater{}}) pour un fichier Markdown, ce qui n'aura aucun sens.
Le code ci-dessus ne porte pas à conséquence \footnote{Pas immédiatement, en tout cas\ldots}, mais dès que nous invoquerons la méthode \texttt{header()} sur une instance de type \texttt{MarkdownRenderer}, nous obtiendrons un bloc de déclaration XML (\texttt{\textless{}?xml\ version\ =\ "1.0"?\textgreater{}}) pour un fichier Markdown, ce qui n'aura aucun sens.
En revenant à notre proposition d'implémentation, suite au respect d'Open-Closed, une solution serait de n'implémenter la méthode \texttt{header()} qu'au niveau de la classe \texttt{XmlRenderer} :
@ -551,8 +543,8 @@ Lobjectif est donc de découpler ces différentes fonctionnalités en plusieu
\end{listing}
Cette réflexion s'applique à n'importe quel composant: votre système d'exploitation, les librairies et dépendances tierces, les variables déclarées, ...
Quel que soit le composant que l'on utilise ou analyse, il est plus qu'intéressant de se limiter uniquement à ce dont nous avons besoin plutôt que d'embarquer le must absolu qui peut faire 1000x fonctions de plus que n'importe quel autre produit, alors que seules deux d'entre elles seront nécessaires.
Cette réflexion s'applique à n'importe quel composant : votre système d'exploitation, les librairies et dépendances tierces, les variables déclarées, \ldots
Quel que soit le composant que l'on utilise ou analyse, il est plus qu'intéressant de se limiter uniquement à ce dont nous avons besoin plutôt que d'embarquer le must absolu qui peut faire 1000 fonctions de plus que n'importe quel autre produit, alors que seules deux d'entre elles seront nécessaires.
En Python, ce comportement est inféré lors de l'exécution, et donc pas vraiment d'application pour ce contexte d'étude : de manière plus générale, les langages dynamiques sont plus flexibles et moins couplés que les langages statiquement typés, pour lesquels l'application de ce principe-ci permettrait de juste mettre à jour une DLL ou un JAR sans que cela n'ait d'impact sur le reste de l'application.
@ -749,8 +741,7 @@ Une bonne architecture va rendre le système facile à lire, facile à développ
Un des autres objectifs d'une bonne architecture consiste à 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, \ldots) le plus tard possible, tout en conservant la politique principale en ligne de mire.
Ceci 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.
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 : ne pas investir à améliorer l'architecture dès que ce sera possible fera lentement (mais sûrement!) dériver la base de code vers une augmentation de la dette technique.
Faire évoluer correctement l'architecture d'un projet demande une bonne expérience, mais également un bon sens de l'observation, un investissement non négligeable en attention portée aux détails et de la patience :
@ -763,7 +754,8 @@ Faire évoluer correctement l'architecture d'un projet demande une bonne expéri
You pay attention as the system evolves.
You note where boundaries may be required, and then carefully watch for the first inkling of friction because those boundaries don't exist.
At that point, you weight the costs of implementing those boundaries versus the cost of ignoring them and you review that decision frequently. Your goal is to implement the boundaries right at the inflection point where the cost of implementing becomes less than the cost of ignoring.
At that point, you weight the costs of implementing those boundaries versus the cost of ignoring them and you review that decision frequently.
Your goal is to implement the boundaries right at the inflection point where the cost of implementing becomes less than the cost of ignoring.
\end{quote}
\section{Politiques et règles métiers}
@ -772,12 +764,9 @@ Faire évoluer correctement l'architecture d'un projet demande une bonne expéri
\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.
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}
@ -787,14 +776,10 @@ L'idée est que le framework doit se conformer à la définition de l'applicatio
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 Votre modèle métier sera largement couplé avec le type de base de données (relationnelle, indépendamment ???? -de quoi-)
\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}
@ -803,15 +788,8 @@ Le point à comprendre ici n'est pas que "Django, c'est mal", mais qu'une fois q
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.
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}
@ -819,9 +797,7 @@ Cette décision ne sera pas irrévocable, mais difficile à contourner.
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.
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}
@ -841,10 +817,8 @@ En substance, cela signifie que le framework se chargeait lui-même de construir
Réécrit, cela signifie que :
\begin{enumerate}
\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.
\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.
@ -877,8 +851,7 @@ Avec cette approche, les composants seront déjà découplés au mieux.
Les composants peuvent être découpés au niveau :
\begin{itemize}
\item
\textbf{Du code source}, via des modules, paquets, dépendances, \ldots
\item \textbf{Du code source}, via des modules, paquets, dépendances, \ldots
\item
\textbf{Du déploiement ou de l'exécution}, au travers de dll, jar, linked libraries, \ldots, voire au travers de threads ou de processus locaux.
\item
@ -900,7 +873,7 @@ Cette section se base sur deux ressources principales \cite{maintainable_softwar
\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).
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.