[{"data":1,"prerenderedAt":6030},["ShallowReactive",2],{"tag-posts-api":3},[4,3238,5135],{"id":5,"title":6,"author":7,"body":8,"category":3200,"categorySlug":3201,"date":3202,"description":14,"excerpt":3203,"extension":3225,"location":3226,"meta":3227,"navigation":147,"path":3228,"published":147,"seo":3229,"slug":3230,"stem":3231,"tags":3232,"timeToRead":785,"__hash__":3237},"posts\u002Fposts\u002FProgrammation\u002F2020-11-29_creation-api-3.md","Comment créer une bonne API Web - Partie 3","Ulrich Vandenhekke",{"type":9,"value":10,"toc":3179},"minimark",[11,15,18,41,44,49,52,62,65,72,75,78,81,250,257,263,266,450,453,456,464,468,471,474,477,480,483,491,494,499,502,505,617,620,623,630,633,641,645,652,660,664,667,960,963,980,989,992,1024,1027,1044,1047,1050,1135,1138,1149,1163,1211,1214,1369,1372,1376,1379,1382,1385,1388,1391,1395,1460,1463,1466,1473,1477,1480,1556,1569,1574,1577,1709,1716,1720,1723,1731,1744,1753,1756,1760,1763,1766,1769,1780,1786,1813,1930,1938,1942,1945,1948,1954,1965,2134,2143,2147,2156,2159,2224,2227,2236,2303,2306,2409,2415,2419,2424,2427,2438,2441,2449,2453,2456,2474,2478,2484,2487,2584,2587,2590,2593,2633,2636,2818,2824,2827,2835,2838,2847,2850,2858,2862,2869,2872,2875,2878,2921,2924,2933,3109,3116,3120,3123,3126,3130,3175],[12,13,14],"p",{},"Bonjour,",[12,16,17],{},"Cet article fait partie d'un ensemble:",[19,20,21,29,35],"ul",{},[22,23,24],"li",{},[25,26,28],"a",{"href":27},"\u002Fpost\u002Fcreation-api-1\u002F","Généralités sur l'écriture d'une bonne API",[22,30,31],{},[25,32,34],{"href":33},"\u002Fpost\u002Fcreation-api-2\u002F","Qu'est ce qu'une API REST",[22,36,37],{},[25,38,40],{"href":39},"\u002Fpost\u002Fcreation-api-3\u002F","Qu'est ce qu'une API GraphQL",[12,42,43],{},"Il y a quelques années de cela, j'ai souhaité résoudre un problème que j'ai depuis longtemps avec les API REST: comment bien\nnormaliser les tris, les projections, et les filtres. En effectuant mes recherches je suis tombé sur deux frameworks qui permettent\nde résoudre le problème des projections.",[45,46,48],"h2",{"id":47},"quest-quune-api-falcor","Qu'est qu'une API Falcor",[12,50,51],{},"Je ne vais parler que succinctement de Falcor. C'est un framework que je n'ai pas utilisé mais j'ai tout de même été très intéressé\npar ce dernier et je vais écrire quelques lignes sur ce Framework.",[12,53,54,55,61],{},"Pour plus d'informations, vous pourrez vous référer à la ",[25,56,60],{"href":57,"rel":58},"https:\u002F\u002Fnetflix.github.io\u002Ffalcor\u002Fstarter\u002Fwhat-is-falcor.html",[59],"nofollow","documentation",".",[12,63,64],{},"Pour reprendre les explications de la documentation, Falcor est un middleware de votre application qui permet d'interroger des ressources\nau format JSON sur le serveur, comme votre application le ferait sur des données en mémoire.",[12,66,67],{},[68,69],"img",{"alt":70,"src":71},"Falcor","https:\u002F\u002Fnetflix.github.io\u002Ffalcor\u002Fdocumentation\u002Fnetwork-diagram.png",[12,73,74],{},"La requête envoyée alors au serveur ne contient que les champs demandés par le client, ce qui permet au serveur de sélectionner les champs\net de ne retourner que ces champs. De plus le serveur peut n'exposer qu'un seul modèle contenant toutes les ressources (que ce soit pour\nretourner des listes ou des items particuliers).",[12,76,77],{},"En une seule requête, le client peut alors demander l'ensemble des données dont il a besoin, et l'afficher.",[12,79,80],{},"Nous n'avons plus alors le dilemne \"faut-il créér une sous-resource, ou l'intégrer dans la ressource actuelle ?\". Du point de vue du client:\nplus besoin d'effectuer plusieurs requêtes complexes pour récupérer plusieurs ressources, un seul appel suffit.",[82,83,88],"pre",{"className":84,"code":85,"language":86,"meta":87,"style":87},"language-js shiki shiki-themes one-dark-pro","\u002Fmodel.json?paths=[\"user.name\", \"user.surname\", \"user.address\"]\n\nGET \u002Fmodel.json?paths=[\"user.name\", \"user.surname\", \"user.address\"]\n{\n  user: {\n    name: \"Frank\",\n    surname: \"Underwood\",\n    address: \"1600 Pennsylvania Avenue, Washington, DC\"\n  }\n}\n","js","",[89,90,91,142,149,184,190,199,214,227,238,244],"code",{"__ignoreMap":87},[92,93,96,100,104,107,111,115,118,121,124,128,131,134,136,139],"span",{"class":94,"line":95},"line",1,[92,97,99],{"class":98},"sjrmR","\u002F",[92,101,103],{"class":102},"sU0A5","model",[92,105,61],{"class":106},"sn6KH",[92,108,110],{"class":109},"sVyAn","json",[92,112,114],{"class":113},"seHd6","?",[92,116,117],{"class":109},"paths",[92,119,120],{"class":98},"=",[92,122,123],{"class":106},"[",[92,125,127],{"class":126},"subq3","\"user.name\"",[92,129,130],{"class":106},", ",[92,132,133],{"class":126},"\"user.surname\"",[92,135,130],{"class":106},[92,137,138],{"class":126},"\"user.address\"",[92,140,141],{"class":106},"]\n",[92,143,145],{"class":94,"line":144},2,[92,146,148],{"emptyLinePlaceholder":147},true,"\n",[92,150,152,155,158,160,162,164,166,168,170,172,174,176,178,180,182],{"class":94,"line":151},3,[92,153,154],{"class":102},"GET",[92,156,157],{"class":98}," \u002F",[92,159,103],{"class":102},[92,161,61],{"class":106},[92,163,110],{"class":109},[92,165,114],{"class":113},[92,167,117],{"class":109},[92,169,120],{"class":98},[92,171,123],{"class":106},[92,173,127],{"class":126},[92,175,130],{"class":106},[92,177,133],{"class":126},[92,179,130],{"class":106},[92,181,138],{"class":126},[92,183,141],{"class":106},[92,185,187],{"class":94,"line":186},4,[92,188,189],{"class":106},"{\n",[92,191,193,196],{"class":94,"line":192},5,[92,194,195],{"class":109},"  user",[92,197,198],{"class":106},": {\n",[92,200,202,205,208,211],{"class":94,"line":201},6,[92,203,204],{"class":109},"    name",[92,206,207],{"class":106},": ",[92,209,210],{"class":126},"\"Frank\"",[92,212,213],{"class":106},",\n",[92,215,217,220,222,225],{"class":94,"line":216},7,[92,218,219],{"class":109},"    surname",[92,221,207],{"class":106},[92,223,224],{"class":126},"\"Underwood\"",[92,226,213],{"class":106},[92,228,230,233,235],{"class":94,"line":229},8,[92,231,232],{"class":109},"    address",[92,234,207],{"class":106},[92,236,237],{"class":126},"\"1600 Pennsylvania Avenue, Washington, DC\"\n",[92,239,241],{"class":94,"line":240},9,[92,242,243],{"class":106},"  }\n",[92,245,247],{"class":94,"line":246},10,[92,248,249],{"class":106},"}\n",[12,251,252,253,256],{},"Le ",[89,254,255],{},"Router"," falcor côté serveur, s'occupe alors de dispatcher les différentes parties demandées par le client à différents backends, qui peuvent\nalors répondre indépendament les uns des autres.\nFalcor va s'occuper alors d'aggréger le résultat.",[12,258,259,61],{},[68,260],{"alt":261,"src":262},"Diagramme de service","https:\u002F\u002Fnetflix.github.io\u002Ffalcor\u002Fimages\u002Fservices-diagram.png",[12,264,265],{},"Pour récupérer des données depuis le front:",[82,267,269],{"className":84,"code":268,"language":86,"meta":87,"style":87},"\u002F\u002F ask for name and age of user with id = 5\nmodel.get(\"users[5]['name','age']\");\u002F\u002F which will eventually return something like\n{\n  \"users\": {\n    \"5\": {\n      \"name\": \"John Doe\",\n      \"age\": 33\n    }\n  }\n}\u002F\u002F you can also ask for ranges\nmodel.get(\"users[5..7].name);\u002F\u002F which eventually returns the following\n{\n  \"users\": {\n    \"5\": { \"name\": \"John Doe\" },\n    \"6\": { \"name\": \"Jane Doe\" },\n    \"7\": { \"name\": \"Mary Poppins\" }\n  }\n}\n",[89,270,271,277,299,303,310,317,329,340,345,349,357,375,380,387,405,422,440,445],{"__ignoreMap":87},[92,272,273],{"class":94,"line":95},[92,274,276],{"class":275},"sV9Aq","\u002F\u002F ask for name and age of user with id = 5\n",[92,278,279,281,283,287,290,293,296],{"class":94,"line":144},[92,280,103],{"class":102},[92,282,61],{"class":106},[92,284,286],{"class":285},"sVbv2","get",[92,288,289],{"class":106},"(",[92,291,292],{"class":126},"\"users[5]['name','age']\"",[92,294,295],{"class":106},");",[92,297,298],{"class":275},"\u002F\u002F which will eventually return something like\n",[92,300,301],{"class":94,"line":151},[92,302,189],{"class":106},[92,304,305,308],{"class":94,"line":186},[92,306,307],{"class":126},"  \"users\"",[92,309,198],{"class":106},[92,311,312,315],{"class":94,"line":192},[92,313,314],{"class":126},"    \"5\"",[92,316,198],{"class":106},[92,318,319,322,324,327],{"class":94,"line":201},[92,320,321],{"class":126},"      \"name\"",[92,323,207],{"class":106},[92,325,326],{"class":126},"\"John Doe\"",[92,328,213],{"class":106},[92,330,331,334,336],{"class":94,"line":216},[92,332,333],{"class":126},"      \"age\"",[92,335,207],{"class":106},[92,337,339],{"class":338},"sVC51","33\n",[92,341,342],{"class":94,"line":229},[92,343,344],{"class":106},"    }\n",[92,346,347],{"class":94,"line":240},[92,348,243],{"class":106},[92,350,351,354],{"class":94,"line":246},[92,352,353],{"class":106},"}",[92,355,356],{"class":275},"\u002F\u002F you can also ask for ranges\n",[92,358,360,362,364,366,368,371],{"class":94,"line":359},11,[92,361,103],{"class":102},[92,363,61],{"class":106},[92,365,286],{"class":285},[92,367,289],{"class":106},[92,369,370],{"class":126},"\"users[5..7].name);\u002F\u002F which eventually returns the followin",[92,372,374],{"class":373},"sLaUg","g\n",[92,376,378],{"class":94,"line":377},12,[92,379,189],{"class":106},[92,381,383,385],{"class":94,"line":382},13,[92,384,307],{"class":126},[92,386,198],{"class":106},[92,388,390,392,395,398,400,402],{"class":94,"line":389},14,[92,391,314],{"class":126},[92,393,394],{"class":106},": { ",[92,396,397],{"class":126},"\"name\"",[92,399,207],{"class":106},[92,401,326],{"class":126},[92,403,404],{"class":106}," },\n",[92,406,408,411,413,415,417,420],{"class":94,"line":407},15,[92,409,410],{"class":126},"    \"6\"",[92,412,394],{"class":106},[92,414,397],{"class":126},[92,416,207],{"class":106},[92,418,419],{"class":126},"\"Jane Doe\"",[92,421,404],{"class":106},[92,423,425,428,430,432,434,437],{"class":94,"line":424},16,[92,426,427],{"class":126},"    \"7\"",[92,429,394],{"class":106},[92,431,397],{"class":126},[92,433,207],{"class":106},[92,435,436],{"class":126},"\"Mary Poppins\"",[92,438,439],{"class":106}," }\n",[92,441,443],{"class":94,"line":442},17,[92,444,243],{"class":106},[92,446,448],{"class":94,"line":447},18,[92,449,249],{"class":106},[12,451,452],{},"L'avantage de ce framework, est qu'il permet de simplifier l'écriture des projections et de la partie lecture d'une API.",[12,454,455],{},"Ce pourquoi je n'ai pas choisi ce framework vient en deux choses:",[19,457,458,461],{},[22,459,460],{},"Il est disponible principalement pour Javascript (NodeJS + Front) ; Il existe une version Java même qui n'a pas été mise à jour\ndepuis 2018 et donc je ne retrouve pas les sources.",[22,462,463],{},"J'ai trouvé mieux en GraphQL.",[45,465,467],{"id":466},"quest-quune-api-graphql","Qu'est qu'une API GraphQL",[12,469,470],{},"Venant du constat que Falcor répondait à mon besoin de pouvoir normaliser la projection mais avec quelques limites, j'ai continué mes\nrecherches et je suis tombé sur GraphQL.",[12,472,473],{},"GraphQL répond à la même problèmatique: pouvoir laisser au client choisir la projection qu'il souhaite des données. Comme Falcor,\nGraphQL permet de ramener plusieurs ressources en une requête. Et comme Falcor, GraphQL permet d'aggréger le résultat côté serveur\nde façon asynchrone.",[12,475,476],{},"L'avantage de GraphQL sur Falcor est que GraphQL est une norme écrite par Facebook alors que Falcor est une librairie Javascript.\nDe la norme GraphQL, découle une implémentation officielle en Javascript mais aussi dans plein d'autres languages.",[12,478,479],{},"Par contre GraphQL n'est qu'un language de requête. Il ne décrit pas comment doit être transportée la requête sur le réseau, ni\ncomment doit être transférée la réponse. Seul le contenu est normalisé. Les frameworks sont tout de même compatibles entre eux.",[12,481,482],{},"Par contre cela permet d'utiliser GraphQL pour autre chose que des requêtes réseaux. On pourrait envisager de faire un service qui\nne répond qu'à des requêtes GraphQL. Ce service est alors rattaché au contrôleur pour une exposition mais aussi directement\nappelable en interne par d'autres services. Cela pourrait permettre de faire une couche d'abstraction interne.",[12,484,485,486,61],{},"Il existe plusieurs frameworks ajoutant la couche de transport à GraphQL. Dans la suite je parlerai d'une des implémentations qui se\nnomme ",[25,487,490],{"href":488,"rel":489},"https:\u002F\u002Fwww.apollographql.com\u002F",[59],"Apollo",[12,492,493],{},"Maintenant passons au vif du sujet.",[495,496,498],"h3",{"id":497},"query-introduction"," Query - Introduction",[12,500,501],{},"Le système de requête de graphql permet au client de décrire ce qu'il souhaite récupérer. C'est au client de décider des éléments\nqu'il souhaite et de construire sa requête.",[12,503,504],{},"Voici un exemple de requête de requête:",[82,506,510],{"className":507,"code":508,"language":509,"meta":87,"style":87},"language-graphql shiki shiki-themes one-dark-pro","query Dashboard {\n  queueStats {\n    waiting\n    active\n    failed\n    lastExecution\n    nextWakeup\n  }\n  diskUsageStats {\n    currentRepartition {\n      host\n      total\n    }\n    currentSpace {\n      size\n      used\n    }\n  }\n}\n","graphql",[89,511,512,523,530,535,540,545,550,555,559,566,573,578,583,587,594,599,604,608,612],{"__ignoreMap":87},[92,513,514,517,520],{"class":94,"line":95},[92,515,516],{"class":113},"query",[92,518,519],{"class":285}," Dashboard",[92,521,522],{"class":106}," {\n",[92,524,525,528],{"class":94,"line":144},[92,526,527],{"class":109},"  queueStats",[92,529,522],{"class":106},[92,531,532],{"class":94,"line":151},[92,533,534],{"class":109},"    waiting\n",[92,536,537],{"class":94,"line":186},[92,538,539],{"class":109},"    active\n",[92,541,542],{"class":94,"line":192},[92,543,544],{"class":109},"    failed\n",[92,546,547],{"class":94,"line":201},[92,548,549],{"class":109},"    lastExecution\n",[92,551,552],{"class":94,"line":216},[92,553,554],{"class":109},"    nextWakeup\n",[92,556,557],{"class":94,"line":229},[92,558,243],{"class":106},[92,560,561,564],{"class":94,"line":240},[92,562,563],{"class":109},"  diskUsageStats",[92,565,522],{"class":106},[92,567,568,571],{"class":94,"line":246},[92,569,570],{"class":109},"    currentRepartition",[92,572,522],{"class":106},[92,574,575],{"class":94,"line":359},[92,576,577],{"class":109},"      host\n",[92,579,580],{"class":94,"line":377},[92,581,582],{"class":109},"      total\n",[92,584,585],{"class":94,"line":382},[92,586,344],{"class":106},[92,588,589,592],{"class":94,"line":389},[92,590,591],{"class":109},"    currentSpace",[92,593,522],{"class":106},[92,595,596],{"class":94,"line":407},[92,597,598],{"class":109},"      size\n",[92,600,601],{"class":94,"line":424},[92,602,603],{"class":109},"      used\n",[92,605,606],{"class":94,"line":442},[92,607,344],{"class":106},[92,609,610],{"class":94,"line":447},[92,611,243],{"class":106},[92,613,615],{"class":94,"line":614},19,[92,616,249],{"class":106},[12,618,619],{},"Derrière chaqu'un des membres ci-dessus, on peut retrouver un resolver. Un resolver c'est l'équivalent du contrôleur pour une API REST.\nC'est le resolver qui va récupérer les informations demandées par le client (arguments, projection, ...) et les transférer au service.",[12,621,622],{},"Il nous faudra alors faire un lien entre le schéma et ces resolvers. Pour le client, peu importe qu'il faille, pour une requête, exécuter\nen tâche de fond 1 resolver ou 10 resolver. Le client n'a pas besoin de le savoir.",[12,624,625,626,629],{},"Imaginons que dans l'exemple ci-dessous le champs ",[89,627,628],{},"nextWakeup"," nécessite un calcul complexe. Il suffit de créer un resolver qui\neffectue ce calcul.\nSi jamais le client n'a pas besoin de l'afficher et du coup, ne le demande pas, alors ce calcul complexe ne sera pas fait et c'est du temps\nde traitement gagné sur le serveur.",[12,631,632],{},"En REST, un champ qui nécessite un calcul complexe et soit",[19,634,635,638],{},[22,636,637],{},"inclus dans la resource et donc toujours effectué quelque soit le besoin du client",[22,639,640],{},"séparé dans une ressource séparée, au risque de complexifier l'API et de demander au client de faire plusieurs requêtes si nécessaire (surtout si\non a plus d'un champ calculé).",[495,642,644],{"id":643},"nommer-ces-requêtes","Nommer ces requêtes",[12,646,647,648,651],{},"Dans l'exemple ci-dessus, on peut remarquer que la requête possède un nom. C'est une bonne pratique de toujours nommer l'opération\n(ici ",[89,649,650],{},"Dashboard","). Nommer les opérations permet de :",[19,653,654,657],{},[22,655,656],{},"mettre plusieurs opérations nommées dans une seule requête.",[22,658,659],{},"mais aussi de mieux tracer et débugger les requêtes côté serveur dans les logs.",[495,661,663],{"id":662},"query-coté-serveur","Query - Coté serveur",[12,665,666],{},"Côté serveur nous allons commencer par décrire le schéma que pourra alors utiliser le client pour effectuer ces requêtes. Par exemple :",[82,668,670],{"className":507,"code":669,"language":509,"meta":87,"style":87},"scalar Date\n\n\"\"\"\nDefine the state of the queue\n\"\"\"\ntype QueueStats {\n  \"\"\"\n  Number of task waiting in queue\n  \"\"\"\n  waiting: Int!\n  \"\"\"\n  Number of task active in queue\n  \"\"\"\n  active: Int!\n  \"\"\"\n  Number of task that have failed\n  \"\"\"\n  failed: Int!\n  \"\"\"\n  Date of the last execution\n  \"\"\"\n  lastExecution: Date!\n  \"\"\"\n  Date of the next wakeup\n  \"\"\"\n  nextWakeup: Date!\n}\n\ntype DiskCurrentRepartition {\n  host: Int!\n  total: Int!\n}\n\ntype DiskCurrentSpace {\n  size: Int!\n  used: Int!\n}\n\ntype DiskUsageStats {\n  currentRepartition: [DiskCurrentRepartition!]!\n  currentSpace: DiskCurrentSpace!\n}\n\ntype Query {\n  queueStats: QueueStats!\n  diskUsageState: DiskUsageStats!\n}\n",[89,671,672,680,684,689,694,698,706,711,716,720,728,732,737,741,748,752,757,761,768,772,778,783,792,797,803,808,816,821,826,834,842,850,855,860,868,876,884,889,894,902,911,920,925,930,938,946,955],{"__ignoreMap":87},[92,673,674,677],{"class":94,"line":95},[92,675,676],{"class":113},"scalar",[92,678,679],{"class":106}," Date\n",[92,681,682],{"class":94,"line":144},[92,683,148],{"emptyLinePlaceholder":147},[92,685,686],{"class":94,"line":151},[92,687,688],{"class":275},"\"\"\"\n",[92,690,691],{"class":94,"line":186},[92,692,693],{"class":275},"Define the state of the queue\n",[92,695,696],{"class":94,"line":192},[92,697,688],{"class":275},[92,699,700,703],{"class":94,"line":201},[92,701,702],{"class":113},"type",[92,704,705],{"class":106}," QueueStats {\n",[92,707,708],{"class":94,"line":216},[92,709,710],{"class":275},"  \"\"\"\n",[92,712,713],{"class":94,"line":229},[92,714,715],{"class":275},"  Number of task waiting in queue\n",[92,717,718],{"class":94,"line":240},[92,719,710],{"class":275},[92,721,722,725],{"class":94,"line":246},[92,723,724],{"class":109},"  waiting",[92,726,727],{"class":106},": Int!\n",[92,729,730],{"class":94,"line":359},[92,731,710],{"class":275},[92,733,734],{"class":94,"line":377},[92,735,736],{"class":275},"  Number of task active in queue\n",[92,738,739],{"class":94,"line":382},[92,740,710],{"class":275},[92,742,743,746],{"class":94,"line":389},[92,744,745],{"class":109},"  active",[92,747,727],{"class":106},[92,749,750],{"class":94,"line":407},[92,751,710],{"class":275},[92,753,754],{"class":94,"line":424},[92,755,756],{"class":275},"  Number of task that have failed\n",[92,758,759],{"class":94,"line":442},[92,760,710],{"class":275},[92,762,763,766],{"class":94,"line":447},[92,764,765],{"class":109},"  failed",[92,767,727],{"class":106},[92,769,770],{"class":94,"line":614},[92,771,710],{"class":275},[92,773,775],{"class":94,"line":774},20,[92,776,777],{"class":275},"  Date of the last execution\n",[92,779,781],{"class":94,"line":780},21,[92,782,710],{"class":275},[92,784,786,789],{"class":94,"line":785},22,[92,787,788],{"class":109},"  lastExecution",[92,790,791],{"class":106},": Date!\n",[92,793,795],{"class":94,"line":794},23,[92,796,710],{"class":275},[92,798,800],{"class":94,"line":799},24,[92,801,802],{"class":275},"  Date of the next wakeup\n",[92,804,806],{"class":94,"line":805},25,[92,807,710],{"class":275},[92,809,811,814],{"class":94,"line":810},26,[92,812,813],{"class":109},"  nextWakeup",[92,815,791],{"class":106},[92,817,819],{"class":94,"line":818},27,[92,820,249],{"class":106},[92,822,824],{"class":94,"line":823},28,[92,825,148],{"emptyLinePlaceholder":147},[92,827,829,831],{"class":94,"line":828},29,[92,830,702],{"class":113},[92,832,833],{"class":106}," DiskCurrentRepartition {\n",[92,835,837,840],{"class":94,"line":836},30,[92,838,839],{"class":109},"  host",[92,841,727],{"class":106},[92,843,845,848],{"class":94,"line":844},31,[92,846,847],{"class":109},"  total",[92,849,727],{"class":106},[92,851,853],{"class":94,"line":852},32,[92,854,249],{"class":106},[92,856,858],{"class":94,"line":857},33,[92,859,148],{"emptyLinePlaceholder":147},[92,861,863,865],{"class":94,"line":862},34,[92,864,702],{"class":113},[92,866,867],{"class":106}," DiskCurrentSpace {\n",[92,869,871,874],{"class":94,"line":870},35,[92,872,873],{"class":109},"  size",[92,875,727],{"class":106},[92,877,879,882],{"class":94,"line":878},36,[92,880,881],{"class":109},"  used",[92,883,727],{"class":106},[92,885,887],{"class":94,"line":886},37,[92,888,249],{"class":106},[92,890,892],{"class":94,"line":891},38,[92,893,148],{"emptyLinePlaceholder":147},[92,895,897,899],{"class":94,"line":896},39,[92,898,702],{"class":113},[92,900,901],{"class":106}," DiskUsageStats {\n",[92,903,905,908],{"class":94,"line":904},40,[92,906,907],{"class":109},"  currentRepartition",[92,909,910],{"class":106},": [DiskCurrentRepartition!]!\n",[92,912,914,917],{"class":94,"line":913},41,[92,915,916],{"class":109},"  currentSpace",[92,918,919],{"class":106},": DiskCurrentSpace!\n",[92,921,923],{"class":94,"line":922},42,[92,924,249],{"class":106},[92,926,928],{"class":94,"line":927},43,[92,929,148],{"emptyLinePlaceholder":147},[92,931,933,935],{"class":94,"line":932},44,[92,934,702],{"class":113},[92,936,937],{"class":106}," Query {\n",[92,939,941,943],{"class":94,"line":940},45,[92,942,527],{"class":109},[92,944,945],{"class":106},": QueueStats!\n",[92,947,949,952],{"class":94,"line":948},46,[92,950,951],{"class":109},"  diskUsageState",[92,953,954],{"class":106},": DiskUsageStats!\n",[92,956,958],{"class":94,"line":957},47,[92,959,249],{"class":106},[12,961,962],{},"On peut remarquer plusieurs choses:",[19,964,965,968,971,974,977],{},[22,966,967],{},"Tous les champs ont un type (String, Int, Float, Array, Date)",[22,969,970],{},"Il est possible de créer de nouveaux types simples, scalaires: par exemple champ de type Date.",[22,972,973],{},"Il est possible de créer de nouveaux types complexes, ce qui en fait nos structures.",[22,975,976],{},"Le point d'exclamation permet d'indiquer que le champ retourné ne sera jamais null (et donc le client peut compter dessus s'il le souhaite).",[22,978,979],{},"Il est possible de mettre de la documentation au niveau de chaque champ mais aussi au niveau des types.",[12,981,982,983,988],{},"Ce schéma (qui peut aussi être généré à l'aide d'annotations en Typescript, ",[25,984,987],{"href":985,"rel":986},"https:\u002F\u002Fdocs.nestjs.com\u002Fgraphql\u002Fresolvers#code-first-resolver",[59],"par exemple",")\nsert également de documentation (un peu comme swagger).",[12,990,991],{},"Il est alors possible d'utiliser des outils comme",[19,993,994,1002,1009,1017],{},[22,995,996,1001],{},[25,997,1000],{"href":998,"rel":999},"https:\u002F\u002F2fd.github.io\u002Fgraphdoc\u002F",[59],"Graph Doc",",",[22,1003,1004,1001],{},[25,1005,1008],{"href":1006,"rel":1007},"https:\u002F\u002Fdocql.io\u002F",[59],"DocQL",[22,1010,1011,1016],{},[25,1012,1015],{"href":1013,"rel":1014},"https:\u002F\u002Fgithub.com\u002FAPIs-guru\u002Fgraphql-voyager",[59],"GraphQL Voyager",", et",[22,1018,1019],{},[25,1020,1023],{"href":1021,"rel":1022},"https:\u002F\u002Fgithub.com\u002Fwayfair\u002Fdociql",[59],"Doc iQL",[12,1025,1026],{},"pour générer de la documentation mais aussi des outils comme",[19,1028,1029,1036],{},[22,1030,1031],{},[25,1032,1035],{"href":1033,"rel":1034},"https:\u002F\u002Fgithub.com\u002Fgraphql\u002Fgraphql-playground",[59],"GraphQL Playground",[22,1037,1038,1043],{},[25,1039,1042],{"href":1040,"rel":1041},"https:\u002F\u002Fgithub.com\u002Fgraphql\u002Fgraphiql",[59],"GraphiQL"," qui sont des IDE permettant de faire des requêtes (avec documentation et auto-completion).",[12,1045,1046],{},"Une fois le schéma écrit, il peut être communiqué aux équipes front (si les équipes sont séparées). Pendant que l'on développe alors le front, côté\nserveur on peut alors implémenter le schéma.",[12,1048,1049],{},"Pour implémenter le schéma ci-dessus, nous allons écrire un resolver (en Javascript pour l'exemple).",[82,1051,1053],{"className":84,"code":1052,"language":86,"meta":87,"style":87},"const resolvers = {\n  Query: {\n    queueStats() {\n      return getQueueStats();\n    },\n    async diskUsageState() {\n      return await getDiskUsageState();\n    },\n  },\n};\n",[89,1054,1055,1068,1075,1083,1094,1099,1109,1121,1125,1130],{"__ignoreMap":87},[92,1056,1057,1060,1063,1066],{"class":94,"line":95},[92,1058,1059],{"class":113},"const",[92,1061,1062],{"class":102}," resolvers",[92,1064,1065],{"class":98}," =",[92,1067,522],{"class":106},[92,1069,1070,1073],{"class":94,"line":144},[92,1071,1072],{"class":109},"  Query",[92,1074,198],{"class":106},[92,1076,1077,1080],{"class":94,"line":151},[92,1078,1079],{"class":285},"    queueStats",[92,1081,1082],{"class":106},"() {\n",[92,1084,1085,1088,1091],{"class":94,"line":186},[92,1086,1087],{"class":113},"      return",[92,1089,1090],{"class":285}," getQueueStats",[92,1092,1093],{"class":106},"();\n",[92,1095,1096],{"class":94,"line":192},[92,1097,1098],{"class":106},"    },\n",[92,1100,1101,1104,1107],{"class":94,"line":201},[92,1102,1103],{"class":113},"    async",[92,1105,1106],{"class":285}," diskUsageState",[92,1108,1082],{"class":106},[92,1110,1111,1113,1116,1119],{"class":94,"line":216},[92,1112,1087],{"class":113},[92,1114,1115],{"class":113}," await",[92,1117,1118],{"class":285}," getDiskUsageState",[92,1120,1093],{"class":106},[92,1122,1123],{"class":94,"line":229},[92,1124,1098],{"class":106},[92,1126,1127],{"class":94,"line":240},[92,1128,1129],{"class":106},"  },\n",[92,1131,1132],{"class":94,"line":246},[92,1133,1134],{"class":106},"};\n",[12,1136,1137],{},"Un resolver peut pour un champ:",[19,1139,1140,1143,1146],{},[22,1141,1142],{},"retourner une valeur",[22,1144,1145],{},"retourner une fonction qui retourne la valeur",[22,1147,1148],{},"retourner une fonction qui retourne une promesse avec la valeur",[12,1150,1151,1152,1155,1156,1158,1159,1162],{},"Ainsi imaginons que la méthode ",[89,1153,1154],{},"getQueueStats()"," retourne l'objet suivant, dans lequel il manque ",[89,1157,628],{}," et ",[89,1160,1161],{},"lastExecution"," :",[82,1164,1167],{"className":1165,"code":1166,"language":110,"meta":87,"style":87},"language-json shiki shiki-themes one-dark-pro","{\n  \"waiting\": 0,\n  \"active\": 2,\n  \"failed\": 0\n}\n",[89,1168,1169,1173,1185,1197,1207],{"__ignoreMap":87},[92,1170,1171],{"class":94,"line":95},[92,1172,189],{"class":106},[92,1174,1175,1178,1180,1183],{"class":94,"line":144},[92,1176,1177],{"class":109},"  \"waiting\"",[92,1179,207],{"class":106},[92,1181,1182],{"class":338},"0",[92,1184,213],{"class":106},[92,1186,1187,1190,1192,1195],{"class":94,"line":151},[92,1188,1189],{"class":109},"  \"active\"",[92,1191,207],{"class":106},[92,1193,1194],{"class":338},"2",[92,1196,213],{"class":106},[92,1198,1199,1202,1204],{"class":94,"line":186},[92,1200,1201],{"class":109},"  \"failed\"",[92,1203,207],{"class":106},[92,1205,1206],{"class":338},"0\n",[92,1208,1209],{"class":94,"line":192},[92,1210,249],{"class":106},[12,1212,1213],{},"Il est possible d'écrire un resolver :",[82,1215,1217],{"className":84,"code":1216,"language":86,"meta":87,"style":87},"const resolvers = {\n  Query: {\n    queueStats() {\n      return getQueueStats();\n    },\n    async diskUsageState() {\n      return await getDiskUsageState();\n    },\n  },\n  QueueStats: {\n    lastExecution(parent \u002F*: QueueStats *\u002F) {\n      const { active } = parent; \u002F\u002F Ici pour l'exemple on peut récuperer un attribut de parent.\n      return getLastExecution();\n    },\n    nextWakeup() {\n      return getNextWakup();\n    },\n  },\n};\n",[89,1218,1219,1229,1235,1241,1249,1253,1261,1271,1275,1279,1286,1303,1328,1337,1341,1348,1357,1361,1365],{"__ignoreMap":87},[92,1220,1221,1223,1225,1227],{"class":94,"line":95},[92,1222,1059],{"class":113},[92,1224,1062],{"class":102},[92,1226,1065],{"class":98},[92,1228,522],{"class":106},[92,1230,1231,1233],{"class":94,"line":144},[92,1232,1072],{"class":109},[92,1234,198],{"class":106},[92,1236,1237,1239],{"class":94,"line":151},[92,1238,1079],{"class":285},[92,1240,1082],{"class":106},[92,1242,1243,1245,1247],{"class":94,"line":186},[92,1244,1087],{"class":113},[92,1246,1090],{"class":285},[92,1248,1093],{"class":106},[92,1250,1251],{"class":94,"line":192},[92,1252,1098],{"class":106},[92,1254,1255,1257,1259],{"class":94,"line":201},[92,1256,1103],{"class":113},[92,1258,1106],{"class":285},[92,1260,1082],{"class":106},[92,1262,1263,1265,1267,1269],{"class":94,"line":216},[92,1264,1087],{"class":113},[92,1266,1115],{"class":113},[92,1268,1118],{"class":285},[92,1270,1093],{"class":106},[92,1272,1273],{"class":94,"line":229},[92,1274,1098],{"class":106},[92,1276,1277],{"class":94,"line":240},[92,1278,1129],{"class":106},[92,1280,1281,1284],{"class":94,"line":246},[92,1282,1283],{"class":109},"  QueueStats",[92,1285,198],{"class":106},[92,1287,1288,1291,1293,1297,1300],{"class":94,"line":359},[92,1289,1290],{"class":285},"    lastExecution",[92,1292,289],{"class":106},[92,1294,1296],{"class":1295},"s_ZVi","parent",[92,1298,1299],{"class":275}," \u002F*: QueueStats *\u002F",[92,1301,1302],{"class":106},") {\n",[92,1304,1305,1308,1311,1314,1317,1319,1322,1325],{"class":94,"line":377},[92,1306,1307],{"class":113},"      const",[92,1309,1310],{"class":106}," { ",[92,1312,1313],{"class":102},"active",[92,1315,1316],{"class":106}," } ",[92,1318,120],{"class":98},[92,1320,1321],{"class":109}," parent",[92,1323,1324],{"class":106},"; ",[92,1326,1327],{"class":275},"\u002F\u002F Ici pour l'exemple on peut récuperer un attribut de parent.\n",[92,1329,1330,1332,1335],{"class":94,"line":382},[92,1331,1087],{"class":113},[92,1333,1334],{"class":285}," getLastExecution",[92,1336,1093],{"class":106},[92,1338,1339],{"class":94,"line":389},[92,1340,1098],{"class":106},[92,1342,1343,1346],{"class":94,"line":407},[92,1344,1345],{"class":285},"    nextWakeup",[92,1347,1082],{"class":106},[92,1349,1350,1352,1355],{"class":94,"line":424},[92,1351,1087],{"class":113},[92,1353,1354],{"class":285}," getNextWakup",[92,1356,1093],{"class":106},[92,1358,1359],{"class":94,"line":442},[92,1360,1098],{"class":106},[92,1362,1363],{"class":94,"line":447},[92,1364,1129],{"class":106},[92,1366,1367],{"class":94,"line":614},[92,1368,1134],{"class":106},[12,1370,1371],{},"Il est alors possible de créer son schéma en pensant à comment le client va intéroger ce dernier, et lors de l'implémentation ajouter des structures\ncomplexes qui sont issues du calcul synchrone ou asynchrone des données.",[495,1373,1375],{"id":1374},"query-nullable","Query - Nullable",[12,1377,1378],{},"On a vu précédement qu'on pouvait utiliser le point d'exclamation pour indiquer qu'un champ ne sera jamais NULL. Cela peut avoir des avantages pour le client\nmais cela a aussi de grandes implications côté serveur.",[12,1380,1381],{},"Par défaut pour GraphQL, tous les champs sont nullable par défaut. En effet, si un resolver n'arrive pas à récupérer la donnée (erreur côté serveur, back HS, problème\nde base de données, problème réseau, droits d'accès différents selon les champs ...), le serveur pourra retourner NULL à la place de la valeur (et une erreur en\nparallèle du json). Cela permet au client une vue partielle même si certains services ne sont pas disponibles.",[12,1383,1384],{},"Si le champ ne peut pas être null, alors le json ne pourra pas du tout être envoyé et c'est la requête complète qui est en ereur.",[12,1386,1387],{},"C'est pour cela qu'en GraphQL chaque champ peut, par défaut, obtenir la valeur null.",[12,1389,1390],{},"Lors de la conception d'un schéma GraphQL, il faut utiliser la possibilité de rendre le champ non nullable avec réflexion et uniquement pour les champs dont on\nsouhaite garantir la non nullité.",[495,1392,1394],{"id":1393},"query-ajout-darguments","Query - Ajout d'arguments",[82,1396,1398],{"className":507,"code":1397,"language":509,"meta":87,"style":87},"query HeroNameAndFriends($episode: Episode) {\n  hero(episode: $episode) {\n    name\n    friends {\n      name\n    }\n  }\n}\n",[89,1399,1400,1415,1431,1436,1443,1448,1452,1456],{"__ignoreMap":87},[92,1401,1402,1404,1407,1409,1412],{"class":94,"line":95},[92,1403,516],{"class":113},[92,1405,1406],{"class":285}," HeroNameAndFriends",[92,1408,289],{"class":106},[92,1410,1411],{"class":1295},"$episode",[92,1413,1414],{"class":106},": Episode) {\n",[92,1416,1417,1420,1422,1425,1427,1429],{"class":94,"line":144},[92,1418,1419],{"class":109},"  hero",[92,1421,289],{"class":106},[92,1423,1424],{"class":1295},"episode",[92,1426,207],{"class":106},[92,1428,1411],{"class":109},[92,1430,1302],{"class":106},[92,1432,1433],{"class":94,"line":151},[92,1434,1435],{"class":109},"    name\n",[92,1437,1438,1441],{"class":94,"line":186},[92,1439,1440],{"class":109},"    friends",[92,1442,522],{"class":106},[92,1444,1445],{"class":94,"line":192},[92,1446,1447],{"class":109},"      name\n",[92,1449,1450],{"class":94,"line":201},[92,1451,344],{"class":106},[92,1453,1454],{"class":94,"line":216},[92,1455,243],{"class":106},[92,1457,1458],{"class":94,"line":229},[92,1459,249],{"class":106},[12,1461,1462],{},"Il est possible en GraphQL de définir certain champs comme ayant des paramètres. Ils seront alors passés au resolver. Il est également possible d'avoir\ndes paramètres à différents niveaux du schéma (pas seulement au niveau le plus haut).",[12,1464,1465],{},"Le passage de paramètres permet d'écrire son opération une fois et ensuite de l'appeler avec des paramètres. C'est important de définir les saisie utilisateurs\ncomme des paramètres pour éviter les injections GraphQL (comme en SQL, ou autre).",[12,1467,1468,1472],{},[1469,1470,1471],"strong",{},"Règle n°1",": Ne jamais faire confiance à l'utilisateur.",[495,1474,1476],{"id":1475},"query-création-dalias","Query - Création d'alias",[12,1478,1479],{},"Si on souhaite récupérer plusieurs valeurs d'un attribut en fonction de ses paramètres, il est possible de le demander plusieurs fois et de lui associer\nun alias.",[82,1481,1483],{"className":507,"code":1482,"language":509,"meta":87,"style":87},"query aliasQuery {\n  empireHero: hero(episode: EMPIRE) {\n    name\n  }\n  jediHero: hero(episode: JEDI) {\n    name\n  }\n}\n",[89,1484,1485,1494,1516,1520,1524,1544,1548,1552],{"__ignoreMap":87},[92,1486,1487,1489,1492],{"class":94,"line":95},[92,1488,516],{"class":113},[92,1490,1491],{"class":285}," aliasQuery",[92,1493,522],{"class":106},[92,1495,1496,1499,1501,1504,1506,1508,1511,1514],{"class":94,"line":144},[92,1497,1498],{"class":126},"  empireHero",[92,1500,207],{"class":106},[92,1502,1503],{"class":109},"hero",[92,1505,289],{"class":106},[92,1507,1424],{"class":1295},[92,1509,1510],{"class":106},":",[92,1512,1513],{"class":338}," EMPIRE",[92,1515,1302],{"class":106},[92,1517,1518],{"class":94,"line":151},[92,1519,1435],{"class":109},[92,1521,1522],{"class":94,"line":186},[92,1523,243],{"class":106},[92,1525,1526,1529,1531,1533,1535,1537,1539,1542],{"class":94,"line":192},[92,1527,1528],{"class":126},"  jediHero",[92,1530,207],{"class":106},[92,1532,1503],{"class":109},[92,1534,289],{"class":106},[92,1536,1424],{"class":1295},[92,1538,1510],{"class":106},[92,1540,1541],{"class":338}," JEDI",[92,1543,1302],{"class":106},[92,1545,1546],{"class":94,"line":201},[92,1547,1435],{"class":109},[92,1549,1550],{"class":94,"line":216},[92,1551,243],{"class":106},[92,1553,1554],{"class":94,"line":229},[92,1555,249],{"class":106},[12,1557,1558,1559,1158,1562,1565,1566,1568],{},"Dans le JSON résultant, on retrouve alors les deux attributs ",[89,1560,1561],{},"empireHero",[89,1563,1564],{},"jediHero"," qui sont tous les deux issus du membre ",[89,1567,1503],{}," avec un paramètre\ndifférent. Cela peut aussi être utilisé pour simplement renommer un champ.",[1570,1571,1573],"h4",{"id":1572},"query-fragment","Query - Fragment",[12,1575,1576],{},"Les fragments permettent de factoriser et de créer des morceaux de requêtes réutilisables.",[82,1578,1580],{"className":507,"code":1579,"language":509,"meta":87,"style":87},"# Dans le fichier FragmentJob.graphql\n\nfragment FragmentJob on Job {\n  id\n  state\n  failedReason\n  data {\n    host\n  }\n}\n\n# Dans le fichier QueueTasks.graphql\n\n#import \".\u002FFragmentJob.graphql\"\n\nquery QueueTasks($state: [String!]) {\n  queue(state: $state) {\n    ...FragmentJob\n  }\n}\n",[89,1581,1582,1587,1591,1605,1610,1615,1620,1627,1632,1636,1640,1644,1649,1653,1658,1662,1677,1693,1701,1705],{"__ignoreMap":87},[92,1583,1584],{"class":94,"line":95},[92,1585,1586],{"class":275},"# Dans le fichier FragmentJob.graphql\n",[92,1588,1589],{"class":94,"line":144},[92,1590,148],{"emptyLinePlaceholder":147},[92,1592,1593,1596,1599,1602],{"class":94,"line":151},[92,1594,1595],{"class":113},"fragment",[92,1597,1598],{"class":106}," FragmentJob ",[92,1600,1601],{"class":113},"on",[92,1603,1604],{"class":106}," Job {\n",[92,1606,1607],{"class":94,"line":186},[92,1608,1609],{"class":109},"  id\n",[92,1611,1612],{"class":94,"line":192},[92,1613,1614],{"class":109},"  state\n",[92,1616,1617],{"class":94,"line":201},[92,1618,1619],{"class":109},"  failedReason\n",[92,1621,1622,1625],{"class":94,"line":216},[92,1623,1624],{"class":109},"  data",[92,1626,522],{"class":106},[92,1628,1629],{"class":94,"line":229},[92,1630,1631],{"class":109},"    host\n",[92,1633,1634],{"class":94,"line":240},[92,1635,243],{"class":106},[92,1637,1638],{"class":94,"line":246},[92,1639,249],{"class":106},[92,1641,1642],{"class":94,"line":359},[92,1643,148],{"emptyLinePlaceholder":147},[92,1645,1646],{"class":94,"line":377},[92,1647,1648],{"class":275},"# Dans le fichier QueueTasks.graphql\n",[92,1650,1651],{"class":94,"line":382},[92,1652,148],{"emptyLinePlaceholder":147},[92,1654,1655],{"class":94,"line":389},[92,1656,1657],{"class":275},"#import \".\u002FFragmentJob.graphql\"\n",[92,1659,1660],{"class":94,"line":407},[92,1661,148],{"emptyLinePlaceholder":147},[92,1663,1664,1666,1669,1671,1674],{"class":94,"line":424},[92,1665,516],{"class":113},[92,1667,1668],{"class":285}," QueueTasks",[92,1670,289],{"class":106},[92,1672,1673],{"class":1295},"$state",[92,1675,1676],{"class":106},": [String!]) {\n",[92,1678,1679,1682,1684,1687,1689,1691],{"class":94,"line":442},[92,1680,1681],{"class":109},"  queue",[92,1683,289],{"class":106},[92,1685,1686],{"class":1295},"state",[92,1688,207],{"class":106},[92,1690,1673],{"class":109},[92,1692,1302],{"class":106},[92,1694,1695,1698],{"class":94,"line":447},[92,1696,1697],{"class":106},"    ...",[92,1699,1700],{"class":109},"FragmentJob\n",[92,1702,1703],{"class":94,"line":614},[92,1704,243],{"class":106},[92,1706,1707],{"class":94,"line":774},[92,1708,249],{"class":106},[12,1710,1711,1712,1715],{},"Le Fragment ",[89,1713,1714],{},"FragmentJob"," peut alors être réutilisé dans différentes requêtes, voir même plusieurs fois dans la même requête.",[495,1717,1719],{"id":1718},"query-gestion-de-version","Query - Gestion de version",[12,1721,1722],{},"J'en parlais dans les articles précédents, il existe plusieurs manières de versionner une API. GraphQL n'y échappe pas. On peut:",[19,1724,1725,1728],{},[22,1726,1727],{},"ajouter un numéro de version dans le path de l'url, dans le nom de l'hôte, dans un header http.",[22,1729,1730],{},"ou décider de gérer une seule API rétro-compatible où on décommissionne au fur et à mesure les champs.",[12,1732,1733,1734,1739,1740,1743],{},"Les créateurs de GraphQL partagent ",[25,1735,1738],{"href":1736,"rel":1737},"https:\u002F\u002Fgraphql.org\u002Flearn\u002Fbest-practices\u002F#versioning",[59],"leur opinions",". Comme c'est le client qui décide\ndes champs qu'il souhaite rappatrier, toute évolution du schéma ajoutant de nouveaux attributs ne pose aucun problème et ne surchargera pas\nplus le client.\nLes suppressions doivent passer par une phase d'obsolescence ou les attributs sont marqués avec l'annotation ",[89,1741,1742],{},"@deprecated"," et leur décommissionnement\nautomatique.",[12,1745,1746,1747,1752],{},"Afin de pouvoir facilement décommissionner des attributs, le mieux est de savoir quels attributs sont utilisés ou non. La librairie Apollo permet de\nse connecter au ",[25,1748,1751],{"href":1749,"rel":1750},"https:\u002F\u002Fstudio.apollographql.com\u002F",[59],"studio d'appolo"," et de visualiser l'utilisation des attributs. Savoir qu'un attribut n'est pas\nutilisé permet de le décommissionner ou de le modifier. Bien sûr et malheureusement cette partie n'est pas open source. Je n'ai pas encore trouvé de\ndashboard OpenSource permettant d'analyser l'utilisation d'un champ.",[12,1754,1755],{},"Ce qui est important c'est d'avoir une politique de versionning.",[495,1757,1759],{"id":1758},"query-pagination","Query - Pagination",[12,1761,1762],{},"GraphQL n'a pas défini de règle concernant la pagination car il y a plein de manières de la gérer. Pour des listes contenant peu d'éléments, il n'y a\npar exemple pas lieu de gérer la pagination.",[12,1764,1765],{},"Quand on envisage la pagination, on peut en faire une par page avec un nombre d'éléments à passer (skip), ou une basée sur l'id du premier élément\nà afficher.",[12,1767,1768],{},"Il existe également des patterns que l'on peut utiliser pour ne pas réinventer une nouvelle facon de faire.",[12,1770,1771,1772,1775,1776,1779],{},"Par exemple l'un de ses patterns s'appele ",[89,1773,1774],{},"Connections"," et des librairies comme ",[89,1777,1778],{},"Relay"," savent comment gérer automatiquement ce pattern.",[12,1781,1782,1783,1785],{},"Dans le pattern ",[89,1784,1774],{},", nous retrouvons les notions:",[19,1787,1788,1799],{},[22,1789,1790,1791],{},"En entrée",[19,1792,1793,1796],{},[22,1794,1795],{},"nombre d'éléments à récupérer",[22,1797,1798],{},"le curseur à partir duquel retourner les données",[22,1800,1801,1802],{},"En sortie",[19,1803,1804,1807,1810],{},[22,1805,1806],{},"Le nombre d'éléments total",[22,1808,1809],{},"Les informations sur la page comme le prochain curseur et si on a une page suivante",[22,1811,1812],{},"Les différents éléments de la page (avec le curseur associé)",[82,1814,1816],{"className":507,"code":1815,"language":509,"meta":87,"style":87},"{\n  hero {\n    name\n    friendsConnection(first: 2, after: \"Y3Vyc29yMQ==\") {\n      totalCount\n      edges {\n        node {\n          name\n        }\n        cursor\n      }\n      pageInfo {\n        endCursor\n        hasNextPage\n      }\n    }\n  }\n}\n",[89,1817,1818,1822,1828,1832,1858,1863,1870,1877,1882,1887,1892,1897,1904,1909,1914,1918,1922,1926],{"__ignoreMap":87},[92,1819,1820],{"class":94,"line":95},[92,1821,189],{"class":106},[92,1823,1824,1826],{"class":94,"line":144},[92,1825,1419],{"class":109},[92,1827,522],{"class":106},[92,1829,1830],{"class":94,"line":151},[92,1831,1435],{"class":109},[92,1833,1834,1837,1839,1842,1844,1846,1848,1851,1853,1856],{"class":94,"line":186},[92,1835,1836],{"class":109},"    friendsConnection",[92,1838,289],{"class":106},[92,1840,1841],{"class":1295},"first",[92,1843,207],{"class":106},[92,1845,1194],{"class":338},[92,1847,130],{"class":106},[92,1849,1850],{"class":1295},"after",[92,1852,207],{"class":106},[92,1854,1855],{"class":126},"\"Y3Vyc29yMQ==\"",[92,1857,1302],{"class":106},[92,1859,1860],{"class":94,"line":192},[92,1861,1862],{"class":109},"      totalCount\n",[92,1864,1865,1868],{"class":94,"line":201},[92,1866,1867],{"class":109},"      edges",[92,1869,522],{"class":106},[92,1871,1872,1875],{"class":94,"line":216},[92,1873,1874],{"class":109},"        node",[92,1876,522],{"class":106},[92,1878,1879],{"class":94,"line":229},[92,1880,1881],{"class":109},"          name\n",[92,1883,1884],{"class":94,"line":240},[92,1885,1886],{"class":106},"        }\n",[92,1888,1889],{"class":94,"line":246},[92,1890,1891],{"class":109},"        cursor\n",[92,1893,1894],{"class":94,"line":359},[92,1895,1896],{"class":106},"      }\n",[92,1898,1899,1902],{"class":94,"line":377},[92,1900,1901],{"class":109},"      pageInfo",[92,1903,522],{"class":106},[92,1905,1906],{"class":94,"line":382},[92,1907,1908],{"class":109},"        endCursor\n",[92,1910,1911],{"class":94,"line":389},[92,1912,1913],{"class":109},"        hasNextPage\n",[92,1915,1916],{"class":94,"line":407},[92,1917,1896],{"class":106},[92,1919,1920],{"class":94,"line":424},[92,1921,344],{"class":106},[92,1923,1924],{"class":94,"line":442},[92,1925,243],{"class":106},[92,1927,1928],{"class":94,"line":447},[92,1929,249],{"class":106},[12,1931,1932,1933],{},"On peut retrouver la spécification de ce modèle de pagination sur le site de ",[25,1934,1937],{"href":1935,"rel":1936},"https:\u002F\u002Frelay.dev\u002Fgraphql\u002Fconnections.htm",[59],"relay.dev",[495,1939,1941],{"id":1940},"query-cache-control","Query - Cache - Control",[12,1943,1944],{},"Contrairement au REST où on peut peut utiliser les header HTTP, pour contrôler le cache, en GraphQL une requête peut avoir des limites d'âge différentes sur les différentes resources\nappelées. Il faut donc trouver d'autres moyens de gérer le cache.",[12,1946,1947],{},"C'est pour moi un des points les plus gênants de GraphQL.",[12,1949,1950,1951,61],{},"Sur le serveur, il y a plusieurs manières complémentaires (à différentes fins) de gérer le cache. Nous allons nous concentrer sur la librairie ",[89,1952,1953],{},"apollo-server",[12,1955,1956,1957,1960,1961,1964],{},"La 1ère manière s'apparentant au cache-control des requêtes HTTP peut être utilisée avec la directive ",[89,1958,1959],{},"@cacheControl"," ou au niveau du resolver. Elle ne permet pas de cacher directement la donnée\nmais de donner une indication au client sur combien de temps cette donnée peut-être cachée. Si la librairie utilisée côté client est également ",[89,1962,1963],{},"apollo"," elle utilisera alors ces indications pour\ngérer le cache de la donnée (et donc ne pas ré-effectuer la requête.)",[82,1966,1968],{"className":507,"code":1967,"language":509,"meta":87,"style":87},"scalar Date\n\n\"\"\"\nDefine the state of the queue\n\"\"\"\ntype QueueStats @cacheControl(maxAge: 60) {\n  \"\"\"\n  Number of task waiting in queue\n  \"\"\"\n  waiting: Int!\n  \"\"\"\n  Number of task active in queue\n  \"\"\"\n  active: Int!\n  \"\"\"\n  Number of task that have failed\n  \"\"\"\n  failed: Int!\n  \"\"\"\n  Date of the last execution\n  \"\"\"\n  lastExecution: Date! @cacheControl(maxAge: 3600)\n  \"\"\"\n  Date of the next wakeup\n  \"\"\"\n  nextWakeup: Date! @cacheControl(maxAge: 3600)\n}\n",[89,1969,1970,1976,1980,1984,1988,1992,2013,2017,2021,2025,2031,2035,2039,2043,2049,2053,2057,2061,2067,2071,2075,2079,2100,2104,2108,2112,2130],{"__ignoreMap":87},[92,1971,1972,1974],{"class":94,"line":95},[92,1973,676],{"class":113},[92,1975,679],{"class":106},[92,1977,1978],{"class":94,"line":144},[92,1979,148],{"emptyLinePlaceholder":147},[92,1981,1982],{"class":94,"line":151},[92,1983,688],{"class":275},[92,1985,1986],{"class":94,"line":186},[92,1987,693],{"class":275},[92,1989,1990],{"class":94,"line":192},[92,1991,688],{"class":275},[92,1993,1994,1996,1999,2001,2003,2006,2008,2011],{"class":94,"line":201},[92,1995,702],{"class":113},[92,1997,1998],{"class":106}," QueueStats ",[92,2000,1959],{"class":285},[92,2002,289],{"class":106},[92,2004,2005],{"class":1295},"maxAge",[92,2007,207],{"class":106},[92,2009,2010],{"class":338},"60",[92,2012,1302],{"class":106},[92,2014,2015],{"class":94,"line":216},[92,2016,710],{"class":275},[92,2018,2019],{"class":94,"line":229},[92,2020,715],{"class":275},[92,2022,2023],{"class":94,"line":240},[92,2024,710],{"class":275},[92,2026,2027,2029],{"class":94,"line":246},[92,2028,724],{"class":109},[92,2030,727],{"class":106},[92,2032,2033],{"class":94,"line":359},[92,2034,710],{"class":275},[92,2036,2037],{"class":94,"line":377},[92,2038,736],{"class":275},[92,2040,2041],{"class":94,"line":382},[92,2042,710],{"class":275},[92,2044,2045,2047],{"class":94,"line":389},[92,2046,745],{"class":109},[92,2048,727],{"class":106},[92,2050,2051],{"class":94,"line":407},[92,2052,710],{"class":275},[92,2054,2055],{"class":94,"line":424},[92,2056,756],{"class":275},[92,2058,2059],{"class":94,"line":442},[92,2060,710],{"class":275},[92,2062,2063,2065],{"class":94,"line":447},[92,2064,765],{"class":109},[92,2066,727],{"class":106},[92,2068,2069],{"class":94,"line":614},[92,2070,710],{"class":275},[92,2072,2073],{"class":94,"line":774},[92,2074,777],{"class":275},[92,2076,2077],{"class":94,"line":780},[92,2078,710],{"class":275},[92,2080,2081,2083,2086,2088,2090,2092,2094,2097],{"class":94,"line":785},[92,2082,788],{"class":109},[92,2084,2085],{"class":106},": Date! ",[92,2087,1959],{"class":285},[92,2089,289],{"class":106},[92,2091,2005],{"class":1295},[92,2093,207],{"class":106},[92,2095,2096],{"class":338},"3600",[92,2098,2099],{"class":106},")\n",[92,2101,2102],{"class":94,"line":794},[92,2103,710],{"class":275},[92,2105,2106],{"class":94,"line":799},[92,2107,802],{"class":275},[92,2109,2110],{"class":94,"line":805},[92,2111,710],{"class":275},[92,2113,2114,2116,2118,2120,2122,2124,2126,2128],{"class":94,"line":810},[92,2115,813],{"class":109},[92,2117,2085],{"class":106},[92,2119,1959],{"class":285},[92,2121,289],{"class":106},[92,2123,2005],{"class":1295},[92,2125,207],{"class":106},[92,2127,2096],{"class":338},[92,2129,2099],{"class":106},[92,2131,2132],{"class":94,"line":818},[92,2133,249],{"class":106},[12,2135,2136,2137,2142],{},"Je vous invite à lire la ",[25,2138,2141],{"href":2139,"rel":2140},"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Fapollo-server\u002Fperformance\u002Fcaching\u002F",[59],"documentation d'apollo"," qui contient énormément d'informations. Cette méthode permet d'ajouter\ndes header http pour cacher les requêtes dans un CDN intérmédiaire, ou dans une base redis attachée au serveur, ou dans le cache navigateur, voir également au niveau même du client.",[495,2144,2146],{"id":2145},"query-cache-dataloader","Query - Cache - DataLoader",[12,2148,2149,2150,2155],{},"La 2nd méthode de cache consiste à l'utilisation de ",[25,2151,2154],{"href":2152,"rel":2153},"https:\u002F\u002Fgithub.com\u002Fgraphql\u002Fdataloader",[59],"DataLoader",". L'ajout d'un dataloader a pour but de cacher le temps d'une requête les différents éléments pour éviter de multiplier les appels.\nLe but n'étant pas de réellement faire du cache mais plutôt de pouvoir traiter une requête batch le plus rapidement possible.",[12,2157,2158],{},"Imaginons par exemple la requête suivante :",[82,2160,2162],{"className":507,"code":2161,"language":509,"meta":87,"style":87},"query TestDataLoader {\n  backups {\n    id\n    startDate\n    endDate\n    user {\n      firstName\n      lastName\n    }\n  }\n}\n",[89,2163,2164,2173,2180,2185,2190,2195,2202,2207,2212,2216,2220],{"__ignoreMap":87},[92,2165,2166,2168,2171],{"class":94,"line":95},[92,2167,516],{"class":113},[92,2169,2170],{"class":285}," TestDataLoader",[92,2172,522],{"class":106},[92,2174,2175,2178],{"class":94,"line":144},[92,2176,2177],{"class":109},"  backups",[92,2179,522],{"class":106},[92,2181,2182],{"class":94,"line":151},[92,2183,2184],{"class":109},"    id\n",[92,2186,2187],{"class":94,"line":186},[92,2188,2189],{"class":109},"    startDate\n",[92,2191,2192],{"class":94,"line":192},[92,2193,2194],{"class":109},"    endDate\n",[92,2196,2197,2200],{"class":94,"line":201},[92,2198,2199],{"class":109},"    user",[92,2201,522],{"class":106},[92,2203,2204],{"class":94,"line":216},[92,2205,2206],{"class":109},"      firstName\n",[92,2208,2209],{"class":94,"line":229},[92,2210,2211],{"class":109},"      lastName\n",[92,2213,2214],{"class":94,"line":240},[92,2215,344],{"class":106},[92,2217,2218],{"class":94,"line":246},[92,2219,243],{"class":106},[92,2221,2222],{"class":94,"line":359},[92,2223,249],{"class":106},[12,2225,2226],{},"Dans le cas présent, on souhaite récupérer les backups, mais également pour chaque backup récupérer l'utilisateur. Si l'utilisateur est le même pour chaque backup, on se retouve alors à charger\nl'utilisateur plusieurs fois. Le fait d'utiliser un dataloader permettra dans le cas présent de ne les charger qu'une fois.",[12,2228,2229,2230,2235],{},"Pour utiliser le système de data loader, on peut utiliser la bibliothèque du même nom: [",[25,2231,2234],{"href":2232,"rel":2233},"https:\u002F\u002Fgithub.com\u002Fgraphql\u002Fdataloader%5D(Github",[59],"https:\u002F\u002Fgithub.com\u002Fgraphql\u002Fdataloader](Github",": graphql\u002Fdataloader). Il nous faut alors créer un dataloader pour\nun type d'objet.",[82,2237,2239],{"className":84,"code":2238,"language":86,"meta":87,"style":87},"import DataLoader from \"dataloader\";\n\nconst userLoader = new DataLoader((keys) => service.getUsers(keys));\n",[89,2240,2241,2258,2262],{"__ignoreMap":87},[92,2242,2243,2246,2249,2252,2255],{"class":94,"line":95},[92,2244,2245],{"class":113},"import",[92,2247,2248],{"class":109}," DataLoader",[92,2250,2251],{"class":113}," from",[92,2253,2254],{"class":126}," \"dataloader\"",[92,2256,2257],{"class":106},";\n",[92,2259,2260],{"class":94,"line":144},[92,2261,148],{"emptyLinePlaceholder":147},[92,2263,2264,2266,2269,2271,2274,2276,2279,2282,2285,2288,2291,2293,2296,2298,2300],{"class":94,"line":151},[92,2265,1059],{"class":113},[92,2267,2268],{"class":102}," userLoader",[92,2270,1065],{"class":98},[92,2272,2273],{"class":113}," new",[92,2275,2248],{"class":285},[92,2277,2278],{"class":106},"((",[92,2280,2281],{"class":1295},"keys",[92,2283,2284],{"class":106},") ",[92,2286,2287],{"class":113},"=>",[92,2289,2290],{"class":102}," service",[92,2292,61],{"class":106},[92,2294,2295],{"class":285},"getUsers",[92,2297,289],{"class":106},[92,2299,2281],{"class":109},[92,2301,2302],{"class":106},"));\n",[12,2304,2305],{},"Ensuite dans les resolvers, au lieu de récupérer les utilisateurs directement à partir du service, on utilise le data loader pour récupérer la valeur :",[82,2307,2309],{"className":84,"code":2308,"language":86,"meta":87,"style":87},"const resolvers = {\n  async backups() {\n    return service.find();\n  },\n  Backup: {\n    async user(ctx, { id }) {\n      return await userLoader.load(id);\n    },\n  },\n};\n",[89,2310,2311,2321,2331,2345,2349,2356,2377,2397,2401,2405],{"__ignoreMap":87},[92,2312,2313,2315,2317,2319],{"class":94,"line":95},[92,2314,1059],{"class":113},[92,2316,1062],{"class":102},[92,2318,1065],{"class":98},[92,2320,522],{"class":106},[92,2322,2323,2326,2329],{"class":94,"line":144},[92,2324,2325],{"class":113},"  async",[92,2327,2328],{"class":285}," backups",[92,2330,1082],{"class":106},[92,2332,2333,2336,2338,2340,2343],{"class":94,"line":151},[92,2334,2335],{"class":113},"    return",[92,2337,2290],{"class":102},[92,2339,61],{"class":106},[92,2341,2342],{"class":285},"find",[92,2344,1093],{"class":106},[92,2346,2347],{"class":94,"line":186},[92,2348,1129],{"class":106},[92,2350,2351,2354],{"class":94,"line":192},[92,2352,2353],{"class":109},"  Backup",[92,2355,198],{"class":106},[92,2357,2358,2360,2363,2365,2368,2371,2374],{"class":94,"line":201},[92,2359,1103],{"class":113},[92,2361,2362],{"class":285}," user",[92,2364,289],{"class":106},[92,2366,2367],{"class":1295},"ctx",[92,2369,2370],{"class":106},", { ",[92,2372,2373],{"class":1295},"id",[92,2375,2376],{"class":106}," }) {\n",[92,2378,2379,2381,2383,2385,2387,2390,2392,2394],{"class":94,"line":216},[92,2380,1087],{"class":113},[92,2382,1115],{"class":113},[92,2384,2268],{"class":102},[92,2386,61],{"class":106},[92,2388,2389],{"class":285},"load",[92,2391,289],{"class":106},[92,2393,2373],{"class":109},[92,2395,2396],{"class":106},");\n",[92,2398,2399],{"class":94,"line":229},[92,2400,1098],{"class":106},[92,2402,2403],{"class":94,"line":240},[92,2404,1129],{"class":106},[92,2406,2407],{"class":94,"line":246},[92,2408,1134],{"class":106},[12,2410,2411,2414],{},[89,2412,2413],{},"Dataloader"," n'est par contre pas fait pour être utilisé en tant que cache applicatif et ne remplace donc pas un memcached ou un redis.",[495,2416,2418],{"id":2417},"query-batch","Query - Batch",[12,2420,2421,2423],{},[89,2422,490],{}," côté client propose de pouvoir faire du batching de requêtes. Cela consiste à attendre un léger laps de temps pour regrouper en une seule requête plusieurs requêtes.",[12,2425,2426],{},"Attention néanmoins, mettre en place le batching de requêtes implique:",[19,2428,2429,2432,2435],{},[22,2430,2431],{},"qu'on attend (pas très longtemps) pour envoyer les requêtes",[22,2433,2434],{},"qu'on attend l'aggrégat des réponses avant de les recevoir",[22,2436,2437],{},"qu'on ne bénéficie pas du multiplexing des requêtes de HTTP\u002F2.",[12,2439,2440],{},"Il est alors conseillé d'abord de faire d'autres types d'optimisations (comme utiliser les requêtes persistentes, un cache dans un CDN des réponses, utiliser HTTP\u002F2 jusqu'au NodeJS)\navant de mettre en place le système de batch.",[12,2442,2443,2444,61],{},"Si vous voulez le mettre tout de même en place, vous pouvez regarder la ",[25,2445,2448],{"href":2446,"rel":2447},"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Flink\u002Flinks\u002Fbatch-http\u002F",[59],"documentation d'Apollo sur ce sujet",[495,2450,2452],{"id":2451},"query-transfert-réseau","Query - Transfert réseau",[12,2454,2455],{},"Pour une API Web, les requêtes et les réponses sont transférées en utilisant le protocole HTTP. Pour améliorer les performances il est\npossible d'utiliser plusieurs méthodes:",[19,2457,2458,2461,2468,2471],{},[22,2459,2460],{},"Comme en REST, activer la compression GZIP coté serveur, permet de réduire les flux réseaux",[22,2462,2463,2464],{},"Utiliser les requêtes persistentes (stocké coté serveur) : ",[25,2465,2466],{"href":2466,"rel":2467},"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Fapollo-server\u002Fperformance\u002Fapq\u002F",[59],[22,2469,2470],{},"Utiliser les fragments pour réutiliser certaines parties des requêtes qui peuvent se répéter.",[22,2472,2473],{},"Utiliser HTTP\u002F2 et le multiplexing des requêtes. Faire une seule grosse requête peut être contre-productif avec HTTP\u002F2. En effet il faut attendre la fin de la résolution de l'ensemble des resolvers\ncôté serveur avant d'avoir la réponse. Faire plusieurs requêtes peut permettre d'avoir certaines parties de la requête plus vite. De plus HTTP\u002F2 permet de mieux paralléliser les requêtes. Il faut donc\ntrouver le bon nombre de requêtes à executer pour avoir les meilleurs performances.",[495,2475,2477],{"id":2476},"mutation","Mutation",[12,2479,2480,2481,61],{},"Nous avons énormément parlé de toute la partie requête de GraphQL. GraphQL permet également de faire des modifications via des ",[89,2482,2483],{},"mutations",[12,2485,2486],{},"Une mutation est similaire à une requête :",[82,2488,2490],{"className":507,"code":2489,"language":509,"meta":87,"style":87},"mutation CreateBackup($backup: CreateBackupInput!) {\n  createBackup(backup: $backup) {\n    statusCode\n    message\n    error\n    errorCode\n    backup {\n      id\n      state\n      user {\n        id\n      }\n    }\n  }\n",[89,2491,2492,2507,2523,2528,2533,2538,2543,2550,2555,2560,2567,2572,2576,2580],{"__ignoreMap":87},[92,2493,2494,2496,2499,2501,2504],{"class":94,"line":95},[92,2495,2476],{"class":113},[92,2497,2498],{"class":285}," CreateBackup",[92,2500,289],{"class":106},[92,2502,2503],{"class":1295},"$backup",[92,2505,2506],{"class":106},": CreateBackupInput!) {\n",[92,2508,2509,2512,2514,2517,2519,2521],{"class":94,"line":144},[92,2510,2511],{"class":109},"  createBackup",[92,2513,289],{"class":106},[92,2515,2516],{"class":1295},"backup",[92,2518,207],{"class":106},[92,2520,2503],{"class":109},[92,2522,1302],{"class":106},[92,2524,2525],{"class":94,"line":151},[92,2526,2527],{"class":109},"    statusCode\n",[92,2529,2530],{"class":94,"line":186},[92,2531,2532],{"class":109},"    message\n",[92,2534,2535],{"class":94,"line":192},[92,2536,2537],{"class":109},"    error\n",[92,2539,2540],{"class":94,"line":201},[92,2541,2542],{"class":109},"    errorCode\n",[92,2544,2545,2548],{"class":94,"line":216},[92,2546,2547],{"class":109},"    backup",[92,2549,522],{"class":106},[92,2551,2552],{"class":94,"line":229},[92,2553,2554],{"class":109},"      id\n",[92,2556,2557],{"class":94,"line":240},[92,2558,2559],{"class":109},"      state\n",[92,2561,2562,2565],{"class":94,"line":246},[92,2563,2564],{"class":109},"      user",[92,2566,522],{"class":106},[92,2568,2569],{"class":94,"line":359},[92,2570,2571],{"class":109},"        id\n",[92,2573,2574],{"class":94,"line":377},[92,2575,1896],{"class":106},[92,2577,2578],{"class":94,"line":382},[92,2579,344],{"class":106},[92,2581,2582],{"class":94,"line":389},[92,2583,243],{"class":106},[12,2585,2586],{},"Nous retrouvons la partie input, et la partie query (sur la réponse). Alors que généralement sur query on va retrouver des types primitifs en paramètres, on va plutôt retrouver\nen paramètre d'une mutation un objet de type Input. Sur le résultat le fonctionnement est lui le même que sur une Query.",[12,2588,2589],{},"Nous pouvons d'ailleurs utiliser les mêmes resolvers sur les réponses que dans les queries (et ainsi récupérer l'utilisateur de la backup de la même manière).",[12,2591,2592],{},"En bonne pratique, ce que nous pouvons retrouver sur les mutations sont:",[19,2594,2595,2598,2609,2614,2627],{},[22,2596,2597],{},"Ne pas renvoyer l'objet créé\u002Fmodifié directement. Renvoyer un objet permettant à l'utilisateur de récupérer l'erreur fonctionnelle ou l'objet selon le cas. Cela permettra de faire\névoluer plus facilement le résultat si nécessaire.",[22,2599,2600,2601,2604,2605,2608],{},"De la même manière, ",[1469,2602,2603],{},"même"," si la mutation a besoin de très peu de paramètres en entrée, il vaut mieux créer un objet de type ",[89,2606,2607],{},"input"," afin de passer les paramètres. Ceci également\nafin de pouvoir évoluer facilement avec l'API au fur et à mesure des évolutions.",[22,2610,2611,2612,61],{},"Eviter pour le paramètre principal ou la sortie principale de réutiliser un objet. Il vaut mieux en créer un propre à la ",[89,2613,2476],{},[22,2615,2616,2617,2619,2620,2623,2624,2626],{},"Contrairement à REST, les ",[89,2618,2483],{}," GraphQL ressemblent plus à du RPC. Il vaut mieux alors éviter de penser ressources mais plutôt actions. Quels sont les actions que l'on souhaite\npouvoir faire depuis l'IHM.",[2621,2622],"br",{},"Il ne faut pas hésiter à découper ces actions en petites actions que l'utilisateur peut choisir d'appeler ou non (il est possible d'appeler plusieurs ",[89,2625,2483],{}," en un seul appel).",[22,2628,2629,2630,2632],{},"Penser les ",[89,2631,2483],{}," en fonction des appels de vos clients.",[12,2634,2635],{},"Si on reprend le schéma suivant:",[82,2637,2639],{"className":507,"code":2638,"language":509,"meta":87,"style":87},"input InputUser {\n  name: String!\n}\n\ninput CreateBackupInput {\n  host: String!\n  user: InputUser!\n}\n\ntype Backup {\n  host: String!\n  id: Int!\n  startDate: Date\n  endDate: Date\n  user: User\n}\n\ntype User {\n  name: String\n  backups: [Backup!]\n}\n\ntype CreateBackupResponse {\n  statusCode: Int!\n  message: String\n  error: String\n  errorCode: String\n  backup: Backup\n}\n",[89,2640,2641,2648,2656,2660,2664,2671,2677,2684,2688,2692,2699,2705,2712,2720,2727,2734,2738,2742,2749,2756,2763,2767,2771,2778,2785,2792,2799,2806,2814],{"__ignoreMap":87},[92,2642,2643,2645],{"class":94,"line":95},[92,2644,2607],{"class":113},[92,2646,2647],{"class":106}," InputUser {\n",[92,2649,2650,2653],{"class":94,"line":144},[92,2651,2652],{"class":109},"  name",[92,2654,2655],{"class":106},": String!\n",[92,2657,2658],{"class":94,"line":151},[92,2659,249],{"class":106},[92,2661,2662],{"class":94,"line":186},[92,2663,148],{"emptyLinePlaceholder":147},[92,2665,2666,2668],{"class":94,"line":192},[92,2667,2607],{"class":113},[92,2669,2670],{"class":106}," CreateBackupInput {\n",[92,2672,2673,2675],{"class":94,"line":201},[92,2674,839],{"class":109},[92,2676,2655],{"class":106},[92,2678,2679,2681],{"class":94,"line":216},[92,2680,195],{"class":109},[92,2682,2683],{"class":106},": InputUser!\n",[92,2685,2686],{"class":94,"line":229},[92,2687,249],{"class":106},[92,2689,2690],{"class":94,"line":240},[92,2691,148],{"emptyLinePlaceholder":147},[92,2693,2694,2696],{"class":94,"line":246},[92,2695,702],{"class":113},[92,2697,2698],{"class":106}," Backup {\n",[92,2700,2701,2703],{"class":94,"line":359},[92,2702,839],{"class":109},[92,2704,2655],{"class":106},[92,2706,2707,2710],{"class":94,"line":377},[92,2708,2709],{"class":109},"  id",[92,2711,727],{"class":106},[92,2713,2714,2717],{"class":94,"line":382},[92,2715,2716],{"class":109},"  startDate",[92,2718,2719],{"class":106},": Date\n",[92,2721,2722,2725],{"class":94,"line":389},[92,2723,2724],{"class":109},"  endDate",[92,2726,2719],{"class":106},[92,2728,2729,2731],{"class":94,"line":407},[92,2730,195],{"class":109},[92,2732,2733],{"class":106},": User\n",[92,2735,2736],{"class":94,"line":424},[92,2737,249],{"class":106},[92,2739,2740],{"class":94,"line":442},[92,2741,148],{"emptyLinePlaceholder":147},[92,2743,2744,2746],{"class":94,"line":447},[92,2745,702],{"class":113},[92,2747,2748],{"class":106}," User {\n",[92,2750,2751,2753],{"class":94,"line":614},[92,2752,2652],{"class":109},[92,2754,2755],{"class":106},": String\n",[92,2757,2758,2760],{"class":94,"line":774},[92,2759,2177],{"class":109},[92,2761,2762],{"class":106},": [Backup!]\n",[92,2764,2765],{"class":94,"line":780},[92,2766,249],{"class":106},[92,2768,2769],{"class":94,"line":785},[92,2770,148],{"emptyLinePlaceholder":147},[92,2772,2773,2775],{"class":94,"line":794},[92,2774,702],{"class":113},[92,2776,2777],{"class":106}," CreateBackupResponse {\n",[92,2779,2780,2783],{"class":94,"line":799},[92,2781,2782],{"class":109},"  statusCode",[92,2784,727],{"class":106},[92,2786,2787,2790],{"class":94,"line":805},[92,2788,2789],{"class":109},"  message",[92,2791,2755],{"class":106},[92,2793,2794,2797],{"class":94,"line":810},[92,2795,2796],{"class":109},"  error",[92,2798,2755],{"class":106},[92,2800,2801,2804],{"class":94,"line":818},[92,2802,2803],{"class":109},"  errorCode",[92,2805,2755],{"class":106},[92,2807,2808,2811],{"class":94,"line":823},[92,2809,2810],{"class":109},"  backup",[92,2812,2813],{"class":106},": Backup\n",[92,2815,2816],{"class":94,"line":828},[92,2817,249],{"class":106},[12,2819,2820,2821,2823],{},"Contrairement aux paramètres de sortie, il n'est pas possible sur les ",[89,2822,2607],{}," de faire des dépendances circulaires. Un input doit donc être lié à l'action faite\npar la mutation.\nCela veux dire aussi qu'il n'est pas possible de réutiliser un type de sortie en input pour l'entrée. Le point d'exclamation n'est plus un indicateur de non nullité\nmais un indicateur de paramètre obligatoire.",[12,2825,2826],{},"Lors de la conception des ces inputs il faut d'ailleurs penser à plusieurs choses:",[19,2828,2829,2832],{},[22,2830,2831],{},"Certain champs en sortie dans un type sont là pour representer un calcul ou un état qui fait sens mais que l'utilisateur ne peut pas modifier. Il ne faut donc pas\nle mettre en entrée.",[22,2833,2834],{},"Un paramètre qui peut être non nul en sortie n'est pas forcément obligatoire en entrée (et inversement).",[12,2836,2837],{},"Pour une application possédant beaucoup de formulaires et d'écrans de modifications ou d'actions, il peut être donc compliqué d'écrire les requêtes et les mutations associées.",[12,2839,2840,2841,2846],{},"Il peut peut-être être intéressant d'utiliser un transpiler comme ",[25,2842,2845],{"href":2843,"rel":2844},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fgraphql-s2s",[59],"graphql-s2s"," qui permet de simplifier certaines choses\ndans l'écriture du schéma.\nLors de son utilisation sur un projet possédant un gros schéma, il a permis la reprise d'une API Rest existante plus facilement.",[12,2848,2849],{},"Il permet entre autres:",[19,2851,2852,2855],{},[22,2853,2854],{},"de ne pas répéter les champs de l'interface lors de l'héritage d'un type.",[22,2856,2857],{},"de définir des types génériques (qui à la compilation créeront des vrais types).",[495,2859,2861],{"id":2860},"subscription","Subscription",[12,2863,2864,2865,2868],{},"Un des points que j'adore avec GraphQL c'est la facilité d'implémentation qu'apporte Apollo avec les ",[89,2866,2867],{},"Subscritpions",". Quand on souhaite remonter des informations\ndu serveur au client, on peut mettre en place des WebSockets ou des Server-Side-Events. Il faut alors définir un potocole entre le client et le serveur.",[12,2870,2871],{},"GraphQL utilise de façon transparente une WebSocket dans le cadre des souscriptions. L'avantage c'est que le protocole est le même que pour les query. On effectue une\ndemande et au lieu d'avoir une réponse, une fois, on reçois les mise à jours sur la souscription.",[12,2873,2874],{},"Cela permet de mettre en place facilement des pages dynamiques qui évoluent sans actions utilisateurs (progression, chat, ...) sans se casser la tête.",[12,2876,2877],{},"Pour définir une souscription on peut utiliser une requête comme celle-ci:",[82,2879,2881],{"className":507,"code":2880,"language":509,"meta":87,"style":87},"#import \".\u002FFragmentJob.graphql\"\n\nsubscription QueueTasksJobUpdated {\n  jobUpdated {\n    ...FragmentJob\n  }\n}\n",[89,2882,2883,2887,2891,2900,2907,2913,2917],{"__ignoreMap":87},[92,2884,2885],{"class":94,"line":95},[92,2886,1657],{"class":275},[92,2888,2889],{"class":94,"line":144},[92,2890,148],{"emptyLinePlaceholder":147},[92,2892,2893,2895,2898],{"class":94,"line":151},[92,2894,2860],{"class":285},[92,2896,2897],{"class":285}," QueueTasksJobUpdated",[92,2899,522],{"class":106},[92,2901,2902,2905],{"class":94,"line":186},[92,2903,2904],{"class":109},"  jobUpdated",[92,2906,522],{"class":106},[92,2908,2909,2911],{"class":94,"line":192},[92,2910,1697],{"class":106},[92,2912,1700],{"class":109},[92,2914,2915],{"class":94,"line":201},[92,2916,243],{"class":106},[92,2918,2919],{"class":94,"line":216},[92,2920,249],{"class":106},[12,2922,2923],{},"Dans l'exemple ci-dessous je réutilise le fragment d'un job défini dans une query. La souscription dans apollo va automatiquement mettre à jour le résultat et\nrafraichir l'IHM.",[12,2925,2926,2927,2932],{},"Par exemple avec vue cela donne (cf la doc de ",[25,2928,2931],{"href":2929,"rel":2930},"https:\u002F\u002Fapollo.vuejs.org\u002Fguide\u002Fapollo\u002Fsubscriptions.html#subscribe-to-more",[59],"vue-apollo","):",[82,2934,2936],{"className":84,"code":2935,"language":86,"meta":87,"style":87},"apollo: {\n  tags: {\n    query: TAGS_QUERY,\n    subscribeToMore: {\n      document: gql`subscription name($param: String!) {\n        itemAdded(param: $param) {\n          id\n          label\n        }\n      }`,\n      \u002F\u002F Variables passed to the subscription. Since we're using a function,\n      \u002F\u002F they are reactive\n      variables () {\n        return {\n          param: this.param,\n        }\n      },\n      \u002F\u002F Mutate the previous result\n      updateQuery: (previousResult, { subscriptionData }) => {\n        \u002F\u002F Here, return the new result from the previous with the new data\n      },\n    }\n  }\n}\n",[89,2937,2938,2944,2951,2963,2970,2983,2988,2993,2998,3002,3009,3014,3019,3027,3034,3051,3055,3060,3065,3088,3093,3097,3101,3105],{"__ignoreMap":87},[92,2939,2940,2942],{"class":94,"line":95},[92,2941,1963],{"class":109},[92,2943,198],{"class":106},[92,2945,2946,2949],{"class":94,"line":144},[92,2947,2948],{"class":109},"  tags",[92,2950,198],{"class":106},[92,2952,2953,2956,2958,2961],{"class":94,"line":151},[92,2954,2955],{"class":109},"    query",[92,2957,207],{"class":106},[92,2959,2960],{"class":102},"TAGS_QUERY",[92,2962,213],{"class":106},[92,2964,2965,2968],{"class":94,"line":186},[92,2966,2967],{"class":109},"    subscribeToMore",[92,2969,198],{"class":106},[92,2971,2972,2975,2977,2980],{"class":94,"line":192},[92,2973,2974],{"class":109},"      document",[92,2976,207],{"class":106},[92,2978,2979],{"class":285},"gql",[92,2981,2982],{"class":126},"`subscription name($param: String!) {\n",[92,2984,2985],{"class":94,"line":201},[92,2986,2987],{"class":126},"        itemAdded(param: $param) {\n",[92,2989,2990],{"class":94,"line":216},[92,2991,2992],{"class":126},"          id\n",[92,2994,2995],{"class":94,"line":229},[92,2996,2997],{"class":126},"          label\n",[92,2999,3000],{"class":94,"line":240},[92,3001,1886],{"class":126},[92,3003,3004,3007],{"class":94,"line":246},[92,3005,3006],{"class":126},"      }`",[92,3008,213],{"class":106},[92,3010,3011],{"class":94,"line":359},[92,3012,3013],{"class":275},"      \u002F\u002F Variables passed to the subscription. Since we're using a function,\n",[92,3015,3016],{"class":94,"line":377},[92,3017,3018],{"class":275},"      \u002F\u002F they are reactive\n",[92,3020,3021,3024],{"class":94,"line":382},[92,3022,3023],{"class":285},"      variables",[92,3025,3026],{"class":106}," () {\n",[92,3028,3029,3032],{"class":94,"line":389},[92,3030,3031],{"class":113},"        return",[92,3033,522],{"class":106},[92,3035,3036,3039,3041,3044,3046,3049],{"class":94,"line":407},[92,3037,3038],{"class":109},"          param",[92,3040,207],{"class":106},[92,3042,3043],{"class":102},"this",[92,3045,61],{"class":106},[92,3047,3048],{"class":109},"param",[92,3050,213],{"class":106},[92,3052,3053],{"class":94,"line":424},[92,3054,1886],{"class":106},[92,3056,3057],{"class":94,"line":442},[92,3058,3059],{"class":106},"      },\n",[92,3061,3062],{"class":94,"line":447},[92,3063,3064],{"class":275},"      \u002F\u002F Mutate the previous result\n",[92,3066,3067,3070,3073,3076,3078,3081,3084,3086],{"class":94,"line":614},[92,3068,3069],{"class":109},"      updateQuery",[92,3071,3072],{"class":106},": (",[92,3074,3075],{"class":1295},"previousResult",[92,3077,2370],{"class":106},[92,3079,3080],{"class":1295},"subscriptionData",[92,3082,3083],{"class":106}," }) ",[92,3085,2287],{"class":113},[92,3087,522],{"class":106},[92,3089,3090],{"class":94,"line":774},[92,3091,3092],{"class":275},"        \u002F\u002F Here, return the new result from the previous with the new data\n",[92,3094,3095],{"class":94,"line":780},[92,3096,3059],{"class":106},[92,3098,3099],{"class":94,"line":785},[92,3100,344],{"class":106},[92,3102,3103],{"class":94,"line":794},[92,3104,243],{"class":106},[92,3106,3107],{"class":94,"line":799},[92,3108,249],{"class":106},[12,3110,3111,3112,3115],{},"La requête initiale est faite grâce à ",[89,3113,3114],{},"TAG_QUERY",", puis toutes les mises à jours se font via la souscription. L'IHM est alors automatiquement mise à jour.",[495,3117,3119],{"id":3118},"conclusion","Conclusion",[12,3121,3122],{},"GraphQL est super puissant et facilite l'écriture d'API et son utilisation par des clients. Par contre la montée en compétence pour faire du GraphQL est un\npeu plus élevée que pour faire du REST (Bien qu'il n'est pas facile de faire du bon REST).",[12,3124,3125],{},"Il peut toujours être utile de proposer des API Rest en plus des API GraphQL. Pour par exemple permettre au client de choisir ce qu'il souhaite utiliser ou\nmême par exemple pour des cas d'usage particuliers. (Par exemple le téléchargement\u002Fl'upload d'un document binaire est plus facile en REST que en GraphQL).",[495,3127,3129],{"id":3128},"références","Références",[19,3131,3132,3139,3145,3152,3158,3165,3170],{},[22,3133,3134],{},[25,3135,3138],{"href":3136,"rel":3137},"https:\u002F\u002Fgraphql.org\u002Flearn\u002Fqueries\u002F",[59],"GraphQL",[22,3140,3141,61],{},[25,3142,490],{"href":3143,"rel":3144},"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Fapollo-server\u002Fdata\u002Fresolvers\u002F",[59],[22,3146,3147],{},[25,3148,3151],{"href":3149,"rel":3150},"https:\u002F\u002Fwww.moesif.com\u002Fblog\u002Ftechnical\u002Fapi-design\u002FBest-Practices-for-Versioning-REST-and-GraphQL-APIs\u002F",[59],"Best Practices for Versioning REST and GraphQL APIs",[22,3153,3154],{},[25,3155,3157],{"href":1736,"rel":3156},[59],"GraphQL Versioning",[22,3159,3160],{},[25,3161,3164],{"href":3162,"rel":3163},"https:\u002F\u002Ftowardsdatascience.com\u002Fgraphql-best-practices-3fda586538c4",[59],"GraphQL Best Practices",[22,3166,3167],{},[25,3168,3149],{"href":3149,"rel":3169},[59],[22,3171,3172],{},[25,3173,1736],{"href":1736,"rel":3174},[59],[3176,3177,3178],"style",{},"html pre.shiki code .sjrmR, html code.shiki .sjrmR{--shiki-default:#56B6C2}html pre.shiki code .sU0A5, html code.shiki .sU0A5{--shiki-default:#E5C07B}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .seHd6, html code.shiki .seHd6{--shiki-default:#C678DD}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sV9Aq, html code.shiki .sV9Aq{--shiki-default:#7F848E;--shiki-default-font-style:italic}html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}html pre.shiki code .sLaUg, html code.shiki .sLaUg{--shiki-default:#FFFFFF}html pre.shiki code .s_ZVi, html code.shiki .s_ZVi{--shiki-default:#E06C75;--shiki-default-font-style:italic}",{"title":87,"searchDepth":144,"depth":144,"links":3180},[3181,3182],{"id":47,"depth":144,"text":48},{"id":466,"depth":144,"text":467,"children":3183},[3184,3185,3186,3187,3188,3189,3190,3191,3192,3193,3194,3195,3196,3197,3198,3199],{"id":497,"depth":151,"text":498},{"id":643,"depth":151,"text":644},{"id":662,"depth":151,"text":663},{"id":1374,"depth":151,"text":1375},{"id":1393,"depth":151,"text":1394},{"id":1475,"depth":151,"text":1476},{"id":1718,"depth":151,"text":1719},{"id":1758,"depth":151,"text":1759},{"id":1940,"depth":151,"text":1941},{"id":2145,"depth":151,"text":2146},{"id":2417,"depth":151,"text":2418},{"id":2451,"depth":151,"text":2452},{"id":2476,"depth":151,"text":2477},{"id":2860,"depth":151,"text":2861},{"id":3118,"depth":151,"text":3119},{"id":3128,"depth":151,"text":3129},"Programmation","programmation","2020-11-29",{"type":9,"value":3204},[3205,3207,3209,3223],[12,3206,14],{},[12,3208,17],{},[19,3210,3211,3215,3219],{},[22,3212,3213],{},[25,3214,28],{"href":27},[22,3216,3217],{},[25,3218,34],{"href":33},[22,3220,3221],{},[25,3222,40],{"href":39},[12,3224,43],{},"md","Lille, France",{"planet":147},"\u002Fpost\u002Fcreation-api-3",{"title":6,"description":14},"creation-api-3","posts\u002FProgrammation\u002F2020-11-29_creation-api-3",[3233,509,3234,3235,3236],"api","rest","javascript","nodejs","HYw0wo6aBhxcGM0oSLGWN8-QiSEHsmV51HJU8dZLB8E",{"id":3239,"title":3240,"author":7,"body":3241,"category":3200,"categorySlug":3201,"date":5097,"description":14,"excerpt":5098,"extension":3225,"location":3226,"meta":5128,"navigation":147,"path":5129,"published":147,"seo":5130,"slug":5131,"stem":5132,"tags":5133,"timeToRead":447,"__hash__":5134},"posts\u002Fposts\u002FProgrammation\u002F2020-11-02_creation-api-2.md","Comment créer une bonne API Web - Partie 2",{"type":9,"value":3242,"toc":5085},[3243,3245,3247,3261,3265,3268,3271,3278,3282,3290,3300,3303,3308,3347,3352,3422,3427,3493,3496,3500,3503,3505,3545,3552,3559,3562,3566,3575,3584,3588,3593,3600,3607,3610,3618,3632,3642,3645,3657,3660,3674,3682,3696,3701,3704,3717,3726,3784,3787,3790,3805,3808,3814,4045,4048,4057,4063,4129,4132,4140,4148,4152,4157,4169,4176,4185,4189,4199,4207,4211,4217,4220,4223,4240,4243,4246,4249,4253,4257,4260,4266,4269,4272,4277,4280,4283,4286,4289,4303,4306,4309,4322,4325,4331,4335,4338,4344,4348,4352,4358,4361,4365,4371,4380,4384,4392,4398,4401,4405,4419,4422,4444,4448,4454,4459,4463,4467,4470,4473,4477,4480,4483,4487,4490,4493,4496,4499,4502,4506,4509,4513,4516,4520,4526,4530,4533,4537,4541,4544,4547,4551,4554,4558,4561,4565,4568,4571,4574,4588,4591,4594,4937,4940,4944,4947,4950,4953,4957,4960,4963,4977,4983,4987,4990,4993,4996,5000,5032,5082],[12,3244,14],{},[12,3246,17],{},[19,3248,3249,3253,3257],{},[22,3250,3251],{},[25,3252,28],{"href":27},[22,3254,3255],{},[25,3256,34],{"href":33},[22,3258,3259],{},[25,3260,40],{"href":39},[45,3262,3264],{"id":3263},"quest-quune-api-rest","Qu'est qu'une API REST",[12,3266,3267],{},"REST est une norme dont voici les grandes lignes. Il n'est pas dans mon but de faire un cours sur REST (et il y en\ndéjà de très bons sur internet). Je souhaiterais surtout parler des points qui me semblent importants. N'hésitez pas à venir\nme dire si vous pensez qu'il manque des points importants.\nJe viendrai alors compléter mon article.",[12,3269,3270],{},"Le principe de REST est de séparer l'API en différentes ressources logiques qui peuvent être manipulées par les verbes\nHTTP (GET, POST, ...). La réponse de son côté se base également sur les codes http.",[12,3272,3273,3274,3277],{},"Le contenu de la requête et de la réponse peut être dans le format objet de votre choix (json, yaml, xml, ...). On\nutilise alors le header ",[89,3275,3276],{},"Content-Type"," pour définir le contenu. Une API peut d'ailleurs gérer plusieurs formats et\nrépondre au client le bon format en fonction de la demande du client.",[495,3279,3281],{"id":3280},"basé-sur-des-ressources","Basé sur des ressources",[12,3283,3284,3285,3289],{},"Quand on parle de REST, il faut penser ",[3286,3287,3288],"em",{},"ressources",". Mais qu'est qu'une ressource ?",[12,3291,3292,3293,3296,3297,3299],{},"Une ressource c'est un concept abstrait de REST. Une ressource c'est ce qui va être représenté par les données JSON\nque vous allez manipuler. Pour représenter une ressource on n'utilise pas un verbe, ou une action mais un ",[1469,3294,3295],{},"nom",".\nL'utilisation du ",[1469,3298,3295],{}," est important car le verbe est representé par la méthode HTTP.",[12,3301,3302],{},"Une ressource peut:",[19,3304,3305],{},[22,3306,3307],{},"être un singleton",[82,3309,3311],{"className":1165,"code":3310,"language":110,"meta":87,"style":87},"GET \u002Fapi\u002Fhosts\u002F:id\n\n{\n  \"id\": 1,\n  ...\n}\n",[89,3312,3313,3318,3322,3326,3338,3343],{"__ignoreMap":87},[92,3314,3315],{"class":94,"line":95},[92,3316,3317],{"class":106},"GET \u002Fapi\u002Fhosts\u002F:id\n",[92,3319,3320],{"class":94,"line":144},[92,3321,148],{"emptyLinePlaceholder":147},[92,3323,3324],{"class":94,"line":151},[92,3325,189],{"class":106},[92,3327,3328,3331,3333,3336],{"class":94,"line":186},[92,3329,3330],{"class":109},"  \"id\"",[92,3332,207],{"class":106},[92,3334,3335],{"class":338},"1",[92,3337,213],{"class":106},[92,3339,3340],{"class":94,"line":192},[92,3341,3342],{"class":373},"  ...\n",[92,3344,3345],{"class":94,"line":201},[92,3346,249],{"class":106},[19,3348,3349],{},[22,3350,3351],{},"être une collection",[82,3353,3355],{"className":1165,"code":3354,"language":110,"meta":87,"style":87},"GET \u002Fapi\u002Fhosts\n\n[\n  {\n    \"id\": 1,\n    ...\n  },\n  {\n    \"id\": 2,\n    ...\n  },\n]\n",[89,3356,3357,3362,3366,3371,3376,3387,3392,3396,3400,3410,3414,3418],{"__ignoreMap":87},[92,3358,3359],{"class":94,"line":95},[92,3360,3361],{"class":106},"GET \u002Fapi\u002Fhosts\n",[92,3363,3364],{"class":94,"line":144},[92,3365,148],{"emptyLinePlaceholder":147},[92,3367,3368],{"class":94,"line":151},[92,3369,3370],{"class":106},"[\n",[92,3372,3373],{"class":94,"line":186},[92,3374,3375],{"class":106},"  {\n",[92,3377,3378,3381,3383,3385],{"class":94,"line":192},[92,3379,3380],{"class":109},"    \"id\"",[92,3382,207],{"class":106},[92,3384,3335],{"class":338},[92,3386,213],{"class":106},[92,3388,3389],{"class":94,"line":201},[92,3390,3391],{"class":373},"    ...\n",[92,3393,3394],{"class":94,"line":216},[92,3395,1129],{"class":106},[92,3397,3398],{"class":94,"line":229},[92,3399,3375],{"class":106},[92,3401,3402,3404,3406,3408],{"class":94,"line":240},[92,3403,3380],{"class":109},[92,3405,207],{"class":106},[92,3407,1194],{"class":338},[92,3409,213],{"class":106},[92,3411,3412],{"class":94,"line":246},[92,3413,3391],{"class":373},[92,3415,3416],{"class":94,"line":359},[92,3417,1129],{"class":106},[92,3419,3420],{"class":94,"line":377},[92,3421,141],{"class":106},[19,3423,3424],{},[22,3425,3426],{},"être une sous-ressource",[82,3428,3430],{"className":1165,"code":3429,"language":110,"meta":87,"style":87},"GET \u002Fapi\u002Fhosts\u002F:id\u002Fbackups\n\n[\n  {\n    \"id\": 1,\n    ...\n  },\n  {\n    \"id\": 2,\n    ...\n  },\n]\n",[89,3431,3432,3437,3441,3445,3449,3459,3463,3467,3471,3481,3485,3489],{"__ignoreMap":87},[92,3433,3434],{"class":94,"line":95},[92,3435,3436],{"class":106},"GET \u002Fapi\u002Fhosts\u002F:id\u002Fbackups\n",[92,3438,3439],{"class":94,"line":144},[92,3440,148],{"emptyLinePlaceholder":147},[92,3442,3443],{"class":94,"line":151},[92,3444,3370],{"class":106},[92,3446,3447],{"class":94,"line":186},[92,3448,3375],{"class":106},[92,3450,3451,3453,3455,3457],{"class":94,"line":192},[92,3452,3380],{"class":109},[92,3454,207],{"class":106},[92,3456,3335],{"class":338},[92,3458,213],{"class":106},[92,3460,3461],{"class":94,"line":201},[92,3462,3391],{"class":373},[92,3464,3465],{"class":94,"line":216},[92,3466,1129],{"class":106},[92,3468,3469],{"class":94,"line":229},[92,3470,3375],{"class":106},[92,3472,3473,3475,3477,3479],{"class":94,"line":240},[92,3474,3380],{"class":109},[92,3476,207],{"class":106},[92,3478,1194],{"class":338},[92,3480,213],{"class":106},[92,3482,3483],{"class":94,"line":246},[92,3484,3391],{"class":373},[92,3486,3487],{"class":94,"line":359},[92,3488,1129],{"class":106},[92,3490,3491],{"class":94,"line":377},[92,3492,141],{"class":106},[12,3494,3495],{},"La ressource est généralement nommée au pluriel et pour des raisons de consistance, doit toujours être nommée de\nla même manière même si le but est de récupérer un unique élément.",[495,3497,3499],{"id":3498},"utilise-les-verbes-http","Utilise les verbes HTTP",[12,3501,3502],{},"Donc une API REST ce sont des ressources qui peuvent être manipulées par les verbes HTTP. Du coup l'action n'est pas\nportée par le nom de la ressource mais par la méthode. Quels sont donc ces méthodes ?",[1570,3504,154],{"id":286},[12,3506,3507,3509,3510,3512,3513,3516,3517,3526,3527,3529,3530,3537,3538,130,3541,3544],{},[89,3508,154],{}," est utilisé pour récupérer une ressource. En aucun cas ",[89,3511,154],{}," doit être utilisé pour effectuer une modification\n(ce serait comme effectuer des modifications dans le ",[89,3514,3515],{},"getter"," d'une classe",[3518,3519,3520],"sup",{},[25,3521,3335],{"href":3522,"ariaDescribedBy":3523,"dataFootnoteRef":87,"id":3525},"#user-content-fn-getter",[3524],"footnote-label","user-content-fnref-getter",").\n",[89,3528,154],{}," ne doit pas modifier l'état de la ressource et doit être idempotent",[3518,3531,3532],{},[25,3533,1194],{"href":3534,"ariaDescribedBy":3535,"dataFootnoteRef":87,"id":3536},"#user-content-fn-idempotent",[3524],"user-content-fnref-idempotent",". On peut donc appeler autant de fois que\nl'on souhaite la méthode GET, le résultat doit toujours être le même tant qu'une autre méthode (",[89,3539,3540],{},"POST",[89,3542,3543],{},"PUT",", ...)\nne vient pas modifier l'état de la ressource.",[12,3546,3547,3548,3551],{},"Si la ressource peut être produite par le serveur, le code HTTP de réponse doit être ",[89,3549,3550],{},"200 OK"," et contenir le contenu de\nla ressource de la réponse dans le corps.",[12,3553,3554,3555,3558],{},"Le client peut alors cacher la requête s'il le souhaite, et il est possible d'utiliser les headers HTTP standards pour lui\nindiquer combien de temps (cf header Cache-Control). Attention ce header peut être ignoré ou peut être utilisé par le\nclient afin d'agir sur son ",[3286,3556,3557],{},"rate limit",". A aucun moment il ne faut faire confiance au client et il faut préférer cacher\nles requêtes en interne (via un mêmcached) et définir une limite d'appels plutôt que de solliciter son back si ce\ndernier ne peut pas tenir la charge.",[12,3560,3561],{},"ne doit pas avoir d'impact\u002Fd'effet de bord dans l'application.",[1570,3563,3565],{"id":3564},"head","HEAD",[12,3567,3568,3569,3571,3572,3574],{},"La méthode ",[89,3570,3565],{}," a le même effet que ",[89,3573,154],{}," mais ne retourne pas de contenu. Le header peut-être utilisé pour vérifier\nl'état de la ressource (et voir si le cache doit être invalidé).",[12,3576,3577,3578,1158,3581,61],{},"La méthode est considéré comme ",[3286,3579,3580],{},"safe",[3286,3582,3583],{},"idempotent",[1570,3585,3587],{"id":3586},"option","OPTION",[12,3589,3568,3590,3592],{},[89,3591,3587],{}," est utilisée pour vérifier les capacités de la méthode HTTP. Il est notament utilisé par les\nnavigateurs pour vérifier les entêtes CORS d'une API (en mode preflight) avant d'effectuer la véritable requête.",[12,3594,3595,3596,3599],{},"Pour interroger le serveur de facon générale ",[89,3597,3598],{},"*"," peut être utilisé à la place de l'URI. Ce seront donc les capacités\nglobales du serveur qui seront retournées",[12,3601,3602,3603,1158,3605,61],{},"La méthode est considérée comme ",[3286,3604,3580],{},[3286,3606,3583],{},[1570,3608,3540],{"id":3609},"post",[12,3611,3612,3614,3615,3617],{},[89,3613,3540],{}," doit être utilisé pour créer une nouvelle ressource associée à la requête demandée. ",[89,3616,3540],{}," est utilisé pour\najouter un nouvel élément à une collection.",[12,3619,3620,3621,3624,3625,3627,3628,3631],{},"Lorsque la ressource a été créé, la réponse doit être ",[89,3622,3623],{},"201 Created"," et le corps du message ne doit pas contenir\nd'informations. A la place on laisse le client récuperer la ressource en utilisant la méthode ",[89,3626,154],{}," ci-dessus. Pour cela,\nle lien d'accès à la ressource doit être positionné dans le header ",[89,3629,3630],{},"Location"," de la réponse HTTP.",[12,3633,3634,3635,3638,3639,3641],{},"Si toutefois la création de la ressource ne peut pas être liée à une ressource identifiable par un URI, la méthode peut\nalors retourner ",[89,3636,3637],{},"204 No Content"," ou ",[89,3640,3550],{}," suivant si la réponse possède un contenu ou non.",[12,3643,3644],{},"La réponse ne peut pas être cachée (sauf indication contraire par header HTTP).",[12,3646,3647,3648,3650,3651,3653,3654,3656],{},"Les requêtes ",[89,3649,3540],{}," ne sont pas ",[3286,3652,3583],{}," et peuvent changer l'état de la ressource. Dans ce cas le client devra\nd'ailleurs invalider son cache. Deux appels identiques ",[89,3655,3540],{}," peuvent génerer des résultats différents.",[1570,3658,3543],{"id":3659},"put",[12,3661,3662,3663,1158,3665,3667,3668,3670,3671,3673],{},"La différence la plus importante entre ",[89,3664,3540],{},[89,3666,3543],{}," se situe sur l'URI. La méthode ",[89,3669,3543],{}," est utilisée sur les URI\navec un identifiant (et donc unique), alors que la méthode ",[89,3672,3540],{}," est utilisée sur URI représentant une collection.",[12,3675,3676,3678,3679,61],{},[89,3677,3543],{}," doit alors être utilisé pour mettre à jour une ressource existante (voir en créer une nouvelle si elle n'existe\npas). La ressource intégrale doit être passée. Pour une mise à jour partielle, il faut voir la méthode ",[89,3680,3681],{},"PATCH",[12,3683,3684,3685,3687,3688,3690,3691,3638,3693,3695],{},"S'il y a création d'une ressource, comme pour ",[89,3686,3540],{},", l'API doit retourner un ",[89,3689,3623],{},". Dans le cas d'une mise à\njour la méthode retourne alors ",[89,3692,3637],{},[89,3694,3550],{}," suivant que la réponse possède un contenu ou non.",[12,3697,3647,3698,3700],{},[89,3699,3543],{}," mettant à jour la ressource, le client devra invalider son cache sur la base de l'URI. La méthode est\nconsidérée comme idempotent car c'est la ressource entière qui est remplacée.",[1570,3702,3681],{"id":3703},"patch",[12,3705,3706,3708,3709,61],{},[89,3707,3681],{}," est utilisé pour mettre à jour une ressource partiellement. La mise à jour doit être également atomique",[3518,3710,3711],{},[25,3712,3716],{"href":3713,"ariaDescribedBy":3714,"dataFootnoteRef":87,"id":3715},"#user-content-fn-atomic",[3524],"user-content-fnref-atomic","3",[12,3718,3719,3720,3725],{},"Dans la RFC ",[25,3721,3724],{"href":3722,"rel":3723},"https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc5789",[59],"RFC 5789"," de la méthode PATCH, il y est écrit que le contenu doit\ncontenir la description des modifications :",[82,3727,3729],{"className":1165,"code":3728,"language":110,"meta":87,"style":87},"PATCH \u002Ffile.txt HTTP\u002F1.1\nHost: www.example.com\nContent-Type: application\u002Fexample\nIf-Match: \"e0023aa4e\"\nContent-Length: 100\n\n[description of changes]\n",[89,3730,3731,3739,3744,3749,3757,3765,3769],{"__ignoreMap":87},[92,3732,3733,3736],{"class":94,"line":95},[92,3734,3735],{"class":106},"PATCH \u002Ffile.txt HTTP\u002F",[92,3737,3738],{"class":338},"1.1\n",[92,3740,3741],{"class":94,"line":144},[92,3742,3743],{"class":106},"Host: www.example.com\n",[92,3745,3746],{"class":94,"line":151},[92,3747,3748],{"class":106},"Content-Type: application\u002Fexample\n",[92,3750,3751,3754],{"class":94,"line":186},[92,3752,3753],{"class":106},"If-Match: ",[92,3755,3756],{"class":126},"\"e0023aa4e\"\n",[92,3758,3759,3762],{"class":94,"line":192},[92,3760,3761],{"class":106},"Content-Length: ",[92,3763,3764],{"class":338},"100\n",[92,3766,3767],{"class":94,"line":201},[92,3768,148],{"emptyLinePlaceholder":147},[92,3770,3771,3773,3776,3779,3782],{"class":94,"line":216},[92,3772,123],{"class":106},[92,3774,3775],{"class":373},"description",[92,3777,3778],{"class":373}," of",[92,3780,3781],{"class":373}," changes",[92,3783,141],{"class":106},[12,3785,3786],{},"Utiliser une implémentation free-style de la description des changements risque d'être source d'erreurs pour le client\ncomme pour le serveur. En effet, le client pourrait s'attendre dans certains cas à des ajouts dans un tableau, ou au\nremplacement du tableau.",[12,3788,3789],{},"Il existe différentes RFC décrivant la bonne manière de faire pour PATCHer un document JSON. Il vaut mieux alors se baser\ndessus pour développer nos API. Cela permet d'être clair sur le fonctionnement de la méthode.",[12,3791,3792,3793,3798,3799,3804],{},"On peut alors se baser sur les ",[25,3794,3797],{"href":3795,"rel":3796},"https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc6902",[59],"RFC 6902"," et\n",[25,3800,3803],{"href":3801,"rel":3802},"https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc7396",[59],"RFC 7396"," pour décrire le contenu de cette description des modifications.",[12,3806,3807],{},"Pour les API basées sur des XML, référez-vous aux RFC équivalents décrivant les méthodes de PATCH pour le XML.",[12,3809,3810,3811,3813],{},"Si on se base sur la ",[89,3812,3797],{}," on peut alors reprendre l'exemple qui décrit un changement comme étant une liste de\ndifférences à appliquer (noté le Content-Type):",[82,3815,3817],{"className":1165,"code":3816,"language":110,"meta":87,"style":87},"PATCH \u002Fmy\u002Fdata HTTP\u002F1.1\nHost: example.org\nContent-Length: 326\nContent-Type: application\u002Fjson-patch+json\nIf-Match: \"abc123\"\n\n[\n  { \"op\": \"test\", \"path\": \"\u002Fa\u002Fb\u002Fc\", \"value\": \"foo\" },\n  { \"op\": \"remove\", \"path\": \"\u002Fa\u002Fb\u002Fc\" },\n  { \"op\": \"add\", \"path\": \"\u002Fa\u002Fb\u002Fc\", \"value\": [ \"foo\", \"bar\" ] },\n  { \"op\": \"replace\", \"path\": \"\u002Fa\u002Fb\u002Fc\", \"value\": 42 },\n  { \"op\": \"move\", \"from\": \"\u002Fa\u002Fb\u002Fc\", \"path\": \"\u002Fa\u002Fb\u002Fd\" },\n  { \"op\": \"copy\", \"from\": \"\u002Fa\u002Fb\u002Fd\", \"path\": \"\u002Fa\u002Fb\u002Fe\" }\n]\n",[89,3818,3819,3826,3831,3838,3843,3850,3854,3858,3893,3914,3950,3980,4011,4041],{"__ignoreMap":87},[92,3820,3821,3824],{"class":94,"line":95},[92,3822,3823],{"class":106},"PATCH \u002Fmy\u002Fdata HTTP\u002F",[92,3825,3738],{"class":338},[92,3827,3828],{"class":94,"line":144},[92,3829,3830],{"class":106},"Host: example.org\n",[92,3832,3833,3835],{"class":94,"line":151},[92,3834,3761],{"class":106},[92,3836,3837],{"class":338},"326\n",[92,3839,3840],{"class":94,"line":186},[92,3841,3842],{"class":106},"Content-Type: application\u002Fjson-patch+json\n",[92,3844,3845,3847],{"class":94,"line":192},[92,3846,3753],{"class":106},[92,3848,3849],{"class":126},"\"abc123\"\n",[92,3851,3852],{"class":94,"line":201},[92,3853,148],{"emptyLinePlaceholder":147},[92,3855,3856],{"class":94,"line":216},[92,3857,3370],{"class":106},[92,3859,3860,3863,3866,3868,3871,3873,3876,3878,3881,3883,3886,3888,3891],{"class":94,"line":229},[92,3861,3862],{"class":106},"  { ",[92,3864,3865],{"class":109},"\"op\"",[92,3867,207],{"class":106},[92,3869,3870],{"class":126},"\"test\"",[92,3872,130],{"class":106},[92,3874,3875],{"class":109},"\"path\"",[92,3877,207],{"class":106},[92,3879,3880],{"class":126},"\"\u002Fa\u002Fb\u002Fc\"",[92,3882,130],{"class":106},[92,3884,3885],{"class":109},"\"value\"",[92,3887,207],{"class":106},[92,3889,3890],{"class":126},"\"foo\"",[92,3892,404],{"class":106},[92,3894,3895,3897,3899,3901,3904,3906,3908,3910,3912],{"class":94,"line":240},[92,3896,3862],{"class":106},[92,3898,3865],{"class":109},[92,3900,207],{"class":106},[92,3902,3903],{"class":126},"\"remove\"",[92,3905,130],{"class":106},[92,3907,3875],{"class":109},[92,3909,207],{"class":106},[92,3911,3880],{"class":126},[92,3913,404],{"class":106},[92,3915,3916,3918,3920,3922,3925,3927,3929,3931,3933,3935,3937,3940,3942,3944,3947],{"class":94,"line":246},[92,3917,3862],{"class":106},[92,3919,3865],{"class":109},[92,3921,207],{"class":106},[92,3923,3924],{"class":126},"\"add\"",[92,3926,130],{"class":106},[92,3928,3875],{"class":109},[92,3930,207],{"class":106},[92,3932,3880],{"class":126},[92,3934,130],{"class":106},[92,3936,3885],{"class":109},[92,3938,3939],{"class":106},": [ ",[92,3941,3890],{"class":126},[92,3943,130],{"class":106},[92,3945,3946],{"class":126},"\"bar\"",[92,3948,3949],{"class":106}," ] },\n",[92,3951,3952,3954,3956,3958,3961,3963,3965,3967,3969,3971,3973,3975,3978],{"class":94,"line":359},[92,3953,3862],{"class":106},[92,3955,3865],{"class":109},[92,3957,207],{"class":106},[92,3959,3960],{"class":126},"\"replace\"",[92,3962,130],{"class":106},[92,3964,3875],{"class":109},[92,3966,207],{"class":106},[92,3968,3880],{"class":126},[92,3970,130],{"class":106},[92,3972,3885],{"class":109},[92,3974,207],{"class":106},[92,3976,3977],{"class":338},"42",[92,3979,404],{"class":106},[92,3981,3982,3984,3986,3988,3991,3993,3996,3998,4000,4002,4004,4006,4009],{"class":94,"line":377},[92,3983,3862],{"class":106},[92,3985,3865],{"class":109},[92,3987,207],{"class":106},[92,3989,3990],{"class":126},"\"move\"",[92,3992,130],{"class":106},[92,3994,3995],{"class":109},"\"from\"",[92,3997,207],{"class":106},[92,3999,3880],{"class":126},[92,4001,130],{"class":106},[92,4003,3875],{"class":109},[92,4005,207],{"class":106},[92,4007,4008],{"class":126},"\"\u002Fa\u002Fb\u002Fd\"",[92,4010,404],{"class":106},[92,4012,4013,4015,4017,4019,4022,4024,4026,4028,4030,4032,4034,4036,4039],{"class":94,"line":382},[92,4014,3862],{"class":106},[92,4016,3865],{"class":109},[92,4018,207],{"class":106},[92,4020,4021],{"class":126},"\"copy\"",[92,4023,130],{"class":106},[92,4025,3995],{"class":109},[92,4027,207],{"class":106},[92,4029,4008],{"class":126},[92,4031,130],{"class":106},[92,4033,3875],{"class":109},[92,4035,207],{"class":106},[92,4037,4038],{"class":126},"\"\u002Fa\u002Fb\u002Fe\"",[92,4040,439],{"class":106},[92,4042,4043],{"class":94,"line":389},[92,4044,141],{"class":106},[12,4046,4047],{},"Le format à l'avantage d'être complet et standard.",[12,4049,4050,4051,4056],{},"Pour gérer le format dans l'application, on peut retrouver des librairies capables de lire et de générer ce genre de\ndifférenciel (par exemple ",[25,4052,4055],{"href":4053,"rel":4054},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Ffast-json-patch",[59],"fast-json-patch",").",[12,4058,4059,4060,4062],{},"Avec la ",[89,4061,3803],{},", on peut adapter le Content-Type et avoir un format de patch plus lisible :",[82,4064,4066],{"className":1165,"code":4065,"language":110,"meta":87,"style":87},"PATCH \u002Ftarget HTTP\u002F1.1\nHost: example.org\nContent-Type: application\u002Fmerge-patch+json\n\n{\n  \"a\":\"z\",\n  \"c\": {\n    \"f\": null\n  }\n}\n",[89,4067,4068,4075,4079,4084,4088,4092,4104,4111,4121,4125],{"__ignoreMap":87},[92,4069,4070,4073],{"class":94,"line":95},[92,4071,4072],{"class":106},"PATCH \u002Ftarget HTTP\u002F",[92,4074,3738],{"class":338},[92,4076,4077],{"class":94,"line":144},[92,4078,3830],{"class":106},[92,4080,4081],{"class":94,"line":151},[92,4082,4083],{"class":106},"Content-Type: application\u002Fmerge-patch+json\n",[92,4085,4086],{"class":94,"line":186},[92,4087,148],{"emptyLinePlaceholder":147},[92,4089,4090],{"class":94,"line":192},[92,4091,189],{"class":106},[92,4093,4094,4097,4099,4102],{"class":94,"line":201},[92,4095,4096],{"class":109},"  \"a\"",[92,4098,1510],{"class":106},[92,4100,4101],{"class":126},"\"z\"",[92,4103,213],{"class":106},[92,4105,4106,4109],{"class":94,"line":216},[92,4107,4108],{"class":109},"  \"c\"",[92,4110,198],{"class":106},[92,4112,4113,4116,4118],{"class":94,"line":229},[92,4114,4115],{"class":109},"    \"f\"",[92,4117,207],{"class":106},[92,4119,4120],{"class":338},"null\n",[92,4122,4123],{"class":94,"line":240},[92,4124,243],{"class":106},[92,4126,4127],{"class":94,"line":246},[92,4128,249],{"class":106},[12,4130,4131],{},"Par contre ce format a ses limitations :",[19,4133,4134,4137],{},[22,4135,4136],{},"il ne peut que mettre à jour des éléments (pas de suppression)",[22,4138,4139],{},"dans le cadre de valeurs comme des tableaux, c'est le contenu intégral qui est remplacé. Pas d'ajout ou de merge.",[12,4141,4142],{},[1469,4143,4144,4145,4147],{},"Il est alors très important de définir dans sa documentation la liste des ",[89,4146,3276],{}," accepté par sont API",[1570,4149,4151],{"id":4150},"delete","DELETE",[12,4153,3568,4154,4156],{},[89,4155,4151],{}," porte bien son nom car elle sert à supprimer une ressource identifiée par son URI.",[12,4158,4159,4160,4162,4163,4165,4166,61],{},"Si la suppression a été effectuée, l'API retourne un ",[89,4161,3550],{}," si le contenu de la réponse inclut un status de la\nsuppression ou un ",[89,4164,3637],{}," sinon. Si la ressource n'existe pas, l'API retourne alors un ",[89,4167,4168],{},"404 Not Found",[12,4170,4171,4172,4175],{},"L'API peut retourner un ",[89,4173,4174],{},"202 Accepted"," si la demande a été prise en compte mais sera traitée\nultérieurement.",[12,4177,4178,4179,4181,4182,4184],{},"Plusieurs appels à la méthode ",[89,4180,4151],{}," sur une même URI ne doivent pas avoir d'effets de bord. La seule différence est\nsur la réponse qui passe à ",[89,4183,4168],{}," une fois que la ressource a été supprimée.",[1570,4186,4188],{"id":4187},"link-et-unlink","LINK et UNLINK",[12,4190,4191,4192,1158,4195,4198],{},"Les méthodes ",[89,4193,4194],{},"LINK",[89,4196,4197],{},"UNLINK"," servent à lier\u002Fdélier des ressources entre elles.",[12,4200,4201,4202,61],{},"Je ne détaillerai pas leurs utilisations car elles sont peut utilisées. Vous pouvez en lire plus sur le site de\nl'",[25,4203,4206],{"href":4204,"rel":4205},"https:\u002F\u002Ftools.ietf.org\u002Fid\u002Fdraft-snell-link-method-01.html",[59],"IETF",[495,4208,4210],{"id":4209},"respecter-les-codes-retour-http","Respecter les codes retour HTTP",[12,4212,4213,4214,4216],{},"La norme REST se base sur les code de retour HTTP. Par exemple un code HTTP ",[89,4215,4168],{}," signifie que la ressource ne\npeut pas être trouvée. Il est important pour les applications clientes de respecter ses codes HTTP.",[12,4218,4219],{},"Commençons par un petit rappel sur les codes de retour.",[12,4221,4222],{},"Ces derniers sont classés en 5 catégories:",[19,4224,4225,4228,4231,4234,4237],{},[22,4226,4227],{},"1xx: Informationnel. Ces codes HTTP sont utilisés pour communiquer des informations sur l'état du transfert à un\nniveau protocolaire (donc pas au niveau applicatif).",[22,4229,4230],{},"2xx: Succès. Ces codes HTTP sont utilisés pour signifier le succès de la requête.",[22,4232,4233],{},"3xx: Redirection. Le client doit prendre des actions supplémentaires afin de terminer la requête.",[22,4235,4236],{},"4xx: Erreur dûe à la requête du client. C'est de la faute du client si la réponse ne peut pas être fournie. Il doit\nla reformuler (si la requête est mauvaise) ou attendre dans le cas d'un rate limit.",[22,4238,4239],{},"5xx: Erreur dûe au serveur. Le serveur n'a pas réussi à répondre au client, et c'est de son entière responsabilité.",[12,4241,4242],{},"Il est important de bien choisir son code de retour pour que le client puisse bien comprendre quelles actions il peut\nentreprendre pour avoir sa réponse. Envoyer un code 5xx alors que la requête ne contient pas tout les champs\nobligatoires est le meilleur moyen pour remplir votre boîte de support.",[12,4244,4245],{},"Inversement renvoyer un code 4xx pour une erreur côté serveur, risque de perturber l'utilisateur qui ne comprendra pas\npourquoi l'API ne fonctionne pas comme attendu.",[12,4247,4248],{},"Passons en revue quelques exemples de codes utilisables (j'ai fait uniquement une sélection de codes qui peuvent avoir un intérêt\npour une API REST) :",[1570,4250,4252],{"id":4251},"_2xx","2xx",[4254,4255,3550],"h5",{"id":4256},"_200-ok",[12,4258,4259],{},"Parfait, pas de problème, c'est le succès complet.",[12,4261,4262,4263,4265],{},"Contrairement à la réponse 204 ci-dessous, il est nécessaire de fournir un corps au message (sauf pour le header\n",[89,4264,3565],{}," qui ne renvoit jamais de corps de message).",[4254,4267,3623],{"id":4268},"_201-created",[12,4270,4271],{},"Une nouvelle ressource a été créé avec succès et ajoutée à la collection. Le corps du message doit être vide.\nLe client peut invalider le cache de la collection.",[12,4273,4274,4275,61],{},"Ce code de retour doit être utilisé une fois que la ressource a été créé par le serveur et l'emplacement de celle-ci\ndoit être retourné dans le header HTTP ",[89,4276,3630],{},[12,4278,4279],{},"Si la ressource ne peut être créé tout de suite, alors voir le header suivant.",[4254,4281,4174],{"id":4282},"_202-accepted",[12,4284,4285],{},"La requête a bien été reçue et sera traitée ultérieurement (via un batch par exemple). La création peut se produire, ou\nune erreur peut arriver ultérieurement.",[12,4287,4288],{},"Dans le corps du message le serveur doit retourner au client le maximum d'informations qui permettent à ce dernier de\nsavoir quand sa demande sera traitée. Par exemple on peut mettre dans le corps du message :",[19,4290,4291,4294,4297,4300],{},[22,4292,4293],{},"Le statut de la demande",[22,4295,4296],{},"La date d'exécution du process",[22,4298,4299],{},"L'URI pour accéder à la demande et obtenir son statut",[22,4301,4302],{},"Une estimation du temps de traitement.",[4254,4304,3637],{"id":4305},"_204-no-content",[12,4307,4308],{},"Le serveur a bien traité la demande mais aucune information ne sera retournée dans le body.",[12,4310,4311,4312,130,4314,130,4316,4318,4319,4321],{},"Ce code HTTP est généralement utilisé avec les méthodes ",[89,4313,3540],{},[89,4315,3543],{},[89,4317,4151],{}," quand le serveur n'a aucune information\n(au dela du header Location) à retourner au client. Il peut également être utilisé avec les méthodes ",[89,4320,154],{}," pour indiquer\nque la ressource existe (contrairement au 404) mais aucune représentation de la ressource n'existe.",[12,4323,4324],{},"La réponse ne doit jamais contenir de contenu.",[12,4326,4327,4328,4330],{},"Sauf si un header (tel que ",[89,4329,3630],{},") l'indique, le client n'a pas besoin de modifier son cache.",[4254,4332,4334],{"id":4333},"_206-partial-content","206 Partial Content",[12,4336,4337],{},"Utilisé avec le header Range dans la requête pour n'envoyer qu'un résultat partiel.",[12,4339,4340,4341,61],{},"Cette réponse peut-être utilisée dans les requêtes de pagination en conjonction uniquement avec le header ",[89,4342,4343],{},"Range",[1570,4345,4347],{"id":4346},"_3xx","3xx",[4254,4349,4351],{"id":4350},"_301-moved-permanently","301 Moved Permanently",[12,4353,4354,4355,4357],{},"La ressource a été déplacée définitivement à un autre endroit. Le header ",[89,4356,3630],{}," contient le nouvel emplacement. Le\nrésultat de la requête peut être caché.",[12,4359,4360],{},"On peut utiliser ce retour pour signifier un changement de l'API suffisamment important pour que cette version ne soit\nplus à jour, mais il est préférable de versionner l'API, plutôt que d'utiliser les redirections.",[4254,4362,4364],{"id":4363},"_302-found","302 Found",[12,4366,4367,4368,4370],{},"La ressource a été déplacée temporairement à un autre endroit. Le header ",[89,4369,3630],{}," contient l'emplacement. La requête\nne peut être cachée que si les headers de caches sont présents.",[12,4372,4373,4374,4376,4377,4379],{},"Le client doit refaire exactement la même requête mais sur le nouvel emplacement défini dans le header ",[89,4375,3630],{},".\nCette règle n'est pas respectée par les navigateurs qui remplacent la méthode par ",[89,4378,154],{}," et c'est pour ça que les codes\n303 et 307 ont été créés.",[4254,4381,4383],{"id":4382},"_303-see-other","303 See Other",[12,4385,4386,4387,3638,4389,4391],{},"Ce code de retour, généralement utilisé avec des ",[89,4388,3543],{},[89,4390,3540],{}," indique que le serveur a terminé son travail mais\npréfère retourner au client une URI où trouver la ressource ou un message de status.",[12,4393,4394,4395,4397],{},"Le client peut alors décider ou non d'aller voir à l'adresse donnée via la méthode ",[89,4396,154],{}," uniquement.",[12,4399,4400],{},"La réponse ne peut pas être modifiée.",[4254,4402,4404],{"id":4403},"_304-not-modified","304 Not Modified",[12,4406,4407,4408,3638,4410,4412,4413,1158,4416,4056],{},"Utilisé généralement avec une requête ",[89,4409,154],{},[89,4411,3565],{}," pour indiquer que la ressource n'a pas été modifiée par rapport aux\ninformations spécifiées dans le header (",[89,4414,4415],{},"If-None-Match",[89,4417,4418],{},"If-Modified-Since",[12,4420,4421],{},"Cette réponse est utilisée pour économiser du temps de traitement et de la bande passsante côté serveur et côté client,\nvu que le client peut reprendre la réponse qui se présente dans son cache.",[12,4423,4424,4425,213,4428,130,4431,130,4434,130,4437,4440,4441,61],{},"Généralement, une réponse précédente a été déjà envoyée au client avec les headers suivants: ",[89,4426,4427],{},"Cache-Control",[89,4429,4430],{},"Content-Location",[89,4432,4433],{},"Date",[89,4435,4436],{},"ETag",[89,4438,4439],{},"Expires",", et ",[89,4442,4443],{},"Vary",[4254,4445,4447],{"id":4446},"_307-temporary-redirect","307 Temporary Redirect",[12,4449,4450,4451,4453],{},"Le serveur indique que la ressource doit être récupérée à un autre endroit en ré-envoyant la requête telle quelle à\nl'URI spécifiée dans le header ",[89,4452,3630],{},". Toute future requête doit continuer à être envoyée au endpoint actuel.",[12,4455,4456,4457,61],{},"La différence entre une réponse 302 et 307 réside dans le fait que la requête ne doit pas être modifiée lors de l'appel\nà l'URI de la réponse retournée par la 307. Avec une 302, certains clients changent incorectement cette valeur en ",[89,4458,154],{},[1570,4460,4462],{"id":4461},"_4xx","4xx",[4254,4464,4466],{"id":4465},"_400-bad-request","400 Bad Request",[12,4468,4469],{},"Le serveur ne comprend pas la requête à cause de sa syntaxe. Le client ne doit pas répéter sa requête sans\nmodification.",[12,4471,4472],{},"Ce message peut être utilisé quand aucun autre message de type 4xx n'est approprié.",[4254,4474,4476],{"id":4475},"_401-unauthorized","401 Unauthorized",[12,4478,4479],{},"La ressource nécessite une authentification. Le client peut répéter sa requête s'il possède le bon header\nd'Authorization.",[12,4481,4482],{},"Si le header d'authentification est présent mais que le serveur retourne une 401, cela signifie que\nl'authentification a été refusée avec ces paramètres.",[4254,4484,4486],{"id":4485},"_403-forbidden","403 Forbidden",[12,4488,4489],{},"L'authentification du client est bien reconnue par le serveur, mais le client ne possède pas les autorisations\nnécessaires pour accéder à la ressource.",[12,4491,4492],{},"Le client ne doit pas répéter la demande.",[4254,4494,4168],{"id":4495},"_404-not-found",[12,4497,4498],{},"La ressource n'existe pas sur le serveur, pour le moment. Le client peut potentiellement refaire la requête plus tard.",[12,4500,4501],{},"Ce status est également utilisé lorsque le serveur ne veut pas révéler pourquoi la requête a été refusée.",[4254,4503,4505],{"id":4504},"_409-conflict","409 Conflict",[12,4507,4508],{},"La requête est bien formée (contrairement à 400) mais elle ne peut pas être complétée dû à l'état de la ressource.",[4254,4510,4512],{"id":4511},"_410-gone","410 Gone",[12,4514,4515],{},"La ressource n'existe pas sur le serveur, mais le serveur sait (par un moyen interne) que la ressource a existé par le\npassé et qu'il n'est pas dans la capacité de fournir une URI où trouver la nouvelle version.",[4254,4517,4519],{"id":4518},"_416-requested-range-not-satisfiable","416 Requested Range Not Satisfiable",[12,4521,4522,4523,4525],{},"L'intervalle utilisé dans le header ",[89,4524,4343],{}," ne peut pas être satisfait.",[4254,4527,4529],{"id":4528},"_429-too-many-requests","429 Too Many Requests",[12,4531,4532],{},"Utilisé pour prévenir le client qu'il a atteint ses limites (rate limit)",[1570,4534,4536],{"id":4535},"_5xx","5xx",[4254,4538,4540],{"id":4539},"_500-internal-server-error","500 Internal Server Error",[12,4542,4543],{},"Le serveur clame haut et fort \"Une erreur inattendue s’est produite\".",[12,4545,4546],{},"L'erreur 500 n'est jamais de la faute du client. Le client peut donc retenter sa requête plus tard.",[4254,4548,4550],{"id":4549},"_501-not-implemented","501 Not Implemented",[12,4552,4553],{},"Pratique pour les API en cours de développement, l'API existe mais n'a pas encore été implémentée.",[4254,4555,4557],{"id":4556},"_503-service-unavailable","503 Service Unavailable",[12,4559,4560],{},"Le serveur n'est pas encore prêt à répondre à la requête (par exemple il démarre ou il s'arrête).",[495,4562,4564],{"id":4563},"utilise-la-notion-de-lien-hypermedia-hateoas","Utilise la notion de lien hypermedia (HATEOAS)",[12,4566,4567],{},"HATEOAS, c'est pour Hypermedia As The Engine Of Application State.",[12,4569,4570],{},"Peu d'API sont REST HATEOAS. Le but est de REST HATEOAS est de lier les réponses REST les unes aux autres tout comme\nle Web lie les pages les unes aux autres. Le principe est donc d'ajouter la notion de lien hypermédia dans une API.",[12,4572,4573],{},"Dans la théorie, un client pourra donc s'adapter aux changement de l'API grâce à ces liens.",[12,4575,4576,4577,4582,4583,61],{},"Pour cela vous pouvez pouvez dans cadre du JSON vous baser sur\n",[25,4578,4581],{"href":4579,"rel":4580},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHypertext_Application_Language",[59],"HAL"," ou sur ",[25,4584,4587],{"href":4585,"rel":4586},"https:\u002F\u002Fjsonapi.org\u002F",[59],"JSON:API",[1570,4589,4587],{"id":4590},"jsonapi",[12,4592,4593],{},"Voici un exemple utilisant la syntaxe JSON:API.",[82,4595,4597],{"className":1165,"code":4596,"language":110,"meta":87,"style":87},"{\n  \"links\": {\n    \"self\": \"\u002Fapi\u002Fhosts\",\n    \"next\": \"\u002Fapi\u002Fhosts?page[offset]=2\",\n    \"last\": \"\u002Fapi\u002Fhosts?page[offset]=10\"\n  },\n  \"data\": [\n    {\n      \"type\": \"hosts\",\n      \"id\": \"pc-ulrich\",\n      \"attributes\": {\n        \"dhcp\": [\n          {\n            \"address\": \"192.168.101\",\n            \"start\": 0,\n            \"end\": 50\n          }\n        ]\n      },\n      \"relationships\": {\n        \"backups\": {\n          \"links\": {\n            \"self\": \"\u002Fapi\u002Fhosts\u002Fpc-ulrich\u002Frelationships\u002Fbackups\",\n            \"related\": \"\u002Fapi\u002Fhosts\u002Fpc-ulrich\u002Fbackups\"\n          },\n          \"data\": [\n            { \"type\": \"backups\", \"id\": 0 },\n            { \"type\": \"backups\", \"id\": 1 },\n            { \"type\": \"backups\", \"id\": 2 },\n            { \"type\": \"backups\", \"id\": 3 }\n          ]\n        }\n      },\n      \"links\": {\n        \"self\": \"\u002Fhosts\u002Fpc-ulrich\"\n      }\n    }\n  ]\n}\n",[89,4598,4599,4603,4610,4622,4634,4644,4648,4656,4661,4673,4685,4692,4699,4704,4716,4727,4737,4742,4747,4751,4758,4765,4772,4784,4794,4799,4806,4830,4850,4870,4890,4895,4899,4903,4910,4920,4924,4928,4933],{"__ignoreMap":87},[92,4600,4601],{"class":94,"line":95},[92,4602,189],{"class":106},[92,4604,4605,4608],{"class":94,"line":144},[92,4606,4607],{"class":109},"  \"links\"",[92,4609,198],{"class":106},[92,4611,4612,4615,4617,4620],{"class":94,"line":151},[92,4613,4614],{"class":109},"    \"self\"",[92,4616,207],{"class":106},[92,4618,4619],{"class":126},"\"\u002Fapi\u002Fhosts\"",[92,4621,213],{"class":106},[92,4623,4624,4627,4629,4632],{"class":94,"line":186},[92,4625,4626],{"class":109},"    \"next\"",[92,4628,207],{"class":106},[92,4630,4631],{"class":126},"\"\u002Fapi\u002Fhosts?page[offset]=2\"",[92,4633,213],{"class":106},[92,4635,4636,4639,4641],{"class":94,"line":192},[92,4637,4638],{"class":109},"    \"last\"",[92,4640,207],{"class":106},[92,4642,4643],{"class":126},"\"\u002Fapi\u002Fhosts?page[offset]=10\"\n",[92,4645,4646],{"class":94,"line":201},[92,4647,1129],{"class":106},[92,4649,4650,4653],{"class":94,"line":216},[92,4651,4652],{"class":109},"  \"data\"",[92,4654,4655],{"class":106},": [\n",[92,4657,4658],{"class":94,"line":229},[92,4659,4660],{"class":106},"    {\n",[92,4662,4663,4666,4668,4671],{"class":94,"line":240},[92,4664,4665],{"class":109},"      \"type\"",[92,4667,207],{"class":106},[92,4669,4670],{"class":126},"\"hosts\"",[92,4672,213],{"class":106},[92,4674,4675,4678,4680,4683],{"class":94,"line":246},[92,4676,4677],{"class":109},"      \"id\"",[92,4679,207],{"class":106},[92,4681,4682],{"class":126},"\"pc-ulrich\"",[92,4684,213],{"class":106},[92,4686,4687,4690],{"class":94,"line":359},[92,4688,4689],{"class":109},"      \"attributes\"",[92,4691,198],{"class":106},[92,4693,4694,4697],{"class":94,"line":377},[92,4695,4696],{"class":109},"        \"dhcp\"",[92,4698,4655],{"class":106},[92,4700,4701],{"class":94,"line":382},[92,4702,4703],{"class":106},"          {\n",[92,4705,4706,4709,4711,4714],{"class":94,"line":389},[92,4707,4708],{"class":109},"            \"address\"",[92,4710,207],{"class":106},[92,4712,4713],{"class":126},"\"192.168.101\"",[92,4715,213],{"class":106},[92,4717,4718,4721,4723,4725],{"class":94,"line":407},[92,4719,4720],{"class":109},"            \"start\"",[92,4722,207],{"class":106},[92,4724,1182],{"class":338},[92,4726,213],{"class":106},[92,4728,4729,4732,4734],{"class":94,"line":424},[92,4730,4731],{"class":109},"            \"end\"",[92,4733,207],{"class":106},[92,4735,4736],{"class":338},"50\n",[92,4738,4739],{"class":94,"line":442},[92,4740,4741],{"class":106},"          }\n",[92,4743,4744],{"class":94,"line":447},[92,4745,4746],{"class":106},"        ]\n",[92,4748,4749],{"class":94,"line":614},[92,4750,3059],{"class":106},[92,4752,4753,4756],{"class":94,"line":774},[92,4754,4755],{"class":109},"      \"relationships\"",[92,4757,198],{"class":106},[92,4759,4760,4763],{"class":94,"line":780},[92,4761,4762],{"class":109},"        \"backups\"",[92,4764,198],{"class":106},[92,4766,4767,4770],{"class":94,"line":785},[92,4768,4769],{"class":109},"          \"links\"",[92,4771,198],{"class":106},[92,4773,4774,4777,4779,4782],{"class":94,"line":794},[92,4775,4776],{"class":109},"            \"self\"",[92,4778,207],{"class":106},[92,4780,4781],{"class":126},"\"\u002Fapi\u002Fhosts\u002Fpc-ulrich\u002Frelationships\u002Fbackups\"",[92,4783,213],{"class":106},[92,4785,4786,4789,4791],{"class":94,"line":799},[92,4787,4788],{"class":109},"            \"related\"",[92,4790,207],{"class":106},[92,4792,4793],{"class":126},"\"\u002Fapi\u002Fhosts\u002Fpc-ulrich\u002Fbackups\"\n",[92,4795,4796],{"class":94,"line":805},[92,4797,4798],{"class":106},"          },\n",[92,4800,4801,4804],{"class":94,"line":810},[92,4802,4803],{"class":109},"          \"data\"",[92,4805,4655],{"class":106},[92,4807,4808,4811,4814,4816,4819,4821,4824,4826,4828],{"class":94,"line":818},[92,4809,4810],{"class":106},"            { ",[92,4812,4813],{"class":109},"\"type\"",[92,4815,207],{"class":106},[92,4817,4818],{"class":126},"\"backups\"",[92,4820,130],{"class":106},[92,4822,4823],{"class":109},"\"id\"",[92,4825,207],{"class":106},[92,4827,1182],{"class":338},[92,4829,404],{"class":106},[92,4831,4832,4834,4836,4838,4840,4842,4844,4846,4848],{"class":94,"line":823},[92,4833,4810],{"class":106},[92,4835,4813],{"class":109},[92,4837,207],{"class":106},[92,4839,4818],{"class":126},[92,4841,130],{"class":106},[92,4843,4823],{"class":109},[92,4845,207],{"class":106},[92,4847,3335],{"class":338},[92,4849,404],{"class":106},[92,4851,4852,4854,4856,4858,4860,4862,4864,4866,4868],{"class":94,"line":828},[92,4853,4810],{"class":106},[92,4855,4813],{"class":109},[92,4857,207],{"class":106},[92,4859,4818],{"class":126},[92,4861,130],{"class":106},[92,4863,4823],{"class":109},[92,4865,207],{"class":106},[92,4867,1194],{"class":338},[92,4869,404],{"class":106},[92,4871,4872,4874,4876,4878,4880,4882,4884,4886,4888],{"class":94,"line":836},[92,4873,4810],{"class":106},[92,4875,4813],{"class":109},[92,4877,207],{"class":106},[92,4879,4818],{"class":126},[92,4881,130],{"class":106},[92,4883,4823],{"class":109},[92,4885,207],{"class":106},[92,4887,3716],{"class":338},[92,4889,439],{"class":106},[92,4891,4892],{"class":94,"line":844},[92,4893,4894],{"class":106},"          ]\n",[92,4896,4897],{"class":94,"line":852},[92,4898,1886],{"class":106},[92,4900,4901],{"class":94,"line":857},[92,4902,3059],{"class":106},[92,4904,4905,4908],{"class":94,"line":862},[92,4906,4907],{"class":109},"      \"links\"",[92,4909,198],{"class":106},[92,4911,4912,4915,4917],{"class":94,"line":870},[92,4913,4914],{"class":109},"        \"self\"",[92,4916,207],{"class":106},[92,4918,4919],{"class":126},"\"\u002Fhosts\u002Fpc-ulrich\"\n",[92,4921,4922],{"class":94,"line":878},[92,4923,1896],{"class":106},[92,4925,4926],{"class":94,"line":886},[92,4927,344],{"class":106},[92,4929,4930],{"class":94,"line":891},[92,4931,4932],{"class":106},"  ]\n",[92,4934,4935],{"class":94,"line":896},[92,4936,249],{"class":106},[12,4938,4939],{},"Comme vous pouvez voir, l'idée est de pouvoir à la lecture de la réponse comprendre automatiquement les relations\nentre les différents composants de l'API.",[1570,4941,4943],{"id":4942},"faut-il-faire-une-api-hateaos","Faut-il faire une API HATEAOS",[12,4945,4946],{},"Le but d'une telle API est de permettre au client de la découvrir automatiquement. Il serait envisageable alors de\ncréer un client capable, sur la base de l'API et des types de données qui y sont décrits, de pouvoir génerer une IHM\ndirectement.",[12,4948,4949],{},"Même si le principe d'une API HATEAOS est louable, le temps d'écriture d'une telle API est très consommatrice de temps.\nDe plus, généralement, un client est généralement créé pour des utilisateurs et non par une API.",[12,4951,4952],{},"Une bonne documentation peut amplement suffir pour développer une application, et comprendre les interactions entre\nles différentes APIs ?",[495,4954,4956],{"id":4955},"générér-sa-documentation-avec-swagger","Générér sa documentation avec Swagger",[12,4958,4959],{},"Il existe de très bon outils pour écrire sa documentation.",[12,4961,4962],{},"L'un d'entre eux s'appelle Swagger basé sur la spécification OpenAPI. Il y a alors plusieurs manières de faire pour\ngénérer sa documention qui dépendent également des capacités du language.",[19,4964,4965,4968,4974],{},[22,4966,4967],{},"Sur un language typé, on peut générer sa documentation à partir du code. Il est alors possible via des annotations\nou des commentaires du code de définir les informations.",[22,4969,4970,4971,4973],{},"Il est également possible d'écrire le Swagger directement, et de générer le code après coup. Cela à l'avantage de\npouvoir travailler l'API avant sans écrire une ligne de code.",[2621,4972],{},"L'important sur ces deux méthodes c'est que le code de l'application et le code du Swagger soient synchronisé.",[22,4975,4976],{},"Si l'API a déjà été développée, il est beaucoup plus dur de générer une API par dessus. Surtout si le language est\nnon typé, et que toute la description doit être synchronisée à la main avec les objets de l'application, il y aura\nalors un risque que la documentation ne soit pas à jour.",[12,4978,4979],{},[68,4980],{"alt":4981,"src":4982},"Swagger WoodstockBackup","\u002FProgrammation\u002Fcreation-api\u002Fdocumentation-swagger.png",[495,4984,4986],{"id":4985},"cacher-ses-requêtes","Cacher ses requêtes",[12,4988,4989],{},"Il est important côté client de cacher les requêtes afin de rester en dessous du Rate Limit, et aussi d'économiser les\nressources de bande passante de l'application.",[12,4991,4992],{},"Comme dit plus haut par contre, il ne faut pas que le serveur prenne comme hypothèse que le client va cacher les\nrequêtes au risque de se faire saturer par des requêtes. Côté serveur, il faut définir des limites d'accès de l'API,\net surtout définir également les headers de Cache qui vont bien afin d'avertir le client de la durée pendant laquelle\nil doit garder la requête.",[12,4994,4995],{},"En effet seul le serveur connaît fonctionnellement combien de temps une donnée peut être gardée.",[495,4997,4999],{"id":4998},"quelques-références","Quelques références",[19,5001,5002,5008,5014,5020,5026],{},[22,5003,5004],{},[25,5005,5006],{"href":5006,"rel":5007},"https:\u002F\u002Fzestedesavoir.com\u002Ftutoriels\u002F299\u002Fla-theorie-rest-restful-et-hateoas\u002F",[59],[22,5009,5010],{},[25,5011,5012],{"href":5012,"rel":5013},"https:\u002F\u002Fwww.vinaysahni.com\u002Fbest-practices-for-a-pragmatic-restful-api",[59],[22,5015,5016],{},[25,5017,5018],{"href":5018,"rel":5019},"https:\u002F\u002Fstackoverflow.blog\u002F2020\u002F03\u002F02\u002Fbest-practices-for-rest-api-design\u002F",[59],[22,5021,5022],{},[25,5023,5024],{"href":5024,"rel":5025},"https:\u002F\u002Frestfulapi.net\u002Fhttp-methods\u002F",[59],[22,5027,5028],{},[25,5029,5030],{"href":5030,"rel":5031},"https:\u002F\u002Fwilliamdurand.fr\u002F2014\u002F02\u002F14\u002Fplease-do-not-patch-like-an-idiot\u002F",[59],[5033,5034,5037,5042],"section",{"className":5035,"dataFootnotes":87},[5036],"footnotes",[45,5038,5041],{"className":5039,"id":3524},[5040],"sr-only","Footnotes",[5043,5044,5045,5059,5073],"ol",{},[22,5046,5048,5049,5051,5052],{"id":5047},"user-content-fn-getter","Ce que j'ai déjà vu faire. Pour préciser les choses, un getter doit être ",[3286,5050,3580],{},". Cela signifie que l'appel ",[25,5053,5058],{"href":5054,"ariaLabel":5055,"className":5056,"dataFootnoteBackref":87},"#user-content-fnref-getter","Back to reference 1",[5057],"data-footnote-backref","↩",[22,5060,5062,5067,5068],{"id":5061},"user-content-fn-idempotent",[25,5063,5066],{"href":5064,"rel":5065},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FIdempotence#:~:text=En%20math%C3%A9matiques%20et%20en%20informatique,applique%20une%20ou%20plusieurs%20fois.",[59],"En mathématiques et en informatique, l'idempotence signifie qu'une opération a le même effet qu'on l'applique une ou plusieurs fois.",": Dans notre cas cela signifie qu'elles n'ont pas d'effets de bord. ",[25,5069,5058],{"href":5070,"ariaLabel":5071,"className":5072,"dataFootnoteBackref":87},"#user-content-fnref-idempotent","Back to reference 2",[5057],[22,5074,5076,5077],{"id":5075},"user-content-fn-atomic","Ce qui veux dire que tout est fait ou rien. ",[25,5078,5058],{"href":5079,"ariaLabel":5080,"className":5081,"dataFootnoteBackref":87},"#user-content-fnref-atomic","Back to reference 3",[5057],[3176,5083,5084],{},"html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}html pre.shiki code .sLaUg, html code.shiki .sLaUg{--shiki-default:#FFFFFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}",{"title":87,"searchDepth":144,"depth":144,"links":5086},[5087,5096],{"id":3263,"depth":144,"text":3264,"children":5088},[5089,5090,5091,5092,5093,5094,5095],{"id":3280,"depth":151,"text":3281},{"id":3498,"depth":151,"text":3499},{"id":4209,"depth":151,"text":4210},{"id":4563,"depth":151,"text":4564},{"id":4955,"depth":151,"text":4956},{"id":4985,"depth":151,"text":4986},{"id":4998,"depth":151,"text":4999},{"id":3524,"depth":144,"text":5041},"2020-11-02",{"type":9,"value":5099},[5100,5102,5104,5118,5120,5122,5124],[12,5101,14],{},[12,5103,17],{},[19,5105,5106,5110,5114],{},[22,5107,5108],{},[25,5109,28],{"href":27},[22,5111,5112],{},[25,5113,34],{"href":33},[22,5115,5116],{},[25,5117,40],{"href":39},[45,5119,3264],{"id":3263},[12,5121,3267],{},[12,5123,3270],{},[12,5125,3273,5126,3277],{},[89,5127,3276],{},{"planet":147},"\u002Fpost\u002Fcreation-api-2",{"title":3240,"description":14},"creation-api-2","posts\u002FProgrammation\u002F2020-11-02_creation-api-2",[3233,509,3234,3235,3236],"qRIJgww-9nhPwt3g0Lbn9M52urBRQ1PwFy4IGXjFHQI",{"id":5136,"title":5137,"author":7,"body":5138,"category":3200,"categorySlug":3201,"date":5998,"description":14,"excerpt":5999,"extension":3225,"location":3226,"meta":6023,"navigation":147,"path":6024,"published":147,"seo":6025,"slug":6026,"stem":6027,"tags":6028,"timeToRead":359,"__hash__":6029},"posts\u002Fposts\u002FProgrammation\u002F2020-10-11_creation-api-1.md","Comment créer une bonne API Web - Partie 1",{"type":9,"value":5139,"toc":5988},[5140,5142,5145,5159,5162,5165,5168,5171,5177,5194,5197,5214,5217,5221,5232,5235,5241,5244,5247,5250,5253,5256,5259,5263,5266,5269,5274,5277,5297,5300,5328,5335,5338,5342,5345,5348,5367,5370,5373,5467,5478,5482,5485,5498,5613,5619,5622,5727,5730,5736,5860,5864,5867,5917,5921,5924,5927,5930,5933,5936,5939,5961,5964,5966,5969,5985],[12,5141,14],{},[12,5143,5144],{},"Je souhaite vous parler de l'écriture d'API. Je vais découper cet article en 3 parties:",[19,5146,5147,5151,5155],{},[22,5148,5149],{},[25,5150,28],{"href":27},[22,5152,5153],{},[25,5154,34],{"href":33},[22,5156,5157],{},[25,5158,40],{"href":39},[12,5160,5161],{},"Je me limiterai au WEB et aux normes REST et GraphQL même s'il y a d'autres normes\u002Fframeworks pour écrire des API.",[12,5163,5164],{},"Commençons donc par le début ! Qu'est-ce qu'une API ? API signifie Application Programming Interface. C'est une\ninterface de programmation prête à être consommée par un client.",[12,5166,5167],{},"Par exemple quand on développe une librairie (en C\u002FC++, voir un module NodeJS, ...), on définit une liste de méthodes\nque l'on rend publique et qui sont utilisées pour appeler cette librairie. Ces méthodes sont alors utilisées par des\nclients. L'API c'est ce contrat entre la librairie et le client.",[12,5169,5170],{},"Dans le cadre d'un site internet, l'API est le contrat entre un site internet et le client qui l'appelle. Comme tout\ncontrat il faut que celui-ci soit clairement défini si on veut que ça se passe bien entre les clients et les services.",[12,5172,5173,5174,1510],{},"Il est important pour une bonne API ",[1469,5175,5176],{},"public",[19,5178,5179,5182,5185,5188,5191],{},[22,5180,5181],{},"d'être stable dans le temps, ceci afin qu'un client qui utilise une API puisse continuer à l'utiliser sans devoir\ntout réécrire entre chaque version,",[22,5183,5184],{},"d'être fiable, cela va sans dire mais une API qui retourne des erreurs une fois sur deux ou qui ne fait pas ce qu'on\nlui demande va vite être abandonnée,",[22,5186,5187],{},"d'être simple d'utilisation et bien documentée, afin que le développeur passe le moins de temps possible à apprendre à\nl'utiliser,",[22,5189,5190],{},"d'avoir une bonne gestion des erreurs,",[22,5192,5193],{},"d'être performante.",[12,5195,5196],{},"Dans le monde du web, il existe plusieurs protocole\u002Fnorme afin de faciliter l'écriture de ce contrat d'interface, on\npeut citer par exemple:",[19,5198,5199,5202,5205,5208,5211],{},[22,5200,5201],{},"SOAP (Simple Object Access Protocol)",[22,5203,5204],{},"REST (Representational State Transfer)",[22,5206,5207],{},"XML-RPC (XML Remote Procedure Call)",[22,5209,5210],{},"GraphQL (créé par Facebook)",[22,5212,5213],{},"Falcor (qui est un protocole basé sur une implémentation de référence ; créé par Netflix)",[12,5215,5216],{},"Il existe plein de manières d'écrire des API. On peut aussi en écrire à base de socket réseau. Dans cet article je ne\nvais pas détailler toutes les méthodes possibles mais je vais présenter uniquement une selection d'API Web (Rest et\nGraphQL).",[45,5218,5220],{"id":5219},"pourquoi-écrire-une-api-web","Pourquoi écrire une API Web",[12,5222,5223,5224,5231],{},"Pour commencer si votre application web est une SPA",[3518,5225,5226],{},[25,5227,3335],{"href":5228,"ariaDescribedBy":5229,"dataFootnoteRef":87,"id":5230},"#user-content-fn-spa",[3524],"user-content-fnref-spa",", il sera nécessaire d'avoir un contrat d'interface entre le client (SPA)\net le serveur (sauf s'il n'y a pas de serveur...). Ce contrat est généralement implicite.",[12,5233,5234],{},"Même pour un site web statique, ou pour un site dont les pages HTML sont générées coté serveur, nous pouvons considérer\nque l'API (l'interface de programation exposée) de l'application sont ses pages HTML. En effet, il existe des web\nscrapper qui lisent le contenu HTML des pages afin d'en lire le contenu (voir même qui en expose une API plus compréhensible).",[12,5236,5237,5238,61],{},"Du coup, la question la plus importante est ",[3286,5239,5240],{},"\"Faut-il que mon interface de programmation soit publique ou privée ? Faut-il qu'elle\nsoit simple d'utilisation\"",[12,5242,5243],{},"Pour un site, dont les pages sont générées côté serveur et dont le rendu est en HTML, l'API n'est ni simple\nd'utilisation, ni documentable. Pour une SPA (Single Page Application), l'écriture d'une API utilisable par un\nprogramme est la norme et elle sera alors consumée par Angular, Vue, React, jQuery ....",[12,5245,5246],{},"Souhaite-t-on alors que notre application soit utilisable publiquement ?",[12,5248,5249],{},"Souhaite-t-on que d'autres applications développées par des tiers puisse utiliser notre API, librement ? gratuitement ?",[12,5251,5252],{},"Si oui, dois-je mettre en place une politique sur le nombre de requêtes maximales par utilisateur (OUI) ? Et de combien ?\nVais-je pouvoir supporter la charge ?",[12,5254,5255],{},"Prenez en considération dans votre décision que de tout facon, les gens feront ce qu'ils veulent. Après tout, si un\nhumain peut visualiser la page, un programme le peu également (WebScrapper).",[12,5257,5258],{},"communique avec le serveur via ses API. C'est en quelque sorte une application lourde mais écrite en JavaScript. Au démarrage du\nsite l'interface (le client) est chargée en mémoire (par morceau si on prend le lazy loading) et l'interface est ensuite générée à la\nvolée. Les pages HTML sont donc générées côté client et non coté serveur.",[45,5260,5262],{"id":5261},"quest-quune-bonne-documentation","Qu'est qu'une bonne documentation",[12,5264,5265],{},"Si on veux que l'API soit utilisée, il faut qu'elle soit bien documentée et surtout à jour. Sans cela personne ne voudra\nl'utiliser.",[12,5267,5268],{},"Pour faciliter l'écriture de la documentation certains outils permettent de générer celle-ci à partir du code (des\ntypes de données, du nom des méthodes, d'annotation ...), ce qui permet de maintenir plus facilement sa documentation avec\nl'évolution du code.\nPar contre, l'utilisation de générateur ne dédouanne pas de l'écriture de la partie qui ne peux pas être documentée (comme\nles descriptions, les exemples, ...).",[12,5270,5271],{},[68,5272],{"alt":4981,"src":5273},"\u002FProgrammation\u002Fcreation-api\u002Fdocumentation-swagger-head.png",[12,5275,5276],{},"La documentation doit contenir :",[19,5278,5279,5282,5285,5288,5291,5294],{},[22,5280,5281],{},"des exemples d'appels",[22,5283,5284],{},"des descriptions détaillées de ce que fait chaque point d'entrée,",[22,5286,5287],{},"quels type de données sont présentes en entrée et comment les utiliser,",[22,5289,5290],{},"mais aussi le type des données que l'on a en sortie,",[22,5292,5293],{},"les erreurs que peut retourner le endpoint et dans quelles circonstances,",[22,5295,5296],{},"comment seront traitées les données en entrée (batch, immédiat).",[12,5298,5299],{},"Au début de la documentation, ne pas oublier de documenter:",[19,5301,5302,5305],{},[22,5303,5304],{},"le fonctionnement de l'authentification",[22,5306,5307,5308],{},"les règles d'utilisation\n",[19,5309,5310,5313,5316,5325],{},[22,5311,5312],{},"ce qu'on est autorisé à faire",[22,5314,5315],{},"ce qu'on n'est pas autorisé à faire",[22,5317,5318,5319,5324],{},"le nombre d'appel par seconde\u002Fminutes\u002Fheures\u002F...et quels sont les headers HTTP permettant de récuperer le résultat\n(par exemple dans l'",[25,5320,5323],{"href":5321,"rel":5322},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Frest\u002Foverview\u002Fresources-in-the-rest-api#rate-limiting",[59],"API Github",",\non peut retrouver les headers: X-RateLimit-Limit, X-RateLimit-Remaining)",[22,5326,5327],{},"le fonctionnement de la pagination dans l'API",[12,5329,5330,5331,5334],{},"Il peut être efficace d'avoir une page au démarrage de l'API (",[3286,5332,5333],{},"Quick Start",") donnant un première exemple complet d'un\npremier appel à l'API.",[12,5336,5337],{},"Dans le développement de l'API, l'écriture de la documentation est aussi important que l'écriture du code.",[45,5339,5341],{"id":5340},"gestion-des-erreurs","Gestion des erreurs",[12,5343,5344],{},"Pour la gestion des erreurs, l'API doit retourner le maximum d'informations pour que le développeur puisse comprendre\nl'erreur et effectuer une correction mais également suffisament d'informations pour que le développeur puisse les utiliser\ndans son programme pour retourner les problèmes fonctionnels à l'utilisateur final.",[12,5346,5347],{},"Par exemple dans une API Rest, il est important que les différents cas d'erreur soit explicités:",[19,5349,5350,5353,5359,5362],{},[22,5351,5352],{},"400 - BadRequest: The request is malformed.",[22,5354,5355,5356,5358],{},"404 - NotFound: The resource ",[3286,5357,2516],{}," can't be found",[22,5360,5361],{},"401 - Unauthorized: The user is not authentified.",[22,5363,5364,5365,61],{},"403 - Forbidden: The user is not authorized to access to the resource ",[3286,5366,2516],{},[12,5368,5369],{},"Ceci afin d'aider les développeurs à traiter tout les cas d'erreurs et d'afficher un message cohérent à l'utilisateur.",[12,5371,5372],{},"Lors d'une erreur, le retour de la requête doit toujours être le même. Voici un exemple de requête:",[82,5374,5376],{"className":1165,"code":5375,"language":110,"meta":87,"style":87},"GET http:\u002F\u002F192.168.101.205:3000\u002Fapi\u002Fhosts\u002Funknownhost\n\n{\n  \"statusCode\": 404,\n  \"message\": \"Can't find configuration for the host with name unknownhost\",\n  \"error\": \"Not Found\",\n  \"errorCode\": \"HOST_NOT_FOUND\",\n  \"params\": {\n      \"host\": \"unknownhost\"\n  }\n}\n",[89,5377,5378,5386,5390,5394,5406,5418,5430,5442,5449,5459,5463],{"__ignoreMap":87},[92,5379,5380,5383],{"class":94,"line":95},[92,5381,5382],{"class":106},"GET http:",[92,5384,5385],{"class":275},"\u002F\u002F192.168.101.205:3000\u002Fapi\u002Fhosts\u002Funknownhost\n",[92,5387,5388],{"class":94,"line":144},[92,5389,148],{"emptyLinePlaceholder":147},[92,5391,5392],{"class":94,"line":151},[92,5393,189],{"class":106},[92,5395,5396,5399,5401,5404],{"class":94,"line":186},[92,5397,5398],{"class":109},"  \"statusCode\"",[92,5400,207],{"class":106},[92,5402,5403],{"class":338},"404",[92,5405,213],{"class":106},[92,5407,5408,5411,5413,5416],{"class":94,"line":192},[92,5409,5410],{"class":109},"  \"message\"",[92,5412,207],{"class":106},[92,5414,5415],{"class":126},"\"Can't find configuration for the host with name unknownhost\"",[92,5417,213],{"class":106},[92,5419,5420,5423,5425,5428],{"class":94,"line":201},[92,5421,5422],{"class":109},"  \"error\"",[92,5424,207],{"class":106},[92,5426,5427],{"class":126},"\"Not Found\"",[92,5429,213],{"class":106},[92,5431,5432,5435,5437,5440],{"class":94,"line":216},[92,5433,5434],{"class":109},"  \"errorCode\"",[92,5436,207],{"class":106},[92,5438,5439],{"class":126},"\"HOST_NOT_FOUND\"",[92,5441,213],{"class":106},[92,5443,5444,5447],{"class":94,"line":229},[92,5445,5446],{"class":109},"  \"params\"",[92,5448,198],{"class":106},[92,5450,5451,5454,5456],{"class":94,"line":240},[92,5452,5453],{"class":109},"      \"host\"",[92,5455,207],{"class":106},[92,5457,5458],{"class":126},"\"unknownhost\"\n",[92,5460,5461],{"class":94,"line":246},[92,5462,243],{"class":106},[92,5464,5465],{"class":94,"line":359},[92,5466,249],{"class":106},[12,5468,5469,5470,5473,5474,5477],{},"Dans l'exemple ci-dessus, ",[89,5471,5472],{},"errorCode"," peut être utilisé pour indiquer à l'utilisateur, un champ obligatoire, (avec dans ",[89,5475,5476],{},"params"," le\nnom du champ) ou une règle de gestion mal utilisée.\nCe code erreur pourra alors être remontée à l'utilisateur final directement pour rendre l'interface plus réactive.",[45,5479,5481],{"id":5480},"cohérence","Cohérence",[12,5483,5484],{},"Pour faire une bonne documentation il faut également de bonnes bases. Pour cela il faut que l'API soit cohérente dans\nson fonctionnement. La cohérence est importante tant au niveau des paramètres que dans le contenu de la requête ou de la\nréponse.",[12,5486,5487,5488,5491,5492,1158,5495,1510],{},"Par exemple, sur une API REST, imaginons que pour gérer la pagination un ",[3286,5489,5490],{},"endpoint"," demande les paramètres ",[89,5493,5494],{},"skip",[89,5496,5497],{},"limit",[82,5499,5501],{"className":1165,"code":5500,"language":110,"meta":87,"style":87},"GET \u002Fhosts\u002Fpc-ulrich\u002Fbackups?skip=5&limit=10\n\n{\n    \"skip\": 5,\n    \"limit\": 10,\n    \"size\": 100,\n    \"result\": [\n        {\n            \"id\": 1\n        },\n        {\n            \"id\": 2\n        }\n    ]\n}\n",[89,5502,5503,5517,5521,5525,5536,5548,5560,5567,5572,5582,5587,5591,5600,5604,5609],{"__ignoreMap":87},[92,5504,5505,5508,5511,5514],{"class":94,"line":95},[92,5506,5507],{"class":106},"GET \u002Fhosts\u002Fpc-ulrich\u002Fbackups?skip=",[92,5509,5510],{"class":338},"5",[92,5512,5513],{"class":106},"&limit=",[92,5515,5516],{"class":338},"10\n",[92,5518,5519],{"class":94,"line":144},[92,5520,148],{"emptyLinePlaceholder":147},[92,5522,5523],{"class":94,"line":151},[92,5524,189],{"class":106},[92,5526,5527,5530,5532,5534],{"class":94,"line":186},[92,5528,5529],{"class":109},"    \"skip\"",[92,5531,207],{"class":106},[92,5533,5510],{"class":338},[92,5535,213],{"class":106},[92,5537,5538,5541,5543,5546],{"class":94,"line":192},[92,5539,5540],{"class":109},"    \"limit\"",[92,5542,207],{"class":106},[92,5544,5545],{"class":338},"10",[92,5547,213],{"class":106},[92,5549,5550,5553,5555,5558],{"class":94,"line":201},[92,5551,5552],{"class":109},"    \"size\"",[92,5554,207],{"class":106},[92,5556,5557],{"class":338},"100",[92,5559,213],{"class":106},[92,5561,5562,5565],{"class":94,"line":216},[92,5563,5564],{"class":109},"    \"result\"",[92,5566,4655],{"class":106},[92,5568,5569],{"class":94,"line":229},[92,5570,5571],{"class":106},"        {\n",[92,5573,5574,5577,5579],{"class":94,"line":240},[92,5575,5576],{"class":109},"            \"id\"",[92,5578,207],{"class":106},[92,5580,5581],{"class":338},"1\n",[92,5583,5584],{"class":94,"line":246},[92,5585,5586],{"class":106},"        },\n",[92,5588,5589],{"class":94,"line":359},[92,5590,5571],{"class":106},[92,5592,5593,5595,5597],{"class":94,"line":377},[92,5594,5576],{"class":109},[92,5596,207],{"class":106},[92,5598,5599],{"class":338},"2\n",[92,5601,5602],{"class":94,"line":382},[92,5603,1886],{"class":106},[92,5605,5606],{"class":94,"line":389},[92,5607,5608],{"class":106},"    ]\n",[92,5610,5611],{"class":94,"line":407},[92,5612,249],{"class":106},[12,5614,5615,5616,5618],{},"N'utilisez pas pour un autre ",[3286,5617,5490],{}," de votre application des paramètres différents. De même, n'utilisez pas dans\nle résultat de la requête des noms différents de ceux utilisés dans les paramètres.",[12,5620,5621],{},"Enfin structurez le contenu de la réponse toujours de la même manière, pour que vos utilisateurs puissent développer\ndes méthodes génériques lors de l'utilisation de votre API (Par exemple une méthode de pagination générique).",[82,5623,5625],{"className":1165,"code":5624,"language":110,"meta":87,"style":87},"GET \u002Fhosts?start=5&size=10\n\n{\n    \"debut\": 5,\n    \"taille\": 10,\n    \"fin\": 100,\n    \"liste\": [\n        {\n            \"id\": 1\n        },\n        {\n            \"id\": 2\n        }\n    ]\n}\n",[89,5626,5627,5639,5643,5647,5658,5669,5680,5687,5691,5699,5703,5707,5715,5719,5723],{"__ignoreMap":87},[92,5628,5629,5632,5634,5637],{"class":94,"line":95},[92,5630,5631],{"class":106},"GET \u002Fhosts?start=",[92,5633,5510],{"class":338},[92,5635,5636],{"class":106},"&size=",[92,5638,5516],{"class":338},[92,5640,5641],{"class":94,"line":144},[92,5642,148],{"emptyLinePlaceholder":147},[92,5644,5645],{"class":94,"line":151},[92,5646,189],{"class":106},[92,5648,5649,5652,5654,5656],{"class":94,"line":186},[92,5650,5651],{"class":109},"    \"debut\"",[92,5653,207],{"class":106},[92,5655,5510],{"class":338},[92,5657,213],{"class":106},[92,5659,5660,5663,5665,5667],{"class":94,"line":192},[92,5661,5662],{"class":109},"    \"taille\"",[92,5664,207],{"class":106},[92,5666,5545],{"class":338},[92,5668,213],{"class":106},[92,5670,5671,5674,5676,5678],{"class":94,"line":201},[92,5672,5673],{"class":109},"    \"fin\"",[92,5675,207],{"class":106},[92,5677,5557],{"class":338},[92,5679,213],{"class":106},[92,5681,5682,5685],{"class":94,"line":216},[92,5683,5684],{"class":109},"    \"liste\"",[92,5686,4655],{"class":106},[92,5688,5689],{"class":94,"line":229},[92,5690,5571],{"class":106},[92,5692,5693,5695,5697],{"class":94,"line":240},[92,5694,5576],{"class":109},[92,5696,207],{"class":106},[92,5698,5581],{"class":338},[92,5700,5701],{"class":94,"line":246},[92,5702,5586],{"class":106},[92,5704,5705],{"class":94,"line":359},[92,5706,5571],{"class":106},[92,5708,5709,5711,5713],{"class":94,"line":377},[92,5710,5576],{"class":109},[92,5712,207],{"class":106},[92,5714,5599],{"class":338},[92,5716,5717],{"class":94,"line":382},[92,5718,1886],{"class":106},[92,5720,5721],{"class":94,"line":389},[92,5722,5608],{"class":106},[92,5724,5725],{"class":94,"line":407},[92,5726,249],{"class":106},[12,5728,5729],{},"Cela aurait comme conséquence de perdre les utilisateurs (développeurs) qui utiliseront votre API.",[12,5731,5732,5733,5735],{},"Les structures, les champs utilisés sur les différents ",[3286,5734,5490],{}," de votre API doivent toujours suivre la même\nlogique et par exemple:",[19,5737,5738,5744,5854,5857],{},[22,5739,5740,5741,1001],{},"nommer toujours les champs ayant la même fonction de la même manière: toujours nommer la date de création ",[89,5742,5743],{},"createdAt",[22,5745,5746,5747,5750,5806,5808,5809],{},"toujours retourner la resource demandé directement ",[3286,5748,5749],{},"ou inversement toujours encapsuler la resource demandée dans un\nsous-objet",[82,5751,5753],{"className":1165,"code":5752,"language":110,"meta":87,"style":87},"{\n  \"result\": [\n    {\n      \"id\": 1\n    },\n    {\n      \"id\": 2\n    }\n  ]\n}\n",[89,5754,5755,5759,5766,5770,5778,5782,5786,5794,5798,5802],{"__ignoreMap":87},[92,5756,5757],{"class":94,"line":95},[92,5758,189],{"class":106},[92,5760,5761,5764],{"class":94,"line":144},[92,5762,5763],{"class":109},"  \"result\"",[92,5765,4655],{"class":106},[92,5767,5768],{"class":94,"line":151},[92,5769,4660],{"class":106},[92,5771,5772,5774,5776],{"class":94,"line":186},[92,5773,4677],{"class":109},[92,5775,207],{"class":106},[92,5777,5581],{"class":338},[92,5779,5780],{"class":94,"line":192},[92,5781,1098],{"class":106},[92,5783,5784],{"class":94,"line":201},[92,5785,4660],{"class":106},[92,5787,5788,5790,5792],{"class":94,"line":216},[92,5789,4677],{"class":109},[92,5791,207],{"class":106},[92,5793,5599],{"class":338},[92,5795,5796],{"class":94,"line":229},[92,5797,344],{"class":106},[92,5799,5800],{"class":94,"line":240},[92,5801,4932],{"class":106},[92,5803,5804],{"class":94,"line":246},[92,5805,249],{"class":106},[2621,5807],{},"vs",[82,5810,5812],{"className":1165,"code":5811,"language":110,"meta":87,"style":87},"[\n  {\n    \"id\": 1\n  },\n  {\n    \"id\": 2\n  }\n]\n",[89,5813,5814,5818,5822,5830,5834,5838,5846,5850],{"__ignoreMap":87},[92,5815,5816],{"class":94,"line":95},[92,5817,3370],{"class":106},[92,5819,5820],{"class":94,"line":144},[92,5821,3375],{"class":106},[92,5823,5824,5826,5828],{"class":94,"line":151},[92,5825,3380],{"class":109},[92,5827,207],{"class":106},[92,5829,5581],{"class":338},[92,5831,5832],{"class":94,"line":186},[92,5833,1129],{"class":106},[92,5835,5836],{"class":94,"line":192},[92,5837,3375],{"class":106},[92,5839,5840,5842,5844],{"class":94,"line":201},[92,5841,3380],{"class":109},[92,5843,207],{"class":106},[92,5845,5599],{"class":338},[92,5847,5848],{"class":94,"line":216},[92,5849,243],{"class":106},[92,5851,5852],{"class":94,"line":229},[92,5853,141],{"class":106},[22,5855,5856],{},"toujours gérer la pagination de la même manière dans la réponse (par des entête http, ou directement dans le body,\nmais surtout avec les mêmes noms de champs).",[22,5858,5859],{},"toujours gérer les erreurs de la même manière.",[45,5861,5863],{"id":5862},"gérer-le-versionning-de-lapi","Gérer le versionning de l'API",[12,5865,5866],{},"Si vous souhaitez changer le fonctionnement de l'API, surtout ne cassez pas la cohérence de l'API, ni la version\nutilisée par tous. Pour cela vous avez plusieurs choix, et ils sont complémentaires:",[19,5868,5869,5889,5905],{},[22,5870,5871,5872,5874,5875,5878,5879,5882,5883,5885,5886,5888],{},"Vous pouvez créer une nouvelle version de l'API.",[2621,5873],{},"En créant une nouvelle version de l'API (passage de ",[89,5876,5877],{},"\u002Fapi\u002Fv1\u002F..."," à ",[89,5880,5881],{},"\u002Fapi\u002Fv2\u002F...","), vous vous assurez que l'ancienne\nversion fonctionne toujours et que les clients pourront migrer doucement de l'ancienne version vers la nouvelle.",[2621,5884],{},"Si vous souhaitez casser la cohérence de l'API (par exemple remonter les infos skip et limit du body dans des headers\nX-Pagination-Skip et X-Pagination-Limit), vous pouvez le faire une à une sur chaque API.",[2621,5887],{},"Pensez à un plan de décommissionnement pour ne pas maintenir 50 versions d'API différentes. Prévenez vos utilisateurs\nque les anciennes versions vont être décommissionées avec une date raisonable pour qu'ils puissent modifier leurs\napplications.",[22,5890,5891,5892,5895,5896,5898,5899,5901,5902,5904],{},"Lors de la création d'une nouvelle API, il peut être intéressant de ",[3286,5893,5894],{},"déprécier"," les attributs de l'ancienne API, cela\nvous permet de prévenir l'utilisateur qu'il utilise un champ déprécié et qu'il sera décommisionné.",[2621,5897],{},"L'ajout d'attribut ne pose généralement pas de problème, mais cela peut vous permettre de supprimer des attributs\naprès avoir veillé à prévenir vos utilisateurs de leur obsolescence future sans forcément créer une nouvelle API.",[2621,5900],{},"Par contre cela nécessite de faire des modifications peu structurantes.",[2621,5903],{},"Pour des modifications plus structurantes vous devrez alors créer des nouveaux endpoint au coeur de l'API au risque\nde perdre l'utilisateur.",[22,5906,5907,5908,5910,5911,5913,5914,5916],{},"Monitorer votre API et son utilisation (analytics).",[2621,5909],{},"Cela vous permettra de savoir si vos API sont utilisées, leurs taux d'utilisation.",[2621,5912],{},"Etudier le taux d'utilisation vous permet ainsi de savoir le risque à décommissionner une API, mais aussi peut vous\nmotiver à améliorer les API les plus utilisées.",[2621,5915],{},"Avec certains framework (GraphQL) il est même possible de monitorer l'utilisation des attributs de votre API. Cela\nvous permet de plus facilement décommissionner les attributs de votre API au fur et à mesure de leur dépréciation.",[45,5918,5920],{"id":5919},"pour-le-client","Pour le client",[12,5922,5923],{},"Une bonne API doit être écrite pour le client qui va l'utiliser.",[12,5925,5926],{},"Il est important de ne pas créer son API en se basant sur son modèle interne mais en créant son API sur son usage.",[12,5928,5929],{},"Un appel d'API peut nécessiter plusieurs appels internes, et inversement plusieurs API peuvent faire appel aux mêmes\ndonnées internes. C'est au serveur derrière l'API ensuite de gérer un système de cache (avec invalidation de celui-ci)\npour limiter les appels à son back, ou sa base de données trop régulièrement.",[12,5931,5932],{},"De la même manière certains champs internes peuvent nécessiter d'être transformés pour être intégrés ou pour être exposés.",[12,5934,5935],{},"Certaines données internes n'ont parfois même pas besoin d'être exposées.",[12,5937,5938],{},"Du coup il faut se poser les bonnes questions lors de la création de son API :",[19,5940,5941,5944,5947],{},[22,5942,5943],{},"Qui sont les clients qui vont appeler mon API ?",[22,5945,5946],{},"Quelles cinématiques utilisateurs se cachent derrière ses clients ?",[22,5948,5949,5950],{},"Lors de chaque appel de quelles données auront besoin les clients ?\n",[19,5951,5952,5955,5958],{},[22,5953,5954],{},"Dois-je regrouper certains champs dans une même resource pour limiter le nombre d'appels ?",[22,5956,5957],{},"Dois-je déplacer certaines données dans des sous-resources pour limiter la quantité de données lors d'un appel ?",[22,5959,5960],{},"Dois-je ajouter de la pagination, de la projection, de la recherche pour limiter la quantité de données qui\nressortira de mon API ?",[12,5962,5963],{},"Il faut prendre en compte qu'en fonction de l'appelant le résultat peut être différent. Une application mobile ne\nprésentera pas forcément les données de la même manière qu'un site WEB. Est-ce que l'API doit savoir répondre aux deux ?\n(Certains framework savent mieux répondre à ce genre de questions que d'autres).",[45,5965,3119],{"id":3118},[12,5967,5968],{},"Si vous avez aimé cet article, je vous invite à lire la suite qui paraitera bientôt.",[5033,5970,5972,5975],{"className":5971,"dataFootnotes":87},[5036],[45,5973,5041],{"className":5974,"id":3524},[5040],[5043,5976,5977],{},[22,5978,5980,5981],{"id":5979},"user-content-fn-spa","une SPA (Single Page Application) est une application dont l'interface Web est entièrement écrite en javascript et qui ",[25,5982,5058],{"href":5983,"ariaLabel":5055,"className":5984,"dataFootnoteBackref":87},"#user-content-fnref-spa",[5057],[3176,5986,5987],{},"html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .sV9Aq, html code.shiki .sV9Aq{--shiki-default:#7F848E;--shiki-default-font-style:italic}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":87,"searchDepth":144,"depth":144,"links":5989},[5990,5991,5992,5993,5994,5995,5996,5997],{"id":5219,"depth":144,"text":5220},{"id":5261,"depth":144,"text":5262},{"id":5340,"depth":144,"text":5341},{"id":5480,"depth":144,"text":5481},{"id":5862,"depth":144,"text":5863},{"id":5919,"depth":144,"text":5920},{"id":3118,"depth":144,"text":3119},{"id":3524,"depth":144,"text":5041},"2020-10-11",{"type":9,"value":6000},[6001,6003,6005,6019,6021],[12,6002,14],{},[12,6004,5144],{},[19,6006,6007,6011,6015],{},[22,6008,6009],{},[25,6010,28],{"href":27},[22,6012,6013],{},[25,6014,34],{"href":33},[22,6016,6017],{},[25,6018,40],{"href":39},[12,6020,5161],{},[12,6022,5164],{},{"planet":147},"\u002Fpost\u002Fcreation-api-1",{"title":5137,"description":14},"creation-api-1","posts\u002FProgrammation\u002F2020-10-11_creation-api-1",[3233,509,3234,3235,3236],"TBg-AMlBnBvdkbTX1a-CGytRJUL2yk4FcVzMHR6lj6c",1777849586550]