12 factors are on the run!
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
02930bd52f
commit
261fa2c7c8
|
@ -1,146 +1,10 @@
|
||||||
|
|
||||||
\hypertarget{_fiabilituxe9_uxe9volutivituxe9_et_maintenabilituxe9}{%
|
|
||||||
\section{Fiabilité, évolutivité et
|
|
||||||
maintenabilité}\label{_fiabilituxe9_uxe9volutivituxe9_et_maintenabilituxe9}}
|
|
||||||
|
|
||||||
\hypertarget{_12_facteurs}{%
|
|
||||||
\subsection{12 facteurs}\label{_12_facteurs}}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
\hypertarget{_concevoir_pour_lopuxe9rationnel}{%
|
|
||||||
\subsection{Concevoir pour
|
|
||||||
l'opérationnel}\label{_concevoir_pour_lopuxe9rationnel}}
|
|
||||||
|
|
||||||
Une application devient nettement plus maintenable dès lors que l'équipe
|
|
||||||
de développement suit de près les différentes étapes de sa conception,
|
|
||||||
de la demande jusqu'à son aboutissement en production.
|
|
||||||
cite:{[}devops\_handbook(293-294){]} Au fur et à mesure que le code est
|
|
||||||
délibérément construit pour être maintenable, l'équipe gagne en
|
|
||||||
rapidité, en qualité et en fiabilité de déploiement, ce qui facilite les
|
|
||||||
tâches opérationnelles:
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item
|
|
||||||
Activation d'une télémétrie suffisante dans les applications et les
|
|
||||||
environnements.
|
|
||||||
\item
|
|
||||||
Conservation précise des dépendances nécessaires
|
|
||||||
\item
|
|
||||||
Résilience des services et plantage élégant (i.e. \textbf{sans finir
|
|
||||||
sur un SEGFAULT avec l'OS dans les choux et un écran bleu})
|
|
||||||
\item
|
|
||||||
Compatibilité entre les différentes versions (n+1, \ldots\hspace{0pt})
|
|
||||||
\item
|
|
||||||
Gestion de l'espace de stockage associé à un environnement (pour
|
|
||||||
éviter d'avoir un environnement de production qui fait 157
|
|
||||||
Tera-octets)
|
|
||||||
\item
|
|
||||||
Activation de la recherche dans les logs
|
|
||||||
\item
|
|
||||||
Traces des requêtes provenant des utilisateurs, indépendamment des
|
|
||||||
services utilisés
|
|
||||||
\item
|
|
||||||
Centralisation de la configuration (\textbf{via} ZooKeeper, par
|
|
||||||
exemple)
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
\hypertarget{_robustesse_et_flexibilituxe9}{%
|
|
||||||
\subsection{Robustesse et
|
|
||||||
flexibilité}\label{_robustesse_et_flexibilituxe9}}
|
|
||||||
|
|
||||||
\begin{quote}
|
|
||||||
Un code mal pensé entraîne nécessairement une perte d'énergie et de
|
|
||||||
temps. Il est plus simple de réfléchir, au moment de la conception du
|
|
||||||
programme, à une architecture permettant une meilleure maintenabilité
|
|
||||||
que de devoir corriger un code "sale" \emph{a posteriori}. C'est pour
|
|
||||||
aider les développeurs à rester dans le droit chemin que les principes
|
|
||||||
SOLID ont été énumérés. cite:{[}gnu\_linux\_mag\_hs\_104{]}
|
|
||||||
\end{quote}
|
|
||||||
|
|
||||||
Les principes SOLID, introduit par Robert C. Martin dans les années 2000
|
|
||||||
sont les suivants:
|
|
||||||
|
|
||||||
\begin{enumerate}
|
|
||||||
\def\labelenumi{\arabic{enumi}.}
|
|
||||||
\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}
|
|
||||||
|
|
||||||
En plus de ces principes de développement, il faut ajouter des principes
|
|
||||||
au niveau des composants, puis un niveau plus haut, au niveau, au niveau
|
|
||||||
architectural :
|
|
||||||
|
|
||||||
\begin{enumerate}
|
|
||||||
\def\labelenumi{\arabic{enumi}.}
|
|
||||||
\item
|
|
||||||
Reuse/release équivalence principle,
|
|
||||||
\item
|
|
||||||
Common Closure Principle,
|
|
||||||
\item
|
|
||||||
Common Reuse Principle.
|
|
||||||
\end{enumerate}
|
|
||||||
|
|
||||||
\hypertarget{_single_responsibility_principle}{%
|
\hypertarget{_single_responsibility_principle}{%
|
||||||
\subsubsection{Single Responsibility
|
\subsubsection{Single Responsibility
|
||||||
Principle}\label{_single_responsibility_principle}}
|
Principle}\label{_single_responsibility_principle}}
|
||||||
|
|
||||||
Le principe de responsabilité unique conseille de disposer de concepts
|
|
||||||
ou domaines d'activité qui ne s'occupent chacun que d'une et une seule
|
|
||||||
chose. Ceci rejoint (un peu) la
|
|
||||||
\href{https://en.wikipedia.org/wiki/Unix_philosophy}{Philosophie Unix},
|
|
||||||
documentée par Doug McIlroy et qui demande de "\emph{faire une seule
|
|
||||||
chose, mais le faire bien}" cite:{[}unix\_philosophy{]}. Une classe ou
|
|
||||||
un élément de programmtion ne doit donc pas avoir plus d'une raison de
|
|
||||||
changer.
|
|
||||||
|
|
||||||
Il est également possible d'étendre ce principe en fonction d'acteurs:
|
|
||||||
|
|
||||||
\begin{quote}
|
|
||||||
A module should be responsible to one and only one actor.
|
|
||||||
cite:{[}clean\_architecture{]}
|
|
||||||
|
|
||||||
--- Robert C. Martin
|
|
||||||
\end{quote}
|
|
||||||
|
|
||||||
Plutôt que de centraliser le maximum de code à un seul endroit ou dans
|
|
||||||
une seule classe par convenance ou commodité \footnote{Aussi appelé
|
|
||||||
\emph{God-Like object}}, le principe de responsabilité unique suggère
|
|
||||||
que chaque classe soit responsable d'un et un seul concept.
|
|
||||||
|
|
||||||
Une manière de voir les choses consiste à différencier les acteurs ou
|
|
||||||
les intervenants: imaginez disposer d'une classe représentant des
|
|
||||||
données de membres du personnel. Ces données pourraient être demandées
|
|
||||||
par trois acteurs, le CFO, le CTO et le COO. Ceux-ci ont tous besoin de
|
|
||||||
données et d'informations relatives à une même base de données
|
|
||||||
centralisées, mais ont chacun besoin d'une représentation différente ou
|
|
||||||
de traitements distincts. cite:{[}clean\_architecture{]}
|
|
||||||
|
|
||||||
Nous sommes d'accord qu'il s'agit à chaque fois de données liées aux
|
|
||||||
employés, mais elles vont un cran plus loin et pourraient nécessiter des
|
|
||||||
ajustements spécifiques en fonction de l'acteur concerné et de la
|
|
||||||
manière dont il souhaite disposer des données. Dès que possible,
|
|
||||||
identifiez les différents acteurs et demandeurs, en vue de prévoir les
|
|
||||||
modifications qui pourraient être demandées par l'un d'entre eux.
|
|
||||||
|
|
||||||
Dans le cas d'un élément de code centralisé, une modification induite
|
|
||||||
par un des acteurs pourrait ainsi avoir un impact sur les données
|
|
||||||
utilisées par les autres.
|
|
||||||
|
|
||||||
Vous trouverez ci-dessous une classe \texttt{Document}, dont chaque
|
|
||||||
instance est représentée par trois propriétés: son titre, son contenu et
|
|
||||||
sa date de publication. Une méthode \texttt{render} permet également de
|
|
||||||
proposer (très grossièrement) un type de sortie et un format de contenu:
|
|
||||||
\texttt{XML} ou \texttt{Markdown}.
|
|
||||||
|
|
||||||
\begin{Shaded}
|
\begin{Shaded}
|
||||||
\begin{Highlighting}[]
|
\begin{Highlighting}[]
|
||||||
|
|
|
@ -19,73 +19,58 @@ Sans aller jusqu'à demander de développer vos algorithmes sur douze pieds, la
|
||||||
|
|
||||||
\section{12 facteurs}
|
\section{12 facteurs}
|
||||||
|
|
||||||
|
|
||||||
Pour la méthode de travail et de développement, nous allons nous baser
|
Pour la méthode de travail et de développement, nous allons nous baser
|
||||||
sur les \href{https://12factor.net/fr/}{The Twelve-factor App} - ou plus
|
sur les \href{https://12factor.net/fr/}{The Twelve-factor App} - ou plus
|
||||||
simplement les \textbf{12 facteurs}.
|
simplement les \textbf{12 facteurs}.
|
||||||
|
Suivre ces concepts permet de:
|
||||||
L'idée derrière cette méthode, et indépendamment des langages de
|
|
||||||
développement utilisés, consiste à suivre un ensemble de douze concepts,
|
|
||||||
afin de:
|
|
||||||
|
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
\def\labelenumi{\arabic{enumi}.}
|
|
||||||
\item
|
\item
|
||||||
\textbf{Faciliter la mise en place de phases d'automatisation}; plus
|
\textbf{Faciliter la mise en place de phases d'automatisation}; plus
|
||||||
concrètement, de faciliter les mises à jour applicatives, simplifier
|
concrètement, de faciliter les mises à jour applicatives, simplifier
|
||||||
la gestion de l'hôte, diminuer la divergence entre les différents
|
la gestion de l'hôte qui héberge l'application ou les services,
|
||||||
environnements d'exécution et offrir la possibilité d'intégrer le
|
diminuer la divergence entre les différents environnements d'exécution et offrir la possibilité d'intégrer le
|
||||||
projet dans un processus
|
projet dans un processus
|
||||||
d'\href{https://en.wikipedia.org/wiki/Continuous_integration}{intégration
|
d'\href{https://en.wikipedia.org/wiki/Continuous_integration}{intégration
|
||||||
continue} ou
|
continue} ou
|
||||||
\href{https://en.wikipedia.org/wiki/Continuous_deployment}{déploiement
|
\href{https://en.wikipedia.org/wiki/Continuous_deployment}{déploiement
|
||||||
continu}
|
continu}
|
||||||
\item
|
\item
|
||||||
\textbf{Faciliter la mise à pied de nouveaux développeurs ou de
|
\textbf{Faciliter l'intégration de nouveaux développeurs dans l'équipe ou de
|
||||||
personnes souhaitant rejoindre le projet}, dans la mesure où la mise à
|
personnes souhaitant rejoindre le projet}, dans la mesure où la construction d'un nouvel environnement sera grandement facilitée.
|
||||||
disposition d'un environnement sera grandement facilitée.
|
|
||||||
\item
|
\item
|
||||||
\textbf{Minimiser les divergences entre les différents environnemens
|
\textbf{Minimiser les divergences entre les différents environnemens}
|
||||||
sur lesquels un projet pourrait être déployé}
|
sur lesquels un projet pourrait être déployé, pour éviter de découvrir un bogue sur l'environnement de production qui serait impossible à reproduire ailleurs, simplement parce qu'un des composants varierait
|
||||||
\item
|
\item
|
||||||
\textbf{Augmenter l'agilité générale du projet}, en permettant une
|
\textbf{Augmenter l'agilité générale du projet}, en permettant une
|
||||||
meilleure évolutivité architecturale et une meilleure mise à l'échelle
|
meilleure évolutivité architecturale et une meilleure mise à l'échelle.
|
||||||
- \emph{Vous avez 5000 utilisateurs en plus? Ajoutez un serveur et on
|
|
||||||
n'en parle plus ;-)}.
|
|
||||||
\end{enumerate}
|
\end{enumerate}
|
||||||
|
|
||||||
|
En pratique, les points ci-dessus permettront de gagner un temps précieux à la construction d'un nouvel environnement - qu'il soit sur la machine du petit nouveau dans l'équipe, sur un serveur Azure/Heroku/Digital Ocean ou votre nouveau
|
||||||
En pratique, les points ci-dessus permettront de monter facilement un
|
Raspberry Pi Zéro planqué à la cave - et vous feront gagner un temps
|
||||||
nouvel environnement - qu'il soit sur la machine du petit nouveau dans
|
|
||||||
l'équipe, sur un serveur Azure/Heroku/Digital Ocean ou votre nouveau
|
|
||||||
Raspberry Pi Zéro caché à la cave - et vous feront gagner un temps
|
|
||||||
précieux.
|
précieux.
|
||||||
|
|
||||||
Pour reprendre de manière très brute les différentes idées derrière
|
Pour reprendre plus spécifiquement les différentes idées derrière
|
||||||
cette méthode, nous avons:
|
cette méthode, nous avons:
|
||||||
|
|
||||||
\subsection{\#1 - Une base de code unique, suivie par un système de contrôle
|
\subsection{Une base de code unique, suivie par un contrôle de versions}
|
||||||
de versions}.
|
|
||||||
|
|
||||||
Chaque déploiement de l'application se basera sur cette source, afin de
|
Chaque déploiement de l'application, et quel que soit l'environnement ciblé, se basera sur une source unique, afin de minimiser les différences que l'on pourrait trouver entre deux environnements d'un même projet.
|
||||||
minimiser les différences que l'on pourrait trouver entre deux
|
Git est reconnu dans l'industrie comme standard des systèmes de contrôles de versions, malgré une courbe d'apprentissage assez ardue.
|
||||||
environnements d'un même projet. On utilisera un dépôt Git - Github,
|
Comme dépôt, nous pourrons par exemple utiliser GitHub, Gitea ou Gitlab, suivant que vous ayez besoin d'une plateforme centralisée, propriétaire, payante ou auto-hébergée. \index{Git} \index{Github} \index{Gitlab} \index{Gitea}
|
||||||
Gitlab, Gitea, \ldots\hspace{0pt} Au choix.
|
|
||||||
|
|
||||||
\includegraphics{images/diagrams/12-factors-1.png}
|
\includegraphics{images/diagrams/12-factors-1.png}
|
||||||
|
|
||||||
Comme l'explique Eran Messeri, ingénieur dans le groupe Google Developer
|
Comme l'explique Eran Messeri, ingénieur dans le groupe Google Developer
|
||||||
Infrastructure, un des avantages d'utiliser un dépôt unique de sources,
|
Infrastructure: "Un des avantages d'utiliser un dépôt unique de sources,
|
||||||
est qu'il permet un accès facile et rapide à la forme la plus à jour du
|
est qu'il permet un accès facile et rapide à la forme la plus à jour du
|
||||||
code, sans aucun besoin de coordination. \footnote{The DevOps Handbook,
|
code, sans aucun besoin de coordination. \cite[pp. 288-298]{devops_handbook}.
|
||||||
Part V, Chapitre 20, Convert Local Discoveries into Global
|
Ce cépôt n'est pas uniquement destiné à hébergé le code source, mais
|
||||||
Improvements} Ce dépôt ne sert pas seulement au code source, mais
|
également à d'autres artefacts et autres formes de connaissance:
|
||||||
également à d'autres artefacts et formes de connaissance:
|
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item
|
\item
|
||||||
Standards de configuration (Chef recipes, Puppet manifests,
|
Standards de configuration (Chef recipes, Puppet manifests, ...
|
||||||
\ldots\hspace{0pt})
|
|
||||||
\item
|
\item
|
||||||
Outils de déploiement
|
Outils de déploiement
|
||||||
\item
|
\item
|
||||||
|
@ -98,44 +83,39 @@ code, sans aucun besoin de coordination. \footnote{The DevOps Handbook,
|
||||||
Tutoriaux
|
Tutoriaux
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
\subsection{\#2 - Déclarez explicitement les dépendances nécessaires au
|
\subsection{Déclarez explicitement et isolez les dépendances du projet}
|
||||||
projet, et les isoler du reste du système lors de leur installation}
|
|
||||||
|
|
||||||
Chaque installation ou configuration doit toujours être faite de la même
|
Chaque installation ou configuration doit toujours être faite de la même
|
||||||
manière, et doit pouvoir être répétée quel que soit l'environnement
|
manière, et doit pouvoir être répétée quel que soit l'environnement
|
||||||
cible.
|
cible.
|
||||||
|
Ceci permet d'éviter que l'application n'utilise une dépendance qui ne soit
|
||||||
Cela permet d'éviter que l'application n'utilise une dépendance qui soit
|
|
||||||
déjà installée sur un des sytèmes de développement, et qu'elle soit
|
déjà installée sur un des sytèmes de développement, et qu'elle soit
|
||||||
difficile, voire impossible, à répercuter sur un autre environnement.
|
difficile, voire impossible, à répercuter sur un autre environnement.
|
||||||
Dans notre cas, cela pourra être fait au travers de
|
Dans le cas de Python, cela pourra être fait au travers de
|
||||||
\href{https://pypi.org/project/pip/}{PIP - Package Installer for Python}
|
\href{https://pypi.org/project/pip/}{PIP - Package Installer for Python}
|
||||||
ou \href{https://python-poetry.org/}{Poetry}.
|
ou \href{https://python-poetry.org/}{Poetry}.
|
||||||
|
La majorité des langages moderners proposent des mécanismes similaires (\href{https://rubygems.org/}{Gem} pour Ruby, \href{https://www.npmjs.com/}{NPM} pour NodeJS, ...)
|
||||||
|
|
||||||
Mais dans tous les cas, chaque application doit disposer d'un
|
Dans tous les cas, chaque application doit disposer d'un environnement sain, qui lui est assigné. Vu le peu de ressources que cela coûte, il ne faut pas s'en priver.
|
||||||
environnement sain, qui lui est assigné, et vu le peu de ressources que
|
|
||||||
cela coûte, il ne faut pas s'en priver.
|
|
||||||
|
|
||||||
Chaque dépendance pouvant être déclarée et épinglée dans un fichier, il
|
Chaque dépendance devra déclarer et épingler dans un fichier la version nécessaire.
|
||||||
suffira de créer un nouvel environment vierge, puis d'utiliser ce
|
Lors de la création d'un nouvel environnement vierge, il suffira d'utiliser ce
|
||||||
fichier comme paramètre pour installer les prérequis au bon
|
fichier comme paramètre afin d'installer les prérequis au bon
|
||||||
fonctionnement de notre application et vérifier que cet environnement
|
fonctionnement de notre application.
|
||||||
est bien reproductible.
|
Ceci autorise une reproductibilité quasi parfaite de l'environnement.
|
||||||
|
|
||||||
Il est important de bien "épingler" les versions liées aux dépendances
|
Il est important de bien "épingler" les versions liées aux dépendances
|
||||||
de l'application. Cela peut éviter des effets de bord comme une nouvelle
|
de l'application. Cela peut éviter des effets de bord comme une nouvelle
|
||||||
version d'une librairie dans laquelle un bug aurait pu avoir été
|
version d'une librairie dans laquelle un bug aurait pu avoir été
|
||||||
introduit. Parce qu'il arrive que ce genre de problème apparaisse, et
|
introduit. Parce qu'il arrive que ce genre de problème apparaisse, et
|
||||||
lorsque ce sera le cas, ce sera systématiquement au mauvais moment.
|
lorsque ce sera le cas, ce sera systématiquement au mauvais moment \footnote{Le paquet PyLint dépend par exemple d'Astroid; \href{https://github.com/PyCQA/pylint-django/issues/343}{en janvier 2022}, ce dernier a été mis à jour sans respecter le principe de versions sémantiques et introduisant une régression. PyLint spécifiait que sa dépendance avec Astroid devait respecter une version ~2.9. Lors de sa mise à jour en 2.9.1, Astroid a introduit un changement majeur, qui faisait planter Pylint. L'épinglage explicite aurait pu éviter ceci.}
|
||||||
|
|
||||||
\subsection{\#3 - Sauver la configuration directement au niveau de
|
\subsection{Sauver la configuration directement au niveau de l'environnement}
|
||||||
l'environnement}
|
|
||||||
|
|
||||||
Nous voulons éviter d'avoir à recompiler/redéployer l'application parce
|
Il faut éviter d'avoir à recompiler/redéployer l'application simplement parce
|
||||||
que:
|
que:
|
||||||
|
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
\def\labelenumi{\arabic{enumi}.}
|
|
||||||
\item
|
\item
|
||||||
l'adresse du serveur de messagerie a été modifiée,
|
l'adresse du serveur de messagerie a été modifiée,
|
||||||
\item
|
\item
|
||||||
|
@ -143,35 +123,26 @@ que:
|
||||||
\item
|
\item
|
||||||
la base de données a été déplacée
|
la base de données a été déplacée
|
||||||
\item
|
\item
|
||||||
\ldots\hspace{0pt}
|
...
|
||||||
\end{enumerate}
|
\end{enumerate}
|
||||||
|
|
||||||
En pratique, toute information susceptible de modifier un lien vers une
|
En pratique, toute information susceptible d'évoluer ou de changer (un seuil, une ressource externe, un couple utilisateur/mot de passe, ...) doit se trouver dans un fichier ou dans une variable d'environnement, et doit être facilement modifiable.
|
||||||
ressource annexe doit se trouver dans un fichier ou dans une variable
|
|
||||||
d'environnement, et doit être facilement modifiable. En allant un pas
|
|
||||||
plus loin, ceci de paramétrer facilement un environnement (par exemple,
|
|
||||||
un container), simplement en modifiant une variable de configuration qui
|
|
||||||
spécifierait la base de données sur laquelle l'application devra se
|
|
||||||
connecter.
|
|
||||||
|
|
||||||
Toute clé de configuration (nom du serveur de base de données, adresse
|
Ceci permet de paramétrer facilement un environnement (par exemple, un container), simplement en modifiant une variable de configuration qui spécifierait la base de données sur laquelle l'application devra se connecter.
|
||||||
d'un service Web externe, clé d'API pour l'interrogation d'une
|
|
||||||
ressource, \ldots\hspace{0pt}) sera définie directement au niveau de
|
Toute clé de configuration (nom du serveur de base de données, adresse d'un service Web externe, clé d'API pour l'interrogation d'une ressource, ...) sera définie directement au niveau de l'hôte - à aucun moment, nous ne devons trouver un mot de passe en clair dans le dépôt source ou une valeur susceptible d'évoluer, écrite en dur dans le code. \footnote{Ainsi, nous pourrions faire une \href{https://github.com/search?q=filenamefilename:.env DB_USERNAME&type=Code}{recherche sur Github} pour retrouver certaines variables d'environnement qui auraient été laissées en dur dans le code source de certains projets. Le \href{https://github.com/techgaun/github-dorks}{dépôt suivant} liste quelques idées de variables à rechercher...}.
|
||||||
l'hôte - à aucun moment, nous ne devons trouver un mot de passe en clair
|
|
||||||
dans le dépôt source ou une valeur susceptible d'évoluer, écrite en dur
|
|
||||||
dans le code.
|
|
||||||
|
|
||||||
Au moment de développer une nouvelle fonctionnalité, réfléchissez si
|
Au moment de développer une nouvelle fonctionnalité, réfléchissez si
|
||||||
l'un des composants utilisés risquerait de subir une modification: ce
|
l'un des paramètres utilisés risquerait de subir une modification ou s'il concerne un principe de sécurité: ce composant peut concerner une nouvelle chaîne de connexion, un point de terminaison nécessaire à télécharger des données officielles ou un chemin vers un répertoire partagé pour y déposer un fichier.
|
||||||
composant peut concerner une nouvelle chaîne de connexion, un point de
|
|
||||||
terminaison nécessaire à télécharger des données officielles ou un
|
|
||||||
chemin vers un répertoire partagé pour y déposer un fichier.
|
|
||||||
|
|
||||||
\subsection{\#4 - Traiter les ressources externes comme des ressources
|
Le risque est de se retrouver avec une liste colossale de paramètres; pensez à leur assigner une variable par défaut.
|
||||||
|
Par exemple, Gitea expose \href{https://docs.gitea.io/en-us/config-cheat-sheet/}{la liste suivante de paramètres}; il serait impossible de tous les configurer un à un avant de pouvoir démarrer une instance.
|
||||||
|
|
||||||
|
\subsection{Traiter les ressources externes comme des ressources
|
||||||
attachées}
|
attachées}
|
||||||
|
|
||||||
Nous parlons de bases de données, de services de mise en cache, d'API
|
Nous parlons de bases de données, de services de mise en cache, d'API
|
||||||
externes, \ldots\hspace{0pt} L'application doit être capable d'effectuer
|
externes, ... L'application doit être capable d'effectuer
|
||||||
des changements au niveau de ces ressources sans que son code ne soit
|
des changements au niveau de ces ressources sans que son code ne soit
|
||||||
modifié. Nous parlons alors de \textbf{ressources attachées}, dont la
|
modifié. Nous parlons alors de \textbf{ressources attachées}, dont la
|
||||||
présence est nécessaire au bon fonctionnement de l'application, mais
|
présence est nécessaire au bon fonctionnement de l'application, mais
|
||||||
|
@ -187,21 +158,21 @@ Si une base de données ne fonctionne pas correctement (problème matériel
|
||||||
partir d'une précédente sauvegarde, et l'attacher à l'application sans
|
partir d'une précédente sauvegarde, et l'attacher à l'application sans
|
||||||
que le code source ne soit modifié. une solution consiste à passer
|
que le code source ne soit modifié. une solution consiste à passer
|
||||||
toutes ces informations (nom du serveur et type de base de données, clé
|
toutes ces informations (nom du serveur et type de base de données, clé
|
||||||
d'authentification, \ldots\hspace{0pt}) directement \emph{via} des
|
d'authentification, ... directement \emph{via} des
|
||||||
variables d'environnement.
|
variables d'environnement.
|
||||||
|
|
||||||
\includegraphics{images/12factors/attached-resources.png}
|
\includegraphics{images/12factors/attached-resources.png}
|
||||||
|
|
||||||
Nous serons ravis de pouvoir simplement modifier une chaîne
|
Nous serons ravis de pouvoir simplement modifier une chaîne
|
||||||
\texttt{sqlite:////tmp/my-tmp-sqlite.db\textquotesingle{}} en
|
\texttt{sqlite:////tmp/my-tmp-sqlite.db} en
|
||||||
\texttt{psql://user:pass@127.0.0.1:8458/db} lorsque ce sera nécessaire,
|
\texttt{psql://user:pass@127.0.0.1:8458/db} lorsque ce sera nécessaire,
|
||||||
sans avoir à recompiler ou redéployer les modifications.
|
sans avoir à recompiler ou redéployer les modifications.
|
||||||
|
Ces ressources sont donc spécifiés grâce à des variables d'environnement, et chacune d'entre elles dispose également d'un \textbf{type}, afin de profiter d'une correspondance dynamique entre un moteur d'exécution et une information de configuration.
|
||||||
|
|
||||||
\subsection{\#5 - Séparer proprement les phases de construction, de mise à
|
\subsection{Séparer proprement les phases de construction, de mise à
|
||||||
disposition et d'exécution}
|
disposition et d'exécution}
|
||||||
|
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
\def\labelenumi{\arabic{enumi}.}
|
|
||||||
\item
|
\item
|
||||||
La \textbf{construction} (\emph{build}) convertit un code source en un
|
La \textbf{construction} (\emph{build}) convertit un code source en un
|
||||||
ensemble de fichiers exécutables, associé à une version et à une
|
ensemble de fichiers exécutables, associé à une version et à une
|
||||||
|
@ -217,32 +188,23 @@ disposition et d'exécution}
|
||||||
\includegraphics{images/12factors/release.png}
|
\includegraphics{images/12factors/release.png}
|
||||||
|
|
||||||
Parmi les solutions possibles, nous pourrions nous pourrions nous baser
|
Parmi les solutions possibles, nous pourrions nous pourrions nous baser
|
||||||
sur les \emph{releases} de Gitea, sur un serveur d'artefacts ou sur
|
sur les \emph{releases} de Gitea, sur un serveur d'artefacts (\href{https://fr.wikipedia.org/wiki/Capistrano_(logiciel)}{Capistrano}), voire directement au niveau de forge logicielle (Gitea, Github, Gitlab, ...).
|
||||||
\href{https://fr.wikipedia.org/wiki/Capistrano_(logiciel)}{Capistrano}.
|
|
||||||
|
|
||||||
\subsection{\#6 - Les processus d'exécution ne doivent rien connaître ou
|
\subsection{Les processus d'exécution ne doivent rien connaître ou
|
||||||
conserver de l'état de l'application}
|
conserver de l'état de l'application}
|
||||||
|
|
||||||
Toute information stockée en mémoire ou sur disque ne doit pas altérer
|
Toute information stockée en mémoire ou sur disque ne doit pas altérer
|
||||||
le comportement futur de l'application, par exemple après un redémarrage
|
le comportement futur de l'application, par exemple après un redémarrage
|
||||||
non souhaité.
|
non souhaité.
|
||||||
|
Dit autrement, l'exécution de l'application ne doit pas dépendre de la présence d'une information stockée en mémoire ou sur disque.
|
||||||
|
|
||||||
Pratiquement, si l'application devait rencontrer un problème, l'objectif
|
Pratiquement, si l'application devait rencontrer un problème, il est nécessaire qu'elle puisse redémarrer rapidement, éventuellement en étant déployée sur un autre serveur - par exemple suite à un problème matériel.
|
||||||
est de pouvoir la redémarrer rapidement sur un autre serveur (par
|
Toute information stockée physiquement sur le premier hôte durant son exécution sur le premier hôte, puisqu'elle pourra avoir été entretemps perdue.
|
||||||
exemple suite à un problème matériel). Toute information qui aurait été
|
Lors d'une initialisation ou réinitialisation, la solution consiste donc à jouer sur les variables d'environnement (cf. \#3) et sur les informations que l'on pourra trouver au niveau des ressources attachées (cf \#4), et faire en sorte que les informations et données primordiales puissent être récupérées ou reconstruites.
|
||||||
stockée durant l'exécution de l'application sur le premier hôte serait
|
|
||||||
donc perdue. Si une réinitialisation devait être nécessaire,
|
|
||||||
l'application ne devra pas compter sur la présence d'une information au
|
|
||||||
niveau du nouveau système. La solution consiste donc à jouer sur les
|
|
||||||
variables d'environnement (cf. \#3) et sur les informations que l'on
|
|
||||||
pourra trouver au niveau des ressources attachées (cf \#4).
|
|
||||||
|
|
||||||
Il serait également difficile d'appliquer une mise à l'échelle de
|
Il serait également difficile d'appliquer une mise à l'échelle de l'application, en ajoutant un nouveau serveur d'exécution, si une donnée indispensable à son fonctionnement devait se trouver sur la seule machine où elle est actuellement exécutée.
|
||||||
l'application, en ajoutant un nouveau serveur d'exécution, si une donnée
|
|
||||||
indispensable à son fonctionnement devait se trouver sur la seule
|
|
||||||
machine où elle est actuellement exécutée.
|
|
||||||
|
|
||||||
\subsection{\#7 - Autoriser la liaison d'un port de l'application à un port
|
\subsection{Autoriser la liaison d'un port de l'application à un port
|
||||||
du système hôte}
|
du système hôte}
|
||||||
|
|
||||||
Les applications 12-factors sont auto-contenues et peuvent fonctionner
|
Les applications 12-factors sont auto-contenues et peuvent fonctionner
|
||||||
|
@ -253,7 +215,10 @@ en HTTP ou via un autre protocole.
|
||||||
|
|
||||||
\includegraphics{images/diagrams/12-factors-7.png}
|
\includegraphics{images/diagrams/12-factors-7.png}
|
||||||
|
|
||||||
\subsection{\#8 - Faites confiance aux processus systèmes pour l'exécution
|
L'applicatoin fonctionne de manière autonome et expose un port (ici, le 8000).
|
||||||
|
Le serveur (= l'hôte) choisit d'appliquer une correspondance entre "son" port 443 et le port offert par l'application (8000).
|
||||||
|
|
||||||
|
\subsection{Faites confiance aux processus systèmes pour l'exécution
|
||||||
de l'application}
|
de l'application}
|
||||||
|
|
||||||
Comme décrit plus haut (cf. \#6), l'application doit utiliser des
|
Comme décrit plus haut (cf. \#6), l'application doit utiliser des
|
||||||
|
@ -261,40 +226,38 @@ processus \emph{stateless} (sans état). Nous pouvons créer et utiliser
|
||||||
des processus supplémentaires pour tenir plus facilement une lourde
|
des processus supplémentaires pour tenir plus facilement une lourde
|
||||||
charge, ou dédier des processus particuliers pour certaines tâches:
|
charge, ou dédier des processus particuliers pour certaines tâches:
|
||||||
requêtes HTTP \emph{via} des processus Web; \emph{long-running} jobs
|
requêtes HTTP \emph{via} des processus Web; \emph{long-running} jobs
|
||||||
pour des processus asynchrones, \ldots\hspace{0pt} Si cela existe au
|
pour des processus asynchrones, \ldots\hspace{0pt} Si cela existe sur l'hôte hébergeant l'application, ne vous fatiguez pas: utilisez le.
|
||||||
niveau du système, ne vous fatiguez pas: utilisez le.
|
|
||||||
|
|
||||||
\includegraphics{images/12factors/process-types.png}
|
\includegraphics{images/12factors/process-types.png}
|
||||||
|
|
||||||
\subsection{\#9 - Améliorer la robustesse de l'application grâce à des
|
\subsection{Améliorer la robustesse de l'application grâce à des
|
||||||
arrêts élégants et à des démarrages rapides}
|
arrêts élégants et à des démarrages rapides}
|
||||||
|
|
||||||
Par "arrêt élégant", nous voulons surtout éviter le
|
Par "arrêt élégant", nous voulons surtout éviter le fameux
|
||||||
\texttt{kill\ -9\ \textless{}pid\textgreater{}} ou tout autre arrêt
|
\texttt{kill\ -9\ \textless{}pid\textgreater{}} (ou équivalent), ou tout autre arrêt brutal d'un processus qui nécessiterait une intervention urgente du
|
||||||
brutal d'un processus qui nécessiterait une intervention urgente du
|
superviseur.
|
||||||
superviseur. De cette manière, les requêtes en cours peuvent se terminer
|
En prévoyant une manière élégante d'envoyer un signal de terminaison,
|
||||||
au mieux, tandis que le démarrage rapide de nouveaux processus
|
|
||||||
améliorera la balance d'un processus en cours d'extinction vers des
|
\begin{enumerate}
|
||||||
processus tout frais.
|
\item Les requêtes en cours peuvent se terminer au mieux,
|
||||||
|
\item Le démarrage rapide de nouveaux processus améliorera la balance d'un processus en cours d'extinction vers des processus tout frais, en autorisant l'exécution parallèle d'anciens et de nouveaux "types" de processus
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
L'intégration de ces mécanismes dès les premières étapes de
|
L'intégration de ces mécanismes dès les premières étapes de
|
||||||
développement limitera les perturbations et facilitera la prise en
|
développement limitera les perturbations et facilitera la prise en
|
||||||
compte d'arrêts inopinés (problème matériel, redémarrage du système
|
compte d'arrêts inopinés (problème matériel, redémarrage du système
|
||||||
hôte, etc.).
|
hôte, etc.).
|
||||||
|
|
||||||
\subsection{\#10 - Conserver les différents environnements aussi similaires
|
\includegraphics{images/12factors/process-type-chronology.png}
|
||||||
|
|
||||||
|
\subsection{Conserver les différents environnements aussi similaires
|
||||||
que possible, et limiter les divergences entre un environnement de
|
que possible, et limiter les divergences entre un environnement de
|
||||||
développement et de production}
|
développement et de production}
|
||||||
|
|
||||||
L'exemple donné est un développeur qui utilise macOS, NGinx et SQLite,
|
L'exemple donné est un développeur qui utilise macOS, NGinx et SQLite,
|
||||||
tandis que l'environnement de production tourne sur une CentOS avec
|
tandis que l'environnement de production tourne sur une CentOS avec
|
||||||
Apache2 et PostgreSQL. Faire en sorte que tous les environnements soient
|
Apache2 et PostgreSQL.
|
||||||
les plus similaires possibles limite les divergences entre
|
Faire en sorte que tous les environnements soient les plus similaires possibles limite les divergences entre environnements, facilite les déploiements et limite la casse et la découverte de modules non compatibles, au plus proche de la phase de développement, selon le principe de la corde d'Andon \cite[p. 140]{devops_handbook} \index{Andon} \footnote{Pour donner un exemple tout bête, SQLite utilise un
|
||||||
environnements, facilite les déploiements et limite la casse et la
|
|
||||||
découverte de modules non compatibles dès les premières phases de
|
|
||||||
développement.
|
|
||||||
|
|
||||||
Pour vous donner un exemple tout bête, SQLite utilise un
|
|
||||||
\href{https://www.sqlite.org/datatype3.html}{mécanisme de stockage
|
\href{https://www.sqlite.org/datatype3.html}{mécanisme de stockage
|
||||||
dynamique}, associée à la valeur plutôt qu'au schéma, \emph{via} un
|
dynamique}, associée à la valeur plutôt qu'au schéma, \emph{via} un
|
||||||
système d'affinités. Un autre moteur de base de données définira un
|
système d'affinités. Un autre moteur de base de données définira un
|
||||||
|
@ -306,40 +269,162 @@ caractères}. Si vous faites vos développements sous SQLite et que vous
|
||||||
rencontrez une URL de plus de 200 caractères, votre développement sera
|
rencontrez une URL de plus de 200 caractères, votre développement sera
|
||||||
passera parfaitement bien, mais plantera en production (ou en
|
passera parfaitement bien, mais plantera en production (ou en
|
||||||
\emph{staging}, si vous faites les choses un peu mieux) parce que les
|
\emph{staging}, si vous faites les choses un peu mieux) parce que les
|
||||||
données seront tronquées\ldots\hspace{0pt}
|
données seront tronquées, et que cela ne plaira pas à la base de données.
|
||||||
|
|
||||||
Conserver des environements similaires limite ce genre de désagréments.
|
Conserver des environements similaires limite ce genre de désagréments.}
|
||||||
|
|
||||||
\subsection{\#11 - Gérer les journeaux d'évènements comme des flux}
|
\subsection{Gérer les journeaux d'évènements comme des flux}
|
||||||
|
|
||||||
Une application ne doit jamais se soucier de l'endroit où ses évènements
|
Une application ne doit jamais se soucier de l'endroit où les évènements qui la concerne seront écrits, mais se doit simplement de les envoyer sur la sortie \texttt{stdout}.
|
||||||
seront écrits, mais simplement de les envoyer sur la sortie
|
De cette manière, que nous soyons en développement sur le poste d'un développeur avec une sortie console ou sur une machine de production avec un envoi vers une instance \href{https://www.graylog.org/}{Greylog} ou \href{https://sentry.io/welcome/}{Sentry}, le routage des journaux sera réalisé en fonction de sa nécessité et de sa criticité, et non pas parce que le développeur l'a spécifié en dur dans son code.
|
||||||
\texttt{stdout}. De cette manière, que nous soyons en développement sur
|
|
||||||
le poste d'un développeur avec une sortie console ou sur une machine de
|
|
||||||
production avec un envoi vers une instance
|
|
||||||
\href{https://www.graylog.org/}{Greylog} ou
|
|
||||||
\href{https://sentry.io/welcome/}{Sentry}, le routage des journaux sera
|
|
||||||
réalisé en fonction de sa nécessité et de sa criticité, et non pas parce
|
|
||||||
que le développeur l'a spécifié en dur dans son code. Cette phase est
|
|
||||||
critique, dans la mesure où les journaux d'exécution sont la seule
|
|
||||||
manière pour une application de communiquer son état vers l'extérieur:
|
|
||||||
recevoir une erreur interne de serveur est une chose; pouvoir obtenir un
|
|
||||||
minimum d'informations, voire un contexte de plantage complet en est une
|
|
||||||
autre.
|
|
||||||
|
|
||||||
\subsection{\#12 - Isoler les tâches administratives du reste de
|
Cette phase est critique, dans la mesure où les journaux d'exécution sont la seule manière pour une application de communiquer son état vers l'extérieur: recevoir une erreur interne de serveur est une chose; pouvoir obtenir un minimum d'informations, voire un contexte de plantage complet en est une autre.
|
||||||
l'application}
|
La différence entre ces deux points vous fera, au mieux, gagner plusieurs heures sur l'identification ou la résolution d'un problème.
|
||||||
|
|
||||||
|
\subsection{Isoler les tâches administratives}
|
||||||
|
|
||||||
Evitez qu'une migration ne puisse être démarrée depuis une URL de
|
Evitez qu'une migration ne puisse être démarrée depuis une URL de
|
||||||
l'application, ou qu'un envoi massif de notifications ne soit accessible
|
l'application, ou qu'un envoi massif de notifications ne soit accessible
|
||||||
pour n'importe quel utilisateur: les tâches administratives ne doivent
|
pour n'importe quel utilisateur: les tâches administratives ne doivent
|
||||||
être accessibles qu'à un administrateur. Les applications 12facteurs
|
être accessibles qu'à un administrateur. Les applications 12facteurs
|
||||||
favorisent les langages qui mettent un environnement REPL (pour
|
favorisent les langages qui mettent un environnement REPL (pour
|
||||||
\emph{Read}, \emph{Eval}, \emph{Print} et \emph{Loop}) à disposition (au
|
\emph{Read}, \emph{Eval}, \emph{Print} et \emph{Loop}) \index{REPL} à disposition (au
|
||||||
hasard: \href{https://pythonprogramminglanguage.com/repl/}{Python} ou
|
hasard: \href{https://pythonprogramminglanguage.com/repl/}{Python} ou
|
||||||
\href{https://kotlinlang.org/}{Kotlin}), ce qui facilite les étapes de
|
\href{https://kotlinlang.org/}{Kotlin}), ce qui facilite les étapes de
|
||||||
maintenance.
|
maintenance.
|
||||||
|
|
||||||
|
\subsection{Conclusions}
|
||||||
|
|
||||||
|
Une application devient nettement plus maintenable dès lors que l'équipe
|
||||||
|
de développement suit de près les différentes étapes de sa conception,
|
||||||
|
de la demande jusqu'à son aboutissement en production.
|
||||||
|
\cite[pp. 293-294]{devops_handbook}.
|
||||||
|
Au fur et à mesure que le code est délibérément construit pour être maintenable, l'équipe gagne en rapidité, en qualité et en fiabilité de déploiement, ce qui facilite les tâches opérationnelles:
|
||||||
|
|
||||||
|
\begin{enumerate}
|
||||||
|
\item
|
||||||
|
Activation d'une télémétrie suffisante dans les applications et les
|
||||||
|
environnements
|
||||||
|
\item
|
||||||
|
Conservation précise des dépendances nécessaires
|
||||||
|
\item
|
||||||
|
Résilience des services et plantage élégant (i.e. \textbf{sans finir
|
||||||
|
sur un SEGFAULT avec l'OS dans les choux et un écran bleu})
|
||||||
|
\item
|
||||||
|
Compatibilité entre les différentes versions (n+1, \ldots\hspace{0pt})
|
||||||
|
\item
|
||||||
|
Gestion de l'espace de stockage associé à un environnement (pour
|
||||||
|
éviter d'avoir un environnement de production qui fait 157
|
||||||
|
Tera-octets)
|
||||||
|
\item
|
||||||
|
Activation de la recherche dans les logs
|
||||||
|
\item
|
||||||
|
Traces des requêtes provenant des utilisateurs, indépendamment des
|
||||||
|
services utilisés
|
||||||
|
\item
|
||||||
|
Centralisation de la configuration (\textbf{via} ZooKeeper, par
|
||||||
|
exemple)
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
\section{Robustesse et flexibilité du code source}
|
||||||
|
|
||||||
|
\begin{quote}
|
||||||
|
Un code mal pensé entraîne nécessairement une perte d'énergie et de temps.
|
||||||
|
Il est plus simple de réfléchir, au moment de la conception du programme, à une architecture permettant une meilleure maintenabilité que de devoir corriger un code "sale" \emph{a posteriori}.
|
||||||
|
C'est pour aider les développeurs à rester dans le droit chemin que les principes SOLID ont été énumérés. \cite{gnu_linux_mag_hs_104}
|
||||||
|
\end{quote}
|
||||||
|
|
||||||
|
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
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
Des équivalents à ces directives existent au niveau des composants, puis au niveau architectural:
|
||||||
|
|
||||||
|
\begin{enumerate}
|
||||||
|
\item
|
||||||
|
Reuse/release équivalence principle,
|
||||||
|
\item
|
||||||
|
Common Closure Principle,
|
||||||
|
\item
|
||||||
|
Common Reuse Principle.
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
\includegraphics{images/arch-comp-modules.png}
|
||||||
|
|
||||||
|
\subsection{Single Responsility Principle}
|
||||||
|
|
||||||
|
|
||||||
|
Le principe de responsabilité unique conseille de disposer de concepts ou domaines d'activité qui ne s'occupent chacun que d'une et une seule
|
||||||
|
chose.
|
||||||
|
Ceci rejoint (un peu) la \href{https://en.wikipedia.org/wiki/Unix_philosophy}{Philosophie Unix}, documentée par Doug McIlroy et qui demande de "\emph{faire une seule chose, mais de le faire bien}" \cite{unix_philosophy}.
|
||||||
|
|
||||||
|
Une classe ou un élément de programmation ne doit donc pas avoir plus d'une raison de changer.
|
||||||
|
|
||||||
|
Il est également possible d'étendre ce principe en fonction d'acteurs:
|
||||||
|
|
||||||
|
\begin{quote}
|
||||||
|
A module should be responsible to one and only one actor. \cite{clean_architecture}
|
||||||
|
|
||||||
|
--- Robert C. Martin
|
||||||
|
\end{quote}
|
||||||
|
|
||||||
|
Plutôt que de centraliser le maximum de code à un seul endroit ou dans une seule classe par convenance ou commodité \footnote{Aussi appelé
|
||||||
|
\emph{God-Like object}}, le principe de responsabilité unique suggère que chaque classe soit responsable d'un et un seul concept.
|
||||||
|
|
||||||
|
Une manière de voir les choses consiste à différencier les acteurs ou les intervenants: imaginez disposer d'une classe représentant des données de membres du personnel.
|
||||||
|
Ces données pourraient être demandées par trois acteurs, le CFO, le CTO et le COO. Ceux-ci ont tous besoin de données et d'informations relatives à une même base de données centralisées, mais ont chacun besoin d'une représentation différente ou de traitements distincts. \cite{clean_architecture}
|
||||||
|
|
||||||
|
Nous sommes d'accord qu'il s'agit à chaque fois de données liées aux employés, mais elles vont un cran plus loin et pourraient nécessiter des
|
||||||
|
ajustements spécifiques en fonction de l'acteur concerné et de la manière dont il souhaite disposer des données.
|
||||||
|
Dès que possible, identifiez les différents acteurs et demandeurs, en vue de prévoir les modifications qui pourraient être demandées par l'un d'entre eux.
|
||||||
|
|
||||||
|
Dans le cas d'un élément de code centralisé, une modification induite par un des acteurs pourrait ainsi avoir un impact sur les données utilisées par les autres.
|
||||||
|
|
||||||
|
Vous trouverez ci-dessous une classe \texttt{Document}, dont chaque instance est représentée par trois propriétés: son titre, son contenu et sa date de publication.
|
||||||
|
Une méthode \texttt{render} permet également de proposer (très grossièrement) un type de sortie et un format de contenu: \texttt{XML} ou \texttt{Markdown}.
|
||||||
|
|
||||||
|
\begin{listing}[H]
|
||||||
|
\begin{minted}[tabsize=4]{Python}
|
||||||
|
class Document:
|
||||||
|
def __init__(self, title, content, published_at):
|
||||||
|
self.title = title
|
||||||
|
self.content = content
|
||||||
|
self.published_at = published_at
|
||||||
|
|
||||||
|
def render(self, format_type):
|
||||||
|
if format_type == "XML":
|
||||||
|
return """<?xml version = "1.0"?>
|
||||||
|
<document>
|
||||||
|
<title>{}</title>
|
||||||
|
<content>{}</content>
|
||||||
|
<publication_date>{}</publication_date>
|
||||||
|
</document>""".format(
|
||||||
|
self.title,
|
||||||
|
self.content,
|
||||||
|
self.published_at.isoformat()
|
||||||
|
)
|
||||||
|
|
||||||
|
if format_type == "Markdown":
|
||||||
|
import markdown
|
||||||
|
return markdown.markdown(self.content)
|
||||||
|
|
||||||
|
raise ValueError(
|
||||||
|
"Format type '{}' is not known".format(format_type)
|
||||||
|
)
|
||||||
|
\end{minted}
|
||||||
|
\caption{Un convertisseur de document un peu bateau}
|
||||||
|
\end{listing}
|
||||||
|
|
||||||
\section{Tests unitaires et d'intégration}
|
\section{Tests unitaires et d'intégration}
|
||||||
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
1
main.tex
1
main.tex
|
@ -8,6 +8,7 @@
|
||||||
\usepackage{minted}
|
\usepackage{minted}
|
||||||
\usepackage{ulem}
|
\usepackage{ulem}
|
||||||
\usepackage{graphics}
|
\usepackage{graphics}
|
||||||
|
\usepackage{float}
|
||||||
\usepackage[export]{adjustbox}
|
\usepackage[export]{adjustbox}
|
||||||
|
|
||||||
\renewcommand\listlistingname{Liste des morceaux de code}
|
\renewcommand\listlistingname{Liste des morceaux de code}
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue