Bâtir des API que vous aimerez Traduction française de «Build APIs You Won t Hate» Phil Sturgeon and Michaël Lecerf This book is for sale at http://leanpub.com/batir-des-api-que-vous-aimerez This version was published on 2014-10-03 This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and many iterations to get reader feedback, pivot until you have the right book and build traction once you do. 2014 Phil Sturgeon and Michaël Lecerf
Tweet This Book! Please help Phil Sturgeon and Michaël Lecerf by spreading the word about this book on Twitter! The suggested hashtag for this book is #api-que-vous-aimerez. Find out what other people are saying about the book by clicking on this link to search for this hashtag on Twitter : https ://twitter.com/search?q=#api-que-vous-aimerez
Table des matières 1. «Seeding» de base de données............................. 1 1.1 Introduction...................................... 1 1.2 Introduction au «seeding» de base de données................... 1 1.3 Construire des «seeders».............................. 2 1.4 Voilà, c est tout.................................... 4 1.5 Données secondaires................................. 6 1.6 Quand exécuter tout ça?............................... 9
1. «Seeding» de base de données 1.1 Introduction La première étape de toute application est généralement la conception de la base de données. Que vous utilisiez un type de base relationnelle, MongoDB, Riak ou n importe quoi d autre, il vous faudra avoir au moins une vague idée de la façon dont vos données seront stockées. Avec une base de données relationnelle, il est probable que vous démarrerez par un schéma entités-relations et, pour une base orientée documents telle que MongoDB, CouchDB ou ElasticSearch, vous laisserez votre application créer une structure comme par magie. Dans tous les cas, il vous faudra un plan. Même s il s agit d un gribouillage sur un coin de nappe. Ce livre aura pour postulat que vous utiliserez une base de données relationnelle pour stocker vos données mais les principes évoqués pourront facilement être adaptés aux systèmes NoSQL. Ce chapitre part du principe que vous disposez déjà d une base de données construite et fonctionnelle. Il ne traitera pas du «Comment planifier une base de données» car il existe déjà beaucoup d autres livres consacrés à ce sujet. 1.2 Introduction au «seeding» de base de données Une fois la base de donnée schématisée et implémentée, la prochaine étape consiste à stocker des données. Plutôt que d ajouter des informations réelles, il est beaucoup plus simple d utiliser des «données de test» pour vérifier si votre structure est adaptée à votre API. Cette approche permet également de jeter la base à la poubelle et de recommencer sans avoir à se soucier de maintenir l intégrité des données. Le processus consistant à remplir ainsi une base de données est appelé «seeding». Ces données peuvent être : des utilisateurs de test ; des articles, avec leurs commentaires ; de faux lieux auxquels faire des «check-ins» ; des payements par carte de crédit, à différents stades de traitement certains complets, d autres à moitié terminés, et d autres encore ayant l air vraiment soupçonneux. En créant des scripts de seeding, vous vous épargnerez le besoin de recréer tout cela manuellement à chaque fois que vous en aurez besoin. Plus vous pourrez automatiser de choses durant le développement de votre API et plus vous pourrez consacrer de temps aux aspects de votre application qui nécessitent davantage d attention. Des données de test sont également nécessaires pour, entre autres :
«Seeding» de base de données 2 effectuer des tests de conformité ; permettre à des freelances ou de nouveaux collègues de se mettre plus efficacement au travail ; éviter que les données réelles de vos clients soient exposées à des personnes externes à votre entreprise ; résister à la tentation de copier votre base de données de production dans votre environnement de développement. Pourquoi c est mal d utiliser les données de production pour le développement? Avez-vous déjà écrit un script qui envoie des e-mails, et contenant du faux texte durant la phase de développement? Avez-vous déjà utilisé des propos douteux ou des blagues internes dans ce faux texte? Avez-vous déjà accidentellement envoyé cet e-mail à 10 000 véritables adresses parmi votre liste de clients? Avez-vous déjà été viré pour avoir fait perdre 300 000 à votre employeur? Ça n a jamais été mon cas, mais je connais un gars à qui c est arrivé. Ne faites pas comme lui. Quel genre de données utiliser? Du n importe quoi! Utilisez des trucs insensés pour votre base de données de développement, mais des trucs insensés ayant le bon type de donnée, la bonne taille et le bon format. Ceci peut être réalisé grâce une petite bibliothèque sympa appelée [Faker], développée par François Zaninotto¹. Son unique but est de générer du n importe nawak. 1.3 Construire des «seeders» Kapture, mon employeur, utilise le framework Laravel. Celui-ci a le bon goût d intégrer par défaut des fonctionnalités de seeding de bases de données. Cela consiste essentiellement en un outil en ligne de commande, tel qu en possèdent (ou devraient posséder) la plupart des frameworks PHP modernes, ce qui permet aux principes ci-après d être applicables à tous. Divisez vos seeders en groupes logiques. Ça ne doit pas nécessairement être «un seeder par table», mais ça pourrait. Personnellement, je ne tente pas de respecter scrupuleusement cette règle car, parfois, mes données ont besoin d être générées en même temps que d autres données. Par exemple, des utilisateurs peuvent être créés dans le même seeder que leurs paramètres, jetons OAuth et relations avec d autres utilisateurs. Répartir tout ceci dans des seeders différents juste par souci d organisation serait futile et ralentirait les choses pour rien. Dans ce chapitre, j utiliserai en guise d exemple une application de «check-in». Celle-ci gère des «utilisateurs» et stocke leurs «check-ins» chez des «commerçants» (ou dans des «lieux»). En outre, ces «commerçants» fournissent des «campagnes» (ou «promotions»). Voici une version très simplifiée de notre seeder d utilisateurs, qui omet la structure spécifique à Laravel. Si vous utilisez Laravel 4, copiez simplement ceci dans votre méthode run(). ¹https://twitter.com/francoisz/
«Seeding» de base de données 3 Créer un utilisateur avec Faker et l ORM Eloquent 1 $faker = Faker\Factory::create(); 2 3 for ($i = 0; $i < Config::get('seeding.users'); $i++) { 4 5 $user = User::create([ 6 'name' => $faker->name, 7 'email' => $faker->email, 8 'active' => $i === 0? true : rand(0, 1), 9 'gender' => rand(0, 1)? 'male' : 'female', 10 'timezone' => mt_rand(-10, 10), 11 'birthday' => rand(0, 1)? $faker->datetimebetween('-40 yea\ 12 rs', '-18 years') : null, 13 'location' => rand(0, 1)? "{$faker->city}, {$faker->state}\ 14 " : null, 15 'had_feedback_email' => $faker->boolean, 16 'sync_name_bio' => $faker->boolean, 17 'bio' => $faker->sentence(100), 18 'picture_url' => $this->picture_url[rand(0, 19)], 19 ]); 20 } Alors, qu avons-nous donc ici? Parcourons ce code morceau par morceau : 1 $faker = Faker\Factory::create(); On crée une instance de Faker, notre expert en génération de n importe nawak. 3 for ($i = 0; $i < Config::get('seeding.users'); $i++) { On va vouloir un certain nombre d utilisateurs. Je recommande d en avoir un peu moins en développement qu en environnement de test ou de staging, afin de gagner du temps. 5 $user = User::create([ 6 'name' => $faker->name, 7 'email' => $faker->email, On crée aléatoirement un nom et une adresse e-mail. Pas besoin de définir un ensemble de données aléatoires à utiliser, parce que C EST MAGIQUE! 8 'active' => $i === 0? true : rand(0, 1), Bon, ok, j ai menti, nos données ne sont pas 100 % aléatoires. On veut que l utilisateur numéro 1 soit actif, pour simplifier nos tests plus tard.
«Seeding» de base de données 4 9 'gender' => $faker->randomelement(['male', 'female']), L égalité des sexes, c est important. 10 'timezone' => mt_rand(-10, 10), Notre développeur d origine a cru intelligent de stocker les fuseaux horaires sous forme de nombres entiers. Stockez des fuseaux horaires, pas des décalages Saviez-vous que certains fuseaux ne sont pas formés d heures entières? Que le Népal est à UTC/GMT + 5h45? Que les îles Chatham (Nouvelle-Zélande) vont de UTC/GMT + 12h45 à UTC/GMT + 13h45 pendant l heure d été? Saviez-vous que certaines régions du monde n ajoutent qu une demi-heure pour passer à l heure d été? N utilisez pas des nombres entiers en tant que timestamps. PHP intègre la liste de fuseaux horaires IANA², qui est un standard. Si vous stockez Europe/Brussels ou Asia/Khandyga, le décalage ainsi que l heure d été pourront être calculés automatiquement. 11 'birthday' => rand(0, 1)? $faker->datetimebetween('-40 years', '-18 year\ 12 s') : null, On place nos utilisateurs dans une tranche d âge précise. 13 'location' => rand(0, 1)? "{$faker->city}, {$faker->state}" : null, Ceci nous donne un nom de ville et d État. Ça marche aussi avec les pays étrangers, et ça c est cool. 15 'had_feedback_email' => $faker->boolean, 16 'sync_name_bio' => $faker->boolean, Quelques flags sans grande importance. On les met à vrai ou faux au hasard. 17 'bio' => $faker->sentence(100), Crée une phrase comportant une centaine de caractères. 1.4 Voilà, c est tout Vous allez créer un paquet de fichiers de ce genre, et vous voudrez remplir à peu près toutes vos tables avec des données. Vous voudrez aussi que vos seeders commencent par vider les tables avant de les remplir. Faites ça de façon globale au début du processus. Ne videz pas les tables en haut de chaque seeder sinon le contenu de cette table provenant d autres seeders sera effacé. ²http://www.iana.org/time-zones
«Seeding» de base de données 5 Exemple d un système complet avec Laravel 4 1 class DatabaseSeeder extends Seeder 2 { 3 public function run() 4 { 5 if (App::environment() === 'production') { 6 exit('je viens juste de vous empêcher de vous faire virer. Bisous\ 7, Phil'); 8 } 9 10 Eloquent::unguard(); 11 12 $tables = [ 13 'locations', 14 'merchants', 15 'opps', 16 'opps_locations', 17 'moments', 18 'rewards', 19 'users', 20 'oauth_sessions', 21 'notifications', 22 'favorites', 23 'settings', 24 'friendships', 25 'impressions', 26 ]; 27 28 foreach ($tables as $table) { 29 DB::table($table)->truncate(); 30 } 31 32 $this->call('merchanttableseeder'); 33 $this->call('placetableseeder'); 34 $this->call('usertableseeder'); 35 $this->call('opptableseeder'); 36 $this->call('momenttableseeder'); 37 } 38 } Ceci supprime tout, puis exécute les classes de seeders pour qu elles fassent leurs trucs.
«Seeding» de base de données 6 Clés étrangères Il peut être difficile de vider une base de données quand des contraintes d intégrité sont en place. Dans un tel cas de figure, votre seeder devrait exécuter DB ::statement('set FOREIGN_KEY_CHECKS=0 ;') ; avant le vidage des tables et DB ::statement('set FOREIGN_KEY_CHECKS=1 ;') ; après pour réactiver les contraintes. 1.5 Données secondaires Comme je l ai dit, il est très probable que vous ayez besoin d ajouter des données dépendantes les unes des autres. Pour cela, vous devez déterminer quelles sont les données principales (par exemple des utilisateurs). Dans le cas d un système de check-in, des «lieux» ou «commerçants» pourraient probablement en faire partie aussi (tout dépend de la nomenclature de votre système). Pour cet exemple, je vais montrer comment créer des «commerçants» puis ensuite leur associer des «promotions». Seeder principal pour la table des commerçants 1 <?php 2 3 class MerchantTableSeeder extends Seeder 4 { 5 /** 6 * Exécute les seeders. 7 * 8 * @return void 9 */ 10 public function run() 11 { 12 $faker = Faker\Factory::create(); 13 14 // Crée un certain nombre de commerçants 15 for ($i = 0; $i < Config::get('seeding.merchants'); $i++) { 16 Merchant::create([ 17 'name' => $faker->company, 18 'website' => $faker->url, 19 'phone' => $faker->phonenumber, 20 'description' => $faker->text(200), 21 ]); 22 } 23 } 24 }
«Seeding» de base de données 7 Seeder principal pour la table des promotions 1 <?php 2 3 use Carbon\Carbon; 4 use Kapture\CategoryFinder; 5 6 class OppTableSeeder extends Seeder 7 { 8 /** 9 * Initialise le tout 10 * 11 * @param Place 12 */ 13 public function construct(categoryfinder $finder, Place $places) 14 { 15 $this->categoryfinder = $finder; 16 $this->places = $places; 17 } 18 19 /** 20 * Images. 21 * 22 * @var string 23 */ 24 protected $imagearray = [ 25 'http://example.com/images/example1.jpg', 26 'http://example.com/images/example2.jpg', 27 'http://example.com/images/example3.jpg', 28 'http://example.com/images/example4.jpg', 29 'http://example.com/images/example5.jpg', 30 ]; 31 32 /** 33 * Exécute les seeders. 34 * 35 * @return void 36 */ 37 public function run() 38 { 39 $faker = Faker\Factory::create(); 40 41 foreach (Merchant::all() as $merchant) { 42 43 // Crée un nombre aléatoire de promos pour ce commerçant 44 foreach (range(1, rand(2, 4)) as $i) {
«Seeding» de base de données 8 45 46 // Il y a trois types d images à ajouter 47 $image = Image::create([ 48 'name' => "{$merchant->name} Image #{$i}", 49 'url' => $faker->randomelement($this->imagearray), 50 ]); 51 52 // On démarre tout de suite la promo et on la fait 53 // durer deux mois 54 $starts = Carbon::now(); 55 56 // On s assure d en avoir au moins une dont on 57 // connait les données 58 if ($i === 1) { 59 // Cette promotion cessera très bientôt 60 $ends = Carbon::now()->addDays(2); 61 $teaser = 'Quelque chose à propos de fromage'; 62 63 } else { 64 $ends = Carbon::now()->addDays(60); 65 $teaser = $faker->sentence(rand(3, 5)); 66 } 67 68 $category = $this->categoryfinder->setrandom()->getone(); 69 70 $opp = Opp::create([ 71 'name' => $faker->sentence(rand(3, 5)), 72 'teaser' => $teaser, 73 'details' => $faker->paragraph(3), 74 'starts' => $starts->format('y-m-d H:i:s'), 75 'ends' => $ends->format('y-m-d H:i:s'), 76 'category_id' => $category->id, 77 'merchant_id' => $merchant->id, 78 'published' => true, 79 ]); 80 81 // Associe le lieu à la promo 82 $opp->images()->attach($image, [ 83 'published' => true 84 ]); 85 } 86 87 echo "Created $i Opps for $merchant->name \n"; 88 } 89 } 90 }
«Seeding» de base de données 9 Ça peut paraître un peu bordélique, et ça mélange des appels pseudo-statiques à l ORM avec un peu d injection de dépendances, mais ces seeders n ont pas reçu beaucoup d amour. Ils pourraient toujours être améliorés mais, quoi qu il en soit, ils font leur boulot. Les grands lignes sont : 41 foreach (Merchant::all() as $merchant) { Boucle sur tous les commerçants. 43 // Crée un nombre aléatoire de promos pour ce commerçant 44 foreach (range(1, rand(2, 4)) as $i) { Crée entre une et quatre promotions pour chaque commerçant. 46 // Il y a trois types d images à ajouter 47 $image = Image::create([ 48 'name' => "{$merchant->name} Image #{$i}", 49 'url' => $faker->randomelement($this->imagearray), 50 ]); Ajoute une image choisie parmi notre tableau d exemples. Plus il y en a, mieux c est. 68 $category = $this->categoryfinder->setrandom()->getone(); Je parlerai davantage de «finders» dans un chapitre ultérieur mais, pour l instant, retenez juste que c est un moyen pratique de récupérer une catégorie au hasard. Le reste devrait être relativement évident. Si vous utilisez Laravel 4 (ou plus récent), vous pouvez exécuter tout ça depuis la ligne de commande avec $ php artisan db :seed. 1.6 Quand exécuter tout ça? C est souvent déclenché manuellement, et parfois de façon automatique en fonction des circonstances. Par exemple, si vous venez juste d ajouter une nouvelle route à votre API ainsi que des données associées, vous voudrez sans doute prévenir les autres membres de votre équipe afin qu ils récupèrent les dernières mises à jour du code, lancent les migrations et exécutent les seeders. C est aussi pratique quand un freelance arrive pour faire quelques trucs, ou quand un nouveau développeur commence, ou que votre dev iphone veut récupérer des données. Dans toutes ces situations, il suffit simplement de lancer la commande dans le terminal. À l occasion, ça peut aussi être lancé manuellement sur le serveur de validation, et automatiquement sur le serveur Jenkins de test quand on déploie de nouvelles versions de l API.