<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Romuald Oluwatobi on Medium]]></title>
        <description><![CDATA[Stories by Romuald Oluwatobi on Medium]]></description>
        <link>https://medium.com/@romualdoluwatobi?source=rss-a5eb6b8691ac------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*nTtbFHeV1wV1B5lj</url>
            <title>Stories by Romuald Oluwatobi on Medium</title>
            <link>https://medium.com/@romualdoluwatobi?source=rss-a5eb6b8691ac------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 16 May 2026 13:11:52 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@romualdoluwatobi/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Python Gunicorn internals — Step-by-step walkthrough]]></title>
            <link>https://medium.com/@romualdoluwatobi/python-gunicorn-internals-step-by-step-walkthrough-aafc4dbc37b5?source=rss-a5eb6b8691ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/aafc4dbc37b5</guid>
            <category><![CDATA[web-server]]></category>
            <category><![CDATA[gunicorn]]></category>
            <category><![CDATA[wsgi]]></category>
            <category><![CDATA[python-web-server]]></category>
            <dc:creator><![CDATA[Romuald Oluwatobi]]></dc:creator>
            <pubDate>Wed, 11 Mar 2026 13:58:11 GMT</pubDate>
            <atom:updated>2026-03-11T13:58:11.812Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Gunicorn internals — Romuald Oluwatobi" src="https://cdn-images-1.medium.com/max/1024/0*tjlyS4KpvFn9MRzg.png" /></figure><p>Have you ever asked yourself how <a href="https://github.com/benoitc/gunicorn"><strong>Gunicorn</strong></a> works internally?</p><p>How does it actually serve your application?</p><p>What exactly happens when you run the command <strong><em>gunicorn module:app</em></strong>?</p><p>Gunicorn is a Python web server with millions of downloads and is used in production by many Python applications.</p><p>If you’re curious about its internal workings — like a code walkthrough of what happens at each step, from running the Gunicorn command to handling a client request — this repo walks you through all of it, with references to the actual Gunicorn source code.</p><p>In particular, it may be useful if you:</p><ul><li>use Gunicorn in production and want a clearer mental model of its internals</li><li>are curious about process models, worker management, and request handling</li><li>want to learn from a real-world, battle-tested code base</li><li>enjoy reading source code and understanding why certain architectural decisions were made</li></ul><p><a href="https://github.com/Romulad/g-process">Start reading by clicking here.</a></p><p><em>Originally published at </em><a href="https://romualdoluwatobi.substack.com/p/python-gunicorn-internals-step-by"><em>https://romualdoluwatobi.substack.com</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aafc4dbc37b5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Are you making three network calls in your refresh token process?]]></title>
            <link>https://medium.com/@romualdoluwatobi/are-you-making-three-network-calls-in-your-refresh-token-process-9ca45c559268?source=rss-a5eb6b8691ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/9ca45c559268</guid>
            <category><![CDATA[stateless-authentication]]></category>
            <category><![CDATA[refresh-token]]></category>
            <dc:creator><![CDATA[Romuald Oluwatobi]]></dc:creator>
            <pubDate>Wed, 11 Mar 2026 10:54:36 GMT</pubDate>
            <atom:updated>2026-03-11T10:54:36.606Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Refresh token process — Romuald Oluwatobi" src="https://cdn-images-1.medium.com/max/1024/0*2ZpiskU4mWySn6iC.png" /></figure><p>If you are using stateless token-based authentication in your apps, your refresh token flow probably looks like this:</p><ol><li>The client makes a request to a protected resource using the access token.</li><li>An error occurs because the access token has expired.</li><li>You make another request to the server to request a new token pair using the refresh token.</li><li>Once you have the new tokens, you make a new request to retrieve the resource.</li></ol><p>There’s nothing wrong with this process.</p><p>The goal is still to get the data or <em>perform the action the user requested</em>.</p><p>However, we end up making <strong>three requests</strong> instead of one, with three round trips (a round trip is a request from the client to the server and back).</p><p>Let’s consider your access token has a short expiration time, as recommended.</p><p>This means that most user interactions with your app could require <strong>at least six network calls</strong> -or three round trips-before we get the final data.</p><p>That’s three times what it would take without a refresh token process — two network calls, or one round trip.</p><p>Here, I’m talking in terms of network calls, but the real issue is <strong><em>latency</em></strong>.</p><p>For example, if a client-server round trip takes 400ms, serving a single client request could take more than 1.2 seconds (1200ms).</p><p>And this latency scales as more users interact with your service.</p><p>It’s not just about optimizing your endpoints or application; <strong><em>it’s about how your authentication flow works</em></strong>.</p><p>So the question is:</p><p><strong>Is this latency a problem in your typical application?</strong></p><p>If so, here’s one way to handle it:</p><ul><li><strong>Server side:</strong> When issuing tokens, include both the <strong><em>expires_in</em></strong> and <strong><em>created_at</em></strong> fields.</li><li><strong>Client side:</strong> When you receive the tokens, store both <strong><em>expires_in</em></strong> and <strong><em>created_at </em></strong>along with he tokens.</li><li><strong>Before each request requiring the access token:</strong> Check whether the access token is about to expire based on <strong><em>created_at</em></strong> and <strong><em>expires_in</em></strong>.</li><li><strong>Only if it’s about to expire:</strong> Include the refresh token in a custom HTTP header along with the usual request headers.</li><li><strong>Server side during token verification:</strong> Check if the verification of the access token failed due to expiration only, everything else should be valid</li><li>If everything else is valid:</li><li>Look for the custom header containing the refresh token.</li><li>If present, verify that it’s <strong><em>totally</em></strong> <strong><em>valid and correctly paired with the access token</em></strong> (for example, via the user identifier).</li><li>Only then allow the request to proceed as if the access token were valid.</li><li>While responding to the client, create a new token pair and include it in the response, either in headers or the response body.</li><li><strong>Client side:</strong> Whenever a response includes new tokens, replace the old tokens with the new ones</li></ul><p>This approach reduces your flow from three RTTs (round trip time) to one RTT while maintaining refresh token functionality.</p><p>The flow above is about the refresh token process not how every requests should be authenticated and does not replace an existing mechanism like rotation or blacklisting.</p><p>Of course, this implementation requires control over your authentication code: having a more granular control — and can design it carefully — than if you rely solely on a component or lib that handles everything for you without customization.</p><p><strong>Question:</strong> What do you think is the best way to handle refresh tokens without tripling response time and impacting user experience?</p><p><strong>PS:</strong> When optimizing the flow, always remember: <strong><em>security &gt; latency.</em></strong></p><p><em>Originally published at </em><a href="https://romualdoluwatobi.substack.com/p/are-you-making-three-network-calls"><em>https://romualdoluwatobi.substack.com</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9ca45c559268" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Web Server does not make you app magically faster]]></title>
            <link>https://medium.com/@romualdoluwatobi/a-web-server-does-not-make-you-app-magically-faster-4c0e2aa82b5a?source=rss-a5eb6b8691ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/4c0e2aa82b5a</guid>
            <dc:creator><![CDATA[Romuald Oluwatobi]]></dc:creator>
            <pubDate>Tue, 03 Mar 2026 11:44:03 GMT</pubDate>
            <atom:updated>2026-03-03T11:44:03.931Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*PiVVdyuq7_lkeNlp.png" /></figure><p>Are you still trying to find the “perfect” web server to improve your app’s responsiveness?</p><p>In most cases, it doesn’t have a real impact on your production performance.</p><p>I say <em>real</em> impact because the web server is still important and can become a bottleneck. But most of the time, it isn’t. Your own code is.</p><p>The web server, framework, and surrounding tools you use to serve your app are ultimately there to run your application. The real behavior, performance, and scalability come from what your code does.</p><p>So why this reminder?</p><p>Developers often overthink:</p><ul><li>The right web server to use</li><li>The perfect configuration</li><li>The ideal deployment setup</li></ul><p>And forget that the server’s primary job is to handle the network layer between you and the client. The rest is your responsibility.</p><p>Most modern web servers do that job extremely well-often better than we assume.</p><p>So when you want to optimize your app’s responsiveness, your first step should always be to investigate your own application code.</p><p>Before anything else.</p><p>If you’re building a web API, for example, focus on endpoint processing time. That’s where you have the most control.</p><blockquote>Greater control → Greater impact.</blockquote><p>Your web server is a component of the building. It’s not what will magically make your app faster.</p><p>Your code is.</p><p>Think about that before redesigning your architecture or switching tools.</p><p><em>Originally published at </em><a href="https://romualdoluwatobi.substack.com/p/a-web-server-does-not-make-you-app"><em>https://romualdoluwatobi.substack.com</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4c0e2aa82b5a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Exécution CPU-bound et I/O-bound : Explications pour Développeurs avec des Exemples Concrets]]></title>
            <link>https://medium.com/@romualdoluwatobi/ex%C3%A9cution-cpu-bound-et-i-o-bound-explications-pour-d%C3%A9veloppeurs-avec-des-exemples-concrets-8972911c6e66?source=rss-a5eb6b8691ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/8972911c6e66</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[io-bound]]></category>
            <category><![CDATA[cpu-bound]]></category>
            <dc:creator><![CDATA[Romuald Oluwatobi]]></dc:creator>
            <pubDate>Tue, 30 Dec 2025 15:33:06 GMT</pubDate>
            <atom:updated>2025-12-30T15:41:51.200Z</atom:updated>
            <content:encoded><![CDATA[<h3>Exécution CPU-bound et I/O-bound : Explications pour Développeurs avec des Exemples Concrets</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iykQEIbYkoLybFTEWRjVQg.png" /></figure><p>English version:</p><p><a href="https://medium.com/@romualdoluwatobi/cpu-bound-and-i-o-bound-execution-explained-for-developers-with-real-life-examples-1b4a6abb2b42">CPU-bound and I/O-bound Execution: Explained for Developers with Real-Life Examples</a></p><blockquote>CPU-bound : Le programme passe la majorité de son temps à utiliser le processeur plutôt qu’à attendre des ressources externes comme le réseau. Ces tâches sont limitées par la vitesse du CPU, par exemple les programmes intensifs ou le traitement de grandes quantités de données.</blockquote><blockquote>I/O-bound : Le programme passe la majorité de son temps à attendre des ressources externes plutôt qu’à utiliser activement le CPU. Ces tâches sont limitées par les opérations d’input/output, comme les requêtes réseau ou l’accès au disque.</blockquote><p>Si vous n’avez pas totalement compris les définitions précédentes, alors ce tutoriel a été conçu pour vous.</p><p>Plongeons dans le sujet étape par étape.</p><h3>Qu’est-ce que le CPU et quand est ce que le CPU est il actif ?</h3><p>Le CPU (Central Processing Unit, ou processeur) est le composant de votre système informatique chargé d’exécuter les instructions.</p><p>Les instructions sont générées à partir de programmes écrits dans des langages comme Python, C, Rust, etc. Ces programmes sont finalement traduits en <strong>code machine</strong>, qui est la forme compréhensible et exécutable par le CPU.</p><p>Pour simplifier, résumons le travail du CPU à des <strong>opérations arithmétiques et logiques</strong>.</p><p>Lorsque vous écrivez un programme en Python, C ou dans tout autre langage et que vous l’exécutez, ce programme est transformé en code machine. Le CPU exécute ensuite ce code machine en effectuant des <strong>opérations arithmétiques et logiques</strong> afin que le programme réalise les actions nécessaires et produise les résultats attendus.</p><p>Autrement dit, le CPU est le composant principal de votre machine responsable de l’exécution des instructions générées par vos programmes Python, C, Rust, etc.</p><p>Le CPU est considéré comme <strong>actif</strong> lorsqu’il effectue des opérations <strong>arithmétiques et logiques</strong> pour exécuter ce code machine.</p><p><strong>En résumé :</strong> le CPU est le cœur de votre machine. Il exécute les programmes pour produire des résultats et est <strong>actif</strong> dès qu’il effectue des <strong>calculs et des opérations logiques.</strong></p><h3>Qu’est-ce que l’I/O et quand les opérations d’I/O ont-elles lieu ?</h3><p>I/O signifie <strong>Input/Output</strong> (entrée/sortie). Cela désigne les situations dans lesquelles votre programme fournit une entrée à un composant sur lequel il a peu ou pas de contrôle, puis attend que ce composant lui retourne quelque chose en réponse : la sortie.</p><p>Au sens très large, même l’appel d’une simple fonction avec un paramètre et la récupération d’une valeur de retour peuvent être considérés comme un mécanisme d’input/output. Toutefois, dans ce tutoriel, nous nous concentrerons sur les <strong>opérations d’I/O pendant lesquelles le CPU n’effectue pas activement d’opérations arithmétiques ou logiques</strong>.</p><p>Par exemple :</p><ul><li>Lorsque votre programme utilise une API pour effectuer une opération liée aux fichiers, comme l’écriture sur le disque, vous réalisez une opération d’I/O. Vous fournissez une entrée (les données à écrire) et la fonction vous renvoie une sortie (par exemple une confirmation ou le nombre d’octets écrits).</li><li>Lorsque votre programme, en tant que client, effectue une requête HTTP vers un serveur afin de récupérer des données, il s’agit également d’une opération d’I/O. Vous envoyez une entrée à travers le réseau et attendez une sortie : les données renvoyées par un serveur ou un système externe.</li></ul><p>En résumé, une opération d’I/O se produit lorsque vous fournissez une entrée à un système que vous ne contrôlez pas directement et que vous attendez qu’il vous renvoie une sortie. Pendant une opération d’I/O, le CPU est considéré comme <strong>inactif</strong>, c’est-à-dire qu’il n’effectue pas de <strong>calculs</strong> <strong>arithmétiques ou d’opérations logiques.</strong></p><blockquote>Lors d’une <strong>exécution CPU</strong>, le CPU est actif et réalise des opérations arithmétiques et logiques. Lors d’une <strong>opération/exécution d’I/O</strong>, votre programme continue d’exister, mais le CPU est inactif pour cette tâche : il attend simplement que l’opération d’I/O se termine.</blockquote><p>Jusqu’ici, nous n’avons pas encore parlé d’<strong>exécution CPU-bound</strong> et d’<strong>exécution I/O-bound</strong>. Jusqu’à présent, l’objectif était de vous donner une vision claire de ce qui se passe lors d’une exécution CPU et lors d’une exécution I/O — il ne faut pas les confondre.</p><h3>Manger vs faire la lessive : une analogie réelle</h3><p>Utilisons un exemple réel pour illustrer la différence entre une <strong>exécution CPU-bound</strong> et une <strong>exécution I/O-bound</strong>.</p><h4>Exemple 1 : Faire la lessive avec une machine</h4><p>Commençons par la lessive à l’aide d’une machine. Le processus est généralement simple : vous prenez vos vêtements, vous les mettez dans la machine avec ce qu’il faut, vous démarrez la machine en appuyant sur quelques boutons, puis vous attendez qu’elle fasse son travail.</p><p>Le temps d’attente peut durer plusieurs minutes, et vous n’avez aucun contrôle réel sur le moment où la machine aura terminé. Une fois le cycle terminé, vous récupérez vos vêtements propres.</p><p>Dans ce cas, pour que la tâche soit complétée, il faut <strong>vous</strong>, <strong>les vêtements</strong> et <strong>la machine</strong>.</p><h4>Exemple 2 : Manger</h4><p>Voyons maintenant le cas où vous mangez. Ici, il n’y a que vous et la nourriture. Une fois que vous commencez à manger, vous n’avez pas besoin d’attendre qu’un élément externe agisse pour terminer votre repas.</p><p>Vous pouvez faire des pauses — regarder la télévision, attendre que quelqu’un vous apporte quelque chose — mais la complétion du repas ne dépend pas explicitement de quelque chose d&#39;autre. Elle dépend uniquement de vous.</p><p>Cela signifie également que vous pouvez terminer votre repas en quelques secondes ou en plusieurs minutes : vous êtes totalement maître du timing.</p><p>Dans ce cas, pour que la tâche soit complétée, il faut <strong>vous</strong> et <strong>la nourriture</strong>, et c’est vous qui décidez du rythme.</p><h4>La différence en pratique</h4><p>Rendons la distinction claire :</p><ul><li>Dans l’exemple de la <strong>lessive</strong>, vous lancez la tâche puis vous attendez qu’elle se termine, car le résultat final dépend d’un système externe sur lequel vous n’avez pas de contrôle direct, la machine.</li><li>Dans l’exemple du <strong>repas</strong>, vous lancez la tâche et la terminez à votre propre rythme. Il n’y a aucune dépendance externe : c’est uniquement vous et la nourriture.</li></ul><h4>Le lien avec CPU-bound et I/O-bound</h4><p>Si l’on transpose cette analogie à l’informatique:</p><ul><li><strong>Manger</strong> représente une <strong>exécution CPU-bound</strong>. C’est une tâche active pendant laquelle du travail est continuellement effectué, tout comme un programme qui réalise principalement des opérations CPU (arithmétiques et logiques) avec peu ou pas de dépendance à des systèmes externes.</li><li><strong>Faire la lessive</strong> représente une <strong>exécution I/O-bound</strong>. Vous initiez la tâche en fournissant une entrée, puis vous attendez qu’un système externe fasse le travail, avant de récupérer le résultat (la sortie). Pendant ce temps, vous attendez bien plus que vous n’agissez — exactement comme un programme en attente d’une opération d’I/O.</li></ul><p>(Sous l’hypothèse que vous ne faites rien d’autre pendant que la machine fonctionne.)</p><h3>Une chose importante à savoir</h3><p>Lorsque vous appelez un serveur distant avec une bibliothèque cliente, ou que vous interagissez avec des bases de données comme PostgreSQL ou MongoDB pour effectuer des requêtes, ou plus généralement lorsque vous effectuez un appel réseau, il se passe quelque chose d’important : <strong>dès que la requête réseau est initié, le CPU de votre système devient inactif et ne recommence à travailler que lorsque la réponse est</strong> <strong>disponible</strong>.</p><p>Le même principe s’applique lorsque vous lisez ou écrivez sur un disque (SSD, HDD, etc.).</p><p>Un concept similaire existe pour l’accès à la <strong>RAM</strong>, mais ici le temps d’accès est si court qu’il peut généralement être ignoré.</p><p>Je tenais à clarifier ce point, car il constitue souvent un obstacle qui empêche de bien comprendre la différence entre les exécutions CPU-bound et I/O-bound.</p><blockquote>Lors d’un appel réseau ou d’une opération sur disque, après que le CPU a initié le processus, il reste inactif jusqu’à ce que l’opération soit terminée.</blockquote><h3>Qu’est-ce qu’une exécution CPU-bound et I/O-bound ? Ou, qu’est-ce qui rend un programme CPU-bound ou I/O-bound ?</h3><h4>Exécution CPU-bound :</h4><p>Un programme est CPU-bound lorsqu’il passe la majeure partie de son temps à utiliser le CPU plutôt qu’à attendre des ressources externes comme le réseau. Ce sont des tâches limitées par la vitesse du CPU, comme les calculs intensifs ou le traitement massif de données.</p><p><strong>Comportement du programme dans ce cas :</strong><br> Le CPU est très actif, effectuant en continu des opérations arithmétiques et logiques, avec peu ou pas d’activité I/O.</p><h4>Exécution I/O-bound :</h4><p>Un programme est I/O-bound lorsqu’il passe la majeure partie de son temps à attendre des ressources externes plutôt qu’à utiliser activement le CPU. Ce sont des tâches limitées par les opérations d’input/output, comme les requêtes réseau ou l’accès au disque.</p><p><strong>Comportement du programme dans ce cas :</strong><br> Le programme réalise beaucoup d’opérations I/O (appels réseau, opérations sur disque) et passe beaucoup de temps à attendre des systèmes externes. L’utilisation du CPU est relativement faible, ce qui signifie que moins d’opérations arithmétiques et logiques sont effectuées.</p><p>J’espère que cela est clair et vous permet de bien comprendre la différence entre ces deux modes d’exécution.</p><h3>Tous les programmes sont I/O-bound</h3><p>D’accord, ne vous laissez pas confondre. Voici la vérité :</p><p>si l’on y regarde de plus près, même lors d’un programme CPU-bound, intensif en calculs, on utilise beaucoup la RAM. Et l’accès à la RAM est, techniquement, une opération I/O.</p><p>Alors, si toute exécution CPU-bound est implicitement I/O-bound, pourquoi fait-on cette distinction ?</p><p>La raison est que <strong>l’accès à la RAM est extrêmement rapide</strong>, si rapide que le temps d’accès peut généralement être ignoré. On parle de nanosecondes, comparées aux microsecondes ou millisecondes nécessaires pour accéder à un disque ou effectuer un appel réseau.</p><p>En comparaison, la différence est énorme. L’accès à la RAM peut être <strong>des milliers de fois plus rapide que l’accès à un disque</strong>, et les appels réseau sont encore beaucoup plus lents. C’est pour cela que l’on considère les programmes CPU-bound comme CPU-bound, malgré l’I/O implicite que représente l’accès à la RAM.</p><h3>Pourquoi est-il important de connaître l’exécution CPU-bound et I/O-bound ?</h3><p>Vous vous dites peut-être : « On a juste besoin d’écrire des programmes et de livrer des produits. Pourquoi se soucier de CPU-bound ou I/O-bound ? »</p><p>Vous n’avez pas tort. Si votre objectif est simplement d’écrire un programme qui fonctionne et produit un résultat, connaître ces distinctions peut sembler peu utile.</p><p>Mais dans les <strong>applications réelles</strong>, utilisées par de vraies personnes sur de vrais systèmes, <strong>la performance et l’optimisation ne sont pas un simple “plus”, elles sont au cœur du système</strong>, car elles impactent directement l’expérience utilisateur.</p><p>Et comment optimiser quelque chose si vous ne savez pas <strong>ce qu’il faut optimiser</strong> ou <strong>comment le système se comporte</strong> ?</p><p>Reprenons nos exemples du monde réel et voyons comment rendre les tâches plus rapides :</p><ul><li><strong>Manger :</strong> Si vous voulez finir de manger plus vite, il vous suffit de manger plus vite. Vous êtes en plein contrôle, et vous êtes le goulot d’étranglement. Plus vous êtes rapide, plus la tâche se termine vite.</li><li><strong>Faire la lessive :</strong> Si vous voulez que la lessive soit terminée plus rapidement, vous ne pouvez rien faire pour accélérer la machine. Il faut optimiser la machine elle-même. Ici, c’est la machine qui est le goulot d’étranglement, et votre capacité ne change pas grand-chose.</li></ul><p>Vous voyez la différence ? En comprenant la nature de la tâche, vous savez <strong>quoi optimiser</strong> pour atteindre votre objectif.</p><p>C’est la même chose pour les programmes. Dans les systèmes réels, si vous voulez que votre application soit réactive :</p><ul><li>Pour un programme <strong>CPU-bound</strong>, il peut être nécessaire d’<strong>ajouter plus de core CPU</strong> pour gérer davantage de calculs plus rapidement.</li><li>Pour un programme <strong>I/O-bound</strong>, il peut être nécessaire de <strong>prioriser la concurrence</strong> et de paralléliser les appels réseau pour améliorer les performances.</li></ul><p>Dans de nombreux systèmes, <strong>la vitesse n’est pas un simple bonus, elle est cruciale</strong>. Imaginez effectuer une recherche sur Google qui prend plusieurs secondes à répondre, ou faire défiler votre réseau social préféré alors que le contenu met plusieurs minutes à charger. Quelle serait votre réaction ?</p><p>Comprendre les exécutions CPU-bound et I/O-bound vous aide à <strong>prendre des décisions éclairées sur la manière d’optimiser votre programme</strong> selon vos objectifs.</p><p>Merci d’avoir lu !</p><p>Si vous êtes développeur Python et que vous voulez explorer les différences d’exécution entre <strong>threads</strong> et <strong>asyncio</strong> pour les tâches CPU-bound et I/O-bound, consultez cet article:</p><p><a href="https://medium.com/@romualdoluwatobi/python-cpu-et-io-bound-ex%C3%A9cution-asyncio-vs-thread-vs-sync-6e9e19848d1c">Python CPU et IO bound exécution: Asyncio vs Thread vs Sync</a></p><p>À bientôt !</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8972911c6e66" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[CPU-bound and I/O-bound Execution: Explained for Developers with Real-Life Examples]]></title>
            <link>https://medium.com/@romualdoluwatobi/cpu-bound-and-i-o-bound-execution-explained-for-developers-with-real-life-examples-1b4a6abb2b42?source=rss-a5eb6b8691ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/1b4a6abb2b42</guid>
            <category><![CDATA[io-bound]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[cpu-bound]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Romuald Oluwatobi]]></dc:creator>
            <pubDate>Tue, 30 Dec 2025 15:07:57 GMT</pubDate>
            <atom:updated>2025-12-30T15:36:48.768Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="CPU-bound and io-bound, Romuald Oluwatobi" src="https://cdn-images-1.medium.com/max/1024/1*iykQEIbYkoLybFTEWRjVQg.png" /></figure><p>Version française:</p><p><a href="https://medium.com/@romualdoluwatobi/ex%C3%A9cution-cpu-bound-et-i-o-bound-explications-pour-d%C3%A9veloppeurs-avec-des-exemples-concrets-8972911c6e66">Exécution CPU-bound et I/O-bound : Explications pour Développeurs avec des Exemples Concrets</a></p><blockquote><strong>CPU-bound execution:</strong> The program spends most of its time using the processor rather than waiting for external resources such as the network. These are tasks limited by the speed of the CPU, like heavy calculations or intensive data processing.</blockquote><blockquote><strong>I/O-bound execution:</strong> The program spends most of its time waiting for external resources instead of actively using the CPU. These are tasks limited by input/output operations, such as network requests or disk access.</blockquote><p>If you did not fully understand the statements above, then this tutorial is for you.</p><p>Let’s dive into it step by step.</p><h3>What is the CPU, and when does CPU execution happen (active)?</h3><p>The CPU (Central Processing Unit) is the component of your computer system that executes instructions.</p><p>Instructions are generated from programs written in languages such as Python, C, Rust, etc. These programs are eventually translated into <strong>machine code</strong>, which is the form the CPU can understand and execute.</p><p>To simplify things, we can reduce the work done by the CPU to <strong>arithmetic and logic operations</strong>.</p><p>When you write a program in Python, C, or any other language and execute it, the program is transformed into machine code. The CPU then executes this machine code by performing<strong> arithmetic and logic operations</strong> so that your program can produce the expected results.</p><p>In other words, the CPU is the main component of your machine responsible for executing the instructions generated by your Python, C, Rust, or other programs.</p><p>The CPU is considered <strong><em>active</em></strong> when it is performing arithmetic and logic operations to execute this machine code.</p><p><strong>Put simply:</strong> the CPU is the core component of your machine that executes programs to produce results, and it is <strong><em>active</em></strong> whenever it is performing <strong>arithmetic</strong> and <strong>logical</strong> computations.</p><h3>What is I/O, and when does I/O execution happen?</h3><p>I/O stands for <strong>Input/Output</strong>. It refers to situations where your program provides an input to a component over which it has little or no control, and then waits for that component to return something in response — the output.</p><p>In a very broad sense, even calling a simple function with a parameter and receiving a return value could be seen as an input/output mechanism. However, in this tutorial, we will focus on <strong>I/O execution during which the CPU is not actively performing arithmetic or logical operations</strong>.</p><p>For example:</p><ul><li>When your program uses an API to perform a file-related operation, such as writing data to disk, you are performing an I/O execution. You provide an input (the data to be written), and the function returns an output (such as a confirmation or the number of bytes written).</li><li>When your program, acting as a client, makes an HTTP request to a server to retrieve data, this is also an I/O execution. You send an input over the network and wait for an output — the data returned by an external server or system.</li></ul><p>In summary, I/O occurs when you provide an input to a system you do not directly control and wait for that system to produce an output. During an I/O execution, the CPU is <strong>inactive</strong>, meaning it is not performing <strong>arithmetic or logical computations.</strong></p><blockquote>During <strong>CPU execution</strong>, the CPU is active and performs arithmetic and logical operations. During an <strong>I/O ex</strong>ecution, your program is still running, but the CPU is idle with respect to that task — it is essentially waiting for the I/O execution to complete.</blockquote><p>So far, we haven’t yet discussed <strong>CPU-bound execution</strong> and <strong>I/O-bound execution</strong>, which is the main reason we are here. Up to this point, the goal was to give you a clear perspective on what happens during CPU execution versus I/O execution — don’t confuse the two.</p><h3>Eating vs. Doing Laundry: a real-world analogy</h3><p>Let’s use a real-world example to illustrate the difference between <strong>CPU-bound</strong> and <strong>I/O-bound</strong> execution.</p><h4>Example 1: Doing laundry with a machine</h4><p>Let’s start with doing laundry using a washing machine. The process is usually simple: you take your clothes, put them into the machine with everything needed, start the machine, and then wait for it to do its job after pressing a few buttons.</p><p>The waiting time can take several minutes, and you have no real control over when the process will finish. Once it’s done, you collect your clean clothes.</p><p>In this case, completing the task requires <strong>you</strong>, <strong>the clothes</strong>, and <strong>the machine</strong>.</p><h4>Example 2: Eating</h4><p>Now let’s consider the case of eating. Here, it’s just you and the food. Once you start eating, you don’t need to wait for something external to happen in order to finish your meal, right?</p><p>You might pause for distractions — watching TV or waiting for someone to bring something — but the completion of the task does not explicitly depend on something out of your control. It depends only on you.</p><p>This also means you can finish eating in a matter of seconds or take several minutes — it’s entirely under your control.</p><p>In this case, completing the task requires <strong>you</strong> and <strong>the food</strong>, and you decide everything else.</p><h4>Spotting the difference</h4><p>Let’s make the distinction clear:</p><ul><li>In the <strong>laundry example</strong>, you initiate the task and then wait for it to complete because the final result depends on an external system over which you have no direct control (the machine).</li><li>In the <strong>eating example</strong>, you initiate the task and complete it at your own pace. There are no dependencies — it’s just you and the food.</li></ul><h4>Mapping this to CPU-bound and I/O-bound execution</h4><p>If we map this analogy to computing:</p><ul><li><strong>Eating</strong> represents a <strong>CPU-bound execution</strong>. It’s an active task where work is continuously being done, a program performing mostly CPU execution (arithmetic and logic) with little or no dependency on external systems.</li><li><strong>Doing laundry</strong> represents an <strong>I/O-bound execution</strong>. You initiate the task by providing an input, then you wait while an external system does the work, and finally you receive the result (the output). During this time, you are mostly waiting rather than actively doing work — just like a program waiting for I/O to complete.</li></ul><p>(Assuming, of course, that you’re doing nothing else while the machine is running.)</p><h3>One Important Thing You Need to Know</h3><p>When you call a remote server using a client library, or interact with databases like PostgreSQL or MongoDB, performing queries, or more generally making any network request, something important happens: <strong>from the moment the network request is initiated, the CPU of your system becomes idle and only starts working again when the response comes back</strong>.</p><p>The same principle applies when you are reading from or writing to a disk (SSD, HDD, etc.).</p><p>A similar concept exists for accessing <strong>RAM</strong>, but here the access time is so short that it can generally be ignored.</p><p>I wanted to make this point clear because it is often a sticking point that can prevent a full understanding of CPU-bound vs. I/O-bound execution.</p><blockquote>During a network request or disk operation, after the CPU initiates the process, it becomes idle until the operation completes.</blockquote><h3>What Is CPU-bound and I/O-bound Execution? Or, what makes a program CPU-bound or I/O-bound?</h3><h4>CPU-bound execution:</h4><p>A program is CPU-bound when it spends most of its time using the CPU rather than waiting for external resources such as the network. These are tasks limited by the CPU’s speed, like heavy calculations or intensive data processing.</p><p><strong>Behavior of the program in this case:</strong><br>The CPU is highly active, continuously performing arithmetic and logic operations, with little or no I/O activity.</p><h4>I/O-bound execution:</h4><p>A program is I/O-bound when it spends most of its time waiting for external resources rather than actively using the CPU. These are tasks limited by input/output operations, such as network requests or disk access.</p><p><strong>Behavior of the program in this case:</strong><br> The program performs a high amount of I/O operations (network calls, disk operations) and spends a lot of time waiting for external systems. CPU usage is relatively low, meaning fewer arithmetic and logic operations are performed.</p><p>I hope this makes sense and helps you clearly understand the difference between these two execution modes.</p><h3>Every Program Is I/O-bound</h3><p>Okay, don’t let this confuse you. Here’s the truth:</p><p>if we look closely, even during a CPU-bound, CPU-intensive program, we are using RAM a lot. And accessing RAM is technically an I/O operation.</p><p>So if every CPU-bound execution is implicitly I/O-bound, why do we make a distinction?</p><p>The reason is that <strong>accessing RAM is extremely fast</strong>, so fast that the access time can generally be ignored. We are talking nanoseconds here, compared to microseconds or milliseconds for disk or network access.</p><p>By comparison, the difference is huge. Accessing RAM can be <strong>thousand of times faster than accessing a disk</strong>, and network calls are even slower by orders of magnitude. That’s why CPU-bound programs are considered CPU-bound, despite the implicit I/O involved in RAM access.</p><h3>Why Does It Matter to Know About CPU-bound and I/O-bound Execution?</h3><p>Right? You might be thinking: “We just need to write programs and ship products. Why worry about CPU-bound or I/O-bound execution?”</p><p>You’re not wrong. If your goal is just to write a program that runs and produces a result, knowing about these distinctions may not seem immediately helpful.</p><p>But in <strong>real-world applications</strong>, used by real people on real systems, <strong>performance and optimization are not just nice-to-have — they are at the heart of the system</strong>, because they directly impact user experience.</p><p>And how can you optimize something if you don’t know <strong>what to actually optimize</strong> or even <strong>how it behaves</strong>?</p><p>Let’s go back to our real-world examples and consider how we could make tasks faster:</p><ul><li><strong>Eating:</strong> If you want to finish eating faster, the only way to achieve that is by eating faster yourself. You are in full control, and you are the bottleneck. The faster you are, the faster the task is completed.</li><li><strong>Laundry:</strong> If you want the laundry to finish faster, you cannot speed it up by doing anything yourself — you need to optimize the washing machine. Here, the machine is the bottleneck, and your speed doesn’t matter as much.</li></ul><p>Do you see the difference? By understanding the nature of the task, you know <strong>what to optimize</strong> to achieve your goal.</p><p>It’s the same with programs. In real-world systems, if you want your application to be responsive:</p><ul><li>For <strong>CPU-bound programs</strong>, you might need to <strong>add more CPU cores</strong> to handle more computation.</li><li>For <strong>I/O-bound programs</strong>, you might need to <strong>prioritize concurrency</strong> and parallelize network calls to improve performance.</li></ul><p>In many systems, <strong>speed is not just a bonus — it is critical</strong>. Imagine performing a Google search that takes several seconds to respond, or scrolling through your favorite social media feed while content takes minutes to load. How would that feel?</p><p>Understanding CPU-bound and I/O-bound execution helps you make informed decisions about <strong>how to optimize your program</strong> for your desired outcomes.</p><p>Thanks for reading!</p><p>If you’re a Python developer and want to explore the differences in execution between <strong>threads</strong> and <strong>asyncio</strong> for <strong>CPU-bound vs. I/O-bound </strong>tasks, check out this article:</p><p><a href="https://medium.com/@romualdoluwatobi/python-concurrency-model-comparison-for-cpu-and-io-bound-execution-asyncio-vs-threads-vs-sync-35c114fc0045">Python Concurrency Model Comparison For CPU And IO Bound Execution: Asyncio vs Threads vs Sync</a></p><p>See you there.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1b4a6abb2b42" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Python CPU et IO bound exécution: Asyncio vs Thread vs Sync]]></title>
            <link>https://medium.com/@romualdoluwatobi/python-cpu-et-io-bound-ex%C3%A9cution-asyncio-vs-thread-vs-sync-6e9e19848d1c?source=rss-a5eb6b8691ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/6e9e19848d1c</guid>
            <category><![CDATA[io-bound]]></category>
            <category><![CDATA[python-concurrency]]></category>
            <category><![CDATA[python-thread]]></category>
            <category><![CDATA[cpu-bound]]></category>
            <category><![CDATA[asyncio]]></category>
            <dc:creator><![CDATA[Romuald Oluwatobi]]></dc:creator>
            <pubDate>Sat, 08 Nov 2025 09:02:27 GMT</pubDate>
            <atom:updated>2025-11-08T09:02:27.787Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="cpu-bound python execution: threads, asyncio, sync program" src="https://cdn-images-1.medium.com/max/1024/1*eeKcfmLDT4Q-Ibgkm_oRqA.png" /></figure><blockquote>Pour les résultats complets du benchmark, rendez-vous ici (préparez un café et un peu de temps pour tout parcourir 😉) : <a href="https://github.com/Romulad/asyncio-vs-thread">https://github.com/Romulad/asyncio-vs-thread</a></blockquote><blockquote>Le projet utilise la version 3.12 de python</blockquote><p>De la curiosité aux questions ? J’ai donc décidé de créer ce benchmark.</p><ul><li>Êtes-vous déjà demandé à quoi ressemblerait l’exécution d’<strong>asyncio</strong> à l’intérieur de plusieurs threads Python ?</li><li>Ou encore entre <strong>threads</strong> et <strong>asyncio</strong>, lequel est réellement le plus performant ?</li></ul><p>Oui, je sais — on nous a déjà donné les “bonnes” réponses :<br><strong>Async</strong> pour l’I/O, <strong>threads</strong> pour la concurrence, <strong>sync</strong> pour la simplicité.</p><p>Mais, je voulais le voir de mes propres yeux.</p><p>Alors, j’ai réalisé cette expérience.</p><p><em>Et devinez quoi ?</em></p><p>Je m’attendais à ce que les threads python surpassent le code synchrone pour un programme lié au CPU… mais ils se sont révélés être les plus lents de tous.</p><p>Prêt ? Allons-y.</p><h3>Les expériences</h3><p>J’ai utilisé deux types d&#39;exécutions:</p><ul><li><strong>Exécution liée au CPU</strong> — purement computationnelle, sans I/O</li><li><strong>Exécution liée à l’I/O</strong> — avec de nombreuses requêtes HTTP, afin de mesurer la gestion de la concurrence et de la latence dans des scénarios réels.</li></ul><p>À partir de là, j’ai comparé plusieurs modèles d’exécution — <strong>sync</strong>, <strong>asyncio</strong>, <strong>threads</strong> et une approche <strong>hybride (async + threads)</strong> — pour observer le comportement de chacun sous différentes conditions de charge.</p><p><em>Je ne montrerai ici que le </em><strong><em>graphique du temps d’exécution</em></strong><em>, mais vous pouvez consulter les expériences complètes et tous les graphiques </em><a href="https://github.com/Romulad/asyncio-vs-thread"><strong><em>ici</em></strong></a><em>.</em></p><h3>CPU-Bound Task : Encodage de caractères</h3><p><strong>Programme:</strong> itérer sur 100 000 URL — <em>string</em> — et encoder chaque caractère afin de calculer sa représentation en octets.</p><p><strong>Résultats clés:</strong></p><ul><li><strong>Synchrone :</strong> terminé en environ <strong>9,77 s</strong>, utilisant presque un core CPU complet. Mémoire RAM stable (~16 Mo).</li><li><strong>Asyncio :</strong> temps d’exécution pratiquement identique (<strong>9,78 s</strong>) mais avec une utilisation mémoire <strong>44 % plus élevée (~23 Mo)</strong>. Asyncio augmente l&#39;<em>utilisation de la ram </em>sans aucun gain de vitesse.</li><li><strong>Threading (10 threads) :</strong> plus lent (<strong>12,35 s</strong>), utilisant environ <strong>1,1 core CPU</strong>. Le changement de contexte dégrade les performances — le threading est moins adapté aux tâches liées au CPU en Python à cause du <a href="https://realpython.com/python-gil/"><strong><em>GIL</em></strong></a>.</li></ul><blockquote>Pour les tâches <strong>CPU-bound</strong>, il est meilleure d&#39;utiliser l’exécution <strong>synchrone</strong> ou le <strong>multiprocessing</strong>. <strong>Asyncio</strong> ajoute une surcharge mémoire sans bénéfice de performance, et le <strong>threading</strong> dégrade activement les performances à mesure que le nombre de threads augmente.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eeKcfmLDT4Q-Ibgkm_oRqA.png" /></figure><h3>IO-Bound Task: Requêtes HTTP</h3><p><strong>Programme:</strong> je fais un <strong><em>get</em></strong> request vers plusieurs URL(10,000+) et enregistre les données de réponse.</p><p><strong>synchrone</strong></p><ul><li>100 URL : <strong>18,16 s</strong> (~180 ms/requête), CPU utilisé (~3 %), fiabilité parfaite.</li><li>1 000 URL : <strong>182,84 s</strong>, montée en charge linéaire, utilisation CPU toujours très faible.</li></ul><p><strong>Observation :</strong> le programme synchrone reste inactif en attendant chaque requête. Le CPU est presque pas utilisé.</p><p><strong>Asyncio</strong></p><ul><li>10 000 URL : <strong>14,08 s</strong> — ≈130× plus rapide que la référence synchrone</li><li>100 000 URL : <strong>117,47 s</strong> — toujours très rapide, débit ~1 647 Ko/s<br> L’utilisation du CPU et de la mémoire évolue avec la concurrence, tout en conservant les même performances et fiabilité (faible taux de requête échoué).</li></ul><p><strong>Observation :</strong> asyncio gère efficacement des milliers de requêtes simultanées avec un minimum d’échecs et une utilisation des ressources maîtrisée.</p><p><strong>Threading</strong></p><ul><li>100 threads : <strong>50,01 s</strong> pour 10 000 URL (3,5× plus lent qu’asyncio, mais 36× plus rapide que le synchrone)</li><li>1 000 threads : <strong>61,63 s</strong> — rendements décroissants, utilisation du CPU plus élevée et quelques échecs</li></ul><p><strong>Observation :</strong> les threads aident modérément mais sont limités. Trop de threads dégradent les performances.</p><p><strong>Hybride : Asyncio + Threads</strong></p><ul><li>10 threads + asyncio : <strong>14,96 s</strong> — similaire à asyncio pur, mais légèrement plus d’utilisation CPU et mémoire</li><li>100 threads + asyncio : <strong>27,40 s</strong> — performance qui s’effondre, pic d’échecs des requêtes (7,2 %)</li><li>1 000 threads + asyncio : <strong>33,20 s</strong> — performances catastrophiques, 56 % des requêtes échouent</li></ul><p><strong>Observation :</strong> l&#39;approche hybride ajoute de la complexité sans <em>aucun bénéfices</em>.</p><blockquote>Pour les tâches liées à l’I/O:</blockquote><blockquote><strong>- asyncio</strong> est clairement le meilleur choix — offrant les meilleures performances (<strong>14,08 s</strong>), un excellent débit (<strong>1 435 Ko/s</strong>) et une bonne fiabilité (<strong>0,06 % d’échecs</strong>).</blockquote><blockquote>- Le <strong>threading</strong> peut convenir pour des besoins de concurrence modérés (≤100 threads), mais les performance se dégrade a mesure que le nombre de thread augmente.</blockquote><blockquote>- L’approche hybride complique les choses sans avantage.</blockquote><blockquote>- L’exécution <strong>synchrone</strong> ne doit être utilisée que pour des scénarios IO simples et à faible volume. (Si non ton programme risque de dormir 😂)</blockquote><figure><img alt="Io-bound execution python comparison: threads, asyncio, sync programe" src="https://cdn-images-1.medium.com/max/1024/1*-aL7_pX7Ao0b6tv5Pic__g.png" /></figure><h3>Envie de voir le benchmark complet ?</h3><p>La suite complète de benchmarking, les scripts et les graphiques sont disponibles sur mon dépôt : <a href="https://github.com/Romulad/asyncio-vs-thread">https://github.com/Romulad/asyncio-vs-thread</a>.</p><p>Vous pouvez exécuter les benchmarks liés au CPU et à l’I/O, ajuster le nombre de threads et explorer les données par vous-même.</p><p>Cela vous a été utile ? Faites-moi part de votre avis dans la section des commentaires.</p><p>Merci de votre lecture !</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6e9e19848d1c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Python Concurrency Model Comparison For CPU And IO Bound Execution: Asyncio vs Threads vs Sync]]></title>
            <link>https://medium.com/@romualdoluwatobi/python-concurrency-model-comparison-for-cpu-and-io-bound-execution-asyncio-vs-threads-vs-sync-35c114fc0045?source=rss-a5eb6b8691ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/35c114fc0045</guid>
            <category><![CDATA[asyncio]]></category>
            <category><![CDATA[threads]]></category>
            <category><![CDATA[concurrency]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[python-concurrency]]></category>
            <dc:creator><![CDATA[Romuald Oluwatobi]]></dc:creator>
            <pubDate>Mon, 03 Nov 2025 19:04:00 GMT</pubDate>
            <atom:updated>2025-11-04T02:15:01.213Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="python Cpu bound execution time comparison for asyncio, threads, sync" src="https://cdn-images-1.medium.com/max/1024/1*eeKcfmLDT4Q-Ibgkm_oRqA.png" /></figure><blockquote>For the complete benchmark results and experiment, head over here (you will need some coffee and time to cover it all 😉): <a href="https://github.com/Romulad/asyncio-vs-thread">https://github.com/Romulad/asyncio-vs-thread</a></blockquote><blockquote>This project use python version 3.12</blockquote><p>From wondering to questions — and then, this benchmark was born.</p><p>Have you ever asked yourself: <em>what would it look like to run </em><strong><em>asyncio</em></strong><em> inside multiple Python </em><strong><em>threads</em></strong><em>?</em> And between <strong><em>threads</em></strong> and <strong><em>asyncio</em></strong>, which one is <em>actually</em> more performant?</p><p>Yeah, I know — people have told us the “right” answers. Async for I/O, threads for concurrency, sync for simplicity. But I wanted to see it with my own eyes.</p><p>So, I built an experiment.</p><p>And guess what? I expected threads to outperform sync code in CPU-bound tests… but it turned out to be the <strong>slowest</strong> of all.</p><p>Ready? Let’s dive into it.</p><h3>The Experiments</h3><p>I used two workloads:</p><ol><li><strong>CPU-bound ex</strong>ecution — pure computation, no I/O, to test raw execution performance.</li><li><strong>IO-bound </strong>execution— heavy on HTTP requests, to measure real-world concurrency and latency handling.</li></ol><p>From there, I benchmarked multiple execution models — <strong>sync</strong>, <strong>asyncio</strong>, <strong>threads</strong>, and <strong>hybrid (async + threads)</strong> approach — to see how each behaves under different stress conditions.</p><p><em>I will only show </em><strong><em>time execution plot</em></strong><em> in this post, you can read the full experiments and see all plots </em><a href="https://github.com/Romulad/asyncio-vs-thread"><em>here</em></a><em>.</em></p><h3>CPU-Bound Tasks: Character Encoding</h3><p><strong>Workload:</strong> Iterating through 100,000 URLs, encoding each character to calculate its byte representation.</p><p><strong>Key Findings</strong></p><ul><li><strong>Synchronous:</strong> Completed in ~9.77s, using almost a full CPU core. Memory stable (~16 MB)</li><li><strong>Asyncio:</strong> Virtually identical execution time (9.78s) but 44% higher memory usage (~23 MB). Asyncio adds overhead but no speed advantage.</li><li><strong>Threading (10 threads):</strong> Slower (12.35s), using ~1.1 CPU cores. Context switching slows down performance — threading is worse for CPU-bound tasks in Python due to the GIL.</li></ul><blockquote>For CPU-bound tasks, use synchronous execution or multiprocessing. Asyncio adds memory overhead without performance benefits, and threading actively degrades performance as the thread count increases.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eeKcfmLDT4Q-Ibgkm_oRqA.png" /></figure><h3>IO-Bound Tasks: HTTP Requests</h3><p><strong>Workload:</strong> Fetching thousands of URLs from an HTTP server, storing response data.</p><h4><strong>Synchronous Execution</strong></h4><ul><li>100 URLs: 18.16s (~180ms/request), CPU barely used (~3%), perfect reliability.</li><li>1,000 URLs: 182.84s, linear scaling, still very low CPU usage.</li></ul><p><strong>Observation:</strong> Sync program waits idle for each request.</p><h4><strong>Asyncio</strong></h4><ul><li>10,000 URLs: 14.08s — <strong>≈130x faster than sync baseline</strong></li><li>100,000 URLs: 117.47s — still blazing fast, throughput ~1,647 KB/s</li><li>CPU and memory usage scale with concurrency and performance and reliability remain excellent.</li></ul><p><strong>Observation:</strong> Asyncio handles thousands of concurrent requests efficiently with minimal failures and active resource usage.</p><h4>Threading</h4><ul><li>100 threads: 50.01s for 10,000 URLs (3.5× slower than <strong><em>asyncio</em></strong> and 36× faster than <strong><em>sync</em></strong>)</li><li>1,000 threads: 61.63s — diminishing returns, higher CPU overhead, some failures</li></ul><p><strong>Observation:</strong> Threads help moderately but are limited by context switching. Too many threads hurt performance.</p><h4>Hybrid: Asyncio + Threads</h4><ul><li>10 threads + asyncio: 14.96s — similar to pure asyncio, but slightly more CPU and memory usage</li><li>100 threads + asyncio: 27.40s — performance collapses, failures spike (7.2%)</li><li>1,000 threads + asyncio: 33.20s — catastrophic performance, 56% request failures</li></ul><p><strong>Observation:</strong> Hybrid approaches <strong>add complexity without benefits. Pure asyncio</strong> for IO-bound tasks, optionally offloading blocking tasks to a small thread pool (≤100 threads).</p><blockquote>For IO-bound tasks, asyncio is the clear winner — offering the best performance (14.08s), excellent throughput (1,435 KB/s), and outstanding reliability (0.06% failure rate). Threading can work for moderate concurrency needs (≤100 threads) but shows diminishing returns beyond that. The hybrid approach adds complexity without benefits. Sync execution should only be used for simple, low-volume scenarios.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-aL7_pX7Ao0b6tv5Pic__g.png" /></figure><h3>Want the Full Benchmark?</h3><p>The full benchmarking suite, scripts, and plots are available in my repo: <a href="https://github.com/Romulad/asyncio-vs-thread">https://github.com/Romulad/asyncio-vs-thread</a></p><p>You can run CPU-bound and IO-bound benchmarks, tweak thread counts, and explore the data yourself.</p><p>Did you find this useful? let me know what you think in the comment section. Thanks for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=35c114fc0045" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Configuration complète d’applications web avec Docker et Compose: Exemple pratique.]]></title>
            <link>https://medium.com/@romualdoluwatobi/configuration-compl%C3%A8te-dapplications-web-avec-docker-et-compose-exemple-pratique-a59e97a01701?source=rss-a5eb6b8691ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/a59e97a01701</guid>
            <category><![CDATA[docker-compose]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[full-stack-application]]></category>
            <dc:creator><![CDATA[Romuald Oluwatobi]]></dc:creator>
            <pubDate>Wed, 01 Jan 2025 12:36:44 GMT</pubDate>
            <atom:updated>2025-01-01T12:36:44.406Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/626/1*qWvL3DCNoDSz3ipgNRZv3A.png" /></figure><p>Rien de plus excitant que de pouvoir mettre en place un environnement avec une simple commande; c&#39;est exactement ce que permet Docker Compose.</p><p>Vous définissez comment votre application et les composants nécessaires doivent être configurée dans un fichier, et avec une simple commande comme “docker compose up -d”, tout se met en marche.</p><p>Plus besoin, par exemple, de configurer manuellement votre service backend avec la base de données, le frontend, le serveur proxy web ou d&#39;autres services.</p><p>Voici comment cela fonctionne globalement :</p><ol><li>Vous définissez les fichiers Docker pour les différents services qui composent votre application et son déploiement.</li><li>À la racine du projet, vous créez un fichier <strong><em>compose.yaml</em></strong> qui contiendra la manière dont les images et les conteneurs de votre application seront mis en place, ainsi que tout autre paramétrage nécessaire.</li><li>Ensuite, avec une seule commande, Docker Compose s&#39;occupera de construire vos images puis d&#39;exécuter les différents conteneurs pour chaque image.</li></ol><p>Pour en savoir plus :</p><ul><li><a href="https://docs.docker.com/get-started/">Docker</a></li><li><a href="https://docs.docker.com/compose/gettingstarted/">Docker Compose</a></li></ul><p>Une bonne compréhension de Docker et une idée générale de Docker Compose sont idéales.</p><p>Dans la suite, nous allons voir à travers deux exemples comment utiliser Docker et Docker Compose pour mettre en place ou déployer une infrastructure.</p><p>Les langages ou frameworks utilisés ne sont présents qu&#39;à titre d&#39;exemple, et vous pouvez appliquer le principe à n&#39;importe quel langage ou framework.</p><p>Dans le premier exemple, nous allons mettre en place un environnement avec Next.js et Nginx. Ensuite, dans le second exemple, un autre composée de FastAPI, React, Redis et Nginx.</p><p>Ready?</p><h3><strong>Exemple 1 : Next.js et Nginx</strong></h3><p>Il s’agit d’un tableau de bord de crypto-monnaie réalisé avec Next.js. Vous pouvez le consulter <a href="https://tokena-ro.vercel.app/dashboard">ici</a>.</p><p>Le dépôt GitHub est disponible <a href="https://github.com/Romulad/figma-to-code-ed2-week3">ici </a>.</p><p>Imaginons que l’on nous donne un serveur pour un simple déploiement de notre application de crypto-monnaie afin qu’elle soit accessible sur internet :</p><h4><strong>Première méthode</strong></h4><p>Nous devrons nous connecter au serveur et, manuellement, exécuter les commandes nécessaires étape par étape pour :</p><ul><li>Build notre application Next.js puis l’exécuter sur le serveur avec Node.js.</li><li>Configurer un proxy comme Nginx, qui gérera les requêtes et les redirigera vers notre application Node.js. Pour comprendre pourquoi utiliser un proxy comme Nginx, vous pouvez lire <a href="https://medium.com/@mak0024/nginx-as-a-reverse-proxy-benefits-and-best-practices-928863bfd317">cet article</a>.</li></ul><h4><strong>Deuxième méthode</strong></h4><p>Une fois connectés au serveur, nous aurons simplement besoin de configurer Docker et Docker Compose, puis de déployer l’application avec une simple commande. En résumé, le code source de l’application inclut les instructions pour son déploiement.</p><p>Après avoir cloné le dépôt, vous trouverez un dossier nommé <strong>docker</strong>, qui contient des fichiers Docker permettant d’exécuter notre application en mode développement ou production.</p><p>À l’intérieur du dossier <strong>docker</strong>, le fichier <strong><em>prod.Dockerfile</em></strong> sera utilisé pour créer l&#39;image nécessaire à la génération du conteneur qui exécutera notre application Next.js en mode production.</p><p>Le contenu du fichier :</p><pre>FROM node:lts as builder<br><br>WORKDIR /tokena<br><br>COPY package*.json ./<br><br>RUN npm i<br><br>COPY . .<br><br>RUN npm run build<br><br># production stage<br>FROM node:22-alpine<br><br>WORKDIR /app<br><br>## needed in production for image optimization; check this for more info: https://nextjs.org/docs/messages/sharp-missing-in-production<br>RUN npm i sharp<br><br>RUN addgroup -S nextjs &amp;&amp; adduser -S tokena -G nextjs<br>RUN mkdir .next &amp;&amp; chown tokena:nextjs .next<br><br>COPY --from=builder --chown=tokena:nextjs /tokena/.next/standalone ./<br>COPY --from=builder --chown=tokena:nextjs /tokena/.next/static ./.next/static<br><br>USER tokena<br><br>ENV PORT=8080<br>ENV NODE_ENV=production<br><br>EXPOSE 8080<br><br>CMD [ &quot;node&quot;, &quot;server.js&quot; ]</pre><p>Le fichier <strong><em>prod.Dockerfile</em></strong> permet de faire un build du projet, puis d’exécuter le résultat en mode autonome avec Next.js. La commande pour lancer notre application dans le conteneur est <strong><em>node server.js</em></strong>.</p><p>Nous avons également un fichier <strong><em>nginx.Dockerfile</em></strong> dans le dossier <strong><em>docker</em></strong>, qui sera utilisé pour créer l’image servant à exécuter Nginx en tant que serveur proxy pour notre application Next.js.</p><p>Le contenu de<em> </em><strong><em>nginx.Dockerfile</em></strong> :</p><pre>FROM nginx:1.26.2-alpine<br>COPY nginx.site.conf /etc/nginx/conf.d/nginx.site.conf</pre><p>Le fichier <strong><em>nginx.Dockerfile</em></strong> utilise l’image Docker officielle de Nginx et copie une configuration contenue dans le fichier <strong><em>nginx.site.conf</em></strong> dans le dossier <strong><em>conf.d</em></strong> de Nginx à l’intérieur de l’image docker.</p><p>Le fichier <strong><em>nginx.site.conf</em></strong> se trouve également à la racine du projet. Nous reviendrons sur son contenu plus tard.</p><p>Ainsi, nous avons la configuration Docker des deux services qui composent notre application. Cela suffirait pour effectuer un véritable déploiement et éviter les configurations manuelles sur un serveur.</p><p>Cependant, nous avons toujours un problème : il nous faudrait nous souvenir de chaque détail nécessaire pour construire nos images et exécuter les conteneurs. Imaginez la complexité si vous aviez une infrastructure avec plusieurs configurations Docker ?</p><p>C’est à ce moment que Docker Compose intervient. Avec un simple fichier YAML, il nous permet de créer plusieurs images et d’exécuter plusieurs conteneurs simultanément de manière automatique.</p><p>À la racine du projet, vous trouverez un fichier <strong><em>compose.yaml</em></strong> et un autre <strong><em>compose.prod.yaml</em></strong>.</p><p>Le fichier <strong><em>compose.yaml</em></strong> sert de fichier de base pour les configurations communes. Vous pouvez en savoir plus <a href="https://docs.docker.com/compose/how-tos/multiple-compose-files/merge/">ici</a>.</p><p>Dans le fichier <strong><em>compose.yaml</em></strong>, nous avons ceci :</p><pre>name: tokena<br><br>services:<br>  nextjs:<br>    image: tokena<br>    env_file:<br>      - ./.env</pre><p>Dans <strong><em>compose.prod.yaml</em></strong>, nous avons:</p><pre>services:<br>  nextjs:<br>    build:<br>      context: .<br>      dockerfile: ./docker/prod.Dockerfile<br>    container_name: nextjs<br>    networks:<br>      - tokena-net<br><br>  nginx-server:<br>    image: nginx-server<br>    build:<br>      context: .<br>      dockerfile: ./docker/nginx.Dockerfile<br>    ports:<br>      - 80:80<br>    depends_on:<br>      - nextjs<br>    networks:<br>      - tokena-net<br><br>networks:<br>  tokena-net:<br>    driver: bridge</pre><h4><strong>À l’intérieur d’un fichier Compose</strong></h4><p>Le fonctionnement de base d’un fichier Compose repose sur des <strong><em>services</em></strong>. Les services représentent les différents composants qui forment l’application dans son ensemble, y compris ceux nécessaires au déploiement, si besoin.</p><p>Dans notre configuration, nous avons deux services : <strong><em>nextjs</em></strong>, qui sert notre application, et <strong><em>nginx</em></strong>, qui joue le rôle de proxy pour le trafic entrant et sortant.</p><p>Ainsi, nous devons définir ces deux services dans notre fichier Compose.</p><p>Un fichier Compose utilise la syntaxe <strong><em>YAML </em></strong>pour la définition des configurations.</p><p>Voyons comment définir les services dans un fichier Compose avec la syntaxe YAML :</p><pre>services:<br>  nextjs:<br>    build:<br>      context: .<br>      dockerfile: ./docker/prod.Dockerfile<br>    container_name: nextjs<br>    networks:<br>      - tokena-net<br><br>  nginx-server:<br>    image: nginx-server<br>    build:<br>      context: .<br>      dockerfile: ./docker/nginx.Dockerfile<br>    ports:<br>      - 80:80<br>    depends_on:<br>      - nextjs<br>    networks:<br>      - tokena-net</pre><p>La directive <strong><em>services </em></strong>dans un fichier Compose nous permet de définir chaque service qui compose une application.</p><p>Chaque service doit spécifier comment l’image Docker de l’application sera construite, ainsi que les configurations et paramètres nécessaires pour exécuter le conteneur issu de l’image.</p><p>Ici, au niveau de la directive <strong><em>services</em></strong>, nous avons la définition de deux services : <strong><em>nextjs </em></strong>et <strong><em>nginx-server</em></strong>.</p><p>Pour le service <strong><em>nextjs:</em></strong></p><pre>nextjs:<br>    build:<br>      context: .<br>      dockerfile: ./docker/prod.Dockerfile<br>    container_name: nextjs<br>    networks:<br>      - tokena-net</pre><ul><li>la directive <strong><em>build </em></strong>indique à Compose que l&#39;image pour ce service doit être construite à partir du fichier <strong><em>prod.Dockerfile</em></strong>, qui se trouve dans le dossier <strong><em>docker </em></strong>à la racine du projet. Pour cela, nous utilisons la directive <strong><em>context </em></strong>à l&#39;intérieur de la directive <strong><em>build </em></strong>avec la valeur <strong><em>. </em></strong>(point), pour indiquer à Compose que le contexte de construction est le dossier actuel. Ensuite, avec la directive <strong><em>dockerfile</em></strong>, nous spécifions où se trouve le fichier Docker à utiliser pour la construction de l&#39;image.</li><li>Après avoir défini comment l’image doit être construite, la directive <strong><em>container_name </em></strong>permet de spécifier que le conteneur issu de cette image doit être nommé <strong><em>nextjs</em></strong>.</li><li>Enfin, avec la directive <strong><em>networks</em></strong>, nous indiquons à Compose que le conteneur issu de notre image doit être exécuté dans un réseau appelé <strong><em>tokena-net</em></strong>. Ici, vous pouvez spécifier plusieurs réseaux en ajoutant des tirets et en indiquant le nom de chaque réseau.</li></ul><p>Et voilà, nous venons de définir notre service pour exécuter Next.js.</p><p>En plus de Next.js, nous avons également un autre service, nommé <strong><em>nginx-server</em></strong>, défini sous la directive <strong><em>services </em></strong>de Compose, comme ceci:</p><pre>nginx-server:<br>    image: nginx-server<br>    build:<br>      context: .<br>      dockerfile: ./docker/nginx.Dockerfile<br>    ports:<br>      - 80:80<br>    depends_on:<br>      - nextjs<br>    networks:<br>      - tokena-net</pre><p>Pour le service <strong><em>nginx-server</em></strong>, nous indiquons à Compose :</p><ul><li>que l’image de notre service doit être nommée <strong><em>nginx-server</em></strong> à l&#39;aide de la directive <strong><em>image</em></strong>.</li><li>que l’image doit être construite à partir du fichier<strong><em> nginx.Dockerfile</em></strong>, qui se trouve dans le dossier <strong><em>docker </em></strong>à la racine du projet, et que le build context doit être le dossier courant où se trouve le fichier Compose lui-même.</li><li>avec la directive <strong><em>ports</em></strong>, nous indiquons à Compose une liste de ports sur lesquels notre service sera accessible publiquement. Ici, nous avons spécifié le port <strong>80</strong>.</li><li>avec la directive <strong><em>depends_on</em></strong>, nous spécifions une liste de services dont le service actuel dépend. En d&#39;autres termes, le service <strong><em>nginx-server </em></strong>chargé d&#39;exécuter Nginx dépend du service <strong><em>nextjs</em></strong>. Cela signifie qu&#39;avant que le conteneur du service Nginx ne soit exécuté, le conteneur du service <strong><em>nextjs </em></strong>doit déjà être en marche.</li><li>encore une fois, avec la directive <strong><em>networks</em></strong>, nous indiquons à Compose que le conteneur issu de notre image doit être exécuté dans un réseau appelé <strong><em>tokena-net</em></strong>. Ici, vous pouvez spécifier plusieurs réseaux en ajoutant des tirets et en indiquant le nom de chaque réseau.</li></ul><p>Super ! Nous avons maintenant la définition des deux services qui composent notre application.</p><p>Après la définition de nos services sous la directive <strong><em>services </em></strong>de Compose, nous avons une autre directive appelée <strong><em>networks</em></strong>.</p><h4><strong>Petit récapitulatif sur les réseaux</strong></h4><p>Après avoir créé une image et exécuté le conteneur issu de cette image, le conteneur s’exécute par défaut en isolation, c’est-à-dire qu’il n’est pas accessible par d’autres applications sur le système.</p><p>Pour permettre à d’autres applications de communiquer avec l’application qui s’exécute à l’intérieur du conteneur, on expose le port du conteneur où l’application est accessible.</p><p>L’exposition du port ouvre par défaut l’application à l’intérieur du conteneur au public, ce qui permet à toute application ou client ayant accès au système d’accéder également à l’application dans le conteneur.</p><p>Cependant, il arrive parfois que nous ne voulions pas que notre service soit accessible publiquement, et que seuls des services vérifiés puissent y accéder, comme d’autres conteneurs.</p><p>Dans ce cas, nous pouvons utiliser les réseaux Docker pour créer un cadre où deux conteneurs (ou plus) peuvent communiquer entre eux et échanger des données, tout en rendant cette communication non accessible publiquement.</p><p>Avec ceci dans notre fichier compose:</p><pre>networks:<br>  tokena-net:<br>    driver: bridge</pre><p>Nous créons un réseau appelé <strong><em>tokena-net</em></strong><em> </em>en utilisant le conducteur (driver) <strong><em>bridge</em></strong>. Pour en savoir plus sur les réseaux, consultez cette documentation : <a href="https://docs.docker.com/engine/network/">Docker Engine Networking</a>.</p><p>Avec cela, tout conteneur auquel nous assignons le réseau <strong><em>tokena-net </em></strong>pourra communiquer avec d’autres conteneurs du même réseau en utilisant le nom du conteneur.</p><p>Dans notre configuration, nous ne voulons pas que notre application Next.js soit accessible publiquement, mais uniquement le serveur web Nginx.</p><p>Pour ce faire, nous créons le réseau <strong><em>tokena-net</em></strong>, puis nous plaçons le conteneur exécutant l’application Next.js et le conteneur exécutant le serveur web Nginx dans le même réseau.</p><p>De cette manière, le conteneur exécutant Nginx pourra directement communiquer avec le conteneur exécutant Next.js en utilisant le nom du conteneur, sans avoir besoin d’exposer de port publiquement.</p><p>Nous exposons ensuite publiquement le conteneur exécutant Nginx sur le port 80, comme indiqué lors de la définition du service un peu plus tôt.</p><h4>Revenons à la configuration de Nginx</h4><p>Comme je l’avais mentionné, nous allons revenir sur le fichier <strong><em>nginx.site.conf</em></strong><em>.</em> Ce fichier contient la configuration qui permet à Nginx de se comporter en tant que proxy pour notre application Next.js.</p><p>Le contenu du fichier <strong><em>nginx.site.conf</em></strong><em> </em>est le suivant :</p><pre>server {<br>    listen 80;<br>    server_name localhost *.play-with-docker.com;<br><br>    location / {<br>        proxy_pass http://nextjs:8080;<br>        proxy_set_header Host      $host;<br>        proxy_set_header X-Real-IP $remote_addr;<br>    }<br>}</pre><p>En savoir plus sur Nginx ici : <a href="http://nginx.org/en/docs/beginners_guide.html">Guide pour débutants de Nginx</a></p><p>Dans la configuration, portez attention à la ligne suivante :</p><pre>proxy_pass http://nextjs:8080;</pre><p>En gros, avec cette ligne, nous indiquons à Nginx que l’application pour laquelle il fait office de proxy se trouve à l’adresse <a href="http://nextjs:8080"><strong>http://nextjs:8080</strong></a>. De cette manière, lorsqu’il recevra une requête de l’extérieur, il la redirigera vers <a href="http://nextjs:8080"><strong>http://nextjs:8080</strong></a>.</p><p>Que constatez-vous dans l’adresse ?</p><p>Le nom de domaine utilisé est celui du conteneur qui exécute l’application Next.js. Nous l’avions défini lors de la configuration du service Next.js dans le fichier <strong>compose</strong>.</p><p>Super, nous sommes prêts à exécuter notre application !</p><p>Mais avant suivez le <a href="https://github.com/Romulad/figma-to-code-ed2-week3/blob/master/README.md">README ici</a> pour plus de détails.</p><p>Il vous suffit de lancer la commande suivante à la racine du projet :</p><pre>docker compose -f compose.yaml -f compose.prod.yaml up -d</pre><p>Ensuite, vous pouvez accéder à l&#39;application via <a href="http://localhost">http://localhost</a>.</p><h3><strong>Exemple 2 : FastAPI, React, Redis et Nginx</strong></h3><p>Il s’agit d’une application de discussion instantanée que nous allons configurer avec Docker et Docker Compose.</p><p>Le dépôt <a href="https://github.com/Romulad/web-chat-app">GitHub</a>.</p><p>Voici le fichier <strong>compose</strong>, essayez de l’expliquer par vous-même avant de continuer :</p><pre> name: open-chat-app<br>  <br>services:<br>  redis:<br>    image: redis:7.4.1-alpine<br>    container_name: redis<br>    restart: always<br>    networks:<br>      - open-chat-net<br><br>  api:<br>    image: api<br>    build:<br>      context: ./api<br>      dockerfile: ../docker/api.Dockerfile<br>    container_name: api<br>    networks:<br>      - open-chat-net<br>    env_file:<br>      - ./api/.env<br>    depends_on:<br>      - redis<br>    restart: on-failure:10<br>    <br>  web:<br>    image: web<br>    build:<br>      context: ./web<br>      dockerfile: ../docker/web.Dockerfile<br>    container_name: web<br>    networks:<br>      - open-chat-net<br>    depends_on:<br>      - api<br>      - redis<br>    restart: on-failure:10<br>  <br>  nginx:<br>    image: nginx-server<br>    build:<br>      context: ./nginx<br>      dockerfile: ../docker/nginx.Dockerfile<br>    container_name: nginx<br>    networks:<br>      - open-chat-net<br>    ports:<br>      - 80:80<br>    depends_on:<br>      - api<br>      - web<br>    restart: on-failure:5<br><br>networks:<br>  open-chat-net:<br>    driver: bridge</pre><p>À travers le fichier <strong>compose</strong>, on peut voir qu’il y a au total 4 services : <strong>redis</strong>, <strong>api</strong>, <strong>web</strong> et <strong>nginx</strong>.</p><p>Après avoir cloné le projet, vous trouverez :</p><ul><li>Un dossier <strong>api</strong>, où se trouve l’application FastAPI, notre API qui utilise Redis comme base de données structurée.</li><li>Un dossier <strong>web</strong>, qui contient le front-end de l’application, construit avec React.</li><li>Un dossier <strong>docker</strong>, qui contient les différents fichiers Docker de l’application.</li><li>Un fichier <strong>compose.yaml</strong>.</li></ul><p>Le principe reste le même que précédemment : ces quatre services sont dans le même réseau, et seul le service <strong>nginx</strong> est accessible publiquement sur le port 80.</p><p>Je vous laisse jeter un coup d’œil au fichier <strong>compose</strong> pour solidifier votre compréhension.</p><p>J’espère que vous avez appris quelque chose et que cela vous a été utile.</p><h4>Passez une merveilleuse année, pleine de progrès et de santé !</h4><p>Merci de m’avoir lue 🤗</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a59e97a01701" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Code splitting : améliorer les performances de votre application React]]></title>
            <link>https://medium.com/@romualdoluwatobi/code-splitting-am%C3%A9liorer-les-performances-de-votre-application-react-8457cedc6710?source=rss-a5eb6b8691ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/8457cedc6710</guid>
            <category><![CDATA[react]]></category>
            <category><![CDATA[beginners-guide]]></category>
            <category><![CDATA[lazy]]></category>
            <category><![CDATA[code-splitting]]></category>
            <category><![CDATA[web-app-development]]></category>
            <dc:creator><![CDATA[Romuald Oluwatobi]]></dc:creator>
            <pubDate>Sat, 25 May 2024 16:18:21 GMT</pubDate>
            <atom:updated>2024-05-25T16:19:07.788Z</atom:updated>
            <content:encoded><![CDATA[<h3>Code splitting : améliorer les performances de votre application React</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9kC4y6f-0j4ny3XYjxby2Q.jpeg" /><figcaption><a href="https://www.freepik.com/free-vector/express-delivery-service-abstract-concept-vector-illustration-air-freight-logistics-global-postal-mail-package-delivery-fast-shipping-order-tracking-number-post-office-abstract-metaphor_11667260.htm#fromView=image_search_similar&amp;page=1&amp;position=0&amp;uuid=d6f88bb5-6459-40de-b157-2d50ae113685">Image by vectorjuice on Freepik</a></figcaption></figure><blockquote>Code splitting : C’est le processus de rendre un composant React lorsqu’il doit être rendu, ni trop tôt, ni trop tard. Cela réduit ainsi le temps de chargement des fichiers JS par le navigateur.</blockquote><p>Lorsque vous accédez à une application créée avec React, par défaut, le serveur renvoie tous les fichiers JavaScript qui composent l’application en entier à votre navigateur, qui à son tour fait des requêtes pour charger ces fichiers et afficher votre application.</p><p>Ce comportement est acceptable lorsque vous développez une landing page avec React, mais il dégrade les performances lorsqu’il s’agit d’une application avec plusieurs fichiers JavaScript, une application avec plusieurs pages.</p><p>Idéalement, vous souhaitez que seuls les fichiers et les composants nécessaires pour afficher la page qu’un visiteur consulte à un instant T soient rendus par le navigateur, et non tous les fichiers JavaScript et les composants de votre application, ce qui est le comportement par défaut.</p><p>C’est exactement à ce moment que le code splitting entre en jeu. C’est le processus de rendre un composant React lorsqu’il doit être rendu, ni trop tôt, ni trop tard, exactement lorsque l’utilisateur en a besoin. Cela réduit ainsi le temps de chargement des fichiers JS par le navigateur.</p><h4>React.lazy</h4><p>Pour servir uniquement le nécessaire à l’utilisateur, on utilise la fonction <strong><em>lazy </em></strong>de React qui nous permet de charger un composant en mode paresseux.</p><p>Au lieu d’utiliser les importations de manière traditionnelle pour les composants avec <strong><em>import</em></strong>, on utilise <strong><em>lazy </em></strong>de React avec l’import de JavaScript.</p><p>Traditionnellement :</p><pre>import Component from &quot;./component&quot;</pre><p>Avec <strong><em>lazy </em></strong>de React :</p><pre>const Component = lazy(() =&gt; import(&quot;./component&quot;))</pre><p>Avec lazy, le composant sera importé, donc chargé, une seule fois et uniquement lorsqu’une partie de l’application essaiera de le rendre.</p><h3>Fonctionnement :</h3><ul><li><strong><em>lazy </em></strong>prend en entrée une fonction qui retourne une <strong><em>Promise </em></strong>ou un objet JavaScript avec la méthode <strong><em>then</em></strong>.</li><li><strong><em>lazy </em></strong>renvoie la valeur <strong><em>.default</em></strong> de la <strong><em>Promise </em></strong>en tant que composant React. Cela signifie que le composant doit être exporté avec le mot-clé <strong><em>default </em></strong>depuis le fichier où il a été défini.</li><li>La valeur retournée par <strong><em>lazy </em></strong>est un composant qui peut être affiché dans l’application.</li></ul><p>Ceci étant dit, voyons un exemple concret avec un site créé avec React.</p><p>Nous allons sur une landing page qui a été construite avec React.</p><p>Lien GitHub : <a href="https://github.com/emcelo/frontend">lien</a></p><p>L’application par défaut n’utilise pas <strong><em>lazy </em></strong>pour importer les composants React, ce qui est normal car il s’agit d’une application à une seule page.</p><p>Nous allons supposer que nous voulons que les composants qui composent l’application soient chargés uniquement pendant que l’utilisateur est en aura besoin. Nous allons également observer les performances avant et après l’utilisation de <strong><em>lazy </em></strong>au niveau du navigateur.</p><p>L’application par défaut charge tous les composants lors du premier chargement du site.</p><p>Commencez le serveur avec<strong><em> npm run dev</em></strong> à l&#39;intérieur du dossier de l&#39;application. L&#39;application est configurée avec <strong><em>Vite </em></strong>par défaut.</p><p>Visitez l’URL affichée avec votre navigateur web. Pour cet exemple, je vais utiliser Chrome.</p><p>Maintenant, ouvrez les outils de développement. Cliquez avec le bouton droit de la souris et choisissez “Inspecter”.</p><p>Ensuite, allez à l’onglet “Network” (Réseau) et sélectionnez uniquement l’onglet concernant JavaScript (JS) comme ceci :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bqpZssdMSSC0tLgpXQqruw.png" /></figure><p>Lorsque vous allez ouvrir l’onglet Network pour la première fois, il se peut que vous ne voyiez rien. Rafraîchissez la page pour voir les informations, comme sur l’image.</p><p>Au niveau du tableau qui s’affiche, intéressez-vous spécifiquement à deux colonnes : name, type, et plus spécifiquement au type script.</p><p>Au niveau de la colonne name, vous verrez plusieurs fichiers <strong><em>js, jsx</em></strong> avec le type script ont été chargés par Chrome. Immédiatement et à mesure que vous faites défiler, vous verrez les noms des fichiers tels que : <strong><em>Header.jsx</em></strong>, <strong><em>RecentWork.jsx</em></strong>, <strong><em>WhyUs.jsx</em></strong>, <strong><em>Services.jsx</em></strong>, etc.</p><p>Tous les composants qui composent le site ont été chargés par le navigateur pour pouvoir afficher la page.</p><p>Faisons une expérience où nous allons supposer que les composants sont rendus sous condition et que cette condition est fausse pour certains composants, ainsi ils ne doivent pas être affichés.</p><p>À partir du fichier<strong><em> main.jsx</em></strong>, rendez-vous dans le composant <strong><em>Home</em></strong>, et remplacez le code de tout le fichier par ce code :</p><pre>import React, { useEffect, useRef, useState } from &quot;react&quot;;<br>import Header from &quot;../header/Header&quot;;<br>import IntroSection from &quot;../introSection/IntroSection&quot;;<br>import Trust from &quot;../Trust&quot;;<br>import HowItWork from &quot;../howitwork/HowItWork&quot;;<br>import Services from &quot;../services/Services&quot;;<br>import WhyUs from &quot;../why-us/WhyUs&quot;;<br>import RecentWork from &quot;../recentWork/RecentWork&quot;;<br>import Help from &quot;../help/Help&quot;;<br>import Process from &quot;../process/Process&quot;;<br>import ClientReviews from &quot;../reviews/ClientReviews&quot;;<br>import FooterSection from &quot;../FooterSection&quot;;<br>import &quot;./main.css&quot;;<br>import { isVisibleByC } from &quot;../../utils&quot;;<br><br>export function MessageContainer({msgColor, setMessageActive}){<br>    const containerRef = useRef(null);<br>    const progressRef = useRef(null);<br>    const [msg, colorClass] = msgColor.split(&#39;;&#39;);<br><br>    useEffect(()=&gt;{<br>        setTimeout(() =&gt; {<br>            let increment = 10;<br>            let progressWait = (msg.length/1.3) * 10;<br>            containerRef.current.classList.add(&#39;show-message&#39;);<br>            let current = 10;<br>            <br>            let int = setInterval(() =&gt; {<br>                    if(current &gt;= 100){<br>                        clearInterval(int)<br>                    }else{<br>                        progressRef.current.style.width = `${current + increment}%`;<br>                        current += increment<br>                    }<br>            }, progressWait);<br><br>            setTimeout(() =&gt; {<br>                containerRef.current.classList.add(&#39;hide-message&#39;);<br>                containerRef.current.classList.remove(&#39;show-message&#39;);<br>                setTimeout(() =&gt; {<br>                    setMessageActive(&#39;&#39;)<br>                }, 200);<br>            }, progressWait*increment*1.2);<br><br>        }, 300);       <br>    }, [])<br><br>    return(<br>        &lt;div className={`message ${colorClass}`} ref={containerRef}&gt;<br>            &lt;p className=&quot;mb-0 p-3&quot;&gt;<br>                {msg}<br>            &lt;/p&gt;<br><br>            &lt;div className=&quot;message-border mt-3&quot; &gt;<br>                &lt;div className=&quot;message-border-inner&quot; ref={progressRef}&gt;&lt;/div&gt;<br>            &lt;/div&gt;<br>        &lt;/div&gt;<br>    )<br>}<br><br>export default function Home(){<br>    const [messageActive, setMessageActive] = useState([]);<br>    const firstInputRef = useRef(null)<br><br>    useEffect(()=&gt;{<br>        document.addEventListener(&#39;scroll&#39;, ()=&gt;{<br>            document.querySelectorAll(&#39;.Tr-1&#39;).forEach((el)=&gt;{<br>                if(isVisibleByC(&quot;&quot;, el.id, false, true)){<br>                    el.classList.add(&#39;An-4&#39;)<br>                }<br>            });<br>        });<br>    }, [])<br><br>    function onContactBtnClick(){<br>        firstInputRef.current.focus();<br>    }<br><br>    function messages(){<br>        let msgCount = 0;<br>        if(messageActive){<br>            const allMessages = messageActive.map((msgColor)=&gt;{<br>                let key = `${msgCount}-${msgColor}`<br>                msgCount += 1;<br>                return(<br>                    &lt;MessageContainer <br>                        msgColor={msgColor}<br>                        setMessageActive={setMessageActive}<br>                        key={key}<br>                    /&gt;<br>                )<br>            })<br>            return allMessages<br>        }<br>        return(<br>            &lt;&gt;&lt;/&gt;<br>        )<br>        <br>    }<br>    const messageResult = messages();<br>    <br>    return(<br>        // avoid rendenring some components<br>        &lt;&gt;<br>            &lt;Header onContactBtnClick={onContactBtnClick}/&gt;<br>            &lt;IntroSection <br>            setMessageActive={setMessageActive}<br>            firstInputRef={firstInputRef}/&gt;<br>            { false &amp;&amp; &lt;Trust /&gt; }<br>            { false &amp;&amp; &lt;HowItWork onContactBtnClick={onContactBtnClick}/&gt; }<br>            { false &amp;&amp; &lt;Services onContactBtnClick={onContactBtnClick}/&gt; }<br>            { false &amp;&amp; &lt;WhyUs /&gt; }<br>            { false &amp;&amp; &lt;RecentWork /&gt; }<br>            { false &amp;&amp; &lt;Help /&gt; }<br>            { false &amp;&amp; &lt;Process /&gt; }<br>            { false &amp;&amp; &lt;ClientReviews onContactBtnClick={onContactBtnClick}/&gt; }<br>            { false &amp;&amp; &lt;FooterSection /&gt; }<br>            {messageResult}<br>        &lt;/&gt;<br>    )<br>}</pre><p>J’ai explicitement utilisé le mot-clé <strong><em>false </em></strong>de JavaScript pour éviter que ces composants ne soient rendus par le navigateur.</p><p>D’abord, videz tout le cache de votre navigateur pour voir les choses actuelles.</p><p>Ensuite, rafraîchissez la page et vous devriez voir que seules deux sections s’affichent dans votre navigateur : le menu et la première section de la page.</p><p>Mais chose surprenante, si vous vous rendez dans votre outil de développement au niveau de Network, vous verrez que tous les fichiers précédents ont été chargés par le navigateur juste pour afficher nos deux sections.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NnioxmBWfCHbCFeXLWCjFw.png" /></figure><p>Ajoutons le lazy load à l’importation de nos composants pour voir ce qui se passe.</p><p>La partie qui changera est les importations, comme ceci :</p><pre>import React, { useEffect, useRef, useState, lazy } from &quot;react&quot;;<br>import &quot;./main.css&quot;;<br>import { isVisibleByC } from &quot;../../utils&quot;;<br><br>const Header = lazy(() =&gt; import(&quot;../header/Header&quot;));<br>const IntroSection = lazy(() =&gt; import(&quot;../introSection/IntroSection&quot;));<br>const Trust = lazy(() =&gt; import(&quot;../Trust&quot;));<br>const HowItWork = lazy(() =&gt; import(&quot;../howitwork/HowItWork&quot;));<br>const Services = lazy(() =&gt; import(&quot;../services/Services&quot;));<br>const WhyUs = lazy(() =&gt; import(&quot;../why-us/WhyUs&quot;));<br>const RecentWork = lazy(() =&gt; import(&quot;../recentWork/RecentWork&quot;));<br>const Help = lazy(() =&gt; import(&quot;../help/Help&quot;));<br>const Process = lazy(() =&gt; import(&quot;../process/Process&quot;));<br>const ClientReviews = lazy(() =&gt; import(&quot;../reviews/ClientReviews&quot;));<br>const FooterSection = lazy(() =&gt; import(&quot;../FooterSection&quot;));</pre><p>Encore une fois, assurez-vous de vider le cache de votre navigateur ou simplement d’ouvrir un nouveau navigateur.</p><p>Rendez-vous dans votre outil de développement dans Network, puis rafraîchissez la page à nouveau. Vous devez voir que les fichiers chargés par le navigateur ont drastiquement diminué, comme le montre l’image :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZToQfcmACgpPilGT7hLIlg.png" /></figure><p>Il n’y a plus de <strong><em>RecentWork.jsx</em></strong>, <strong><em>WhyUs.jsx</em></strong>, <strong><em>Services.jsx</em></strong>, etc., mais uniquement ceux que nous avons voulu afficher par défaut.</p><p>Il s’agissait de rendre le code quand il le faut, au moment idéal.</p><blockquote>Rappelez-vous que trop utiliser quelque chose n’est pas toujours une bonne idée ; utilisez lazy quand il le faut, et non pour importer tous vos composants.</blockquote><h4>Cas d’utilisation</h4><p>C’est encore plus important lorsque vous développez une application avec plusieurs routes, une application divisée en plusieurs pages accessibles avec des URLs, en utilisant par exemple react-router.</p><p>Vous ne voulez pas que lorsque l’utilisateur visite une page, toute l’application soit chargée par le navigateur, mais uniquement ce qui est en train d’être vu par l’utilisateur.</p><p>Rien de plus simple pour améliorer les performances.</p><p>De plus, il est souvent très utile d’utiliser le composant <strong><em>suspense </em></strong>de React afin de notifier à l’utilisateur que quelque chose est en train de se faire en arrière-plan.</p><p>Le principe est que lorsque vous utilisez lazy pour importer des composants, vous devez utiliser <strong><em>suspense </em></strong>pour englober toute votre application afin d’afficher un rappel lorsque des opérations asynchrones se feront en arrière-plan, comme ceci :</p><pre>ReactDOM.createRoot(document.getElementById(&#39;root&#39;)).render(<br>  &lt;React.StrictMode&gt;<br>    &lt;Suspense fallback=&quot;Loading....&quot;&gt;<br>      &lt;Home /&gt;<br>    &lt;/Suspense&gt;<br>  &lt;/React.StrictMode&gt;,<br>)</pre><p>Le <strong><em>fallback </em></strong>peut être un composant React.</p><p>Pour finir, il est souvent hyper utile de créer une Error Boundary lorsque vous utilisez lazy, afin de notifier l’utilisateur lorsqu’une erreur survient au niveau du chargement du composant ou à l’intérieur.</p><p>Lorsque vous utilisez <strong><em>react-router</em></strong>, ceci est pris en charge.</p><p>Pour l’implémenter manuellement, vous devrez créer un composant basé sur les classes. Je vous encourage à faire plus de recherches sur les Error Boundaries.</p><p><strong>Abonnez-vous</strong> <a href="https://medium.com/@romualdoluwatobi">ici</a> pour les prochains articles. Retrouvez-moi sur GitHub <a href="https://github.com/Romulad">ici</a>.</p><p>Merci et à la prochaine !</p><h3>Auteur</h3><p>Romuald Oluwatobi, développeur Django et ReactJS. Besoin d’un développeur compétent ? Je vous accompagne dans la création d’une application web moderne dans l’atteinte de vos objectifs business.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8457cedc6710" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Développement d’applications avec Django : Utilisation des signaux]]></title>
            <link>https://medium.com/@romualdoluwatobi/d%C3%A9veloppement-dapplications-avec-django-utilisation-des-signaux-26c6ec406ccd?source=rss-a5eb6b8691ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/26c6ec406ccd</guid>
            <category><![CDATA[django]]></category>
            <category><![CDATA[app-development]]></category>
            <dc:creator><![CDATA[Romuald Oluwatobi]]></dc:creator>
            <pubDate>Fri, 24 May 2024 05:57:57 GMT</pubDate>
            <atom:updated>2024-05-24T05:57:57.591Z</atom:updated>
            <content:encoded><![CDATA[<h3>Développement d’applications avec Django : Utilisation des signaux</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AfXkc50nfwrlk-p799rKAw.jpeg" /></figure><p>Il est courant lors du développement d’applications que, lorsqu’un événement spécifique se produit, des actions doivent en découler.</p><p>Des événements peuvent être :</p><ul><li>La création d’un nouvel utilisateur</li><li>Le début d’une requête HTTP venant du client</li><li>La fin d’une requête HTTP prête à être envoyée au client</li><li>La suppression d’un objet de la base de données</li><li>Etc.</li></ul><p>Imaginons que, pour l’événement de création d’utilisateur, vous souhaitiez effectuer plusieurs actions, comme créer des informations supplémentaires, ajouter l’utilisateur à un groupe, envoyer un email, et d’autres actions spécifiques pour le bon fonctionnement de votre application.</p><p>De manière traditionnelle, vous allez créer des méthodes ou des fonctions responsables de ces actions, et chaque fois que vous créez un utilisateur dans votre application, que ce soit depuis l’interface principale ou ailleurs, il faudra vous rappeler d’exécuter ces fonctions ou méthodes. Ou, dans le cas où les logiques dépendent les unes des autres de manière à ce qu’une fonction/méthode doive s’exécuter avant qu’une autre ne le fasse, il faudra vous rappeler de cela minutieusement.</p><p>Vous comprenez déjà l’idée : se rappeler de toutes les actions nécessaires à exécuter et de l’ordre d’exécution après qu’un événement spécifique s’est produit. Vous devez sûrement avoir des souvenirs si vous avez déjà été dans une telle situation.</p><p>Maintenant, imaginez un système où Django vous donne la possibilité d’être notifié lorsqu’un événement qui vous intéresse se produit dans votre application. Cela signifie que vous n’avez plus besoin de vous rappeler manuellement de tout ce qu’il faut faire après qu’un événement spécifique s’est produit. Par exemple, lorsque vous créez un utilisateur manuellement, vous n’avez plus besoin de vous rappeler de toutes les méthodes/fonctions à appeler, Django s’en charge.</p><p>De manière simple, vous dites à Django : lorsque telle ou telle chose se produit dans mon application, je veux que tu appelles cette fonction X. Encore plus simple, au lieu de simplement créer une fonction que vous devrez vous rappeler d’appeler, vous créez la fonction et lui donnez la possibilité d’être appelée (notifiée) lorsqu’un événement spécifique se produit.</p><p>Pas génial ? C’est à cela que servent les signaux dans Django. Ils vous permettent d’enregistrer une fonction qui sera appelée lorsqu’un événement se produit dans votre application, comme la création d’un modèle utilisateur.</p><p>Voyons comment tout cela fonctionne concrètement. Nous allons créer une fonction qui sera appelée en réponse à un événement spécifique.</p><p><strong>Note :</strong> Lorsque je parle d’événement, d’action ou de signal, je parle typiquement de la même chose. Vous pouvez vous référer à celui que vous comprenez déjà.</p><h3>Création d’un récepteur</h3><p>Le système est divisé en deux parties : les émetteurs (senders) et les récepteurs (receivers). Un émetteur émet un événement et appelle (notifie) le(s) récepteur(s) (fonctions) qui se sont enregistrés pour cet événement.</p><p>Le processus commence par la création d’une fonction ou méthode réceptrice qui doit être appelée lorsqu’un événement se produit dans votre application. Comme ceci :</p><pre>def my_receiver(sender, **kwargs):<br>    print(&quot;I&#39;ll be called in response to an event&quot;)</pre><p>Une fonction/méthode réceptrice :</p><ul><li>Prend en premier argument le <strong><em>sender</em></strong>, l&#39;objet Python qui émet l&#39;événement en question.</li><li>Prend un <strong><em>**kwargs</em></strong>, qui contient les arguments que l&#39;émetteur peut potentiellement passer aux récepteurs. Potentiellement, car il peut arriver que certains émetteurs n&#39;incluent pas d&#39;argument. Mais Django nous impose de toujours mettre cet argument, car un récepteur doit être en mesure d&#39;accepter des arguments à tout moment.</li></ul><p>Après la création de la fonction réceptrice, nous avons maintenant besoin d’un moyen pour l’enregistrer en tant que récepteur pour l’événement ou l’action souhaitée. Pour ce faire, nous pouvons utiliser le décorateur <strong><em>receiver </em></strong>de Django, comme ceci :</p><pre>@receiver(signal, **kwargs)<br>def my_receiver(sender, **kwargs):<br>    print(&quot;I&#39;ll be called in response to an event&quot;)</pre><p>Le décorateur :</p><ul><li>Prend un événement/signal ou une liste d’événements/signaux auxquels la fonction réceptrice qu’il décore doit répondre.</li><li>Prend des <strong><em>**kwargs</em></strong> pour les arguments supplémentaires à passer à la fonction réceptrice.</li></ul><p>Et la dernière étape est de modifier la fonction ready dans la configuration de notre application pour que Django prenne connaissance de notre fonction réceptrice.</p><p>La fonction <strong><em>my_receiver </em></strong>sera appelée chaque fois que le signal passé à la fonction <strong><em>receiver </em></strong>de Django sera déclenché. Et c&#39;est tout ce qu&#39;il faut pour éviter les maux de tête liés à l&#39;appel de fonctions en réponse à une action spécifique dans Django.</p><p>Cool, voyons un cas concret avec un projet Django où nous allons créer une fonction réceptrice qui sera appelée en réponse à la création d’un nouvel utilisateur dans notre application.</p><p>Le code final se trouve ici sur GitHub : <a href="https://github.com/Romulad/django-signal-tuto">lien</a></p><p>Nous allons commencer avec un environnement virtuel, installer Django et exécuter :</p><pre>django-admin startproject .</pre><p>Ensuite, je vais créer une nouvelle application Django que je vais appeler <strong><em>user </em></strong>:</p><pre>django-admin startapp user</pre><p>Enregistrer l’application au niveau des paramètres .</p><p>Nous allons créer un nouveau modèle utilisateur que je vais appeler <strong><em>MyUser</em></strong>, qui héritera de django.contrib.auth.models.AbstractUser, dans le fichier <strong><em>models.py</em></strong> de la nouvelle application créée.</p><p>Ajoutez cette ligne au niveau des paramètres pour enregistrer le modèle en tant que modèle utilisateur par défaut :</p><pre>AUTH_USER_MODEL = &quot;user.MyUser&quot;</pre><p>Ou adaptez-la en fonction si vous n’avez pas utilisé les mêmes noms que moi.</p><p>Enfin, exécutons <strong><em>makemigrations </em></strong>et <strong><em>migrate </em></strong>pour appliquer les migrations dans la base de données.</p><p>Passons maintenant à la création de nos récepteurs. Pour cela, dans l’application <strong><em>user</em></strong>, je vais créer un dossier que je vais nommer <strong><em>signals</em></strong>, et à l&#39;intérieur de ce dossier, je vais créer un nouveau fichier que je vais appeler <strong><em>model_signals.py</em></strong>. La structure du projet ressemblerait à quelque chose comme ceci :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/383/1*YXXE_I0Qw6QM_A2Ll3HySw.png" /><figcaption>django app</figcaption></figure><p>Dans ce fichier, nous allons définir une fonction qui ajoutera un nouveau utilisateur créé à un groupe appelé “<strong><em>favorite_user</em></strong>”.</p><p>Nous devons nous intéresser à l’événement/signal qui sera déclenché lorsqu’un nouvel enregistrement du modèle MyUser se fera dans notre base de données.</p><p>Pour cela, nous utilisons le signal django.db.models.signals.post_save, qui nous permet d&#39;être notifiés lorsqu&#39;un nouvel enregistrement est effectué dans la base de données pour un modèle particulier.</p><p>Commençons par importer le signal et le décorateur de fonction receiver avec ces lignes :</p><pre>from django.dispatch import receiver<br>from django.db.models.signals import post_save</pre><p>Continuons maintenant avec la définition de notre fonction réceptrice. Je vais l’appeler add_user_to_group, comme ceci :</p><pre>def add_user_to_group():<br>    pass</pre><p>Rappelez-vous, notre fonction réceptrice doit prendre des arguments : le sender et **kwargs. Réécrivons-la ainsi :</p><pre>def add_user_to_group(sender, **kwargs):<br>    pass</pre><p>Pour être reconnue en tant que récepteur, elle doit être décorée par la fonction django.dispatch.receiver de Django. Cette fonction prend en argument le signal/événement pour lequel notre fonction doit être appelée. Dans notre cas, nous voulons que la fonction soit appelée après la création d&#39;un nouvel utilisateur.</p><p>C&#39;est pourquoi nous utilisons le signal post_save (django.db.models.signals.post_save), qui est déclenché chaque fois qu&#39;un modèle est enregistré dans la base de données.</p><p>Donc, notre définition sera maintenant avec le décorateur receiver comme ceci :</p><pre>@receiver(post_save)<br>def add_user_to_group(sender, **kwargs):<br>    pass</pre><p>Constatez-vous un problème ? Oui ?</p><p>Nous voulons que notre récepteur soit appelé uniquement lorsqu’un nouvel utilisateur est ajouté à notre base de données, pas lorsque n’importe quel modèle enregistre un nouvel enregistrement. N’est-ce pas ?</p><p>Nous pouvons spécifier cela à la fonction <strong><em>receiver </em></strong>de Django en utilisant l&#39;argument <strong><em>sender</em></strong>.</p><p>En lui indiquant que oui, nous voulons être notifiés lorsqu&#39;un enregistrement est effectué dans la base de données, mais uniquement lorsqu&#39;il s&#39;agit du <strong><em>sender </em></strong>que nous avons spécifié. Et l<strong><em>e sender </em></strong>dans notre cas sera notre modèle utilisateur.</p><p>Notre code ressemblera à quelque chose comme ceci :</p><pre>@receiver(post_save, sender=MyUser)<br>def add_user_to_group(sender, **kwargs):<br>    pass</pre><p>Jusque-là, le fichier ressemblerait à ceci :</p><pre>from django.dispatch import receiver<br>from django.db.models.signals import post_save<br><br>from ..models import MyUser<br><br>@receiver(post_save, sender=MyUser)<br>def add_user_to_group(sender, **kwargs):<br>    pass</pre><p>Faisons une dernière chose avant d’implémenter la logique de notre récepteur : faire connaître notre récepteur à Django. Pour cela, rendez-vous dans le fichier apps.py de l&#39;application user et modifiez-le pour avoir ceci :</p><pre>from django.apps import AppConfig<br><br>class UserConfig(AppConfig):<br>    default_auto_field = &#39;django.db.models.BigAutoField&#39;<br>    name = &#39;user&#39;<br><br>    def ready(self) -&gt; None:<br>        from .signals import model_signals</pre><p>Nous avons ajouté la méthode ready, et à l&#39;intérieur, nous importons notre fichier qui contient nos récepteurs. La méthode ready est appelée lorsque Django commence le serveur.</p><p>La logique de notre récepteur ressemblerait à ceci :</p><pre>@receiver(post_save, sender=MyUser)<br>def add_user_to_group(sender, **kwargs):<br>    created = kwargs.get(&#39;created&#39;, False)<br>    user_model = kwargs.get(&#39;instance&#39;, None)<br><br>    try:<br>        group = get_object_or_404(Group, name=&quot;favorite_user&quot;)<br>    except Http404:<br>        group = Group.objects.create(<br>            name=&quot;favorite_user&quot;<br>        )<br><br>    # Check if the instance is newly created<br>    if created:<br>        user_model.groups.add(group)<br>    else:<br>        pass</pre><p>Comme dit précédemment, les émetteurs peuvent appeler une fonction réceptrice avec un nombre d’arguments donné. Le signal django.db.models.signals.post_save passe un certain nombre d&#39;arguments à la fonction réceptrice, dont :</p><ul><li>Un booléen appelé <strong><em>created </em></strong>qui indique si un nouvel enregistrement a été ajouté à la base de données.</li><li>Un argument appelé <strong><em>insance </em></strong>qui contient l&#39;objet qui a été créé.</li></ul><p>Et d’autres arguments que vous pouvez consulter ici : <a href="https://docs.djangoproject.com/en/5.0/ref/signals/#post-save">lien</a></p><p>Ainsi, nous avons besoin de ces arguments pour ajouter le nouvel utilisateur créé à un groupe.</p><p>L’heure est venue de tester.</p><p>Vous pouvez ouvrir votre terminal et créer un super utilisateur ; il sera automatiquement ajouté au groupe “<strong><em>favorite_user</em></strong>”. Vous pouvez également utiliser votre shell avec manage.py shell ou simplement lancer votre serveur Django.</p><p>Vous verrez un joli formulaire pour créer un utilisateur (cela n&#39;a rien de joli en soi, mais vous pouvez créer un utilisateur avec 😉).</p><p>Si vous voulez savoir comment créer vos propres signaux dans Django, consultez ce lien : <a href="https://docs.djangoproject.com/en/5.0/topics/signals/#defining-and-sending-signals">lien</a></p><p><strong>Abonnez-vous</strong> <a href="https://medium.com/@romualdoluwatobi">ici</a> pour les prochains articles. Retrouvez-moi sur GitHub <a href="https://github.com/Romulad">ici</a>.</p><p>Merci et à la prochaine !</p><h3>Auteur</h3><p>Romuald Oluwatobi, développeur Django et ReactJS. Besoin d’un développeur compétent ? Je vous accompagne dans la création d’une application web moderne dans l’atteinte de vos objectifs business.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=26c6ec406ccd" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>