From c15e05349d731e5298778b47046dd7f500fc5a35 Mon Sep 17 00:00:00 2001 From: Fred Pauchet Date: Wed, 11 May 2022 15:43:09 +0200 Subject: [PATCH] Writing, again and again --- chapters/deployments.tex | 67 +++++++++++---- chapters/docker-compose.tex | 36 -------- chapters/graphs.tex | 2 - chapters/infrastructure.tex | 46 +++++++--- chapters/maintenability.tex | 146 +++++++++++++------------------- chapters/programming-poetry.tex | 8 -- chapters/python.tex | 141 +++++++++++++++--------------- chapters/trees.tex | 6 +- main.tex | 4 +- parts/environment.tex | 27 +++--- parts/soa.tex | 8 +- 11 files changed, 234 insertions(+), 257 deletions(-) delete mode 100644 chapters/docker-compose.tex delete mode 100644 chapters/graphs.tex delete mode 100644 chapters/programming-poetry.tex diff --git a/chapters/deployments.tex b/chapters/deployments.tex index 76eac9f..bab7932 100644 --- a/chapters/deployments.tex +++ b/chapters/deployments.tex @@ -190,7 +190,7 @@ suivantes: On fait confiance à django\_environ pour traduire la chaîne de connexion à la base de données. \end{itemize} - + \section{Création des répertoires de logs} @@ -198,16 +198,16 @@ suivantes: mkdir -p /var/www/gwift/static \end{verbatim} -\section{Socket} +\section{Socket} Dans le fichier \texttt{/etc/tmpfiles.d/gwift.conf}: - + \begin{verbatim} D /var/run/webapps 0775 gwift gunicorn_sockets - \end{verbatim} - + Suivi de la création par systemd : - + \begin{verbatim} systemd-tmpfiles --create \end{verbatim} @@ -316,13 +316,13 @@ l'aide de la commande \texttt{supervisorctl}: \begin{verbatim} et 443 (HTTPS). \end{verbatim} - + \begin{verbatim} - firewall-cmd --permanent --zone=public --add-service=http - firewall-cmd --permanent --zone=public --add-service=https + firewall-cmd --permanent --zone=public --add-service=http + firewall-cmd --permanent --zone=public --add-service=https firewall-cmd --reload \end{verbatim} - + \begin{itemize} \item 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 usermod -a -G gunicorn_sockets nginx \end{verbatim} - + On configure ensuite le fichier \texttt{/etc/nginx/conf.d/gwift.conf}: - + \begin{verbatim} upstream gwift_app { server unix:/var/run/webapps/gunicorn_gwift.sock fail_timeout=0; } - + server { listen 80; 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; - location /static/ { + location /static/ { access_log off; expires 30d; add_header Pragma public; @@ -372,7 +372,7 @@ l'aide de la commande \texttt{supervisorctl}: } 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_redirect off; @@ -380,7 +380,7 @@ l'aide de la commande \texttt{supervisorctl}: } } \end{verbatim} - + \begin{itemize} \item Ce répertoire sera complété par la commande \texttt{collectstatic} que @@ -450,3 +450,40 @@ Et dans le fichier crontab : \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. \ No newline at end of file diff --git a/chapters/docker-compose.tex b/chapters/docker-compose.tex deleted file mode 100644 index 9e42147..0000000 --- a/chapters/docker-compose.tex +++ /dev/null @@ -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. \ No newline at end of file diff --git a/chapters/graphs.tex b/chapters/graphs.tex deleted file mode 100644 index 4827e10..0000000 --- a/chapters/graphs.tex +++ /dev/null @@ -1,2 +0,0 @@ -\chapter{Graphs} - diff --git a/chapters/infrastructure.tex b/chapters/infrastructure.tex index 3391e59..9d1db89 100644 --- a/chapters/infrastructure.tex +++ b/chapters/infrastructure.tex @@ -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}). 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: \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. \end{enumerate} -\section{Composants fonctionnels} +\subsection{Composants fonctionnels} \begin{figure}[H] \centering @@ -35,18 +37,26 @@ Une application \textit{data-intensive} est généralement composée des blocs f \hline \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. 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. +\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}|} \hline @@ -62,6 +72,8 @@ Il s'agit des composants comme le proxy inverse ou les distributeurs de charge: \hline Healthcheck & & Supervisord \\ \hline + Télémétrie & & \\ + \hline \end{tabular} 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} -\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}} -\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}|} \hline @@ -117,9 +137,9 @@ Le principe du \textbf{proxy inverse} est de pouvoir rediriger du trafic entrant \hline \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: @@ -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 exemples}. -\subsection{Exploitation des journaux} +\subsubsection{Exploitation des journaux} \begin{enumerate} \item diff --git a/chapters/maintenability.tex b/chapters/maintenability.tex index 47e2089..6cf0763 100644 --- a/chapters/maintenability.tex +++ b/chapters/maintenability.tex @@ -1,22 +1,32 @@ \chapter{Fiabilité, évolutivité et maintenabilité} \begin{quote} -The primary cost of maintenance is in spelunking and risk - \cite[139]{clean_architecture} +The primary cost of maintenance is in spelunking and risk \cite[139]{clean_architecture} --- Robert C. Martin \end{quote} -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}. +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} : + +\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: \begin{enumerate} \item \textbf{Faciliter la mise en place de phases d'automatisation}; plus 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 projet dans un processus 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 \item \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} 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} -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. +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. 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} -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}. -Ce cépôt n'est pas uniquement destiné à hébergé le code source, mais -également à d'autres artefacts et autres formes de connaissance: +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}. +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} \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} -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. -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}. +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. +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, ...) - 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. -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. +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. Ceci autorise une reproductibilité quasi parfaite de l'environnement. -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. 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.} +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. +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} @@ -114,7 +108,7 @@ que: ... \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. @@ -128,32 +122,17 @@ Par exemple, Gitea expose \href{https://docs.gitea.io/en-us/config-cheat-sheet/} \section{Ressources externes} -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é. 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 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é. +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", -et pas "une base MariaDB et une instance Memcached". De cette manière, -les ressources peuvent être attachées et détachées d'un déploiement à la -volée. +Nous voulons par exemple "une base de données" et "une mémoire cache", et pas "une base MariaDB et une instance Memcached". +De cette manière, 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 -?), 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. +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. \includegraphics{images/12factors/attached-resources.png} -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. +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. 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} @@ -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é. 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. -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. +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. 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. @@ -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). 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. \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 \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. -En prévoyant une manière élégante d'envoyer un signal de terminaison, +superviseur. +En prévoyant une manière élégante d'envoyer un signal de terminaison, \begin{enumerate} - \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 Les requêtes en cours peuvent se terminer au mieux, + \item Le démarrage rapide de nouveaux processus améliorera la balance d'un processus en cours d'extinction vers des processus tout frais, en autorisant l'exécution parallèle d'anciens et de nouveaux "types" de processus \end{enumerate} L'intégration de ces mécanismes dès les premières étapes de @@ -228,60 +207,51 @@ hôte, etc.). \section{Similarité des environnements} -Conserver les différents environnements aussi similaires -que possible, et limiter les divergences entre un environnement de -développement et de production +Conserver les différents environnements aussi similaires 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 -\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}. +\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}. 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.} +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} -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. +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. 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. \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. \section{Conclusions} -Une application devient nettement plus maintenable dès lors que l'équipe -de développement suit de près les différentes étapes de sa conception, -de la demande jusqu'à son aboutissement en production. -\cite[pp. 293-294]{devops_handbook}. +Une application devient nettement plus maintenable dès lors que l'équipe de développement suit de près les différentes étapes de sa conception, de la demande jusqu'à son aboutissement en production. \cite[pp. 293-294]{devops_handbook}. Au fur et à mesure que le code est délibérément construit pour être maintenable, l'équipe gagne en rapidité, en qualité et en fiabilité de déploiement, ce qui facilite les tâches opérationnelles: \begin{enumerate} \item - Activation d'une télémétrie suffisante dans les applications et les - environnements + Activation d'une télémétrie suffisante dans les applications et les environnements \item Conservation précise des dépendances nécessaires \item - Résilience des services et plantage élégant (i.e. \textbf{sans finir - sur un SEGFAULT avec l'OS dans les choux et un écran bleu}) + 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}) \item - Compatibilité entre les différentes versions (n+1, \ldots\hspace{0pt}) + Compatibilité entre les différentes versions (n+1, \ldots) \item - Gestion de l'espace de stockage associé à un environnement (pour - éviter d'avoir un environnement de production qui fait 157 - Tera-octets) + Gestion de l'espace de stockage associé à un environnement (pour éviter d'avoir un environnement de production qui fait 157 Tera-octets) \item Activation de la recherche dans les logs \item - Traces des requêtes provenant des utilisateurs, indépendamment des - services utilisés + Traces des requêtes provenant des utilisateurs, indépendamment des services utilisés \item - Centralisation de la configuration (\textbf{via} ZooKeeper, par - exemple) + Centralisation de la configuration (\textbf{via} ZooKeeper, par exemple) \end{enumerate} diff --git a/chapters/programming-poetry.tex b/chapters/programming-poetry.tex deleted file mode 100644 index 984f2e2..0000000 --- a/chapters/programming-poetry.tex +++ /dev/null @@ -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} diff --git a/chapters/python.tex b/chapters/python.tex index e000d79..bc3048c 100644 --- a/chapters/python.tex +++ b/chapters/python.tex @@ -8,7 +8,7 @@ Le langage \href{https://www.python.org/}{Python} est un \href{https://docs.pyth \caption{\url{https://xkcd.com/353/}} \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. 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} "\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 -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}. \end{quote} @@ -34,7 +34,7 @@ ressources pourraient vous aider: with Python} \cite{boring_stuff}, aka. \emph{Practical Programming for Total Beginners} \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. \end{itemize} @@ -60,21 +60,21 @@ permet de surcharger l'initialisation d'une instance de classe. \end{minted} \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}. -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. Vous trouverez ci-dessous un tableau reprenant les protocoles les plus courants: \begin{center} \begin{tabular}{ c c c } - cell1 & cell2 & cell3 \\ - cell4 & cell5 & cell6 \\ - cell7 & cell8 & cell9 + cell1 & cell2 & cell3 \\ + cell4 & cell5 & cell6 \\ + cell7 & cell8 & cell9 \end{tabular} -\end{center} +\end{center} 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{minted}[tabsize=4]{python} 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]) ): raise ValueError("Matrix dimensions don't match") return Matrix( [ - [a - b for a, b in zip(a_row, b_row)] - for a_row, b_row in zip(self.rows, other.rows) + [a - b for a, b in zip(a_row, b_row)] + for a_row, b_row in zip(self.rows, other.rows) ] ) \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. -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. \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 easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those! - + \end{verbatim} \caption{The Zen of Python} \end{listing} \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, ... 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. -Lancez ensuite la commande \texttt{flake8} suivie du chemin à analyser (\texttt{.}, le nom d'un répertoire, le nom d'un fichier \texttt{.py}, ...). +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}, ...). 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] @@ -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{verbatim} - $ pyflakes . + $ pyflakes . ... \end{verbatim} \caption{Une utilisation de pyflakes} \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. -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. \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. -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. +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 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} \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, \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 @@ -223,10 +223,10 @@ Il existe plusieurs types de balisages reconnus/approuvés: \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. -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é -Elle contient des conventions, pas des règles ou +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. +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 \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!” @@ -249,7 +249,7 @@ A remplir \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: @@ -257,30 +257,30 @@ L'exemple donné dans les guides de style de Google est celui-ci: \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]]: """Fetches rows from a Smalltable. - + Retrieves rows pertaining to the given keys from the Table instance represented by table_handle. String keys will be UTF-8 encoded. - + Args: table_handle: An open smalltable.Table instance. keys: A sequence of strings representing the key of each table row to fetch. String keys will be UTF-8 encoded. require_all_keys: Optional; If require_all_keys is True only rows with values set for all keys will be returned. - + Returns: A dict mapping keys to the corresponding table row data fetched. Each row is represented as a tuple of strings. For example: - + {b'Serak': ('Rigel VII', 'Preparer'), b'Zim': ('Irk', 'Invader'), b'Lrrr': ('Omicron Persei 8', 'Emperor')} - + 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 table (and require_all_keys must have been False). - + Raises: IOError: An error occurred accessing the smalltable. """ @@ -371,13 +371,13 @@ code pas très propre et qui ne sert à rien: \begin{minted}{python} from datetime import datetime """On stocke la date du jour dans la variable ToD4y""" - + ToD4y = datetime.today() - + def print_today(ToD4y): today = ToD4y print(ToD4y) - + def GetToday(): 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 \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: \begin{itemize} @@ -472,19 +472,19 @@ chaque code possède sa signification: \item \textbf{E} pour les erreurs ou des bugs probablement présents dans le code \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} -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}. 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} \item \textbf{Une ligne de code} \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} \subsection{Ignorer une ligne de code} @@ -496,10 +496,10 @@ Cet élément peut être: \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, ... -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. +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. 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} \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} Est-ce que ce formatage est idéal et accepté par tout le monde ? -\textbf{Non}. -Même Pylint arrivera parfois à râler. +\textbf{Non}. +Même Pylint arrivera parfois à râler. Mais ce formatage conviendra dans 97,83\% des cas (au moins). \begin{quote} @@ -535,7 +535,7 @@ vous concentrer sur le contenu}". \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. 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{minted}{python} from typing import List - + def first_int_elem(l: List[int]) -> int: return l[0] if l else None - + if __name__ == "__main__": print(first_int_elem([1, 2, 3])) 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{verbatim} >>> python mypy-test.py - 1 + 1 a \end{verbatim} \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. 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: \begin{listing}[H] @@ -595,7 +595,7 @@ Pour corriger ceci, nous devons: \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}: \begin{itemize} @@ -624,7 +624,7 @@ complètement biesse; on est sur la partie théorique ici): \end{minted} \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}: \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/}. 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. +\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} \begin{verbatim} @@ -665,17 +670,17 @@ La configuration peut se faire dans un fichier \texttt{.coveragerc} que vous pla \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. 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{tabular}{|c|c|c|c|} - & py37 & py38 & py39 \\ + & py37 & py38 & py39 \\ \hline lib2 & V & V & V \\ lib3 & X & V & V \\ - lib4 & X & V & V + lib4 & X & V & V \end{tabular} \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}. -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 \texttt{ERROR:\ \ \ pyXX:\ InterpreterNotFound:\ pythonX.X}. @@ -716,24 +721,24 @@ Décrire le fichier setup.cfg. \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: \begin{listing} \begin{verbatim} # Makefile for gwift # - + # User-friendly check for coverage ifeq ($(shell which coverage >/dev/null 2>&1; echo $$?), 1) $(error The 'coverage' command was not found. Make sure you have coverage installed) endif - + .PHONY: help coverage - + help: @echo " coverage to run coverage check of the source files." - + coverage: coverage run --source='.' manage.py test; coverage report; coverage html; @echo "Testing of coverage in the sources finished." @@ -759,11 +764,11 @@ facteurs → Construction du fichier setup.cfg [flake8] max-line-length = 100 exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv - + [pycodestyle] max-line-length = 100 exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv - + [mypy] python_version = 3.8 check_untyped_defs = True @@ -772,14 +777,14 @@ facteurs → Construction du fichier setup.cfg warn_redundant_casts = True warn_unused_configs = True plugins = mypy_django_plugin.main - + [mypy.plugins.django-stubs] django_settings_module = config.settings.test - + [mypy-*.migrations.*] # Django migrations should not produce any errors: ignore_errors = True - + [coverage:run] include = khana/* omit = *migrations*, *tests* diff --git a/chapters/trees.tex b/chapters/trees.tex index 2a69a5e..85a8ec5 100644 --- a/chapters/trees.tex +++ b/chapters/trees.tex @@ -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. Comme ça on pourra introduire les graphs juste après. @@ -23,12 +23,12 @@ class Command(BaseCommand): for entity in entities: breadcrumb = [node for node in entity.breadcrumb()] tree = set(EntityTreePath.objects.filter(descendant=entity)) - + for idx, node in enumerate(breadcrumb): tree_path, _ = EntityTreePath.objects.get_or_create( ancestor=node, descendant=entity, weight=idx + 1 ) - + if tree_path in tree: tree.remove(tree_path) for tree_path in tree: diff --git a/main.tex b/main.tex index 4a18f7f..a42d04f 100644 --- a/main.tex +++ b/main.tex @@ -9,7 +9,6 @@ \usepackage{setspace} \usepackage{listing} \usepackage{minted} -\usepackage{ulem} \usepackage{graphics} \usepackage{float} \usepackage[export]{adjustbox} @@ -62,7 +61,6 @@ \include{chapters/heroku.tex} \include{chapters/kubernetes.tex} \include{chapters/deployment-tools.tex} -\include{chapters/deployment-processes.tex} \chapter{Conclusions} \include{parts/principles.tex} @@ -83,8 +81,8 @@ \include{chapters/api.tex} \include{chapters/filters.tex} \include{chapters/trees.tex} -\include{chapters/graphs.tex} \include{chapters/i18n.tex} +\include{chapters/deployment-processes.tex} \chapter{Conclusions} diff --git a/parts/environment.tex b/parts/environment.tex index 8153844..e1d6b19 100644 --- a/parts/environment.tex +++ b/parts/environment.tex @@ -6,28 +6,28 @@ Make it work, make it right, make it fast --- Kent Beck \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". -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. +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. 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. -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}: \begin{quote} -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. +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. Mostly, it takes a passion for the craft and the desire to be a professional. --- Robert C. Martin Clean Architecture \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. -Il nécessite également des intentions, des (bonnes) décisions et énormément d'attention. +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. 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 @@ -55,7 +55,6 @@ Dans une version plus manuelle, cela pourrait se résumer à ces trois étapes (la dernière étant formellement facultative): \begin{enumerate} -\def\labelenumi{\arabic{enumi}.} \item Démarrer un script, \item @@ -66,10 +65,4 @@ Dans une version plus manuelle, cela pourrait se résumer à ces trois technologie existe encore\ldots\hspace{0pt}). \end{enumerate} -\begin{quote} - 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. \ No newline at end of file +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. diff --git a/parts/soa.tex b/parts/soa.tex index 834cf70..917f226 100644 --- a/parts/soa.tex +++ b/parts/soa.tex @@ -1,15 +1,15 @@ \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 -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. +Nous avons fait exprès de reprendre l'acronyme d'une \emph{Services Oriented Architecture} pour cette partie. -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} 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} - + \end{quote} On a parcouru les templates et le mode "monolithique de DJango".