<?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"><channel><title><![CDATA[Jules ADONSI]]></title><description><![CDATA[Jules ADONSI]]></description><link>https://blog.julesadonsi.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 07 Apr 2026 09:49:33 GMT</lastBuildDate><atom:link href="https://blog.julesadonsi.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Le développement mobile cross-plateforme est-il en train de changer de paradigme ?]]></title><description><![CDATA[Pendant longtemps, développer une application mobile signifiait faire un choix clair : iOS avec Swift, Android avec Kotlin, ou recourir à des solutions cross-plateforme comme React Native, Flutter ou ]]></description><link>https://blog.julesadonsi.com/le-d-veloppement-mobile-cross-plateforme-est-il-en-train-de-changer-de-paradigme</link><guid isPermaLink="true">https://blog.julesadonsi.com/le-d-veloppement-mobile-cross-plateforme-est-il-en-train-de-changer-de-paradigme</guid><dc:creator><![CDATA[Jules ADONSI]]></dc:creator><pubDate>Fri, 03 Apr 2026 12:57:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5f86e6c576870f43aa09d895/7ce554ba-fa44-47a0-92ae-6af9ac785b73.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Pendant longtemps, développer une application mobile signifiait faire un choix clair : iOS avec Swift, Android avec Kotlin, ou recourir à des solutions cross-plateforme comme React Native, Flutter ou Ionic. Ces approches ont permis de réduire les coûts et de mutualiser une partie du développement, même si elles reposaient souvent sur des abstractions ou des couches intermédiaires.</p>
<h2><strong>Une évolution majeure : Swift arrive officiellement sur Android</strong></h2>
<p><strong>Officiel — Mars 2026</strong></p>
<p>Le monde du développement évolue vite, et certains changements passent parfois inaperçus. Swift 6.3, publié fin mars 2026, introduit le <strong>premier SDK officiel de Swift pour Android</strong>. Ce n'est plus une initiative communautaire ou un prototype : c'est une release stable, portée par le Swift Android Workgroup.</p>
<p><strong>Ce que ça permet concrètement</strong>Écrire de la logique métier en Swift, la compiler pour iOS et Android, et l'intégrer côté Android via une interopérabilité avec Kotlin/Java (JNI) grâce aux outils <em>Swift Java</em> et <em>Swift Java JNI Core</em>. Swift compile directement en code machine natif sur Android, avec des performances comparables au code C/C++ natif.</p>
<p>Des applications Swift sur Android existent déjà en production depuis des années — Spark (client mail), flowkey (piano interactif) ou MediQuo (santé) — cumulant des millions de téléchargements. La nouveauté, c'est que cette voie est désormais <strong>officiellement supportée et documentée</strong>.</p>
<h2><strong>Une nouvelle architecture devient possible</strong></h2>
<p>Avec cette évolution, un modèle intéressant émerge :</p>
<ul>
<li><p>Une logique métier partagée écrite en Swift</p>
</li>
<li><p>Une interface iOS en SwiftUI</p>
</li>
<li><p>Une interface Android en Jetpack Compose</p>
</li>
</ul>
<p><em>On ne cherche plus à partager toute l'application, mais à partager ce qui a du sens : la logique.</em></p>
<p>Swift reste distinct de Kotlin pour les UI natives — et ce n'est pas un problème. C'est même le point : chaque plateforme garde son identité visuelle, pendant que la complexité métier est mutualisée.</p>
<h2><strong>Du côté de Google : KMP est désormais stable</strong></h2>
<p><strong>Stable depuis 2025</strong></p>
<p>Cette dynamique ne vient pas uniquement de l'écosystème Apple. À Google I/O et KotlinConf 2025, Google a annoncé que plusieurs bibliothèques Jetpack sont désormais officiellement stables pour Kotlin Multiplatform (KMP) :</p>
<ul>
<li><p><strong>Room 2.8.3</strong> — support KMP stable (octobre 2025)</p>
</li>
<li><p><strong>DataStore 1.1.x</strong> — support KMP stable</p>
</li>
<li><p><strong>ViewModel, SavedState, Paging</strong> — également ajoutés</p>
</li>
</ul>
<p>Il ne s'agit plus d'expérimental : Google a officiellement déclaré être "<em>all-in on Kotlin Multiplatform</em>". L'objectif affiché est le partage de la logique métier entre Android et iOS, tout en conservant des UI natives sur chaque plateforme.</p>
<h2><strong>Un glissement de fond</strong></h2>
<p>Ces évolutions convergent vers quelque chose de plus profond : un changement de philosophie dans la façon de concevoir les applications mobiles.</p>
<p>On passe progressivement :</p>
<ul>
<li><p>D'une logique de <strong>frameworks cross-plateforme</strong> (tout partager, y compris l'UI)</p>
</li>
<li><p>Vers une logique de <strong>partage ciblé du code</strong> (partager la logique, garder les UI natives)</p>
</li>
</ul>
<p>Ce n'est pas la mort de Flutter ou React Native — ces outils restent très pertinents pour de nombreux contextes. Mais ils ne sont plus les seules réponses à la question du cross-plateforme.</p>
<h2><strong>Alors, changement de paradigme ?</strong></h2>
<p>Les lignes entre natif et cross-plateforme deviennent de plus en plus floues. Swift qui tourne sur Android. Des bibliothèques Android partagées avec iOS. Des équipes iOS qui peuvent, sans changer de langage, viser un nouveau marché.</p>
<p>Ce qui est certain : les outils s'adaptent enfin à une réalité que les équipes connaissent depuis longtemps — <strong>partager tout n'a pas toujours de sens, mais ne rien partager non plus</strong>.</p>
<p>Et vous, comment vous positionnez-vous face à ces évolutions ? Est-ce que ces changements influencent vos choix d'architecture ?</p>
]]></content:encoded></item><item><title><![CDATA[Jetpack Compose : Optimiser les recompositions et les performances dans une liste dynamique]]></title><description><![CDATA[Quand on développe avec Jetpack Compose, comprendre la recomposition est essentiel pour construire des interfaces performantes et fluides.
Dans cet article, nous allons voir comment gérer efficacement]]></description><link>https://blog.julesadonsi.com/jetpack-compose-optimiser-les-recompositions-et-les-performances-dans-une-liste-dynamique</link><guid isPermaLink="true">https://blog.julesadonsi.com/jetpack-compose-optimiser-les-recompositions-et-les-performances-dans-une-liste-dynamique</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[Jetpack Compose]]></category><category><![CDATA[State Management ]]></category><dc:creator><![CDATA[Jules ADONSI]]></dc:creator><pubDate>Mon, 30 Mar 2026 18:27:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5f86e6c576870f43aa09d895/57b684ed-72c3-4d09-90ff-0460de0a4863.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Quand on développe avec Jetpack Compose, comprendre la recomposition est essentiel pour construire des interfaces performantes et fluides.</p>
<p>Dans cet article, nous allons voir comment gérer efficacement :</p>
<ul>
<li><p>des listes dynamiques (ajout, suppression)</p>
</li>
<li><p>des états UI propres</p>
</li>
<li><p>et éviter les recompositions inutiles</p>
</li>
</ul>
<p>Le tout avec un exemple concret issu d’une application réelle.</p>
<hr />
<h1>Comprendre le problème</h1>
<p>Dans Compose, chaque changement de state déclenche une recomposition.</p>
<p>Ce comportement est normal. Cependant, mal maîtrisé, il peut :</p>
<ul>
<li><p>ralentir l’application</p>
</li>
<li><p>recréer inutilement des composants</p>
</li>
<li><p>impacter les performances sur les grandes listes</p>
</li>
</ul>
<hr />
<h1>Exemple concret : gestion de zones de livraison</h1>
<p>Dans cet écran, on gère :</p>
<ul>
<li><p>l’ajout de zone</p>
</li>
<li><p>la suppression</p>
</li>
<li><p>l’affichage dynamique</p>
</li>
<li><p>les états de chargement</p>
</li>
</ul>
<p>Voici la partie critique :</p>
<pre><code class="language-kotlin">if (uiState.isLoadingZones) {
    item {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .padding(vertical = 24.dp),
            contentAlignment = Alignment.Center
        ) {
            CircularProgressIndicator(color = Primary, modifier = Modifier.size(32.dp))
        }
    }
} else if (uiState.zones.isEmpty()) {
    item { ZoneEmptyState() }
} else {
    items(uiState.zones, key = { it.id }) { zone -&gt;
        ZoneItem(
            zone = zone,
            isDeleting = uiState.deletingZoneId == zone.id,
            onDelete = remember(zone.id) {
                { viewModel.deleteZone(zone.id) }
            }
        )
        HorizontalDivider(modifier = Modifier.padding(start = 52.dp))
    }
}
</code></pre>
<hr />
<h1>La clé (key) : indispensable pour les listes</h1>
<pre><code class="language-kotlin">items(uiState.zones, key = { it.id })
</code></pre>
<p>C’est la bonne pratique la plus importante.</p>
<p>Sans key :</p>
<ul>
<li><p>Compose identifie les éléments par leur position</p>
</li>
<li><p>une suppression ou insertion entraîne une recomposition inutile de plusieurs éléments</p>
</li>
</ul>
<p>Avec key :</p>
<ul>
<li><p>chaque élément a une identité unique</p>
</li>
<li><p>seules les lignes impactées sont recomposées</p>
</li>
</ul>
<p>Cela améliore les performances et permet de conserver l’état des composants.</p>
<hr />
<h1>Éviter la recréation des lambdas</h1>
<pre><code class="language-kotlin">onDelete = remember(zone.id) {
    { viewModel.deleteZone(zone.id) }
}
</code></pre>
<p>Sans remember :</p>
<ul>
<li><p>la lambda est recréée à chaque recomposition</p>
</li>
<li><p>cela peut provoquer des recompositions inutiles</p>
</li>
</ul>
<p>Avec remember :</p>
<ul>
<li><p>la lambda est stable</p>
</li>
<li><p>Compose peut mieux optimiser le rendu</p>
</li>
</ul>
<hr />
<h1>UI pilotée par le state</h1>
<pre><code class="language-kotlin">isDeleting = uiState.deletingZoneId == zone.id
</code></pre>
<p>L’interface dépend uniquement du state.</p>
<p>Avantages :</p>
<ul>
<li><p>logique claire</p>
</li>
<li><p>comportement prévisible</p>
</li>
<li><p>facilité de test</p>
</li>
<li><p>pas d’état local inutile</p>
</li>
</ul>
<hr />
<h1>Gestion propre des états UI</h1>
<pre><code class="language-kotlin">if (uiState.isLoadingZones) { ... }
else if (uiState.zones.isEmpty()) { ... }
else { ... }
</code></pre>
<p>Ce pattern repose sur trois états :</p>
<ul>
<li><p>Loading</p>
</li>
<li><p>Empty</p>
</li>
<li><p>Data</p>
</li>
</ul>
<p>Une alternative plus idiomatique :</p>
<pre><code class="language-kotlin">when {
    uiState.isLoadingZones -&gt; { ... }
    uiState.zones.isEmpty() -&gt; { ... }
    else -&gt; { ... }
}
</code></pre>
<hr />
<h1>LazyColumn et performance</h1>
<pre><code class="language-kotlin">LazyColumn {
    items(...)
}
</code></pre>
<p>Contrairement à une Column :</p>
<ul>
<li><p>seuls les éléments visibles sont composés</p>
</li>
<li><p>cela améliore fortement les performances sur les listes importantes</p>
</li>
</ul>
<hr />
<h1>Résultat</h1>
<p>Avec ces bonnes pratiques :</p>
<ul>
<li><p>les recompositions sont limitées</p>
</li>
<li><p>l’interface reste fluide</p>
</li>
<li><p>le code est maintenable et évolutif</p>
</li>
</ul>
<hr />
<h1>Règles à retenir</h1>
<ol>
<li><p>Toujours utiliser une key dans items()</p>
</li>
<li><p>Utiliser remember pour stabiliser les lambdas</p>
</li>
<li><p>Structurer l’interface avec Loading / Empty / Data</p>
</li>
<li><p>Utiliser LazyColumn pour les listes</p>
</li>
</ol>
<hr />
<h1>Aller plus loin</h1>
<p>Pour approfondir :</p>
<ul>
<li><p>utiliser @Immutable sur les modèles</p>
</li>
<li><p>éviter de recréer les listes inutilement</p>
</li>
<li><p>centraliser le state dans le ViewModel</p>
</li>
<li><p>utiliser derivedStateOf si nécessaire</p>
</li>
</ul>
<hr />
<h1>Conclusion</h1>
<p>Jetpack Compose offre un modèle puissant basé sur le state. La performance dépend directement de la manière dont la recomposition est gérée.</p>
<p>En appliquant quelques principes simples comme l’utilisation des key, de remember et d’une UI pilotée par le state, il est possible de construire des applications rapides, propres et évolutives.</p>
<hr />
<p>#AndroidDev #JetpackCompose #Kotlin #MobileDevelopment #CleanCode</p>
]]></content:encoded></item><item><title><![CDATA[Du backend au mobile android une histoire d’attirance. ]]></title><description><![CDATA[Un backend curieux qui devient, développeur mobile android, une histoire d’attirance pour le mobile.
Ça va être long donc …..
J’ai beaucoup fait du backend, et j’en fait toujours d'ailleurs, le fronte]]></description><link>https://blog.julesadonsi.com/du-backend-au-mobile-android-une-histoire-d-attirance</link><guid isPermaLink="true">https://blog.julesadonsi.com/du-backend-au-mobile-android-une-histoire-d-attirance</guid><dc:creator><![CDATA[Jules ADONSI]]></dc:creator><pubDate>Sat, 21 Mar 2026 19:17:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5f86e6c576870f43aa09d895/98d8cc30-df59-4edb-ac25-0eadf74d04a4.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Un backend curieux qui devient, développeur mobile android, une histoire d’attirance pour le mobile.</p>
<p>Ça va être long donc …..</p>
<p>J’ai beaucoup fait du backend, et j’en fait toujours d'ailleurs, le frontend m’a toujours attiré à côté, en fin d'année 2025 pendant, mes congés, j’ai décidé de découvrir le monde du frontend mobile et plus précisément android.</p>
<p>J’ai donc essayé plusieurs pistes pour faire une app mobile (React native, Flutter, Ionic, Kotlin/xml, Kotlin/Jetpack compose). Après un test rapide sur ces différents outils j’ai décidé de me concentrer sur Jetpack compose et d’en apprendre davantage.</p>
<p>Pourquoi Jetpack compose, et bien c’est simple, Jetpack compose est en Kotlin pure, et je trouve Kotlin plus propre que Dart(Flutter) et TypeScript(React Native), non ça sans blague, cette aspect n’est qu’une préférence personnel, j’aime les langage qui offre une bonne sucre syntaxique.</p>
<p>La première raison qui justifie le choix de jetpack compose, est que c’est l’outils officiel recommandé par google pour la création d’app mobile android moderne en 2025, je voulais surtout pas toucher à Kotlin/XML et utilisé une conception basé sur l'impératif (mon Dieu j’ai essayé quel enfer)</p>
<p>Deuxième raison, je ne voulais pas commencer mon apprentissage du mobile par le cross plateforme ou l’hybride, je voulais comprendre la plateforme en profondeur, peut être je vais passer au cross plateforme après si besoin. Je vais revenir sur le cross plateforme un peu plus bas (tu pourrais découvrir quelque chose de bien).</p>
<p>Ceci étant dit, revenons à Kotlin/Jetpack compose,il s’agit d’une boîte à outils (tollkit) moderne recommandée par Android pour créer des interfaces utilisateur natives. Elle simplifie et accélère le développement d'interfaces utilisateur sur Android. Donner rapidement vie à votre application grâce à moins de code, des outils puissants et des API Kotlin intuitives. (Ça ressemble un peu a flutter).</p>
<p>Mon aventure dans le développement android native m'a donc permis d’apprendre les notions suivantes</p>
<p>La conception d’interface utilisateur avec des composables (c’est le coeur du truc, comme les widgets le sont pour flutter si je dis pas de bétise), les composables vous permet de décrire votre interfaces de façon déclarative, zéro xml juste des fonctions et des class kotlin</p>
<p>La gestion du state avec les api comme remember, remenberSaveable, mutableStateOf, le state hoisting, les viewModels , la navigation avec compose navigation, l’injection de dépendance avec Hilt, la sauvegarde des preferences avec datastore sans être exhaustif.</p>
<p>Les applications faite avec Jetpack compose sont native donc par défaut il ne peuvent que être installé sur android, si vous voulez supporté d’autre plateformes comme ios, il va falloir développer votre app pour ios, c’est pas idéal pour un projet mvp, ou une app qui ne dispose pas de beaucoup de budget, et bien Jetbrains a trouvé une solution pour vous.</p>
<p>Il s’agit de Kotlin multiplateforme (KMP) et Compose multiplateforme (CMP),KPM est approche multiplateforme qui vous permet de choisir la couche de votre application que vous souhaitez partager entre (android et Ios) en gros, vous pouvez partager votre logique métier entre les deux plateforme et gardé vos ui native(Jetpack compose/Xml pour android et objective C/swift ui pour ios), la second approche CMP, utilise un partage de logique métier et ui en utilisant Jetpack compose pour décrire votre interface utilisé, votre interface est compilé en code machine par Skia, vous avez donc une seul code base pour les deux plateformes (ios et android).</p>
<p>C’est bien beau tout ce bavardage mais qu’est ce que j’ai puis réalisé de concret avec Jetpack compose, et bien voilà, j’ai fait une app de suivi de marche, j’ai pour habitude de marche (10km chaque 2jours/7), cela me permet de suivre mes stats nombres de pas, calories bruillés, distance parcourue.</p>
<p>Comptage du nombre de pas grâce au capteur Sensor.TYPE_STEP_COUNTER Sauvegarde des donnés de marche en local grâce a ROOM, Suivi des pas en arriere plan, et envoi des notification grace aux permissions POST_NOTIFICATIONS, FOREGROUND_SERVICE, FOREGROUND_SERVICE_HEALTH, ACTIVITY_RECOGNITION</p>
<p>Voici des captures de l’app et lien vers des resources</p>
<p><a href="https://developer.android.com/compose">https://developer.android.com/compose</a> <a href="https://kmp.jetbrains.com/?android=true&amp;ios=true&amp;iosui=compose&amp;includeTests=true">https://kmp.jetbrains.com/?android=true&amp;ios=true&amp;iosui=compose&amp;includeTests=true</a> <a href="https://kotlinlang.org/multiplatform/">https://kotlinlang.org/multiplatform/</a></p>
]]></content:encoded></item><item><title><![CDATA[Créer un écran d’Onboarding moderne avec Jetpack Compose(HorizontalPager)]]></title><description><![CDATA[L’onboarding est une étape essentielle pour présenter votre application aux nouveaux utilisateurs. Un écran clair, fluide et animé permet d’améliorer l’expérience utilisateur dès les premières seconde]]></description><link>https://blog.julesadonsi.com/jetpack-compose-onboarding-screen-with-horizontalpager</link><guid isPermaLink="true">https://blog.julesadonsi.com/jetpack-compose-onboarding-screen-with-horizontalpager</guid><category><![CDATA[Jetpack Compose]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Android]]></category><dc:creator><![CDATA[Jules ADONSI]]></dc:creator><pubDate>Thu, 19 Feb 2026 21:52:33 GMT</pubDate><enclosure url="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/5f86e6c576870f43aa09d895/5a17fc99-fda2-4a9b-a06d-027f4bec2295.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>L’onboarding est une étape essentielle pour présenter votre application aux nouveaux utilisateurs. Un écran clair, fluide et animé permet d’améliorer l’expérience utilisateur dès les premières secondes.</p>
<p>Dans ce tutoriel, nous allons construire un <strong>écran d’onboarding moderne avec Jetpack Compose</strong>, en utilisant :</p>
<ul>
<li><p><code>HorizontalPager</code></p>
</li>
<li><p>Animations fluides (fade + scale)</p>
</li>
<li><p>Indicateurs de pagination animés</p>
</li>
<li><p>Bouton dynamique (Suivant / Commencer)</p>
</li>
</ul>
<h2><strong>1 - Modéliser les pages</strong></h2>
<p>On commence par définir une classe représentant une page d’onboarding :</p>
<pre><code class="language-kotlin">data class OnboardingPage(
    val title: String,
    val subtitle: String,
    val icon: ImageVector? = null
)
</code></pre>
<p>Ensuite, on crée une liste de pages avec du texte généré qui nous permettra de rendre les donnés de l'onboarding totalement dynamique.</p>
<pre><code class="language-kotlin">private val pages = listOf(
    OnboardingPage(
        title = "Lorem ipsum dolor sit amet",
        subtitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        icon = Icons.Filled.Apartment
    ),
    OnboardingPage(
        title = "Consectetur adipiscing elit",
        subtitle = "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
        icon = Icons.Default.Money
    ),
    OnboardingPage(
        title = "Sed do eiusmod tempor",
        subtitle = "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
        icon = Icons.Default.Description
    )
)
</code></pre>
<h2>2 - Implémenter le composable HorizontalPager</h2>
<p><a href="https://developer.android.com/develop/ui/compose/layouts/pager?hl=fr#horizontalpager">HorizontalPager</a> est un composable qui permet d’afficher du contenu sous forme de pages glissables horizontalement, un peu comme :</p>
<ul>
<li><p>un carrousel d’images</p>
</li>
<li><p>un onboarding (écran d’introduction)</p>
</li>
<li><p>des onglets que l’on swipe</p>
</li>
<li><p>un système type ViewPager (ancien Android)</p>
</li>
</ul>
<p>HorizontalPager est fournit via <code>foundation.pager</code> , voici comment l'implémenter très simplement.</p>
<pre><code class="language-kotlin">val pagerState = rememberPagerState(pageCount = { pages.size })

HorizontalPager(
    state = pagerState,
    modifier = Modifier.weight(1f)
) { pageIndex -&gt;
    // Contenu a swipper
}
</code></pre>
<h2>3 - Structurer l’UI avec des Composables réutilisables.</h2>
<p>Jepack compose recommande de découpé votre UI en de petit composable, et nous allons respectez ce conseil dans ce cas. Créer un petit compsable TextSection qui affichera nos textes, contenu dans les différente page.</p>
<pre><code class="language-kotlin">@Composable
private fun TextSection(title: String, subtitle: String) {
    Column(
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = title,
            fontSize = 26.sp,
            fontWeight = FontWeight.Bold,
            textAlign = TextAlign.Center
        )

        Text(
            text = subtitle,
            fontSize = 15.sp,
            textAlign = TextAlign.Center
        )
    }
}
</code></pre>
<p>, nous avons besoin aussi d'un indicateur pour connaitre la position actuel dans la liste de page, pour ce faire on utiliséra un box</p>
<pre><code class="language-kotlin">val width by animateDpAsState(
    targetValue = if (index == current) 24.dp else 8.dp,
    animationSpec = tween(durationMillis = 300)
)

Box(
    modifier = Modifier
        .height(8.dp)
        .width(width)
        .clip(CircleShape)
)
</code></pre>
<p>Terminons par le bouton qui pourra aussi être utilisé pour défiler les page,</p>
<pre><code class="language-kotlin">Button(
    onClick = {
        if (pagerState.currentPage &lt; pages.lastIndex) {
            scope.launch {
                pagerState.animateScrollToPage(
                    pagerState.currentPage + 1
                )
            }
        } else {
            onNavigateToAuth()
        }
    }
) {
    Text(
        text = if (isLastPage) "Commencer" else "Suivant →"
    )
}
</code></pre>
<p>voici le code complet de notre écran d'onboarding de notre superbe app mobile android, qui inclut les éléments suivant</p>
<ul>
<li><p>HorizontalPager</p>
</li>
<li><p>Animation scale + fade</p>
</li>
<li><p>Pagination animée</p>
</li>
<li><p>Bouton dynamique</p>
</li>
<li><p>Structure propre et modulaire</p>
</li>
<li><p>Preview fonctionnel</p>
</li>
</ul>
<pre><code class="language-kotlin">package com.example.onboarding

import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Apartment
import androidx.compose.material.icons.filled.Description
import androidx.compose.material.icons.filled.Money
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch
import kotlin.math.absoluteValue

// ---------------------------
// Data Model
// ---------------------------

data class OnboardingPage(
    val title: String,
    val subtitle: String,
    val icon: ImageVector
)

private val pages = listOf(
    OnboardingPage(
        title = "Lorem ipsum dolor sit amet",
        subtitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        icon = Icons.Filled.Apartment
    ),
    OnboardingPage(
        title = "Consectetur adipiscing elit",
        subtitle = "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
        icon = Icons.Filled.Money
    ),
    OnboardingPage(
        title = "Sed do eiusmod tempor",
        subtitle = "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
        icon = Icons.Filled.Description
    )
)

// ---------------------------
// Main Screen
// ---------------------------

@Composable
fun OnboardingScreen(
    onNavigateToHome: () -&gt; Unit
) {
    val pagerState = rememberPagerState(pageCount = { pages.size })
    val scope = rememberCoroutineScope()

    Surface(
        modifier = Modifier.fillMaxSize(),
        color = Color.White
    ) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(horizontal = 24.dp, vertical = 50.dp),
            verticalArrangement = Arrangement.SpaceBetween
        ) {

            TopBar(onSkip = onNavigateToHome)

            HorizontalPager(
                state = pagerState,
                modifier = Modifier.weight(1f)
            ) { pageIndex -&gt;

                val pageOffset = (
                        (pagerState.currentPage - pageIndex) +
                                pagerState.currentPageOffsetFraction
                        ).absoluteValue

                Column(
                    modifier = Modifier
                        .fillMaxSize()
                        .graphicsLayer {
                            alpha = 1f - pageOffset.coerceIn(0f, 1f)
                            scaleX = 1f - (pageOffset * 0.1f)
                            scaleY = 1f - (pageOffset * 0.1f)
                        },
                    verticalArrangement = Arrangement.SpaceEvenly,
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    IllustrationSection(page = pages[pageIndex])
                    TextSection(
                        title = pages[pageIndex].title,
                        subtitle = pages[pageIndex].subtitle
                    )
                }
            }

            PaginationDots(
                total = pages.size,
                current = pagerState.currentPage
            )

            Spacer(modifier = Modifier.height(25.dp))

            NextButton(
                isLastPage = pagerState.currentPage == pages.lastIndex,
                onClick = {
                    if (pagerState.currentPage &lt; pages.lastIndex) {
                        scope.launch {
                            pagerState.animateScrollToPage(
                                pagerState.currentPage + 1
                            )
                        }
                    } else {
                        onNavigateToHome()
                    }
                }
            )
        }
    }
}

// ---------------------------
// Top Bar
// ---------------------------

@Composable
private fun TopBar(onSkip: () -&gt; Unit) {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.End
    ) {
        TextButton(onClick = onSkip) {
            Text(
                text = "Passer",
                fontWeight = FontWeight.SemiBold
            )
        }
    }
}

// ---------------------------
// Illustration Section
// ---------------------------

@Composable
private fun IllustrationSection(page: OnboardingPage) {
    Box(
        modifier = Modifier
            .size(150.dp)
            .clip(CircleShape)
            .background(Color(0xFFE8F5E9)),
        contentAlignment = Alignment.Center
    ) {
        Card(
            shape = CircleShape,
            elevation = CardDefaults.cardElevation(6.dp)
        ) {
            Box(
                modifier = Modifier
                    .size(100.dp)
                    .background(Color.White),
                contentAlignment = Alignment.Center
            ) {
                Icon(
                    imageVector = page.icon,
                    contentDescription = null,
                    tint = Color(0xFF2E7D32),
                    modifier = Modifier.size(60.dp)
                )
            }
        }
    }
}

// ---------------------------
// Text Section
// ---------------------------

@Composable
private fun TextSection(title: String, subtitle: String) {
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        Text(
            text = title,
            fontSize = 26.sp,
            fontWeight = FontWeight.Bold,
            textAlign = TextAlign.Center
        )

        Text(
            text = subtitle,
            fontSize = 15.sp,
            textAlign = TextAlign.Center,
            color = Color.Gray
        )
    }
}

// ---------------------------
// Pagination Dots
// ---------------------------

@Composable
private fun PaginationDots(total: Int, current: Int) {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.Center
    ) {
        repeat(total) { index -&gt;
            val width by animateDpAsState(
                targetValue = if (index == current) 24.dp else 8.dp,
                animationSpec = tween(300),
                label = ""
            )

            Box(
                modifier = Modifier
                    .padding(horizontal = 4.dp)
                    .height(8.dp)
                    .width(width)
                    .clip(CircleShape)
                    .background(
                        if (index == current)
                            Color(0xFF2E7D32)
                        else
                            Color.LightGray
                    )
            )
        }
    }
}

// ---------------------------
// Next Button
// ---------------------------

@Composable
private fun NextButton(
    isLastPage: Boolean,
    onClick: () -&gt; Unit
) {
    Button(
        onClick = onClick,
        modifier = Modifier
            .fillMaxWidth()
            .height(56.dp),
        shape = RoundedCornerShape(16.dp)
    ) {
        Text(
            text = if (isLastPage) "Commencer" else "Suivant →",
            fontSize = 16.sp,
            fontWeight = FontWeight.SemiBold
        )
    }
}

// ---------------------------
// Preview
// ---------------------------

@Preview(showBackground = true, showSystemUi = true)
@Composable
fun OnboardingPreview() {
    MaterialTheme {
        OnboardingScreen(onNavigateToHome = {})
    }
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Verrouillage Optimiste et Pessimiste en Django : Guide Complet]]></title><description><![CDATA[Dans le développement d'applications web modernes, la gestion de la concurrence est un défi crucial, particulièrement lorsque plusieurs utilisateurs ou processus tentent de modifier simultanément les mêmes données. Django, en tant que framework web r...]]></description><link>https://blog.julesadonsi.com/pessimistic-locking-optimistic-locking-in-django</link><guid isPermaLink="true">https://blog.julesadonsi.com/pessimistic-locking-optimistic-locking-in-django</guid><category><![CDATA[Django]]></category><category><![CDATA[performance]]></category><category><![CDATA[optimization]]></category><category><![CDATA[concurrency]]></category><category><![CDATA[optimistic locking]]></category><category><![CDATA[Pessimistic Locking]]></category><dc:creator><![CDATA[Jules ADONSI]]></dc:creator><pubDate>Fri, 30 Jan 2026 14:35:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769783727625/d884a8c4-56bd-4604-8cf4-d7f84a53f3e4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dans le développement d'applications web modernes, la gestion de la concurrence est un défi crucial, particulièrement lorsque plusieurs utilisateurs ou processus tentent de modifier simultanément les mêmes données. Django, en tant que framework web robuste, offre plusieurs mécanismes pour gérer ces situations de concurrence. Parmi eux, le verrouillage optimiste (optimistic locking) et le verrouillage pessimiste (pessimistic locking) sont deux stratégies fondamentales mais radicalement différentes.</p>
<p>Cet article explore en profondeur ces deux approches, leurs cas d'usage, et comment les implémenter efficacement dans vos projets Django.</p>
<h2 id="heading-le-probleme-la-concurrence-de-donnees">Le Problème : La Concurrence de Données</h2>
<p>Imaginons un scénario classique d'e-commerce. Deux utilisateurs consultent le même produit dont il ne reste qu'un seul exemplaire en stock. Ils cliquent tous les deux sur "Acheter" au même moment. Sans mécanisme de gestion de concurrence approprié, voici ce qui pourrait se passer :</p>
<ol>
<li><p>Utilisateur A lit : stock = 1</p>
</li>
<li><p>Utilisateur B lit : stock = 1</p>
</li>
<li><p>Utilisateur A décrémente : stock = 0</p>
</li>
<li><p>Utilisateur B décrémente : stock = 0 (devrait être -1, erreur !)</p>
</li>
</ol>
<p>Ce problème de "race condition" peut entraîner des incohérences de données, des surventes, ou même des pertes financières. C'est là qu'interviennent les stratégies de verrouillage.</p>
<h2 id="heading-verrouillage-pessimiste-pessimistic-locking">Verrouillage Pessimiste (Pessimistic Locking)</h2>
<h3 id="heading-concept">Concept</h3>
<p>Le verrouillage pessimiste part du principe que les conflits sont <strong>probables</strong>. Il verrouille donc les données dès qu'elles sont lues, empêchant toute autre transaction de les modifier jusqu'à ce que le verrou soit libéré.</p>
<p>C'est comme si vous mettiez un panneau "En cours de modification - Ne pas toucher" sur vos données.</p>
<h3 id="heading-comment-ca-fonctionne-en-django">Comment ça fonctionne en Django ?</h3>
<p>Django implémente le verrouillage pessimiste via la méthode <code>select_for_update()</code> qui utilise la clause SQL <code>SELECT ... FOR UPDATE</code>.</p>
<h3 id="heading-exemple-basique">Exemple Basique</h3>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> transaction
<span class="hljs-keyword">from</span> myapp.models <span class="hljs-keyword">import</span> Product

<span class="hljs-meta">@transaction.atomic</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">purchase_product</span>(<span class="hljs-params">product_id, quantity</span>):</span>
    <span class="hljs-comment"># Verrouille la ligne en base de données</span>
    product = Product.objects.select_for_update().get(id=product_id)

    <span class="hljs-keyword">if</span> product.stock &gt;= quantity:
        product.stock -= quantity
        product.save()
        <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
</code></pre>
<p>Dans cet exemple :</p>
<ul>
<li><p><code>select_for_update()</code> verrouille la ligne du produit</p>
</li>
<li><p>Aucune autre transaction ne peut modifier cette ligne jusqu'à la fin de la transaction</p>
</li>
<li><p>Le décorateur <code>@transaction.atomic</code> garantit que le verrou est maintenu pendant toute la durée de la transaction</p>
</li>
</ul>
<h3 id="heading-exemple-avance-e-commerce-avec-gestion-de-panier">Exemple Avancé : E-commerce avec Gestion de Panier</h3>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> transaction
<span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> F
<span class="hljs-keyword">from</span> myapp.models <span class="hljs-keyword">import</span> Product, Order, OrderItem

<span class="hljs-meta">@transaction.atomic</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_order</span>(<span class="hljs-params">user, cart_items</span>):</span>
    <span class="hljs-string">"""
    Traite une commande en verrouillant tous les produits concernés
    pour éviter les surventes.
    """</span>
    order = Order.objects.create(user=user, status=<span class="hljs-string">'pending'</span>)

    <span class="hljs-comment"># Récupérer tous les IDs de produits</span>
    product_ids = [item[<span class="hljs-string">'product_id'</span>] <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> cart_items]

    <span class="hljs-comment"># Verrouiller TOUS les produits en une seule requête</span>
    <span class="hljs-comment"># nowait=True : échoue immédiatement si un verrou existe déjà</span>
    products = Product.objects.select_for_update(nowait=<span class="hljs-literal">True</span>).filter(
        id__in=product_ids
    )

    <span class="hljs-comment"># Créer un dictionnaire pour un accès rapide</span>
    products_dict = {p.id: p <span class="hljs-keyword">for</span> p <span class="hljs-keyword">in</span> products}

    <span class="hljs-comment"># Vérifier la disponibilité de tous les produits</span>
    <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> cart_items:
        product = products_dict.get(item[<span class="hljs-string">'product_id'</span>])
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> product <span class="hljs-keyword">or</span> product.stock &lt; item[<span class="hljs-string">'quantity'</span>]:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Stock insuffisant pour <span class="hljs-subst">{product.name}</span>"</span>)

    <span class="hljs-comment"># Si tout est OK, créer les items de commande et décrémenter le stock</span>
    <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> cart_items:
        product = products_dict[item[<span class="hljs-string">'product_id'</span>]]

        OrderItem.objects.create(
            order=order,
            product=product,
            quantity=item[<span class="hljs-string">'quantity'</span>],
            price=product.price
        )

        product.stock -= item[<span class="hljs-string">'quantity'</span>]
        product.save()

    order.status = <span class="hljs-string">'confirmed'</span>
    order.save()

    <span class="hljs-keyword">return</span> order
</code></pre>
<h3 id="heading-options-de-selectforupdate">Options de <code>select_for_update()</code></h3>
<p>Django offre plusieurs options pour affiner le comportement du verrouillage :</p>
<pre><code class="lang-python"><span class="hljs-comment"># nowait : échoue immédiatement si la ligne est déjà verrouillée</span>
Product.objects.select_for_update(nowait=<span class="hljs-literal">True</span>).get(id=<span class="hljs-number">1</span>)

<span class="hljs-comment"># skip_locked : ignore les lignes déjà verrouillées</span>
available_products = Product.objects.select_for_update(skip_locked=<span class="hljs-literal">True</span>).filter(
    stock__gt=<span class="hljs-number">0</span>
)

<span class="hljs-comment"># of : verrouille seulement certaines tables liées</span>
Order.objects.select_for_update(of=(<span class="hljs-string">'self'</span>,)).select_related(<span class="hljs-string">'customer'</span>).get(id=<span class="hljs-number">1</span>)
</code></pre>
<h3 id="heading-exemple-avec-timeout-personnalise">Exemple avec Timeout Personnalisé</h3>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> transaction, OperationalError
<span class="hljs-keyword">import</span> time

<span class="hljs-meta">@transaction.atomic</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">safe_update_with_timeout</span>(<span class="hljs-params">product_id, timeout=<span class="hljs-number">5</span></span>):</span>
    <span class="hljs-string">"""
    Tente de verrouiller un produit avec un timeout personnalisé.
    """</span>
    start_time = time.time()

    <span class="hljs-keyword">while</span> time.time() - start_time &lt; timeout:
        <span class="hljs-keyword">try</span>:
            product = Product.objects.select_for_update(nowait=<span class="hljs-literal">True</span>).get(
                id=product_id
            )

            <span class="hljs-comment"># Effectuer la modification</span>
            product.stock -= <span class="hljs-number">1</span>
            product.save()

            <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>

        <span class="hljs-keyword">except</span> OperationalError:
            <span class="hljs-comment"># Le verrou n'est pas disponible, attendre un peu</span>
            time.sleep(<span class="hljs-number">0.1</span>)

    <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>  <span class="hljs-comment"># Timeout dépassé</span>
</code></pre>
<h3 id="heading-avantages-du-verrouillage-pessimiste">Avantages du Verrouillage Pessimiste</h3>
<ul>
<li><p><strong>Simplicité conceptuelle</strong> : facile à comprendre et à implémenter</p>
</li>
<li><p><strong>Cohérence garantie</strong> : impossible d'avoir des modifications concurrentes</p>
</li>
<li><p><strong>Idéal pour les opérations critiques</strong> : transactions bancaires, gestion de stock</p>
</li>
</ul>
<h3 id="heading-inconvenients">Inconvénients</h3>
<ul>
<li><p><strong>Performance</strong> : peut créer des goulots d'étranglement</p>
</li>
<li><p><strong>Deadlocks potentiels</strong> : deux transactions s'attendent mutuellement</p>
</li>
<li><p><strong>Scalabilité limitée</strong> : problématique avec beaucoup d'utilisateurs concurrents</p>
</li>
</ul>
<h2 id="heading-verrouillage-optimiste-optimistic-locking">Verrouillage Optimiste (Optimistic Locking)</h2>
<h3 id="heading-concept-1">Concept</h3>
<p>Le verrouillage optimiste part du principe que les conflits sont <strong>rares</strong>. Au lieu de verrouiller les données, il vérifie au moment de la sauvegarde si elles ont été modifiées entre-temps.</p>
<p>C'est comme si vous disiez : "Je vais travailler sur ces données, et au moment de sauvegarder, je vérifierai si quelqu'un d'autre les a modifiées."</p>
<h3 id="heading-comment-ca-fonctionne">Comment ça fonctionne ?</h3>
<p>L'approche la plus courante consiste à utiliser un champ de version :</p>
<ol>
<li><p>Lire les données avec leur numéro de version actuel</p>
</li>
<li><p>Effectuer les modifications en mémoire</p>
</li>
<li><p>Au moment de la sauvegarde, vérifier que le numéro de version n'a pas changé</p>
</li>
<li><p>Si changé : conflit détecté</p>
</li>
<li><p>Si inchangé : sauvegarder et incrémenter la version</p>
</li>
</ol>
<h3 id="heading-implementation-manuelle-avec-un-champ-de-version">Implémentation Manuelle avec un Champ de Version</h3>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models
<span class="hljs-keyword">from</span> django.core.exceptions <span class="hljs-keyword">import</span> ValidationError

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Product</span>(<span class="hljs-params">models.Model</span>):</span>
    name = models.CharField(max_length=<span class="hljs-number">200</span>)
    price = models.DecimalField(max_digits=<span class="hljs-number">10</span>, decimal_places=<span class="hljs-number">2</span>)
    stock = models.IntegerField(default=<span class="hljs-number">0</span>)
    version = models.IntegerField(default=<span class="hljs-number">0</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">save</span>(<span class="hljs-params">self, *args, **kwargs</span>):</span>
        <span class="hljs-string">"""
        Sauvegarde avec vérification de version pour le locking optimiste.
        """</span>
        <span class="hljs-keyword">if</span> self.pk <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:  <span class="hljs-comment"># C'est une mise à jour</span>
            <span class="hljs-comment"># Incrémenter la version</span>
            new_version = self.version + <span class="hljs-number">1</span>

            <span class="hljs-comment"># Mettre à jour seulement si la version n'a pas changé</span>
            updated = Product.objects.filter(
                pk=self.pk,
                version=self.version
            ).update(
                name=self.name,
                price=self.price,
                stock=self.stock,
                version=new_version
            )

            <span class="hljs-keyword">if</span> updated == <span class="hljs-number">0</span>:
                <span class="hljs-keyword">raise</span> ValidationError(
                    <span class="hljs-string">"Ce produit a été modifié par un autre utilisateur. "</span>
                    <span class="hljs-string">"Veuillez recharger et réessayer."</span>
                )

            <span class="hljs-comment"># Mettre à jour la version locale</span>
            self.version = new_version
        <span class="hljs-keyword">else</span>:
            <span class="hljs-comment"># Nouvelle instance</span>
            super().save(*args, **kwargs)

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        db_table = <span class="hljs-string">'products'</span>
</code></pre>
<h3 id="heading-utilisation-du-verrouillage-optimiste">Utilisation du Verrouillage Optimiste</h3>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.core.exceptions <span class="hljs-keyword">import</span> ValidationError
<span class="hljs-keyword">from</span> myapp.models <span class="hljs-keyword">import</span> Product

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">update_product_price</span>(<span class="hljs-params">product_id, new_price, max_retries=<span class="hljs-number">3</span></span>):</span>
    <span class="hljs-string">"""
    Met à jour le prix d'un produit avec retry automatique.
    """</span>
    <span class="hljs-keyword">for</span> attempt <span class="hljs-keyword">in</span> range(max_retries):
        <span class="hljs-keyword">try</span>:
            <span class="hljs-comment"># Lire le produit avec sa version actuelle</span>
            product = Product.objects.get(id=product_id)
            original_version = product.version

            <span class="hljs-comment"># Modifier le produit</span>
            product.price = new_price

            <span class="hljs-comment"># Sauvegarder (vérifie automatiquement la version)</span>
            product.save()

            <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>

        <span class="hljs-keyword">except</span> ValidationError:
            <span class="hljs-comment"># Conflit détecté, réessayer</span>
            <span class="hljs-keyword">if</span> attempt == max_retries - <span class="hljs-number">1</span>:
                <span class="hljs-keyword">raise</span>
            <span class="hljs-keyword">continue</span>

    <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
</code></pre>
<h3 id="heading-exemple-avance-systeme-de-reservation">Exemple Avancé : Système de Réservation</h3>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models, transaction
<span class="hljs-keyword">from</span> django.utils <span class="hljs-keyword">import</span> timezone
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> timedelta

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Event</span>(<span class="hljs-params">models.Model</span>):</span>
    name = models.CharField(max_length=<span class="hljs-number">200</span>)
    total_seats = models.IntegerField()
    available_seats = models.IntegerField()
    version = models.IntegerField(default=<span class="hljs-number">0</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">reserve_seats</span>(<span class="hljs-params">self, quantity</span>):</span>
        <span class="hljs-string">"""
        Réserve des sièges avec verrouillage optimiste.
        """</span>
        <span class="hljs-keyword">if</span> self.available_seats &lt; quantity:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"Sièges insuffisants disponibles"</span>)

        <span class="hljs-comment"># Simuler une latence (traitement métier)</span>
        <span class="hljs-comment"># Dans un cas réel : validation de paiement, etc.</span>

        <span class="hljs-comment"># Décrémenter les sièges</span>
        self.available_seats -= quantity
        self.save()  <span class="hljs-comment"># La méthode save() vérifie la version</span>

        <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Reservation</span>(<span class="hljs-params">models.Model</span>):</span>
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    user = models.ForeignKey(<span class="hljs-string">'auth.User'</span>, on_delete=models.CASCADE)
    seats_reserved = models.IntegerField()
    created_at = models.DateTimeField(auto_now_add=<span class="hljs-literal">True</span>)
    expires_at = models.DateTimeField()
    status = models.CharField(
        max_length=<span class="hljs-number">20</span>,
        choices=[
            (<span class="hljs-string">'pending'</span>, <span class="hljs-string">'En attente'</span>),
            (<span class="hljs-string">'confirmed'</span>, <span class="hljs-string">'Confirmée'</span>),
            (<span class="hljs-string">'cancelled'</span>, <span class="hljs-string">'Annulée'</span>),
            (<span class="hljs-string">'expired'</span>, <span class="hljs-string">'Expirée'</span>),
        ],
        default=<span class="hljs-string">'pending'</span>
    )

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_reservation</span>(<span class="hljs-params">user, event_id, seats_count, max_retries=<span class="hljs-number">5</span></span>):</span>
    <span class="hljs-string">"""
    Crée une réservation avec gestion des conflits.
    """</span>
    <span class="hljs-keyword">for</span> attempt <span class="hljs-keyword">in</span> range(max_retries):
        <span class="hljs-keyword">try</span>:
            <span class="hljs-keyword">with</span> transaction.atomic():
                <span class="hljs-comment"># Récupérer l'événement</span>
                event = Event.objects.get(id=event_id)

                <span class="hljs-comment"># Tenter de réserver</span>
                event.reserve_seats(seats_count)

                <span class="hljs-comment"># Créer la réservation</span>
                reservation = Reservation.objects.create(
                    event=event,
                    user=user,
                    seats_reserved=seats_count,
                    expires_at=timezone.now() + timedelta(minutes=<span class="hljs-number">15</span>),
                    status=<span class="hljs-string">'pending'</span>
                )

                <span class="hljs-keyword">return</span> reservation

        <span class="hljs-keyword">except</span> ValidationError <span class="hljs-keyword">as</span> e:
            <span class="hljs-comment"># Conflit de version, réessayer</span>
            <span class="hljs-keyword">if</span> attempt == max_retries - <span class="hljs-number">1</span>:
                <span class="hljs-keyword">raise</span> Exception(
                    <span class="hljs-string">"Impossible de réserver après plusieurs tentatives. "</span>
                    <span class="hljs-string">"L'événement est peut-être complet."</span>
                )
            <span class="hljs-keyword">continue</span>
        <span class="hljs-keyword">except</span> ValueError <span class="hljs-keyword">as</span> e:
            <span class="hljs-comment"># Plus de sièges disponibles</span>
            <span class="hljs-keyword">raise</span> e

    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>
</code></pre>
<h3 id="heading-utilisation-avec-django-concurrency">Utilisation avec Django-Concurrency</h3>
<p>Pour simplifier l'implémentation, vous pouvez utiliser la bibliothèque <code>django-concurrency</code> :</p>
<pre><code class="lang-bash">pip install django-concurrency
</code></pre>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models
<span class="hljs-keyword">from</span> concurrency.fields <span class="hljs-keyword">import</span> IntegerVersionField

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Product</span>(<span class="hljs-params">models.Model</span>):</span>
    name = models.CharField(max_length=<span class="hljs-number">200</span>)
    price = models.DecimalField(max_digits=<span class="hljs-number">10</span>, decimal_places=<span class="hljs-number">2</span>)
    stock = models.IntegerField(default=<span class="hljs-number">0</span>)
    version = IntegerVersionField()  <span class="hljs-comment"># Gère automatiquement le versioning</span>

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        db_table = <span class="hljs-string">'products'</span>

<span class="hljs-comment"># Utilisation</span>
<span class="hljs-keyword">from</span> concurrency.exceptions <span class="hljs-keyword">import</span> RecordModifiedError

<span class="hljs-keyword">try</span>:
    product = Product.objects.get(id=<span class="hljs-number">1</span>)
    product.stock -= <span class="hljs-number">1</span>
    product.save()
<span class="hljs-keyword">except</span> RecordModifiedError:
    <span class="hljs-comment"># Quelqu'un d'autre a modifié le produit</span>
    print(<span class="hljs-string">"Conflit détecté ! Veuillez réessayer."</span>)
</code></pre>
<h3 id="heading-exemple-avec-timestamp-au-lieu-de-version">Exemple avec Timestamp au lieu de Version</h3>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models
<span class="hljs-keyword">from</span> django.utils <span class="hljs-keyword">import</span> timezone

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Document</span>(<span class="hljs-params">models.Model</span>):</span>
    title = models.CharField(max_length=<span class="hljs-number">200</span>)
    content = models.TextField()
    last_modified = models.DateTimeField(auto_now=<span class="hljs-literal">True</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">update_content</span>(<span class="hljs-params">self, new_content, expected_timestamp</span>):</span>
        <span class="hljs-string">"""
        Met à jour le contenu seulement si le timestamp correspond.
        """</span>
        updated = Document.objects.filter(
            pk=self.pk,
            last_modified=expected_timestamp
        ).update(
            content=new_content,
            last_modified=timezone.now()
        )

        <span class="hljs-keyword">if</span> updated == <span class="hljs-number">0</span>:
            <span class="hljs-keyword">raise</span> ValidationError(
                <span class="hljs-string">"Le document a été modifié. Actualisez et réessayez."</span>
            )

        <span class="hljs-comment"># Recharger l'instance</span>
        self.refresh_from_db()
        <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>

<span class="hljs-comment"># Utilisation</span>
document = Document.objects.get(id=<span class="hljs-number">1</span>)
original_timestamp = document.last_modified

<span class="hljs-comment"># L'utilisateur modifie le document (peut prendre du temps)</span>
<span class="hljs-comment"># ...</span>

<span class="hljs-keyword">try</span>:
    document.update_content(
        <span class="hljs-string">"Nouveau contenu"</span>,
        expected_timestamp=original_timestamp
    )
<span class="hljs-keyword">except</span> ValidationError <span class="hljs-keyword">as</span> e:
    print(<span class="hljs-string">"Conflit détecté:"</span>, e)
</code></pre>
<h3 id="heading-avantages-du-verrouillage-optimiste">Avantages du Verrouillage Optimiste</h3>
<ul>
<li><p><strong>Meilleures performances</strong> : pas de verrouillage de base de données</p>
</li>
<li><p><strong>Scalabilité</strong> : supporte beaucoup d'utilisateurs concurrents</p>
</li>
<li><p><strong>Pas de deadlocks</strong> : pas de verrous à gérer</p>
</li>
<li><p><strong>Idéal pour les lectures fréquentes</strong> : les lectures ne sont jamais bloquées</p>
</li>
</ul>
<h3 id="heading-inconvenients-1">Inconvénients</h3>
<ul>
<li><p><strong>Complexité de gestion des conflits</strong> : nécessite une logique de retry</p>
</li>
<li><p><strong>Expérience utilisateur</strong> : l'utilisateur peut perdre son travail en cas de conflit</p>
</li>
<li><p><strong>Plus de code</strong> : implémentation plus complexe</p>
</li>
</ul>
<h2 id="heading-comparaison-et-choix-de-la-strategie">Comparaison et Choix de la Stratégie</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Critère</td><td>Pessimiste</td><td>Optimiste</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Fréquence des conflits</strong></td><td>Élevée</td><td>Faible</td></tr>
<tr>
<td><strong>Durée des transactions</strong></td><td>Courte</td><td>Peut être longue</td></tr>
<tr>
<td><strong>Performance lecture</strong></td><td>Impact négatif</td><td>Aucun impact</td></tr>
<tr>
<td><strong>Performance écriture</strong></td><td>Garantie</td><td>Peut nécessiter des retries</td></tr>
<tr>
<td><strong>Scalabilité</strong></td><td>Limitée</td><td>Excellente</td></tr>
<tr>
<td><strong>Complexité</strong></td><td>Simple</td><td>Moyenne</td></tr>
<tr>
<td><strong>Risque de deadlock</strong></td><td>Oui</td><td>Non</td></tr>
</tbody>
</table>
</div><h3 id="heading-quand-utiliser-le-verrouillage-pessimiste">Quand Utiliser le Verrouillage Pessimiste ?</h3>
<p><strong>Situations idéales :</strong></p>
<ul>
<li><p>Transactions financières (virements, paiements)</p>
</li>
<li><p>Gestion de stock critique (derniers articles en stock)</p>
</li>
<li><p>Systèmes de réservation à forte demande (billets de concert)</p>
</li>
<li><p>Opérations courtes et critiques</p>
</li>
<li><p>Forte probabilité de conflits</p>
</li>
</ul>
<p><strong>Exemple de cas d'usage :</strong></p>
<pre><code class="lang-python"><span class="hljs-comment"># Virement bancaire - DOIT être pessimiste</span>
<span class="hljs-meta">@transaction.atomic</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transfer_money</span>(<span class="hljs-params">from_account_id, to_account_id, amount</span>):</span>
    <span class="hljs-comment"># Verrouiller les deux comptes</span>
    accounts = Account.objects.select_for_update().filter(
        id__in=[from_account_id, to_account_id]
    ).order_by(<span class="hljs-string">'id'</span>)  <span class="hljs-comment"># Ordre fixe pour éviter les deadlocks</span>

    from_account = accounts.get(id=from_account_id)
    to_account = accounts.get(id=to_account_id)

    <span class="hljs-keyword">if</span> from_account.balance &lt; amount:
        <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"Solde insuffisant"</span>)

    from_account.balance -= amount
    to_account.balance += amount

    from_account.save()
    to_account.save()
</code></pre>
<h3 id="heading-quand-utiliser-le-verrouillage-optimiste">Quand Utiliser le Verrouillage Optimiste ?</h3>
<p><strong>Situations idéales :</strong></p>
<ul>
<li><p>Édition collaborative de documents</p>
</li>
<li><p>Mise à jour de profils utilisateurs</p>
</li>
<li><p>Systèmes de gestion de contenu (CMS)</p>
</li>
<li><p>Opérations longues avec faible probabilité de conflit</p>
</li>
<li><p>Applications à fort trafic avec beaucoup de lectures</p>
</li>
</ul>
<p><strong>Exemple de cas d'usage :</strong></p>
<pre><code class="lang-python"><span class="hljs-comment"># Édition de profil utilisateur - peut être optimiste</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserProfile</span>(<span class="hljs-params">models.Model</span>):</span>
    user = models.OneToOneField(<span class="hljs-string">'auth.User'</span>, on_delete=models.CASCADE)
    bio = models.TextField()
    avatar = models.ImageField()
    version = IntegerVersionField()

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">update_profile</span>(<span class="hljs-params">user_id, bio, avatar</span>):</span>
    <span class="hljs-keyword">try</span>:
        profile = UserProfile.objects.get(user_id=user_id)
        profile.bio = bio
        <span class="hljs-keyword">if</span> avatar:
            profile.avatar = avatar
        profile.save()
        <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>
    <span class="hljs-keyword">except</span> RecordModifiedError:
        <span class="hljs-comment"># Conflit rare, l'utilisateur peut réessayer</span>
        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
</code></pre>
<h2 id="heading-approche-hybride">Approche Hybride</h2>
<p>Dans certains cas, vous pouvez combiner les deux approches :</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> transaction
<span class="hljs-keyword">from</span> concurrency.fields <span class="hljs-keyword">import</span> IntegerVersionField
<span class="hljs-keyword">from</span> concurrency.exceptions <span class="hljs-keyword">import</span> RecordModifiedError

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Inventory</span>(<span class="hljs-params">models.Model</span>):</span>
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE)
    quantity = models.IntegerField()
    version = IntegerVersionField()  <span class="hljs-comment"># Optimiste pour les MAJ non-critiques</span>

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        unique_together = [<span class="hljs-string">'product'</span>, <span class="hljs-string">'warehouse'</span>]

<span class="hljs-meta">@transaction.atomic</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transfer_inventory</span>(<span class="hljs-params">product_id, from_warehouse_id, to_warehouse_id, quantity</span>):</span>
    <span class="hljs-string">"""
    Transfert d'inventaire : pessimiste pour la lecture, optimiste pour l'écriture.
    """</span>
    <span class="hljs-comment"># Verrouillage pessimiste pour garantir la cohérence</span>
    inventories = Inventory.objects.select_for_update().filter(
        product_id=product_id,
        warehouse_id__in=[from_warehouse_id, to_warehouse_id]
    )

    from_inv = inventories.get(warehouse_id=from_warehouse_id)
    to_inv = inventories.get(warehouse_id=to_warehouse_id)

    <span class="hljs-comment"># Vérifications métier</span>
    <span class="hljs-keyword">if</span> from_inv.quantity &lt; quantity:
        <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"Quantité insuffisante"</span>)

    <span class="hljs-comment"># Modifications</span>
    from_inv.quantity -= quantity
    to_inv.quantity += quantity

    <span class="hljs-comment"># Sauvegarde avec check de version (optimiste)</span>
    <span class="hljs-keyword">try</span>:
        from_inv.save()
        to_inv.save()
    <span class="hljs-keyword">except</span> RecordModifiedError:
        <span class="hljs-keyword">raise</span> Exception(<span class="hljs-string">"Conflit détecté lors du transfert"</span>)
</code></pre>
<h2 id="heading-bonnes-pratiques">Bonnes Pratiques</h2>
<h3 id="heading-1-toujours-utiliser-des-transactions">1. Toujours Utiliser des Transactions</h3>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> transaction

<span class="hljs-comment"># ✅ BON</span>
<span class="hljs-meta">@transaction.atomic</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">update_with_lock</span>():</span>
    obj = MyModel.objects.select_for_update().get(id=<span class="hljs-number">1</span>)
    obj.value += <span class="hljs-number">1</span>
    obj.save()

<span class="hljs-comment"># ❌ MAUVAIS - le verrou sera libéré immédiatement</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">bad_update</span>():</span>
    obj = MyModel.objects.select_for_update().get(id=<span class="hljs-number">1</span>)
    <span class="hljs-comment"># Le verrou est déjà libéré ici !</span>
    obj.value += <span class="hljs-number">1</span>
    obj.save()
</code></pre>
<h3 id="heading-2-gerer-les-exceptions">2. Gérer les Exceptions</h3>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> OperationalError
<span class="hljs-keyword">from</span> concurrency.exceptions <span class="hljs-keyword">import</span> RecordModifiedError

<span class="hljs-meta">@transaction.atomic</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">safe_operation</span>(<span class="hljs-params">obj_id</span>):</span>
    <span class="hljs-keyword">try</span>:
        obj = MyModel.objects.select_for_update(nowait=<span class="hljs-literal">True</span>).get(id=obj_id)
        <span class="hljs-comment"># ... opérations</span>
    <span class="hljs-keyword">except</span> OperationalError:
        <span class="hljs-comment"># L'objet est déjà verrouillé</span>
        <span class="hljs-keyword">raise</span> Exception(<span class="hljs-string">"Ressource occupée, réessayez plus tard"</span>)
    <span class="hljs-keyword">except</span> RecordModifiedError:
        <span class="hljs-comment"># Conflit de version (optimiste)</span>
        <span class="hljs-keyword">raise</span> Exception(<span class="hljs-string">"Données modifiées, veuillez actualiser"</span>)
</code></pre>
<h3 id="heading-3-eviter-les-deadlocks">3. Éviter les Deadlocks</h3>
<pre><code class="lang-python"><span class="hljs-comment"># ✅ BON - Toujours verrouiller dans le même ordre</span>
<span class="hljs-meta">@transaction.atomic</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transfer</span>(<span class="hljs-params">from_id, to_id, amount</span>):</span>
    <span class="hljs-comment"># Trier les IDs pour garantir un ordre cohérent</span>
    ids = sorted([from_id, to_id])
    accounts = Account.objects.select_for_update().filter(
        id__in=ids
    ).order_by(<span class="hljs-string">'id'</span>)
    <span class="hljs-comment"># ...</span>

<span class="hljs-comment"># ❌ MAUVAIS - Ordre variable peut causer des deadlocks</span>
<span class="hljs-meta">@transaction.atomic</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">bad_transfer</span>(<span class="hljs-params">from_id, to_id, amount</span>):</span>
    from_account = Account.objects.select_for_update().get(id=from_id)
    to_account = Account.objects.select_for_update().get(id=to_id)
    <span class="hljs-comment"># ...</span>
</code></pre>
<h3 id="heading-4-implementer-des-retries-intelligents">4. Implémenter des Retries Intelligents</h3>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> time
<span class="hljs-keyword">from</span> functools <span class="hljs-keyword">import</span> wraps

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">retry_on_conflict</span>(<span class="hljs-params">max_attempts=<span class="hljs-number">3</span>, delay=<span class="hljs-number">0.1</span>, backoff=<span class="hljs-number">2</span></span>):</span>
    <span class="hljs-string">"""
    Décorateur pour réessayer automatiquement en cas de conflit.
    """</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">decorator</span>(<span class="hljs-params">func</span>):</span>
<span class="hljs-meta">        @wraps(func)</span>
        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">wrapper</span>(<span class="hljs-params">*args, **kwargs</span>):</span>
            attempt = <span class="hljs-number">0</span>
            current_delay = delay

            <span class="hljs-keyword">while</span> attempt &lt; max_attempts:
                <span class="hljs-keyword">try</span>:
                    <span class="hljs-keyword">return</span> func(*args, **kwargs)
                <span class="hljs-keyword">except</span> (RecordModifiedError, ValidationError) <span class="hljs-keyword">as</span> e:
                    attempt += <span class="hljs-number">1</span>
                    <span class="hljs-keyword">if</span> attempt &gt;= max_attempts:
                        <span class="hljs-keyword">raise</span>

                    time.sleep(current_delay)
                    current_delay *= backoff

        <span class="hljs-keyword">return</span> wrapper
    <span class="hljs-keyword">return</span> decorator

<span class="hljs-comment"># Utilisation</span>
<span class="hljs-meta">@retry_on_conflict(max_attempts=5, delay=0.2)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">update_product</span>(<span class="hljs-params">product_id, new_stock</span>):</span>
    product = Product.objects.get(id=product_id)
    product.stock = new_stock
    product.save()
</code></pre>
<h3 id="heading-5-monitorer-les-performances">5. Monitorer les Performances</h3>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">from</span> time <span class="hljs-keyword">import</span> time
<span class="hljs-keyword">from</span> contextlib <span class="hljs-keyword">import</span> contextmanager

logger = logging.getLogger(__name__)

<span class="hljs-meta">@contextmanager</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">log_lock_time</span>(<span class="hljs-params">operation_name</span>):</span>
    <span class="hljs-string">"""
    Contexte pour mesurer le temps passé avec un verrou.
    """</span>
    start = time()
    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">yield</span>
    <span class="hljs-keyword">finally</span>:
        duration = time() - start
        logger.info(<span class="hljs-string">f"<span class="hljs-subst">{operation_name}</span> - Lock duration: <span class="hljs-subst">{duration:<span class="hljs-number">.3</span>f}</span>s"</span>)

        <span class="hljs-keyword">if</span> duration &gt; <span class="hljs-number">1.0</span>:  <span class="hljs-comment"># Alerter si &gt; 1 seconde</span>
            logger.warning(<span class="hljs-string">f"Long lock detected: <span class="hljs-subst">{operation_name}</span>"</span>)

<span class="hljs-comment"># Utilisation</span>
<span class="hljs-meta">@transaction.atomic</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">monitored_operation</span>(<span class="hljs-params">obj_id</span>):</span>
    <span class="hljs-keyword">with</span> log_lock_time(<span class="hljs-string">f"Update object <span class="hljs-subst">{obj_id}</span>"</span>):
        obj = MyModel.objects.select_for_update().get(id=obj_id)
        <span class="hljs-comment"># ... opérations</span>
        obj.save()
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Le choix entre verrouillage optimiste et pessimiste dépend fortement du contexte de votre application :</p>
<p><strong>Choisissez le verrouillage pessimiste</strong> quand :</p>
<ul>
<li><p>La cohérence des données est critique</p>
</li>
<li><p>Les conflits sont fréquents</p>
</li>
<li><p>Les transactions sont courtes</p>
</li>
<li><p>Vous gérez des opérations financières ou du stock</p>
</li>
</ul>
<p><strong>Choisissez le verrouillage optimiste</strong> quand :</p>
<ul>
<li><p>La performance et la scalabilité sont prioritaires</p>
</li>
<li><p>Les conflits sont rares</p>
</li>
<li><p>Les opérations peuvent être longues</p>
</li>
<li><p>Vous avez beaucoup de lectures et peu d'écritures</p>
</li>
</ul>
<p>Dans tous les cas :</p>
<ul>
<li><p>Testez votre implémentation sous charge</p>
</li>
<li><p>Mesurez les performances réelles</p>
</li>
<li><p>Gérez correctement les erreurs</p>
</li>
<li><p>Documentez votre choix pour l'équipe</p>
</li>
</ul>
<p>N'oubliez pas que Django offre tous les outils nécessaires pour implémenter ces deux stratégies efficacement. À vous de choisir celle qui correspond le mieux à vos besoins !</p>
<h2 id="heading-ressources-complementaires">Ressources Complémentaires</h2>
<ul>
<li><p><a target="_blank" href="https://docs.djangoproject.com/en/stable/ref/models/querysets/#select-for-update">Documentation Django sur select_for_update()</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/saxix/django-concurrency">Django Concurrency</a></p>
</li>
<li><p><a target="_blank" href="https://docs.djangoproject.com/en/stable/topics/db/transactions/">Database Transactions in Django</a></p>
</li>
</ul>
<hr />
<p><em>Cet article vous a été utile ? N'hésitez pas à partager vos expériences avec le locking en Django dans les commentaires !</em></p>
]]></content:encoded></item><item><title><![CDATA[ForkJoin, l'opérateur RxJS idéal pour attendre la fin de plusieurs requêtes Http avec Angular.]]></title><description><![CDATA[Comment faites-vous lorsque vous devez effectuer plusieurs requêtes HTTP et attendre la réponse de toutes les requêtes avant de continuer ?
Eh bien, une des solutions possibles est d'utiliser l'opérateur RxJS forkJoin. Il permet de combiner plusieurs...]]></description><link>https://blog.julesadonsi.com/forkjoin-loperateur-rxjs-ideal-pour-attendre-la-fin-de-plusieurs-requetes-http-avec-angular</link><guid isPermaLink="true">https://blog.julesadonsi.com/forkjoin-loperateur-rxjs-ideal-pour-attendre-la-fin-de-plusieurs-requetes-http-avec-angular</guid><category><![CDATA[http multiple angula]]></category><category><![CDATA[Forkjoin ]]></category><category><![CDATA[Angular]]></category><category><![CDATA[RxJS]]></category><dc:creator><![CDATA[Jules ADONSI]]></dc:creator><pubDate>Sat, 08 Jun 2024 15:37:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/qK6HAkB91Yc/upload/f1ce90d3b535b85f492732d78f90c05b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Comment faites-vous lorsque vous devez effectuer plusieurs requêtes HTTP et attendre la réponse de toutes les requêtes avant de continuer ?</p>
<p>Eh bien, une des solutions possibles est d'utiliser l'opérateur RxJS <code>forkJoin</code>. Il permet de combiner plusieurs observables en un seul, attendant que tous les observables se terminent avant de commencer à émettre.</p>
<p>Voici quelques avantages que l'opérateur <code>forkJoin</code> apporte à votre application, au cas où vous doutez encore de son utilité 😌😎 :</p>
<ul>
<li><p><strong>Performance</strong> : En envoyant plusieurs requêtes HTTP en parallèle, vous économisez du temps de traitement et améliorez la performance globale de votre application.</p>
</li>
<li><p><strong>Simplicité</strong> : Gérer plusieurs observables individuellement peut rapidement devenir complexe <code>forkJoin</code> simplifie ce processus en centralisant la logique d'attente et de traitement des réponses. Perso, je ne demande pas mieux 😌.</p>
</li>
<li><p><strong>Fiabilité</strong> : En attendant que toutes les requêtes se terminent avant de traiter les données, vous minimisez le risque d'erreurs liées à des requêtes incomplètes ou mal ordonnées.</p>
</li>
</ul>
<p>Assez parlé, je vous montre comment je procède lorsque je suis dans un contexte où l'utilisation de forkJoin est appropriée.</p>
<p>Supposons que nous sommes sur un tableau de bord et souhaitons obtenir d'une API les derniers utilisateurs, les articles et les commentaires créés sur une application X. On pourrait avoir un composant de tableau de bord comme ceci</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, OnInit,signal } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { HttpClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/common/http'</span>;
<span class="hljs-keyword">import</span> { forkJoin } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> {User, Article,Comment} <span class="hljs-keyword">from</span> <span class="hljs-string">'../models'</span>; 

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-dashboard'</span>,
  templateUrl: <span class="hljs-string">'./dashboard.component.html'</span>,
  styleUrls: [<span class="hljs-string">'./dashboard.component.css'</span>]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DashboardComponent <span class="hljs-keyword">implements</span> OnInit {
  users!:User[];
  articles!: Article[];
  comments!: Comment[];

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> http: HttpClient</span>) {}

  ngOnInit() {
    <span class="hljs-keyword">const</span> users$ = <span class="hljs-built_in">this</span>.http.get&lt;User[]&gt;(<span class="hljs-string">'https://api.example.com/users'</span>);
    <span class="hljs-keyword">const</span> articles$ = <span class="hljs-built_in">this</span>.http.get&lt;Article[]&gt;(<span class="hljs-string">'https://api.example.com/articles'</span>);
    <span class="hljs-keyword">const</span> comments$ = <span class="hljs-built_in">this</span>.http.get&lt;Comment[]&gt;(<span class="hljs-string">'https://api.example.com/comments'</span>);

    forkJoin({
        users:users$, 
        articles:articles$, 
        comments:comments$
    }).subscribe({
        next: <span class="hljs-function">(<span class="hljs-params">responses</span>) =&gt;</span> {
          <span class="hljs-built_in">this</span>.users = responses.users.data;
          <span class="hljs-built_in">this</span>.articles = responses.articles.data;
          <span class="hljs-built_in">this</span>.comments = responses.comments.data;
        },
        error: <span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Erreur lors de la récupération des données'</span>, error);
        },
        complete: <span class="hljs-function">() =&gt;</span> {
            <span class="hljs-comment">// Fermer le loader ou effectuer toute autre </span>
           <span class="hljs-comment">//action de nettoyage ici</span>
        },
      })
  }
}
</code></pre>
<p>En utilisant <code>forkJoin</code>, nous pouvons attendre que ces trois requêtes se terminent avant de traiter les données, garantissant ainsi que notre application agit de manière cohérente et fiable.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Enfin, nous sommes à la fin de cette découverte de l'opérateur <code>forkJoin</code>.</p>
<p><code>ForkJoin</code> est un opérateur puissant et flexible dans le cadre de l'utilisation de <code>RxJS</code> avec <code>Angular</code>. Il offre une manière efficace et organisée de gérer et de synchroniser plusieurs requêtes HTTP, contribuant ainsi à une meilleure performance et à une expérience utilisateur fluide.😌😌</p>
<p>Source</p>
<p><a target="_blank" href="https://www.learnrxjs.io/learn-rxjs/operators/combination/forkjoin">https://www.learnrxjs.io/learn-rxjs/operators/combination/forkjoin</a></p>
<p><a target="_blank" href="https://www.learnrxjs.io/">https://www.learnrxjs.io/</a></p>
<p><a target="_blank" href="https://angular.dev/">https://angular.dev/</a></p>
]]></content:encoded></item></channel></rss>