DĂ©couvrez les secrets du verrou pessimiste avec Laravel đ

DĂ©couvrez les secrets du verrou pessimiste avec Laravel đ
Dans cet article, nous allons voir comment éviter certaines catastrophes lors de transactions dans une application bancaire Laravel.
Si vous ne connaissez pas encore la méthode lockForUpdate
(ou sharedLock
) alors ce qui va suivre risque de vous sauver la vie !
Cette connaissance est crucial lorsqu'on manipule des données sensibles, comme les soldes bancaires, pour éviter les erreurs ou duplications dans des situations de forte concurrence.
ProblÚme : Les conflits de transactions simultanées
Pour illustrer cette méthode, nous allons prendre comme exemple une application bancaire tout au long de cet article.
Chaque utilisateur peut transférer des fonds à d'autres bénéficiaires. Ce transfert est géré par une classe BankService
, qui effectue le débit du solde de l'expéditeur et le crédit du bénéficiaire dans une transaction SQL :
1<?php 2 3namespace App\Services; 4 5class BankService 6{ 7 public static function transferFunds(int $from, int $to, float $amount) 8 { 9 return DB::transaction(function () use ($from, $to, $amount) {10 $from = User::query()->findOrFail($from);11 $to = User::query()->findOrFail($to);12 13 if ($from->balance < $amount) {14 return false;15 }16 17 $from->update(['balance' => $from->balance - $amount]);18 $to->update(['balance' => $to->balance + $amount]);19 20 return true;21 });22 }23}
Cependant, si plusieurs transactions sont lancĂ©es en mĂȘme temps (comme dans le cas de transferts simultanĂ©s vers plusieurs bĂ©nĂ©ficiaires), cela peut entraĂźner des anomalies :
- Les vérifications de fonds se font presque simultanément pour chaque transaction.
- Le solde disponible peut ĂȘtre incorrectement partagĂ©, car chaque transaction est exĂ©cutĂ©e sans attendre que la prĂ©cĂ©dente se termine.
ConsĂ©quence : Sans une gestion stricte, des montants peuvent ĂȘtre dĂ©bitĂ©s en double, crĂ©ant un effet de duplication de lâargent.
Solution : Utiliser le lockForUpdate pour bloquer les enregistrements
La méthode lockForUpdate
est un type de verrou pessimiste qui permet de s'assurer qu'un enregistrement reste inchangĂ© durant toute la transaction SQL. En clair, si un autre processus tente dâaccĂ©der Ă cet enregistrement, il attendra la fin de la premiĂšre transaction avant de poursuivre.
Qu'est-ce qu'un verrou pessimiste ?
Un verrou pessimiste (ou pessimistic lock) consiste Ă verrouiller une ressource (dans notre exemple, une ligne de la table users
) de maniĂšre exclusive dĂšs qu'une transaction y accĂšde, empĂȘchant les autres transactions d'effectuer des modifications ou mĂȘme des lectures jusqu'Ă ce que le verrou soit libĂ©rĂ©.
Fonctionnement de lockForUpdate
Lorsqu'une transaction utilise lockForUpdate
, un verrou exclusif est placé sur les lignes sélectionnées. Ce verrou garantit qu'aucune autre transaction ne peut modifier ou lire ces lignes tant que la transaction initiale n'est pas terminée.
Si une autre transaction tente de modifier ou de verrouiller les mĂȘmes lignes (rĂ©cupĂ©ration des users nĂ©cessaires lors du second transfert financier), elle sera mise en attente jusqu'Ă la fin de la premiĂšre transaction. Cela Ă©vite des problĂšmes tels que les conditions de concurrence, oĂč plusieurs transactions pourraient lire et modifier les mĂȘmes donnĂ©es de maniĂšre incohĂ©rente.
Le verrou est ensuite automatiquement libéré à la fin de la transaction, soit par un commit (validation des modifications) soit par un rollback (annulation des modifications).
Ătape 1 : Configurer lockForUpdate notre service
Pour garantir que chaque transfert sâeffectue correctement, modifions la mĂ©thode transferFunds
dans BankService
en ajoutant le verrou :
1<?php 2 3namespace App\Services; 4 5class BankService 6{ 7 public static function transferFunds(int $from, int $to, float $amount) 8 { 9 return DB::transaction(function () use ($from, $to, $amount) {10 $from = User::query()->lockForUpdate()->findOrFail($from); 11 $to = User::query()->lockForUpdate()->findOrFail($to);12 13 if ($from->balance < $amount) {14 return false;15 }16 17 $from->update(['balance' => $from->balance - $amount]);18 $to->update(['balance' => $to->balance + $amount]);19 20 return true;21 });22 }23}
Ătape 2 : Tester le comportement
Ici, pour simuler un fort traffic, nous allons effectivement lancer nos tĂąches en mĂȘme temps via la Façade Concurrency.
Aussi, assurez-vous que seul un transfert réussit lorsque le montant n'est pas suffisant pour plusieurs.
Voici Ă quoi ressemblerait le code dans le ContrĂŽleur :
1<?php 2 3namespace App\Http\Controllers; 4 5class BankController extends Controller 6{ 7 // ... 8 9 public function transferFunds(Request $request) {10 $amount = $request->amount;11 $from = auth()->id();12 13 /**14 * @var array<Closure(): bool> $tasks15 */16 $tasks = [];17 18 $request->collect('beneficiary')->each(function ($id) use ($from, $amount, &$tasks) { 19 $tasks[] = fn() => BankService::transferFunds($from, $id, $amount);20 });21 22 $response = Concurrency::run($tasks);23 }24}
Vérification des Résultats
Pour chaque transfert, nous attendons que la premiĂšre transaction se termine avant que la suivante ne commence, empĂȘchant ainsi le systĂšme de « dupliquer » des fonds lorsque le solde devient insuffisant.
Tutoriel vidéo
Si vous préférez les contenus vidéos pratiques, vous pouvez visionner le tutoriel publié sur ma chaßne LaravelJutsu :
Conclusion
Lâutilisation de lockForUpdate
est essentielle dans des contextes de transactions concurrentes oĂč lâintĂ©gritĂ© des donnĂ©es doit ĂȘtre garantie, comme pour des transferts bancaires. Ce verrou pessimiste assure que seules les transactions valides sont exĂ©cutĂ©es, Ă©vitant les anomalies dues aux mises Ă jour simultanĂ©es. Pour des applications avec un fort trafic, ce type de verrou est indispensable.

A lire
Autres articles de la mĂȘme catĂ©gorie

Les failles RCE dans Laravel
Une faille RCE consiste Ă injecter puis exĂ©cuter arbitrairement du code dans une application, voyons comment exploiter lâune dâentre elles dans un Laravel 9 !

Mathieu De Gracia

Implémentez facilement une authentification OAuth à l'aide de Laravel Socialite
Facilitez l'authentification des utilisateurs avec OAuth dans vos applications web, en utilisant des fournisseurs tels que Google, Facebook et Apple ...

Rémy Guillermic

Encapsuler la logique métier dans des spécifications
Voyons comment le pattern specification peut nous aider Ă mieux encapsuler et tester la logique de nos applications

Mathieu De Gracia