Writing, again and again
This commit is contained in:
parent
2f4718c002
commit
c15e05349d
|
@ -190,7 +190,7 @@ suivantes:
|
||||||
On fait confiance à django\_environ pour traduire la chaîne de
|
On fait confiance à django\_environ pour traduire la chaîne de
|
||||||
connexion à la base de données.
|
connexion à la base de données.
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
\section{Création des répertoires de logs}
|
\section{Création des répertoires de logs}
|
||||||
|
|
||||||
|
|
||||||
|
@ -198,16 +198,16 @@ suivantes:
|
||||||
mkdir -p /var/www/gwift/static
|
mkdir -p /var/www/gwift/static
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
|
||||||
\section{Socket}
|
\section{Socket}
|
||||||
|
|
||||||
Dans le fichier \texttt{/etc/tmpfiles.d/gwift.conf}:
|
Dans le fichier \texttt{/etc/tmpfiles.d/gwift.conf}:
|
||||||
|
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
D /var/run/webapps 0775 gwift gunicorn_sockets -
|
D /var/run/webapps 0775 gwift gunicorn_sockets -
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
|
||||||
Suivi de la création par systemd :
|
Suivi de la création par systemd :
|
||||||
|
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
systemd-tmpfiles --create
|
systemd-tmpfiles --create
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
@ -316,13 +316,13 @@ l'aide de la commande \texttt{supervisorctl}:
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
et 443 (HTTPS).
|
et 443 (HTTPS).
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
firewall-cmd --permanent --zone=public --add-service=http
|
firewall-cmd --permanent --zone=public --add-service=http
|
||||||
firewall-cmd --permanent --zone=public --add-service=https
|
firewall-cmd --permanent --zone=public --add-service=https
|
||||||
firewall-cmd --reload
|
firewall-cmd --reload
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item
|
\item
|
||||||
On ouvre le port 80, uniquement pour autoriser une connexion HTTP,
|
On ouvre le port 80, uniquement pour autoriser une connexion HTTP,
|
||||||
|
@ -338,14 +338,14 @@ l'aide de la commande \texttt{supervisorctl}:
|
||||||
yum install nginx -y
|
yum install nginx -y
|
||||||
usermod -a -G gunicorn_sockets nginx
|
usermod -a -G gunicorn_sockets nginx
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
|
||||||
On configure ensuite le fichier \texttt{/etc/nginx/conf.d/gwift.conf}:
|
On configure ensuite le fichier \texttt{/etc/nginx/conf.d/gwift.conf}:
|
||||||
|
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
upstream gwift_app {
|
upstream gwift_app {
|
||||||
server unix:/var/run/webapps/gunicorn_gwift.sock fail_timeout=0;
|
server unix:/var/run/webapps/gunicorn_gwift.sock fail_timeout=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name <server_name>;
|
server_name <server_name>;
|
||||||
|
@ -362,7 +362,7 @@ l'aide de la commande \texttt{supervisorctl}:
|
||||||
gzip_types gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
|
gzip_types gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
|
||||||
|
|
||||||
|
|
||||||
location /static/ {
|
location /static/ {
|
||||||
access_log off;
|
access_log off;
|
||||||
expires 30d;
|
expires 30d;
|
||||||
add_header Pragma public;
|
add_header Pragma public;
|
||||||
|
@ -372,7 +372,7 @@ l'aide de la commande \texttt{supervisorctl}:
|
||||||
}
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
|
|
||||||
|
@ -380,7 +380,7 @@ l'aide de la commande \texttt{supervisorctl}:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item
|
\item
|
||||||
Ce répertoire sera complété par la commande \texttt{collectstatic} que
|
Ce répertoire sera complété par la commande \texttt{collectstatic} que
|
||||||
|
@ -450,3 +450,40 @@ Et dans le fichier crontab :
|
||||||
|
|
||||||
\section{Ansible}
|
\section{Ansible}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\section{Docker-Compose}
|
||||||
|
|
||||||
|
(c/c Ced' - 2020-01-24)
|
||||||
|
|
||||||
|
Ça y est, j'ai fait un test sur mon portable avec docker et cookiecutter
|
||||||
|
pour django.
|
||||||
|
|
||||||
|
D'abords, après avoir installer docker-compose et les dépendances sous
|
||||||
|
debian, tu dois t'ajouter dans le groupe docker, sinon il faut être root
|
||||||
|
pour utiliser docker. Ensuite, j'ai relancé mon pc car juste relancé un
|
||||||
|
shell n'a pas suffit pour que je puisse utiliser docker avec mon compte.
|
||||||
|
|
||||||
|
Bon après c'est facile, un petit virtualenv pour cookiecutter, suivit
|
||||||
|
d'une installation du template django. Et puis j'ai suivi sans t
|
||||||
|
\url{https://cookiecutter-django.readthedocs.io/en/latest/developing-locally-docker.html}
|
||||||
|
|
||||||
|
Alors, il télécharge les images, fait un petit update, installe les
|
||||||
|
dépendances de dev, install les requirement pip \ldots\hspace{0pt}
|
||||||
|
|
||||||
|
Du coup, ça prend vite de la place: image.png
|
||||||
|
|
||||||
|
L'image de base python passe de 179 à 740 MB. Et là j'en ai pour presque
|
||||||
|
1,5 GB d'un coup.
|
||||||
|
|
||||||
|
Mais par contre, j'ai un python 3.7 direct et postgres 10 sans rien
|
||||||
|
faire ou presque.
|
||||||
|
|
||||||
|
La partie ci-dessous a été reprise telle quelle de
|
||||||
|
\href{https://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html}{la
|
||||||
|
documentation de cookie-cutter-django}.
|
||||||
|
|
||||||
|
le serveur de déploiement ne doit avoir qu'un accès en lecture au dépôt
|
||||||
|
source.
|
||||||
|
|
||||||
|
On peut aussi passer par fabric, ansible, chef ou puppet.
|
|
@ -1,36 +0,0 @@
|
||||||
\chapter{Docker-Compose}
|
|
||||||
|
|
||||||
|
|
||||||
(c/c Ced' - 2020-01-24)
|
|
||||||
|
|
||||||
Ça y est, j'ai fait un test sur mon portable avec docker et cookiecutter
|
|
||||||
pour django.
|
|
||||||
|
|
||||||
D'abords, après avoir installer docker-compose et les dépendances sous
|
|
||||||
debian, tu dois t'ajouter dans le groupe docker, sinon il faut être root
|
|
||||||
pour utiliser docker. Ensuite, j'ai relancé mon pc car juste relancé un
|
|
||||||
shell n'a pas suffit pour que je puisse utiliser docker avec mon compte.
|
|
||||||
|
|
||||||
Bon après c'est facile, un petit virtualenv pour cookiecutter, suivit
|
|
||||||
d'une installation du template django. Et puis j'ai suivi sans t
|
|
||||||
\url{https://cookiecutter-django.readthedocs.io/en/latest/developing-locally-docker.html}
|
|
||||||
|
|
||||||
Alors, il télécharge les images, fait un petit update, installe les
|
|
||||||
dépendances de dev, install les requirement pip \ldots\hspace{0pt}
|
|
||||||
|
|
||||||
Du coup, ça prend vite de la place: image.png
|
|
||||||
|
|
||||||
L'image de base python passe de 179 à 740 MB. Et là j'en ai pour presque
|
|
||||||
1,5 GB d'un coup.
|
|
||||||
|
|
||||||
Mais par contre, j'ai un python 3.7 direct et postgres 10 sans rien
|
|
||||||
faire ou presque.
|
|
||||||
|
|
||||||
La partie ci-dessous a été reprise telle quelle de
|
|
||||||
\href{https://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html}{la
|
|
||||||
documentation de cookie-cutter-django}.
|
|
||||||
|
|
||||||
le serveur de déploiement ne doit avoir qu'un accès en lecture au dépôt
|
|
||||||
source.
|
|
||||||
|
|
||||||
On peut aussi passer par fabric, ansible, chef ou puppet.
|
|
|
@ -1,2 +0,0 @@
|
||||||
\chapter{Graphs}
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
Deux types d'applications peuvent être considérées: les applications consommant intensivement des données (\textit{Data-intensive applications}) et les applications consommant principalement des cycles processeur (\textit{Compute-intensive applications}).
|
Deux types d'applications peuvent être considérées: les applications consommant intensivement des données (\textit{Data-intensive applications}) et les applications consommant principalement des cycles processeur (\textit{Compute-intensive applications}).
|
||||||
Une application consommant principalement des données est rarement limitée par la puisse du CPU, mais plus souvent par la quantité et la complexité des structures de données, et la vitesse à laquelle celles-ci doivent être échangées. \cite[p. 3]{data_intensive}
|
Une application consommant principalement des données est rarement limitée par la puisse du CPU, mais plus souvent par la quantité et la complexité des structures de données, et la vitesse à laquelle celles-ci doivent être échangées. \cite[p. 3]{data_intensive}
|
||||||
|
|
||||||
|
\section{Composants}
|
||||||
|
|
||||||
Nous pouvons distinguer plusieurs types de composants:
|
Nous pouvons distinguer plusieurs types de composants:
|
||||||
|
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
|
@ -11,7 +13,7 @@ Nous pouvons distinguer plusieurs types de composants:
|
||||||
\item \textbf{Les composants de supervision}, qui permettent de s'assurer que tout fonctionne correctement, et qu'aucun incident n'est prévu ou n'a été constaté récemment.
|
\item \textbf{Les composants de supervision}, qui permettent de s'assurer que tout fonctionne correctement, et qu'aucun incident n'est prévu ou n'a été constaté récemment.
|
||||||
\end{enumerate}
|
\end{enumerate}
|
||||||
|
|
||||||
\section{Composants fonctionnels}
|
\subsection{Composants fonctionnels}
|
||||||
|
|
||||||
\begin{figure}[H]
|
\begin{figure}[H]
|
||||||
\centering
|
\centering
|
||||||
|
@ -35,18 +37,26 @@ Une application \textit{data-intensive} est généralement composée des blocs f
|
||||||
\hline
|
\hline
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
|
|
||||||
\subsection{Bases de données}
|
\subsubsection{Bases de données}
|
||||||
|
|
||||||
\subsection{Tâches asynchrones}
|
\subsubsection{Tâches asynchrones}
|
||||||
|
|
||||||
\subsection{Mise en cache}
|
\subsubsection{Mise en cache}
|
||||||
|
|
||||||
\section{Composants périphériques}
|
\subsection{Composants périphériques}
|
||||||
|
|
||||||
Les composants périphériques gravitent autour du fonctionnement normal de l'application.
|
Les composants périphériques gravitent autour du fonctionnement normal de l'application.
|
||||||
Ce sont les "petites mains" qui aident à ce que l'application soit résiliente ou que la charge soit amoindrie ou répartie entre plusieurs instances.
|
Ce sont les "petites mains" qui aident à ce que l'application soit résiliente ou que la charge soit amoindrie ou répartie entre plusieurs instances.
|
||||||
Il s'agit des composants comme le proxy inverse ou les distributeurs de charge: ce sont des composants qui sont fortement recommandés, mais pas obligatoires à au bon fonctionnement de l'application.
|
Il s'agit des composants comme le proxy inverse ou les distributeurs de charge: ce sont des composants qui sont fortement recommandés, mais pas obligatoires à au bon fonctionnement de l'application.
|
||||||
|
|
||||||
|
\begin{quote}
|
||||||
|
Hard disks are reported having a mean time to failure (MTTF) of about 1° to 50 years.
|
||||||
|
Thus, on a storage cluster with 10,000 disks, we should expect on average one disk to die per day. \cite[p. 7]{data_design}
|
||||||
|
\end{quote}
|
||||||
|
|
||||||
|
Avoir des composants périphériques correctement configurés permet d'anticiper ce type d'erreurs, et donc d'augmenter la résilience de l'application. \index{Résilience}
|
||||||
|
Certaines plateformes, comme Amazon Web Services \index{AWS}, favorisent la flexibilité et l'eslasticité plutôt que la disponibilité d'une seule machine. \cite[p. 8]{data_design}
|
||||||
|
C'est donc une bonne idée de faire en sorte que des erreurs d'indisponibilité peuvent arriver \footnote{Netflix a par exemple développer le \href{https://github.com/netflix/chaosmonkey}{Chaos Monkey}, qui s'occupe d'éteindre au hasard des machines virtuels et containers dans un environnement de production, juste pour faire en sorte que les équipes soient drillées à développer toutes sortes de mécanismes de remise en service - }.
|
||||||
|
|
||||||
\begin{tabular}{|p{0.2\linewidth}|p{0.55\linewidth}|p{0.15\linewidth}|}
|
\begin{tabular}{|p{0.2\linewidth}|p{0.55\linewidth}|p{0.15\linewidth}|}
|
||||||
\hline
|
\hline
|
||||||
|
@ -62,6 +72,8 @@ Il s'agit des composants comme le proxy inverse ou les distributeurs de charge:
|
||||||
\hline
|
\hline
|
||||||
Healthcheck & & Supervisord \\
|
Healthcheck & & Supervisord \\
|
||||||
\hline
|
\hline
|
||||||
|
Télémétrie & & \\
|
||||||
|
\hline
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
|
|
||||||
Si nous schématisons l'infrastructure et le chemin parcouru par une requête, nous pourrions arriver à la synthèse suivante:
|
Si nous schématisons l'infrastructure et le chemin parcouru par une requête, nous pourrions arriver à la synthèse suivante:
|
||||||
|
@ -92,18 +104,26 @@ Si nous schématisons l'infrastructure et le chemin parcouru par une requête, n
|
||||||
|
|
||||||
\includegraphics{images/diagrams/architecture.png}
|
\includegraphics{images/diagrams/architecture.png}
|
||||||
|
|
||||||
\subsection{Firewall}
|
\subsubsection{Firewall}
|
||||||
|
|
||||||
|
|
||||||
\subsection{Reverse proxy}
|
\subsubsection{Reverse proxy}
|
||||||
|
|
||||||
Le principe du \textbf{proxy inverse} est de pouvoir rediriger du trafic entrant vers une application hébergée sur le système. Il serait tout à fait possible de rendre notre application directement accessible depuis l'extérieur, mais le proxy a aussi l'intérêt de pouvoir élever la sécurité du serveur (SSL) et décharger le serveur applicatif grâce à un mécanisme de cache ou en compressant certains résultats \footnote{\url{https://fr.wikipedia.org/wiki/Proxy_inverse}}
|
Le principe du \textbf{proxy inverse} est de pouvoir rediriger du trafic entrant vers une application hébergée sur le système. Il serait tout à fait possible de rendre notre application directement accessible depuis l'extérieur, mais le proxy a aussi l'intérêt de pouvoir élever la sécurité du serveur (SSL) et décharger le serveur applicatif grâce à un mécanisme de cache ou en compressant certains résultats \footnote{\url{https://fr.wikipedia.org/wiki/Proxy_inverse}}
|
||||||
|
|
||||||
\subsection{Répartiteur de charge (\textit{Load balancer})}
|
\subsubsection{Répartiteur de charge (\textit{Load balancer})}
|
||||||
|
|
||||||
\subsection{Serveurs d'application (\textit{Workers})}
|
\subsubsection{Serveurs d'application (\textit{Workers})}
|
||||||
|
|
||||||
\section{Composants de supervision}
|
\subsubsection{Télémétrie}
|
||||||
|
|
||||||
|
\begin{quote}
|
||||||
|
Once a rocket has left the ground, telemetry is essential for tracking what's happening and for understanding failures \cite[p. 10]{data_design}
|
||||||
|
\end{quote}
|
||||||
|
|
||||||
|
Le
|
||||||
|
|
||||||
|
\subsection{Composants de supervision}
|
||||||
|
|
||||||
\begin{tabular}{|p{0.2\linewidth}|p{0.55\linewidth}|p{0.15\linewidth}|}
|
\begin{tabular}{|p{0.2\linewidth}|p{0.55\linewidth}|p{0.15\linewidth}|}
|
||||||
\hline
|
\hline
|
||||||
|
@ -117,9 +137,9 @@ Le principe du \textbf{proxy inverse} est de pouvoir rediriger du trafic entrant
|
||||||
\hline
|
\hline
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
|
|
||||||
\subsection{Supervision des processus}
|
\subsubsection{Supervision des processus}
|
||||||
|
|
||||||
\subsection{Journaux}
|
\subsubsection{Journaux}
|
||||||
|
|
||||||
La présence de journaux, leur structure et de la définition précise de leurs niveaux est essentielle; ce sont eux qui permettent d'obtenir des informations quant au statut de l'application:
|
La présence de journaux, leur structure et de la définition précise de leurs niveaux est essentielle; ce sont eux qui permettent d'obtenir des informations quant au statut de l'application:
|
||||||
|
|
||||||
|
@ -185,7 +205,7 @@ Pour utiliser nos loggers, il suffit de copier le petit bout de code suivant:
|
||||||
\href{https://docs.djangoproject.com/en/stable/topics/logging/\#examples}{Par
|
\href{https://docs.djangoproject.com/en/stable/topics/logging/\#examples}{Par
|
||||||
exemples}.
|
exemples}.
|
||||||
|
|
||||||
\subsection{Exploitation des journaux}
|
\subsubsection{Exploitation des journaux}
|
||||||
|
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
\item
|
\item
|
||||||
|
|
|
@ -1,22 +1,32 @@
|
||||||
\chapter{Fiabilité, évolutivité et maintenabilité}
|
\chapter{Fiabilité, évolutivité et maintenabilité}
|
||||||
|
|
||||||
\begin{quote}
|
\begin{quote}
|
||||||
The primary cost of maintenance is in spelunking and risk
|
The primary cost of maintenance is in spelunking and risk \cite[139]{clean_architecture}
|
||||||
\cite[139]{clean_architecture}
|
|
||||||
|
|
||||||
--- Robert C. Martin
|
--- Robert C. Martin
|
||||||
\end{quote}
|
\end{quote}
|
||||||
|
|
||||||
Pour la méthode de travail et de développement, nous allons nous baser
|
Que l'utilisateur soit humain, bot automatique ou client Web, la finalité d'un développement est de fournir des applications résilientes, pouvant être mises à l'échelle et maintenables \cite[p. 6]{data_design} :
|
||||||
sur les \href{https://12factor.net/fr/}{The Twelve-factor App} - ou plus
|
|
||||||
simplement les \textbf{12 facteurs}.
|
\begin{itemize}
|
||||||
|
\item
|
||||||
|
La résilience consiste à ce que l'application continue à fonctionner \textit{correctement} - c'est-à-dire à ce qu'elle fournisse un service correct au niveau de performance désiré, même quand les choses passent mal.
|
||||||
|
Cela signifie que ces systèmes ont la capacité d'anticiper certains types d'erreurs, ou en tout cas, de les gérer de manière propre.
|
||||||
|
\item
|
||||||
|
La mise à échelle consiste à autoriser le système à \textit{grandir} - soit par le trafic pouvant être pris en charge, soit par son volume de données, soit par sa complexité.
|
||||||
|
\item
|
||||||
|
Au fil du temps, il est probable que plusieurs personnes se succèdent à travailler sur l'évolution d'une application, qu'ils travaillent sur sa conception ou sur son exploitation.
|
||||||
|
La maintenabilité consiste à faire en sorte que toute intervention puisse être réalisée de manière productive.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
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 simplement les \textbf{12 facteurs}.
|
||||||
Suivre ces concepts permet de:
|
Suivre ces concepts permet de:
|
||||||
|
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
\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 qui héberge l'application ou les services,
|
la gestion de l'hôte qui héberge l'application ou les services,
|
||||||
diminuer la divergence entre les différents 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
|
||||||
|
@ -31,7 +41,7 @@ Suivre ces concepts permet de:
|
||||||
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
|
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.
|
||||||
\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 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
|
||||||
|
@ -43,18 +53,14 @@ cette méthode, nous avons:
|
||||||
|
|
||||||
\section{Une base de code unique, suivie par un contrôle de versions}
|
\section{Une base de code unique, suivie par un contrôle de versions}
|
||||||
|
|
||||||
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.
|
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.
|
||||||
Git est reconnu dans l'industrie comme standard des systèmes de contrôles de versions, malgré une courbe d'apprentissage assez ardue.
|
Git est reconnu dans l'industrie comme standard des systèmes de contrôles de versions, malgré une courbe d'apprentissage assez ardue.
|
||||||
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}
|
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}
|
||||||
|
|
||||||
\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, est qu'il permet un accès facile et rapide à la forme la plus à jour du code, sans aucun besoin de coordination. \cite[pp. 288-298]{devops_handbook}.
|
||||||
Infrastructure: "Un des avantages d'utiliser un dépôt unique de sources,
|
Ce cépôt n'est pas uniquement destiné à hébergé le code source, mais également à d'autres artefacts et autres formes de connaissance:
|
||||||
est qu'il permet un accès facile et rapide à la forme la plus à jour du
|
|
||||||
code, sans aucun besoin de coordination. \cite[pp. 288-298]{devops_handbook}.
|
|
||||||
Ce cépôt n'est pas uniquement destiné à hébergé le code source, mais
|
|
||||||
également à d'autres artefacts et autres formes de connaissance:
|
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item
|
\item
|
||||||
|
@ -73,30 +79,18 @@ Ce cépôt n'est pas uniquement destiné à hébergé le code source, mais
|
||||||
|
|
||||||
\section{Déclaration explicite et isolation des dépendances}
|
\section{Déclaration explicite et isolation des dépendances}
|
||||||
|
|
||||||
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 cible.
|
||||||
manière, et doit pouvoir être répétée quel que soit l'environnement
|
Ceci permet d'éviter que l'application n'utilise une dépendance qui ne 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.
|
||||||
cible.
|
Dans le cas de Python, cela pourra être fait au travers de \href{https://pypi.org/project/pip/}{PIP - Package Installer for Python} ou \href{https://python-poetry.org/}{Poetry}.
|
||||||
Ceci permet d'éviter que l'application n'utilise une dépendance qui ne 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.
|
|
||||||
Dans le cas de Python, cela pourra être fait au travers de
|
|
||||||
\href{https://pypi.org/project/pip/}{PIP - Package Installer for Python}
|
|
||||||
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, ...)
|
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, ...)
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
Chaque dépendance devra déclarer et épingler dans un fichier la version nécessaire.
|
Chaque dépendance devra déclarer et épingler dans un fichier la version nécessaire.
|
||||||
Lors de la création d'un nouvel environnement vierge, il suffira d'utiliser ce
|
Lors de la création d'un nouvel environnement vierge, il suffira d'utiliser ce fichier comme paramètre afin d'installer les prérequis au bon fonctionnement de notre application.
|
||||||
fichier comme paramètre afin d'installer les prérequis au bon
|
|
||||||
fonctionnement de notre application.
|
|
||||||
Ceci autorise une reproductibilité quasi parfaite de l'environnement.
|
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 version d'une librairie dans laquelle un bug aurait pu avoir été introduit.
|
||||||
de l'application. Cela peut éviter des effets de bord comme une nouvelle
|
Parce qu'il arrive que ce genre de problème apparaisse, et 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.}
|
||||||
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
|
|
||||||
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.}
|
|
||||||
|
|
||||||
\section{Configuration applicative}
|
\section{Configuration applicative}
|
||||||
|
|
||||||
|
@ -114,7 +108,7 @@ que:
|
||||||
...
|
...
|
||||||
\end{enumerate}
|
\end{enumerate}
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
@ -128,32 +122,17 @@ Par exemple, Gitea expose \href{https://docs.gitea.io/en-us/config-cheat-sheet/}
|
||||||
|
|
||||||
\section{Ressources externes}
|
\section{Ressources externes}
|
||||||
|
|
||||||
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, ... L'application doit être capable d'effectuer des changements au niveau de ces ressources sans que son code ne soit modifié.
|
||||||
externes, ... L'application doit être capable d'effectuer
|
Nous parlons alors de \textbf{ressources attachées}, dont la présence est nécessaire au bon fonctionnement de l'application, mais pour lesquelles le \textbf{type} n'est pas obligatoirement défini.
|
||||||
des changements au niveau de ces ressources sans que son code ne soit
|
|
||||||
modifié. Nous parlons alors de \textbf{ressources attachées}, dont la
|
|
||||||
présence est nécessaire au bon fonctionnement de l'application, mais
|
|
||||||
pour lesquelles le \textbf{type} n'est pas obligatoirement défini.
|
|
||||||
|
|
||||||
Nous voulons par exemple "une base de données" et "une mémoire cache",
|
Nous voulons par exemple "une base de données" et "une mémoire cache", et pas "une base MariaDB et une instance Memcached".
|
||||||
et pas "une base MariaDB et une instance Memcached". De cette manière,
|
De cette manière, les ressources peuvent être attachées et détachées d'un déploiement à la volée.
|
||||||
les ressources peuvent être attachées et détachées d'un déploiement à la
|
|
||||||
volée.
|
|
||||||
|
|
||||||
Si une base de données ne fonctionne pas correctement (problème matériel
|
Si une base de données ne fonctionne pas correctement (problème matériel ?), l'administrateur pourrait simplement restaurer un nouveau serveur à partir d'une précédente sauvegarde, et l'attacher à l'application sans 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é d'authentification, ... directement \emph{via} des variables d'environnement.
|
||||||
?), l'administrateur pourrait simplement restaurer un nouveau serveur à
|
|
||||||
partir d'une précédente sauvegarde, et l'attacher à l'application sans
|
|
||||||
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é
|
|
||||||
d'authentification, ... directement \emph{via} des
|
|
||||||
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} en \texttt{psql://user:pass@127.0.0.1:8458/db} lorsque ce sera nécessaire, sans avoir à recompiler ou redéployer les modifications.
|
||||||
\texttt{sqlite:////tmp/my-tmp-sqlite.db} en
|
|
||||||
\texttt{psql://user:pass@127.0.0.1:8458/db} lorsque ce sera nécessaire,
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
\section{Séparation des phases de construction}
|
\section{Séparation des phases de construction}
|
||||||
|
@ -181,8 +160,8 @@ sur les \emph{releases} de Gitea, sur un serveur d'artefacts (\href{https://fr.w
|
||||||
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 non souhaité.
|
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 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.
|
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, 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.
|
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.
|
||||||
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.
|
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.
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
@ -200,9 +179,9 @@ en HTTP ou via un autre protocole.
|
||||||
L'applicatoin fonctionne de manière autonome et expose un port (ici, le 8000).
|
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).
|
Le serveur (= l'hôte) choisit d'appliquer une correspondance entre "son" port 443 et le port offert par l'application (8000).
|
||||||
|
|
||||||
\section{Connaissance et confiance des processys systèmes}
|
\section{Connaissance et confiance des processus systèmes}
|
||||||
|
|
||||||
Comme décrit plus haut (cf. \#6), l'application doit utiliser des processus \emph{stateless} (sans état). Nous pouvons créer et utiliser des processus supplémentaires pour tenir plus facilement une lourde charge, ou dédier des particuliers pour certaines tâches: requêtes HTTP \emph{via} des processus Web; \emph{long-running} jobs pour des processus asynchrones, ...
|
Comme décrit plus haut (cf. \#6), l'application doit utiliser des processus \emph{stateless} (sans état). Nous pouvons créer et utiliser des processus supplémentaires pour tenir plus facilement une lourde charge, ou dédier des particuliers pour certaines tâches: requêtes HTTP \emph{via} des processus Web; \emph{long-running} jobs pour des processus asynchrones, ...
|
||||||
Si cela existe sur l'hôte hébergeant l'application, ne vous fatiguez pas: utilisez le.
|
Si cela existe sur l'hôte hébergeant l'application, ne vous fatiguez pas: utilisez le.
|
||||||
|
|
||||||
\includegraphics{images/12factors/process-types.png}
|
\includegraphics{images/12factors/process-types.png}
|
||||||
|
@ -211,12 +190,12 @@ Si cela existe sur l'hôte hébergeant l'application, ne vous fatiguez pas: util
|
||||||
|
|
||||||
Par "arrêt élégant", nous voulons surtout éviter le fameux
|
Par "arrêt élégant", nous voulons surtout éviter le fameux
|
||||||
\texttt{kill\ -9\ \textless{}pid\textgreater{}} (ou équivalent), ou tout autre arrêt brutal d'un processus qui nécessiterait une intervention urgente du
|
\texttt{kill\ -9\ \textless{}pid\textgreater{}} (ou équivalent), ou tout autre arrêt brutal d'un processus qui nécessiterait une intervention urgente du
|
||||||
superviseur.
|
superviseur.
|
||||||
En prévoyant une manière élégante d'envoyer un signal de terminaison,
|
En prévoyant une manière élégante d'envoyer un signal de terminaison,
|
||||||
|
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
\item Les requêtes en cours peuvent se terminer au mieux,
|
\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
|
\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}
|
\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
|
||||||
|
@ -228,60 +207,51 @@ hôte, etc.).
|
||||||
|
|
||||||
\section{Similarité des environnements}
|
\section{Similarité des environnements}
|
||||||
|
|
||||||
Conserver les différents environnements aussi similaires
|
Conserver les différents environnements aussi similaires que possible, et limiter les divergences entre un environnement de développement et de production.
|
||||||
que possible, et limiter les divergences entre un environnement de
|
|
||||||
développement et de production
|
|
||||||
|
|
||||||
L'exemple donné est un développeur qui utilise macOS, NGinx et SQLite, tandis que l'environnement de production tourne sur une CentOS avec Apache2 et PostgreSQL.
|
L'exemple donné est un développeur qui utilise macOS, NGinx et SQLite, tandis que l'environnement de production tourne sur une CentOS avec Apache2 et PostgreSQL.
|
||||||
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
|
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
|
||||||
\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 système d'affinités. Un autre moteur de base de données définira un schéma statique et rigide, où la valeur sera déterminée par son contenant.
|
\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 système d'affinités. Un autre moteur de base de données définira un schéma statique et rigide, où la valeur sera déterminée par son contenant.
|
||||||
Un champ \texttt{URLField} proposé par Django a une longeur maximale par défaut de \href{https://docs.djangoproject.com/en/3.1/ref/forms/fields/\#django.forms.URLField}{200 caractères}.
|
Un champ \texttt{URLField} proposé par Django a une longeur maximale par défaut de \href{https://docs.djangoproject.com/en/3.1/ref/forms/fields/\#django.forms.URLField}{200 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 passera parfaitement bien, mais plantera en production (ou en \emph{staging}, si vous faites les choses peu mieux) parce que les données seront tronquées, et que cela ne plaira pas à la base de données.
|
Si vous faites vos développements sous SQLite et que vous rencontrez une URL de plus de 200 caractères, votre développement sera passera parfaitement bien, mais plantera en production (ou en \emph{staging}, si vous faites les choses peu mieux) parce que les 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.}
|
||||||
|
|
||||||
|
Ceci permet également de proposer à nos utilisateurs un bac à sable dans lequel ils pourront explorer et réaliser des expérimentations en toute sécurité, sans quel cela n'ait d'impact sur un \textit{réel} environnement de production, où les conséquences pourraient être beaucoup plus graves. \cite[p. 9]{data_design}
|
||||||
|
|
||||||
\section{Journaux de flux évènementiels}
|
\section{Journaux de flux évènementiels}
|
||||||
|
|
||||||
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}.
|
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}.
|
||||||
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.
|
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.
|
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.
|
||||||
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.
|
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.
|
||||||
|
|
||||||
\section{Isolation des tâches administratives}
|
\section{Isolation des tâches administratives}
|
||||||
|
|
||||||
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 pour n'importe quel utilisateur: les tâches administratives ne doivent être accessibles qu'à un administrateur.
|
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 pour n'importe quel utilisateur: les tâches administratives ne doivent être accessibles qu'à un administrateur.
|
||||||
Les applications 12facteurs favorisent les langages qui mettent un environnement REPL (pour \emph{Read}, \emph{Eval}, \emph{Print} et \emph{Loop}) \index{REPL} à disposition (au hasard: \href{https://pythonprogramminglanguage.com/repl/}{Python} ou \href{https://kotlinlang.org/}{Kotlin}), ce qui facilite les étapes de maintenance.
|
Les applications 12facteurs favorisent les langages qui mettent un environnement REPL (pour \emph{Read}, \emph{Eval}, \emph{Print} et \emph{Loop}) \index{REPL} à disposition (au hasard: \href{https://pythonprogramminglanguage.com/repl/}{Python} ou \href{https://kotlinlang.org/}{Kotlin}), ce qui facilite les étapes de maintenance.
|
||||||
|
|
||||||
\section{Conclusions}
|
\section{Conclusions}
|
||||||
|
|
||||||
Une application devient nettement plus maintenable dès lors que l'équipe
|
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}.
|
||||||
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:
|
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}
|
\begin{enumerate}
|
||||||
\item
|
\item
|
||||||
Activation d'une télémétrie suffisante dans les applications et les
|
Activation d'une télémétrie suffisante dans les applications et les environnements
|
||||||
environnements
|
|
||||||
\item
|
\item
|
||||||
Conservation précise des dépendances nécessaires
|
Conservation précise des dépendances nécessaires
|
||||||
\item
|
\item
|
||||||
Résilience des services et plantage élégant (i.e. \textbf{sans finir
|
Résilience des services et plantage élégant (i.e. \textbf{sans finir un SEGFAULT avec l'OS dans les choux et un écran bleu})
|
||||||
sur un SEGFAULT avec l'OS dans les choux et un écran bleu})
|
|
||||||
\item
|
\item
|
||||||
Compatibilité entre les différentes versions (n+1, \ldots\hspace{0pt})
|
Compatibilité entre les différentes versions (n+1, \ldots)
|
||||||
\item
|
\item
|
||||||
Gestion de l'espace de stockage associé à un environnement (pour
|
Gestion de l'espace de stockage associé à un environnement (pour éviter d'avoir un environnement de production qui fait 157 Tera-octets)
|
||||||
éviter d'avoir un environnement de production qui fait 157
|
|
||||||
Tera-octets)
|
|
||||||
\item
|
\item
|
||||||
Activation de la recherche dans les logs
|
Activation de la recherche dans les logs
|
||||||
\item
|
\item
|
||||||
Traces des requêtes provenant des utilisateurs, indépendamment des
|
Traces des requêtes provenant des utilisateurs, indépendamment des services utilisés
|
||||||
services utilisés
|
|
||||||
\item
|
\item
|
||||||
Centralisation de la configuration (\textbf{via} ZooKeeper, par
|
Centralisation de la configuration (\textbf{via} ZooKeeper, par exemple)
|
||||||
exemple)
|
|
||||||
\end{enumerate}
|
\end{enumerate}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
\chapter{Poésie de la programmation}
|
|
||||||
|
|
||||||
\begin{quote}
|
|
||||||
The primary cost of maintenance is in spelunking and risk
|
|
||||||
\cite[139]{clean_architecture}
|
|
||||||
|
|
||||||
--- Robert C. Martin
|
|
||||||
\end{quote}
|
|
|
@ -8,7 +8,7 @@ Le langage \href{https://www.python.org/}{Python} est un \href{https://docs.pyth
|
||||||
\caption{\url{https://xkcd.com/353/}}
|
\caption{\url{https://xkcd.com/353/}}
|
||||||
\end{figure}
|
\end{figure}
|
||||||
|
|
||||||
A première vue, et suivants les autres langages que vous connaitriez ou auriez déjà abordé, certains concepts restent difficiles à aborder: l'indentation définit l'étendue d'un bloc (classe, fonction, méthode, boucle, condition, il n'y a pas de typage fort des variables et le compilateur n'est pas là pour assurer le filet de sécurité avant la mise en production (puisqu'il n'y a pas de compilateur).
|
A première vue, et suivants les autres langages que vous connaitriez ou auriez déjà abordé, certains concepts restent difficiles à aborder: l'indentation définit l'étendue d'un bloc (classe, fonction, méthode, boucle, condition, il n'y a pas de typage fort des variables et le compilateur n'est pas là pour assurer le filet de sécurité avant la mise en production (puisqu'il n'y a pas de compilateur).
|
||||||
Et malgré ces quelques points, Python reste un langage généraliste accessible et "bon partout", et de pouvoir se reposer sur un écosystème stable et fonctionnel.
|
Et malgré ces quelques points, Python reste un langage généraliste accessible et "bon partout", et de pouvoir se reposer sur un écosystème stable et fonctionnel.
|
||||||
|
|
||||||
Il fonctionne avec un système d'améliorations basées sur des propositions: les PEP, ou "\textbf{Python Enhancement Proposal}\index{PEP}".
|
Il fonctionne avec un système d'améliorations basées sur des propositions: les PEP, ou "\textbf{Python Enhancement Proposal}\index{PEP}".
|
||||||
|
@ -19,7 +19,7 @@ Le langage Python utilise un typage dynamique appelé \href{https://fr.wikipedia
|
||||||
\begin{quote}
|
\begin{quote}
|
||||||
"\emph{When I see a bird that quacks like a duck, walks like a duck, has
|
"\emph{When I see a bird that quacks like a duck, walks like a duck, has
|
||||||
feathers and webbed feet and associates with ducks --- I'm certainly
|
feathers and webbed feet and associates with ducks --- I'm certainly
|
||||||
going to assume that he is a duck}"
|
going to assume that he is a duck}"
|
||||||
|
|
||||||
-- Source: \href{http://en.wikipedia.org/wiki/Duck_test}{Wikipedia}.
|
-- Source: \href{http://en.wikipedia.org/wiki/Duck_test}{Wikipedia}.
|
||||||
\end{quote}
|
\end{quote}
|
||||||
|
@ -34,7 +34,7 @@ ressources pourraient vous aider:
|
||||||
with Python} \cite{boring_stuff}, aka. \emph{Practical
|
with Python} \cite{boring_stuff}, aka. \emph{Practical
|
||||||
Programming for Total Beginners}
|
Programming for Total Beginners}
|
||||||
\item
|
\item
|
||||||
\textbf{Pour un (gros) niveau au dessus} et pour un état de l'art du langage, nous ne pouvons que vous recommander le livre Expert Python Programming \cite{expert_python}, qui aborde énormément d'aspects du langage en détails (mais pas toujours en profondeur): les différents types d'interpréteurs, les éléments de langage avancés, différents outils de productivité, métaprogrammation, optimisation de code, programmation orientée évènements, multithreading et concurrence, tests, ...
|
\textbf{Pour un (gros) niveau au dessus} et pour un état de l'art du langage, nous ne pouvons que vous recommander le livre Expert Python Programming \cite{expert_python}, qui aborde énormément d'aspects du langage en détails (mais pas toujours en profondeur): les différents types d'interpréteurs, les éléments de langage avancés, différents outils de productivité, métaprogrammation, optimisation de code, programmation orientée évènements, multithreading et concurrence, tests, ...
|
||||||
A ce jour, c'est le concentré de sujets liés au langage le plus intéressant qui ait pu arriver entre nos mains.
|
A ce jour, c'est le concentré de sujets liés au langage le plus intéressant qui ait pu arriver entre nos mains.
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
|
@ -60,21 +60,21 @@ permet de surcharger l'initialisation d'une instance de classe.
|
||||||
\end{minted}
|
\end{minted}
|
||||||
\end{listing}
|
\end{listing}
|
||||||
|
|
||||||
Ces méthodes, utilisées seules ou selon des combinaisons spécifiques, constituent les \emph{protocoles de langage}.
|
Ces méthodes, utilisées seules ou selon des combinaisons spécifiques, constituent les \emph{protocoles de langage}.
|
||||||
Une liste complètement des \emph{dunder methods} peut être trouvée dans la section \texttt{Data\ Model} de \href{https://docs.python.org/3/reference/datamodel.html}{la documentation du langage Python}.
|
Une liste complètement des \emph{dunder methods} peut être trouvée dans la section \texttt{Data\ Model} de \href{https://docs.python.org/3/reference/datamodel.html}{la documentation du langage Python}.
|
||||||
|
|
||||||
Tous les opérateurs sont également exposés comme des fonctions ordinaires du module \texttt{opeartor}, dont la \href{https://docs.python.org/3.9/library/operator.html}{documentation} donne un bon aperçu.
|
Tous les opérateurs sont également exposés comme des fonctions ordinaires du module \texttt{opeartor}, dont la \href{https://docs.python.org/3.9/library/operator.html}{documentation} donne un bon aperçu.
|
||||||
Indiquer qu'un type d'objet implémente un protocole du langage indique simplement qu'il est compatible avec une partie spécifique de la syntaxe du langage Python.
|
Indiquer qu'un type d'objet implémente un protocole du langage indique simplement qu'il est compatible avec une partie spécifique de la syntaxe du langage Python.
|
||||||
|
|
||||||
Vous trouverez ci-dessous un tableau reprenant les protocoles les plus courants:
|
Vous trouverez ci-dessous un tableau reprenant les protocoles les plus courants:
|
||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tabular}{ c c c }
|
\begin{tabular}{ c c c }
|
||||||
cell1 & cell2 & cell3 \\
|
cell1 & cell2 & cell3 \\
|
||||||
cell4 & cell5 & cell6 \\
|
cell4 & cell5 & cell6 \\
|
||||||
cell7 & cell8 & cell9
|
cell7 & cell8 & cell9
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
Les points principaux à présenter ci-dessus:
|
Les points principaux à présenter ci-dessus:
|
||||||
|
|
||||||
|
@ -103,15 +103,15 @@ L'exemple ci-dessous implémente la soustraction de deux matrices:
|
||||||
\begin{listing}
|
\begin{listing}
|
||||||
\begin{minted}[tabsize=4]{python}
|
\begin{minted}[tabsize=4]{python}
|
||||||
def __sub__(self, other):
|
def __sub__(self, other):
|
||||||
if (len(self.rows) != len(other.rows)
|
if (len(self.rows) != len(other.rows)
|
||||||
or len(self.rows[0]) != len(other.rows[0])
|
or len(self.rows[0]) != len(other.rows[0])
|
||||||
):
|
):
|
||||||
raise ValueError("Matrix dimensions don't match")
|
raise ValueError("Matrix dimensions don't match")
|
||||||
|
|
||||||
return Matrix(
|
return Matrix(
|
||||||
[
|
[
|
||||||
[a - b for a, b in zip(a_row, b_row)]
|
[a - b for a, b in zip(a_row, b_row)]
|
||||||
for a_row, b_row in zip(self.rows, other.rows)
|
for a_row, b_row in zip(self.rows, other.rows)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
\end{minted}
|
\end{minted}
|
||||||
|
@ -119,7 +119,7 @@ L'exemple ci-dessous implémente la soustraction de deux matrices:
|
||||||
|
|
||||||
Il suffit dès lors de réaliser la soustraction matricielle entre deux objets de type \texttt{Matrix} pour que la méthode \texttt{sub())} ci-dessous soit appelée.
|
Il suffit dès lors de réaliser la soustraction matricielle entre deux objets de type \texttt{Matrix} pour que la méthode \texttt{sub())} ci-dessous soit appelée.
|
||||||
|
|
||||||
En fait, l'intérêt concerne surtout la représentation de nos modèles, puisque chaque classe du modèle est représentée par la définition d'un objet Python.
|
En fait, l'intérêt concerne surtout la représentation de nos modèles, puisque chaque classe du modèle est représentée par la définition d'un objet Python.
|
||||||
Nous pouvons donc utiliser ces mêmes \textbf{dunder methods} (\textbf{double-underscores methods}) pour étoffer les protocoles du langage.
|
Nous pouvons donc utiliser ces mêmes \textbf{dunder methods} (\textbf{double-underscores methods}) pour étoffer les protocoles du langage.
|
||||||
|
|
||||||
\section{The Zen of Python}
|
\section{The Zen of Python}
|
||||||
|
@ -148,19 +148,19 @@ Nous pouvons donc utiliser ces mêmes \textbf{dunder methods} (\textbf{double-un
|
||||||
If the implementation is hard to explain, it's a bad idea.
|
If the implementation is hard to explain, it's a bad idea.
|
||||||
If the implementation is easy to explain, it may be a good idea.
|
If the implementation is easy to explain, it may be a good idea.
|
||||||
Namespaces are one honking great idea -- let's do more of those!
|
Namespaces are one honking great idea -- let's do more of those!
|
||||||
|
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
\caption{The Zen of Python}
|
\caption{The Zen of Python}
|
||||||
\end{listing}
|
\end{listing}
|
||||||
|
|
||||||
\section{Guide de style}
|
\section{Guide de style}
|
||||||
|
|
||||||
La première PEP qui va nous intéresser est la PEP8.
|
La première PEP qui va nous intéresser est la PEP8.
|
||||||
Elle spécifie comment du code Python doit être organisé ou formaté, quelles sont les conventions pour l'indentation, le nommage des variables et des classes, ...
|
Elle spécifie comment du code Python doit être organisé ou formaté, quelles sont les conventions pour l'indentation, le nommage des variables et des classes, ...
|
||||||
En bref, elle décrit comment écrire du code proprement, afin que d'autres développeurs puissent le reprendre facilement, ou simplement que votre base de code ne dérive lentement vers un seuil de non-maintenabilité.
|
En bref, elle décrit comment écrire du code proprement, afin que d'autres développeurs puissent le reprendre facilement, ou simplement que votre base de code ne dérive lentement vers un seuil de non-maintenabilité.
|
||||||
|
|
||||||
Dans cet objectif, un outil existe et listera l'ensemble des conventions qui ne sont pas correctement suivies dans votre projet: flake8. Pour l'installer, passez par pip.
|
Dans cet objectif, un outil existe et listera l'ensemble des conventions qui ne sont pas correctement suivies dans votre projet: flake8. Pour l'installer, passez par pip.
|
||||||
Lancez ensuite la commande \texttt{flake8} suivie du chemin à analyser (\texttt{.}, le nom d'un répertoire, le nom d'un fichier \texttt{.py}, ...).
|
Lancez ensuite la commande \texttt{flake8} suivie du chemin à analyser (\texttt{.}, le nom d'un répertoire, le nom d'un fichier \texttt{.py}, ...).
|
||||||
Si vous souhaitez uniquement avoir le nombre d'erreur de chaque type, saisissez les options \texttt{-\/-statistics\ -qq} - l'attribut \texttt{-qq} permettant simplement d'ignorer toute sortie console autre que les statistiques demandées).
|
Si vous souhaitez uniquement avoir le nombre d'erreur de chaque type, saisissez les options \texttt{-\/-statistics\ -qq} - l'attribut \texttt{-qq} permettant simplement d'ignorer toute sortie console autre que les statistiques demandées).
|
||||||
|
|
||||||
\begin{listing}[!ht]
|
\begin{listing}[!ht]
|
||||||
|
@ -182,26 +182,26 @@ Si vous ne voulez pas être dérangé sur votre manière de coder, et que vous v
|
||||||
|
|
||||||
\begin{listing}[!ht]
|
\begin{listing}[!ht]
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
$ pyflakes .
|
$ pyflakes .
|
||||||
...
|
...
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
\caption{Une utilisation de pyflakes}
|
\caption{Une utilisation de pyflakes}
|
||||||
\end{listing}
|
\end{listing}
|
||||||
|
|
||||||
A noter qu'un greffon pour \texttt{flake8} existe et donnera une estimation de la complexité de McCabe pour les fonctions trop complexes.
|
A noter qu'un greffon pour \texttt{flake8} existe et donnera une estimation de la complexité de McCabe pour les fonctions trop complexes.
|
||||||
Installez-le avec \texttt{pip\ install\ mccabe}, et activez-le avec le paramètre \texttt{-\/-max-complexity}.
|
Installez-le avec \texttt{pip\ install\ mccabe}, et activez-le avec le paramètre \texttt{-\/-max-complexity}.
|
||||||
Toute fonction dans la complexité est supérieure à cette valeur sera considérée comme trop complexe.
|
Toute fonction dans la complexité est supérieure à cette valeur sera considérée comme trop complexe.
|
||||||
|
|
||||||
\section{Conventions de documentation}
|
\section{Conventions de documentation}
|
||||||
|
|
||||||
Python étant un langage interprété fortement typé, il est plus que conseillé, au même titre que les tests unitaires que nous verrons plus bas, de documenter son code.
|
Python étant un langage interprété fortement typé, il est plus que conseillé, au même titre que les tests unitaires que nous verrons plus bas, de documenter son code.
|
||||||
Ceci impose une certaine rigueur, tout en améliorant énormément la qualité, la compréhension et la reprise du code par une tierce personne.
|
Ceci impose une certaine rigueur, tout en améliorant énormément la qualité, la compréhension et la reprise du code par une tierce personne.
|
||||||
Ceci implique aussi de \textbf{tout} documenter: les modules, les paquets, les classes, les fonctions, méthodes, ... ce qui peut aller à contrecourant d'autres pratiques \cite{clean_code}{53-74} ; il y a donc une juste mesure à prendre entre "tout documenter" et "tout bien documenter":
|
Ceci implique aussi de \textbf{tout} documenter: les modules, les paquets, les classes, les fonctions, méthodes, ... ce qui peut aller à contrecourant d'autres pratiques \cite{clean_code}{53-74} ; il y a donc une juste mesure à prendre entre "tout documenter" et "tout bien documenter":
|
||||||
|
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item
|
\item
|
||||||
Inutile d'ajouter des watermarks, auteurs, ...
|
Inutile d'ajouter des watermarks, auteurs, ...
|
||||||
Git ou tout VCS s'en sortira très bien et sera beaucoup plus efficace que n'importe quelle chaîne de caractères que vous pourriez indiquer et qui sera fausse dans six mois,
|
Git ou tout VCS s'en sortira très bien et sera beaucoup plus efficace que n'importe quelle chaîne de caractères que vous pourriez indiquer et qui sera fausse dans six mois,
|
||||||
\item
|
\item
|
||||||
Inutile de décrire quelque chose qui est évident; documenter la méthode \mintinline{python}{get_age()} d'une personne n'aura pas beaucoup d'intérêt
|
Inutile de décrire quelque chose qui est évident; documenter la méthode \mintinline{python}{get_age()} d'une personne n'aura pas beaucoup d'intérêt
|
||||||
|
@ -223,10 +223,10 @@ Il existe plusieurs types de balisages reconnus/approuvés:
|
||||||
|
|
||||||
\subsection{PEP 257}
|
\subsection{PEP 257}
|
||||||
|
|
||||||
La \href{https://peps.python.org/pep-0257/#what-is-a-docstring}{PEP-257} nous donne des recommandations haut-niveau concernant la structure des docstrings: ce qu'elles doivent contenir et comment l'expliciter, sans imposer quelle que mise en forme que ce soit.
|
La \href{https://peps.python.org/pep-0257/#what-is-a-docstring}{PEP-257} nous donne des recommandations haut-niveau concernant la structure des docstrings: ce qu'elles doivent contenir et comment l'expliciter, sans imposer quelle que mise en forme que ce soit.
|
||||||
de contenu, mais pas de forme, notamment sur la manière de représenter des docstrings ne nécessitant qu'une seule ligne, nécessitant plusieurs lignes ou de gérer l'indentation.
|
de contenu, mais pas de forme, notamment sur la manière de représenter des docstrings ne nécessitant qu'une seule ligne, nécessitant plusieurs lignes ou de gérer l'indentation.
|
||||||
Son objectif est d'arriver à une forme de cohérence lorsqu'un utilisateur souhaitera accéder à la propriété
|
Son objectif est d'arriver à une forme de cohérence lorsqu'un utilisateur souhaitera accéder à la propriété
|
||||||
Elle contient des conventions, pas des règles ou
|
Elle contient des conventions, pas des règles ou
|
||||||
|
|
||||||
\begin{quote}
|
\begin{quote}
|
||||||
“A universal convention supplies all of maintainability, clarity, consistency, and a foundation for good programming habits too. What it doesn’t do is insist that you follow it against your will. That’s Python!”
|
“A universal convention supplies all of maintainability, clarity, consistency, and a foundation for good programming habits too. What it doesn’t do is insist that you follow it against your will. That’s Python!”
|
||||||
|
@ -249,7 +249,7 @@ A remplir
|
||||||
|
|
||||||
\subsection{Napoleon}
|
\subsection{Napoleon}
|
||||||
|
|
||||||
Les \href{https://google.github.io/styleguide/pyguide.html\#38-comments-and-docstrings}{conventions proposées par Google} nous semblent plus faciles à lire que du RestructuredText, mais sont parfois moins bien intégrées que les docstrings officiellement supportées (par exemple, \href{https://clize.readthedocs.io/en/stable/}{clize} ne reconnait que du RestructuredText; \href{https://docs.djangoproject.com/en/stable/ref/contrib/admin/admindocs/}{l'auto-documentation} de Django également).
|
Les \href{https://google.github.io/styleguide/pyguide.html\#38-comments-and-docstrings}{conventions proposées par Google} nous semblent plus faciles à lire que du RestructuredText, mais sont parfois moins bien intégrées que les docstrings officiellement supportées (par exemple, \href{https://clize.readthedocs.io/en/stable/}{clize} ne reconnait que du RestructuredText; \href{https://docs.djangoproject.com/en/stable/ref/contrib/admin/admindocs/}{l'auto-documentation} de Django également).
|
||||||
|
|
||||||
L'exemple donné dans les guides de style de Google est celui-ci:
|
L'exemple donné dans les guides de style de Google est celui-ci:
|
||||||
|
|
||||||
|
@ -257,30 +257,30 @@ L'exemple donné dans les guides de style de Google est celui-ci:
|
||||||
\begin{minted}{python}
|
\begin{minted}{python}
|
||||||
def fetch_smalltable_rows(table_handle: smalltable.Table, keys: Sequence[Union[bytes, str]], require_all_keys: bool = False,) -> Mapping[bytes, Tuple[str]]:
|
def fetch_smalltable_rows(table_handle: smalltable.Table, keys: Sequence[Union[bytes, str]], require_all_keys: bool = False,) -> Mapping[bytes, Tuple[str]]:
|
||||||
"""Fetches rows from a Smalltable.
|
"""Fetches rows from a Smalltable.
|
||||||
|
|
||||||
Retrieves rows pertaining to the given keys from the Table instance
|
Retrieves rows pertaining to the given keys from the Table instance
|
||||||
represented by table_handle. String keys will be UTF-8 encoded.
|
represented by table_handle. String keys will be UTF-8 encoded.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
table_handle: An open smalltable.Table instance.
|
table_handle: An open smalltable.Table instance.
|
||||||
keys: A sequence of strings representing the key of each table
|
keys: A sequence of strings representing the key of each table
|
||||||
row to fetch. String keys will be UTF-8 encoded.
|
row to fetch. String keys will be UTF-8 encoded.
|
||||||
require_all_keys: Optional; If require_all_keys is True only
|
require_all_keys: Optional; If require_all_keys is True only
|
||||||
rows with values set for all keys will be returned.
|
rows with values set for all keys will be returned.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A dict mapping keys to the corresponding table row data
|
A dict mapping keys to the corresponding table row data
|
||||||
fetched. Each row is represented as a tuple of strings. For
|
fetched. Each row is represented as a tuple of strings. For
|
||||||
example:
|
example:
|
||||||
|
|
||||||
{b'Serak': ('Rigel VII', 'Preparer'),
|
{b'Serak': ('Rigel VII', 'Preparer'),
|
||||||
b'Zim': ('Irk', 'Invader'),
|
b'Zim': ('Irk', 'Invader'),
|
||||||
b'Lrrr': ('Omicron Persei 8', 'Emperor')}
|
b'Lrrr': ('Omicron Persei 8', 'Emperor')}
|
||||||
|
|
||||||
Returned keys are always bytes. If a key from the keys argument is
|
Returned keys are always bytes. If a key from the keys argument is
|
||||||
missing from the dictionary, then that row was not found in the
|
missing from the dictionary, then that row was not found in the
|
||||||
table (and require_all_keys must have been False).
|
table (and require_all_keys must have been False).
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
IOError: An error occurred accessing the smalltable.
|
IOError: An error occurred accessing the smalltable.
|
||||||
"""
|
"""
|
||||||
|
@ -371,13 +371,13 @@ code pas très propre et qui ne sert à rien:
|
||||||
\begin{minted}{python}
|
\begin{minted}{python}
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
"""On stocke la date du jour dans la variable ToD4y"""
|
"""On stocke la date du jour dans la variable ToD4y"""
|
||||||
|
|
||||||
ToD4y = datetime.today()
|
ToD4y = datetime.today()
|
||||||
|
|
||||||
def print_today(ToD4y):
|
def print_today(ToD4y):
|
||||||
today = ToD4y
|
today = ToD4y
|
||||||
print(ToD4y)
|
print(ToD4y)
|
||||||
|
|
||||||
def GetToday():
|
def GetToday():
|
||||||
return ToD4y
|
return ToD4y
|
||||||
|
|
||||||
|
@ -444,7 +444,7 @@ test.py:16:10: E0602: Undefined variable 'Get_Today' (undefined-variable)
|
||||||
Your code has been rated at -5.45/10
|
Your code has been rated at -5.45/10
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
|
||||||
En gros, j'ai programmé comme une grosse bouse anémique (et oui: le score d'évaluation du code permet d'aller en négatif).
|
En gros, j'ai programmé comme une grosse bouse anémique (et oui: le score d'évaluation du code permet d'aller en négatif).
|
||||||
En vrac, nous trouvons des problèmes liés:
|
En vrac, nous trouvons des problèmes liés:
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
|
@ -472,19 +472,19 @@ chaque code possède sa signification:
|
||||||
\item
|
\item
|
||||||
\textbf{E} pour les erreurs ou des bugs probablement présents dans le code
|
\textbf{E} pour les erreurs ou des bugs probablement présents dans le code
|
||||||
\item
|
\item
|
||||||
\textbf{F} pour les erreurs internes au fonctionnement de pylint, qui font que le traitement n'a pas pu aboutir.
|
\textbf{F} pour les erreurs internes au fonctionnement de pylint, qui font que le traitement n'a pas pu aboutir.
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
Connaissant ceci, il est extrêmement pratique d'intégrer pylint au niveau des processus d'intégration continue, puisque la présence d'une
|
Connaissant ceci, il est extrêmement pratique d'intégrer pylint au niveau des processus d'intégration continue, puisque la présence d'une
|
||||||
Pylint propose également une option particulièrement efficace, qui prend le paramètre \texttt{--errors-only}, et qui n'affiche que les occurrences appartenant à la catégorie \textbf{E}.
|
Pylint propose également une option particulièrement efficace, qui prend le paramètre \texttt{--errors-only}, et qui n'affiche que les occurrences appartenant à la catégorie \textbf{E}.
|
||||||
|
|
||||||
Si nous souhaitons ignorer l'une de ces catégories, ce doit être fait explicitement: de cette manière, nous marquons notre approbation pour que pylint ignore consciemment un élément en particulier.
|
Si nous souhaitons ignorer l'une de ces catégories, ce doit être fait explicitement: de cette manière, nous marquons notre approbation pour que pylint ignore consciemment un élément en particulier.
|
||||||
Cet élément peut être:
|
Cet élément peut être:
|
||||||
|
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
\item \textbf{Une ligne de code}
|
\item \textbf{Une ligne de code}
|
||||||
\item \textbf{Un bloc de code} - une fonction, une méthode, une classe, un module, ...
|
\item \textbf{Un bloc de code} - une fonction, une méthode, une classe, un module, ...
|
||||||
\item \textbf{Un projet entier}, en spécifiant la non-prise en compte au niveau du fichier \texttt{.pylintrc}, qui contient
|
\item \textbf{Un projet entier}, en spécifiant la non-prise en compte au niveau du fichier \texttt{.pylintrc}, qui contient
|
||||||
\end{enumerate}
|
\end{enumerate}
|
||||||
|
|
||||||
\subsection{Ignorer une ligne de code}
|
\subsection{Ignorer une ligne de code}
|
||||||
|
@ -496,10 +496,10 @@ Cet élément peut être:
|
||||||
|
|
||||||
\section{Formatage de code}
|
\section{Formatage de code}
|
||||||
|
|
||||||
Nous avons parlé ci-dessous de style de codage pour Python (PEP8), de style de rédaction pour la documentation (PEP257), d'un vérificateur pour nous indiquer quels morceaux de code doivent absolument être revus, ...
|
Nous avons parlé ci-dessous de style de codage pour Python (PEP8), de style de rédaction pour la documentation (PEP257), d'un vérificateur pour nous indiquer quels morceaux de code doivent absolument être revus, ...
|
||||||
Reste que ces tâches sont parfois (très) souvent fastidieuses: écrire un code propre et systématiquement cohérent est une tâche ardue.
|
Reste que ces tâches sont parfois (très) souvent fastidieuses: écrire un code propre et systématiquement cohérent est une tâche ardue.
|
||||||
Heureusement, il existe plusieurs outils pour nous aider au niveau du formatage automatique.
|
Heureusement, il existe plusieurs outils pour nous aider au niveau du formatage automatique.
|
||||||
Même si elle n'est pas parfaite, la librairie \href{https://black.readthedocs.io/en/stable/}{Black} arrive à un très bon compromis entre
|
Même si elle n'est pas parfaite, la librairie \href{https://black.readthedocs.io/en/stable/}{Black} arrive à un très bon compromis entre
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item Clarté du code
|
\item Clarté du code
|
||||||
|
@ -508,8 +508,8 @@ Même si elle n'est pas parfaite, la librairie \href{https://black.readthedocs.i
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
Est-ce que ce formatage est idéal et accepté par tout le monde ?
|
Est-ce que ce formatage est idéal et accepté par tout le monde ?
|
||||||
\textbf{Non}.
|
\textbf{Non}.
|
||||||
Même Pylint arrivera parfois à râler.
|
Même Pylint arrivera parfois à râler.
|
||||||
Mais ce formatage conviendra dans 97,83\% des cas (au moins).
|
Mais ce formatage conviendra dans 97,83\% des cas (au moins).
|
||||||
|
|
||||||
\begin{quote}
|
\begin{quote}
|
||||||
|
@ -535,7 +535,7 @@ vous concentrer sur le contenu}".
|
||||||
|
|
||||||
\section{Typage statique \index{PEP585}}
|
\section{Typage statique \index{PEP585}}
|
||||||
|
|
||||||
Nous vous disions ci-dessus que Python est un langage dynamique interprété.
|
Nous vous disions ci-dessus que Python est un langage dynamique interprété.
|
||||||
Concrètement, cela signifie que des erreurs qui auraient pu avoir été détectées lors de la phase de compilation, ne le sont pas avec Python.
|
Concrètement, cela signifie que des erreurs qui auraient pu avoir été détectées lors de la phase de compilation, ne le sont pas avec Python.
|
||||||
|
|
||||||
Il existe cependant une solution à ce problème, sous la forme de \href{http://mypy-lang.org/}{Mypy}, qui peut vérifier une forme de typage statique de votre code source, grâce à une expressivité du code, basée sur des annotations.
|
Il existe cependant une solution à ce problème, sous la forme de \href{http://mypy-lang.org/}{Mypy}, qui peut vérifier une forme de typage statique de votre code source, grâce à une expressivité du code, basée sur des annotations.
|
||||||
|
@ -545,10 +545,10 @@ Ces vérifications se présentent de la manière suivante:
|
||||||
\begin{listing}[H]
|
\begin{listing}[H]
|
||||||
\begin{minted}{python}
|
\begin{minted}{python}
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
def first_int_elem(l: List[int]) -> int:
|
def first_int_elem(l: List[int]) -> int:
|
||||||
return l[0] if l else None
|
return l[0] if l else None
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print(first_int_elem([1, 2, 3]))
|
print(first_int_elem([1, 2, 3]))
|
||||||
print(first_int_elem(['a', 'b', 'c']))
|
print(first_int_elem(['a', 'b', 'c']))
|
||||||
|
@ -561,7 +561,7 @@ Est-ce que le code ci-dessous fonctionne correctement ? \textbf{Oui}:
|
||||||
\begin{listing}[H]
|
\begin{listing}[H]
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
>>> python mypy-test.py
|
>>> python mypy-test.py
|
||||||
1
|
1
|
||||||
a
|
a
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
\end{listing}
|
\end{listing}
|
||||||
|
@ -569,7 +569,7 @@ Est-ce que le code ci-dessous fonctionne correctement ? \textbf{Oui}:
|
||||||
Malgré que nos annotations déclarent une liste d'entiers, rien ne nous empêche de lui envoyer une liste de caractères, sans que cela ne lui pose de problèmes.
|
Malgré que nos annotations déclarent une liste d'entiers, rien ne nous empêche de lui envoyer une liste de caractères, sans que cela ne lui pose de problèmes.
|
||||||
La signature de notre fonction n'est donc pas cohérente avec son comportement.
|
La signature de notre fonction n'est donc pas cohérente avec son comportement.
|
||||||
|
|
||||||
Est-ce que Mypy va râler ? \textbf{Oui, aussi}.
|
Est-ce que Mypy va râler ? \textbf{Oui, aussi}.
|
||||||
Non seulement nous retournons la valeur \texttt{None} si la liste est vide alors que nous lui annoncions un entier en sortie, mais en plus, nous l'appelons avec une liste de caractères, alors que nous nous attendons à une liste d'entiers:
|
Non seulement nous retournons la valeur \texttt{None} si la liste est vide alors que nous lui annoncions un entier en sortie, mais en plus, nous l'appelons avec une liste de caractères, alors que nous nous attendons à une liste d'entiers:
|
||||||
|
|
||||||
\begin{listing}[H]
|
\begin{listing}[H]
|
||||||
|
@ -595,7 +595,7 @@ Pour corriger ceci, nous devons:
|
||||||
|
|
||||||
\section{Tests unitaires}
|
\section{Tests unitaires}
|
||||||
|
|
||||||
Comme tout bon \textbf{langage de programmation moderne} qui se respecte, Python embarque tout un environnement facilitant le lancement de tests;
|
Comme tout bon \textbf{langage de programmation moderne} qui se respecte, Python embarque tout un environnement facilitant le lancement de tests;
|
||||||
Une bonne pratique (parfois discutée) consiste cependant à switcher vers \texttt{pytest}, qui présente quelques avantages par rapport au module \texttt{unittest}:
|
Une bonne pratique (parfois discutée) consiste cependant à switcher vers \texttt{pytest}, qui présente quelques avantages par rapport au module \texttt{unittest}:
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
|
@ -624,7 +624,7 @@ complètement biesse; on est sur la partie théorique ici):
|
||||||
\end{minted}
|
\end{minted}
|
||||||
\end{listing}
|
\end{listing}
|
||||||
|
|
||||||
Forcément, cela va planter.
|
Forcément, cela va planter.
|
||||||
Pour nous en assurer (dès fois que quelqu'un en doute), il nous suffit de démarrer la commande \texttt{pytest}:
|
Pour nous en assurer (dès fois que quelqu'un en doute), il nous suffit de démarrer la commande \texttt{pytest}:
|
||||||
|
|
||||||
\begin{listing}
|
\begin{listing}
|
||||||
|
@ -654,9 +654,14 @@ Pour nous en assurer (dès fois que quelqu'un en doute), il nous suffit de déma
|
||||||
Avec \texttt{pytest}, il convient d'utiliser le paquet \href{https://pypi.org/project/pytest-cov/}{\texttt{pytest-cov}}, suivi de la commande \texttt{pytest\ -\/-cov=gwift\ tests/}.
|
Avec \texttt{pytest}, il convient d'utiliser le paquet \href{https://pypi.org/project/pytest-cov/}{\texttt{pytest-cov}}, suivi de la commande \texttt{pytest\ -\/-cov=gwift\ tests/}.
|
||||||
|
|
||||||
Si vous préférez rester avec le cadre de tests de Django, vous pouvez passer par le paquet\href{https://pypi.org/project/django-coverage-plugin/}{django-coverage-plugin}.
|
Si vous préférez rester avec le cadre de tests de Django, vous pouvez passer par le paquet\href{https://pypi.org/project/django-coverage-plugin/}{django-coverage-plugin}.
|
||||||
Ajoutez-le dans le fichier \texttt{requirements/base.txt}, et lancez une couverture de code grâce à la commande \texttt{coverage}.
|
Ajoutez-le dans le fichier \texttt{requirements/base.txt}, et lancez une couverture de code grâce à la commande \texttt{coverage}.
|
||||||
La configuration peut se faire dans un fichier \texttt{.coveragerc} que vous placerez à la racine de votre projet, et qui sera lu lors de l'exécution.
|
La configuration peut se faire dans un fichier \texttt{.coveragerc} que vous placerez à la racine de votre projet, et qui sera lu lors de l'exécution.
|
||||||
|
|
||||||
|
\section{Gestion des exceptions}
|
||||||
|
|
||||||
|
Certains bogues causent des erreurs de mal-fonctionnement sont dus à une mauvaise conception endormie, qui ne se présente que dans certains cas très spécifiques, entourés d'une contexte inhabituel \cite[p. 9]{data_design}.
|
||||||
|
Il est primordial de gérer correctement ses exceptions, et de faire en sorte que celles qui peuvent être anticipées le soient dès la phase de développement.
|
||||||
|
|
||||||
\section{Gestion des versions de l'interpréteur}
|
\section{Gestion des versions de l'interpréteur}
|
||||||
|
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
|
@ -665,17 +670,17 @@ La configuration peut se faire dans un fichier \texttt{.coveragerc} que vous pla
|
||||||
|
|
||||||
\section{Matrice de compatibilité}
|
\section{Matrice de compatibilité}
|
||||||
|
|
||||||
Une matrice de compatibilité consiste à spécifier un ensemble de plusieurs versions d'un même interpréteur (ici, Python), afin de s'assurer que votre application continue à fonctionner.
|
Une matrice de compatibilité consiste à spécifier un ensemble de plusieurs versions d'un même interpréteur (ici, Python), afin de s'assurer que votre application continue à fonctionner.
|
||||||
Nous sommes donc un cran plus haut que la spécification des versions des librairies, puisque nous nous situons directement au niveau de l'interpréteur.
|
Nous sommes donc un cran plus haut que la spécification des versions des librairies, puisque nous nous situons directement au niveau de l'interpréteur.
|
||||||
L'objectif consiste à définir un tableau à deux dimensions, dans lequel nous trouverons la compatibilité entre notre application et une version de l'interpréteur.
|
L'objectif consiste à définir un tableau à deux dimensions, dans lequel nous trouverons la compatibilité entre notre application et une version de l'interpréteur.
|
||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tabular}{|c|c|c|c|}
|
\begin{tabular}{|c|c|c|c|}
|
||||||
& py37 & py38 & py39 \\
|
& py37 & py38 & py39 \\
|
||||||
\hline
|
\hline
|
||||||
lib2 & V & V & V \\
|
lib2 & V & V & V \\
|
||||||
lib3 & X & V & V \\
|
lib3 & X & V & V \\
|
||||||
lib4 & X & V & V
|
lib4 & X & V & V
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
|
@ -703,7 +708,7 @@ L'outil le plus connu est \href{https://tox.readthedocs.io/en/latest/}{Tox}, qui
|
||||||
|
|
||||||
Démarrez ensuite la commande \texttt{tox}, pour démarrer la commande \texttt{pytest} sur les environnements Python 3.6, 3.7, 3.8 et 3.9, après avoir installé nos dépendances présentes dans le fichier \texttt{requirements/dev.txt}.
|
Démarrez ensuite la commande \texttt{tox}, pour démarrer la commande \texttt{pytest} sur les environnements Python 3.6, 3.7, 3.8 et 3.9, après avoir installé nos dépendances présentes dans le fichier \texttt{requirements/dev.txt}.
|
||||||
|
|
||||||
Pour que la commande ci-dessus fonctionne correctement, il sera nécessaire que vous ayez les différentes versions d'interpréteurs installées.
|
Pour que la commande ci-dessus fonctionne correctement, il sera nécessaire que vous ayez les différentes versions d'interpréteurs installées.
|
||||||
Ci-dessus, la commande retournera une erreur pour chaque version non trouvée, avec une erreur type
|
Ci-dessus, la commande retournera une erreur pour chaque version non trouvée, avec une erreur type
|
||||||
\texttt{ERROR:\ \ \ pyXX:\ InterpreterNotFound:\ pythonX.X}.
|
\texttt{ERROR:\ \ \ pyXX:\ InterpreterNotFound:\ pythonX.X}.
|
||||||
|
|
||||||
|
@ -716,24 +721,24 @@ Décrire le fichier setup.cfg.
|
||||||
|
|
||||||
\section{Makefile}
|
\section{Makefile}
|
||||||
|
|
||||||
Pour gagner un peu de temps, n'hésitez pas à créer un fichier \texttt{Makefile} que vous placerez à la racine du projet.
|
Pour gagner un peu de temps, n'hésitez pas à créer un fichier \texttt{Makefile} que vous placerez à la racine du projet.
|
||||||
L'exemple ci-dessous permettra, grâce à la commande \texttt{make\ coverage}, d'arriver au même résultat que ci-dessus:
|
L'exemple ci-dessous permettra, grâce à la commande \texttt{make\ coverage}, d'arriver au même résultat que ci-dessus:
|
||||||
|
|
||||||
\begin{listing}
|
\begin{listing}
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
# Makefile for gwift
|
# Makefile for gwift
|
||||||
#
|
#
|
||||||
|
|
||||||
# User-friendly check for coverage
|
# User-friendly check for coverage
|
||||||
ifeq ($(shell which coverage >/dev/null 2>&1; echo $$?), 1)
|
ifeq ($(shell which coverage >/dev/null 2>&1; echo $$?), 1)
|
||||||
$(error The 'coverage' command was not found. Make sure you have coverage installed)
|
$(error The 'coverage' command was not found. Make sure you have coverage installed)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: help coverage
|
.PHONY: help coverage
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo " coverage to run coverage check of the source files."
|
@echo " coverage to run coverage check of the source files."
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
coverage run --source='.' manage.py test; coverage report; coverage html;
|
coverage run --source='.' manage.py test; coverage report; coverage html;
|
||||||
@echo "Testing of coverage in the sources finished."
|
@echo "Testing of coverage in the sources finished."
|
||||||
|
@ -759,11 +764,11 @@ facteurs → Construction du fichier setup.cfg
|
||||||
[flake8]
|
[flake8]
|
||||||
max-line-length = 100
|
max-line-length = 100
|
||||||
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
|
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
|
||||||
|
|
||||||
[pycodestyle]
|
[pycodestyle]
|
||||||
max-line-length = 100
|
max-line-length = 100
|
||||||
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
|
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
|
||||||
|
|
||||||
[mypy]
|
[mypy]
|
||||||
python_version = 3.8
|
python_version = 3.8
|
||||||
check_untyped_defs = True
|
check_untyped_defs = True
|
||||||
|
@ -772,14 +777,14 @@ facteurs → Construction du fichier setup.cfg
|
||||||
warn_redundant_casts = True
|
warn_redundant_casts = True
|
||||||
warn_unused_configs = True
|
warn_unused_configs = True
|
||||||
plugins = mypy_django_plugin.main
|
plugins = mypy_django_plugin.main
|
||||||
|
|
||||||
[mypy.plugins.django-stubs]
|
[mypy.plugins.django-stubs]
|
||||||
django_settings_module = config.settings.test
|
django_settings_module = config.settings.test
|
||||||
|
|
||||||
[mypy-*.migrations.*]
|
[mypy-*.migrations.*]
|
||||||
# Django migrations should not produce any errors:
|
# Django migrations should not produce any errors:
|
||||||
ignore_errors = True
|
ignore_errors = True
|
||||||
|
|
||||||
[coverage:run]
|
[coverage:run]
|
||||||
include = khana/*
|
include = khana/*
|
||||||
omit = *migrations*, *tests*
|
omit = *migrations*, *tests*
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
\chapter{Arborescences}
|
\chapter{Arborescences et graphs}
|
||||||
|
|
||||||
On a un exemple de remplissage/vidage d'une closure table, mais il faudrait en fait présenter les listes adjacentes et les autres structures de données.
|
On a un exemple de remplissage/vidage d'une closure table, mais il faudrait en fait présenter les listes adjacentes et les autres structures de données.
|
||||||
Comme ça on pourra introduire les graphs juste après.
|
Comme ça on pourra introduire les graphs juste après.
|
||||||
|
@ -23,12 +23,12 @@ class Command(BaseCommand):
|
||||||
for entity in entities:
|
for entity in entities:
|
||||||
breadcrumb = [node for node in entity.breadcrumb()]
|
breadcrumb = [node for node in entity.breadcrumb()]
|
||||||
tree = set(EntityTreePath.objects.filter(descendant=entity))
|
tree = set(EntityTreePath.objects.filter(descendant=entity))
|
||||||
|
|
||||||
for idx, node in enumerate(breadcrumb):
|
for idx, node in enumerate(breadcrumb):
|
||||||
tree_path, _ = EntityTreePath.objects.get_or_create(
|
tree_path, _ = EntityTreePath.objects.get_or_create(
|
||||||
ancestor=node, descendant=entity, weight=idx + 1
|
ancestor=node, descendant=entity, weight=idx + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
if tree_path in tree:
|
if tree_path in tree:
|
||||||
tree.remove(tree_path)
|
tree.remove(tree_path)
|
||||||
for tree_path in tree:
|
for tree_path in tree:
|
||||||
|
|
4
main.tex
4
main.tex
|
@ -9,7 +9,6 @@
|
||||||
\usepackage{setspace}
|
\usepackage{setspace}
|
||||||
\usepackage{listing}
|
\usepackage{listing}
|
||||||
\usepackage{minted}
|
\usepackage{minted}
|
||||||
\usepackage{ulem}
|
|
||||||
\usepackage{graphics}
|
\usepackage{graphics}
|
||||||
\usepackage{float}
|
\usepackage{float}
|
||||||
\usepackage[export]{adjustbox}
|
\usepackage[export]{adjustbox}
|
||||||
|
@ -62,7 +61,6 @@
|
||||||
\include{chapters/heroku.tex}
|
\include{chapters/heroku.tex}
|
||||||
\include{chapters/kubernetes.tex}
|
\include{chapters/kubernetes.tex}
|
||||||
\include{chapters/deployment-tools.tex}
|
\include{chapters/deployment-tools.tex}
|
||||||
\include{chapters/deployment-processes.tex}
|
|
||||||
\chapter{Conclusions}
|
\chapter{Conclusions}
|
||||||
|
|
||||||
\include{parts/principles.tex}
|
\include{parts/principles.tex}
|
||||||
|
@ -83,8 +81,8 @@
|
||||||
\include{chapters/api.tex}
|
\include{chapters/api.tex}
|
||||||
\include{chapters/filters.tex}
|
\include{chapters/filters.tex}
|
||||||
\include{chapters/trees.tex}
|
\include{chapters/trees.tex}
|
||||||
\include{chapters/graphs.tex}
|
|
||||||
\include{chapters/i18n.tex}
|
\include{chapters/i18n.tex}
|
||||||
|
\include{chapters/deployment-processes.tex}
|
||||||
|
|
||||||
\chapter{Conclusions}
|
\chapter{Conclusions}
|
||||||
|
|
||||||
|
|
|
@ -6,28 +6,28 @@ Make it work, make it right, make it fast
|
||||||
--- Kent Beck
|
--- Kent Beck
|
||||||
\end{quote}
|
\end{quote}
|
||||||
|
|
||||||
En fonction de vos connaissances et compétences, la création d'une nouvelle application est une étape relativement facile à mettre en place.
|
En fonction de vos connaissances et compétences, la création d'une nouvelle application est une étape relativement facile à mettre en place.
|
||||||
Le code qui permet de faire tourner cette application peut ne pas être élégant, voire buggé jusqu'à la moëlle, il pourra fonctionner et faire "preuve de concept".
|
Le code qui permet de faire tourner cette application peut ne pas être élégant, voire buggé jusqu'à la moëlle, il pourra fonctionner et faire "preuve de concept".
|
||||||
|
|
||||||
Les problèmes arriveront lorsqu'une nouvelle demande sera introduite, lorsqu'un bug sera découvert et devra être corrigé ou lorsqu'une dépendance cessera de fonctionner ou d'être disponible.
|
Les problèmes arriveront lorsqu'une nouvelle demande sera introduite, lorsqu'un bug sera découvert et devra être corrigé ou lorsqu'une dépendance cessera de fonctionner ou d'être disponible.
|
||||||
Or, une application qui n'évolue pas, meurt.
|
Or, une application qui n'évolue pas, meurt.
|
||||||
Toute application est donc destinée, soit à être modifiée, corrigée et suivie, soit à déperrir et à être
|
Toute application est donc destinée, soit à être modifiée, corrigée et suivie, soit à déperrir et à être
|
||||||
délaissée par ses utilisateurs.
|
délaissée par ses utilisateurs.
|
||||||
Et c'est juste cette maintenance qui est difficile.
|
Et c'est juste cette maintenance qui est difficile.
|
||||||
|
|
||||||
L'application des principes présentés et agrégés ci-dessous permet surtout de préparer correctement tout ce qui pourra arriver, sans aller jusqu'au « \textbf{You Ain't Gonna Need It} » (ou \textbf{YAGNI\index{YAGNI}}), qui consiste à surcharger tout développement avec des fonctionnalités non demandées, juste « au cas ou ».
|
L'application des principes présentés et agrégés ci-dessous permet surtout de préparer correctement tout ce qui pourra arriver, sans aller jusqu'au « \textbf{You Ain't Gonna Need It} » (ou \textbf{YAGNI\index{YAGNI}}), qui consiste à surcharger tout développement avec des fonctionnalités non demandées, juste « au cas ou ».
|
||||||
Pour paraphraser une partie de l'introduction du livre \emph{Clean Architecture} \cite{clean_architecture}:
|
Pour paraphraser une partie de l'introduction du livre \emph{Clean Architecture} \cite{clean_architecture}:
|
||||||
|
|
||||||
\begin{quote}
|
\begin{quote}
|
||||||
Getting software right is hard: it takes knowledge and skills that most young programmers don't take the time to develop.
|
Getting software right is hard: it takes knowledge and skills that most young programmers don't take the time to develop.
|
||||||
It requires a level of discipline and dedication that most programmers never dreamed they'd need.
|
It requires a level of discipline and dedication that most programmers never dreamed they'd need.
|
||||||
Mostly, it takes a passion for the craft and the desire to be a professional.
|
Mostly, it takes a passion for the craft and the desire to be a professional.
|
||||||
|
|
||||||
--- Robert C. Martin Clean Architecture
|
--- Robert C. Martin Clean Architecture
|
||||||
\end{quote}
|
\end{quote}
|
||||||
|
|
||||||
Le développement d'un logiciel nécessite une rigueur d'exécution et des connaissances précises dans des domaines extrêmement variés.
|
Le développement d'un logiciel nécessite une rigueur d'exécution et des connaissances précises dans des domaines extrêmement variés.
|
||||||
Il nécessite également des intentions, des (bonnes) décisions et énormément d'attention.
|
Il nécessite également des intentions, des (bonnes) décisions et énormément d'attention.
|
||||||
Indépendamment de l'architecture que vous aurez choisie, des technologies que vous aurez patiemment évaluées et mises en place, une architecture et une solution peuvent être cassées en un instant, en même temps que tout ce que vous aurez construit, dès que vous en aurez détourné le regard.
|
Indépendamment de l'architecture que vous aurez choisie, des technologies que vous aurez patiemment évaluées et mises en place, une architecture et une solution peuvent être cassées en un instant, en même temps que tout ce que vous aurez construit, dès que vous en aurez détourné le regard.
|
||||||
|
|
||||||
Un des objectifs ici est de placer les barrières et les gardes-fous (ou
|
Un des objectifs ici est de placer les barrières et les gardes-fous (ou
|
||||||
|
@ -55,7 +55,6 @@ Dans une version plus manuelle, cela pourrait se résumer à ces trois
|
||||||
étapes (la dernière étant formellement facultative):
|
étapes (la dernière étant formellement facultative):
|
||||||
|
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
\def\labelenumi{\arabic{enumi}.}
|
|
||||||
\item
|
\item
|
||||||
Démarrer un script,
|
Démarrer un script,
|
||||||
\item
|
\item
|
||||||
|
@ -66,10 +65,4 @@ Dans une version plus manuelle, cela pourrait se résumer à ces trois
|
||||||
technologie existe encore\ldots\hspace{0pt}).
|
technologie existe encore\ldots\hspace{0pt}).
|
||||||
\end{enumerate}
|
\end{enumerate}
|
||||||
|
|
||||||
\begin{quote}
|
Sans aller jusqu'à demander de développer vos algorithmes sur douze pieds, la programmation reste un art régit par un ensemble de bonnes pratiques, par des règles à respecter et par la nécessité de travailler avec d'autres personnes qui ont souvent une expérience, des compétences ou une approche différente.
|
||||||
La poésie est "l'art d'évoquer et de suggérer les sensations, les impressions, les émotions les plus vives par l'union intense des sons, des rythmes, des harmonies, en particulier par les vers."
|
|
||||||
|
|
||||||
-- https://www.larousse.fr/dictionnaires/francais/po%C3%A9sie/61960
|
|
||||||
\end{quote}
|
|
||||||
|
|
||||||
Sans aller jusqu'à demander de développer vos algorithmes sur douze pieds, la programmation reste un art régit par un ensemble de bonnes pratiques, par des règles à respecter et par la nécessité de travailler avec d'autres personnes qui ont \sout{parfois} souvent une expérience, des compétences ou une approche différente.
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
\part{Services Oriented Applications}
|
\part{Services Oriented Applications}
|
||||||
|
|
||||||
Nous avons fait exprès de reprendre l'acronyme d'une \emph{Services Oriented Architecture} pour cette partie. L'objectif est de vous mettre
|
Nous avons fait exprès de reprendre l'acronyme d'une \emph{Services Oriented Architecture} pour cette partie.
|
||||||
la puce à l'oreille quant à la finalité du développement: que l'utilisateur soit humain, bot automatique ou client Web, l'objectif est de fournir des applications résilientes, disponibles et accessibles.
|
|
||||||
|
|
||||||
Dans cette partie, nous aborderons les vues, la mise en forme, la mise en page, la définition d'une interface REST, la définition d'une interface GraphQL et le routage d'URLs.
|
|
||||||
|
Dans cette partie, en page, la définition d'une interface REST, la définition d'une interface GraphQL et le routage d'URLs.
|
||||||
|
|
||||||
\begin{quote}
|
\begin{quote}
|
||||||
|
|
||||||
Don't make me think, or why I switched from JS SPAs to Ruby On Rails
|
Don't make me think, or why I switched from JS SPAs to Ruby On Rails
|
||||||
\url{https://news.ycombinator.com/item?id=30206989\&utm_term=comment}
|
\url{https://news.ycombinator.com/item?id=30206989\&utm_term=comment}
|
||||||
|
|
||||||
\end{quote}
|
\end{quote}
|
||||||
|
|
||||||
On a parcouru les templates et le mode "monolithique de DJango".
|
On a parcouru les templates et le mode "monolithique de DJango".
|
||||||
|
|
Loading…
Reference in New Issue