{"id":249,"date":"2020-05-16T19:46:00","date_gmt":"2020-05-16T17:46:00","guid":{"rendered":"http:\/\/baptiste-meynier.com\/?p=249"},"modified":"2023-02-18T18:50:30","modified_gmt":"2023-02-18T17:50:30","slug":"graphql-masterclass","status":"publish","type":"post","link":"https:\/\/baptiste-meynier.com\/index.php\/2020\/05\/16\/graphql-masterclass\/","title":{"rendered":"GraphQL MasterClass"},"content":{"rendered":"\n<h3 class=\"wp-block-heading\">PR\u00c9-REQUIS:<\/h3>\n\n\n\n<p>Vous devrez disposer d\u2019un jdk 8, d\u2019un IDE et si possible du plugin lombok.<\/p>\n\n\n\n<p>Vous trouverez l\u2019int\u00e9gralit\u00e9 du code sur la branche&nbsp;<a href=\"https:\/\/gitlab.com\/bmeynier\/Spring-GraphQL\" target=\"_blank\" rel=\"noreferrer noopener\">master<\/a>. Chacune des \u00e9tapes de ce tutoriel dispose d\u2019une branche que vous pourrez checkouter en cas de souci.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1237\" height=\"637\" src=\"http:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/aquarium.gif\" alt=\"\" class=\"wp-image-252\"\/><\/figure>\n\n\n\n<p>Depuis tout petit j\u2019ai toujours \u00e9t\u00e9 passionn\u00e9 d\u2019aquariophilie. J\u2019ai impl\u00e9ment\u00e9 une petite application me permettant de stocker quelques informations sur mes poissons.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u00c9tape 1 : L\u2019\u00e9tat des lieux<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">A) PRISE DE CONNAISSANCE DE LA STRUCTURE DE L\u2019APPLICATION :<\/h3>\n\n\n\n<p>Il s\u2019agit d\u2019une application Spring boot exposant une api Rest.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/uml.png\" alt=\"\" class=\"wp-image-251\" width=\"591\" height=\"351\" srcset=\"https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/uml.png 1008w, https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/uml-300x178.png 300w, https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/uml-768x456.png 768w\" sizes=\"auto, (max-width: 591px) 100vw, 591px\" \/><\/figure>\n<\/div>\n\n\n<p>A noter que les donn\u00e9es sont servies par une base de donn\u00e9es H2.<\/p>\n\n\n\n<p>Vous y trouverez un certain nombre de fonctionnalit\u00e9s d\u00e9j\u00e0 impl\u00e9ment\u00e9es, l\u2019id\u00e9e de ce tutoriel est de se focaliser sur GraphQL.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">B) EXAMINONS L\u2019API :<\/h3>\n\n\n\n<p>Vous trouverez dans le r\u00e9pertoire doc des exports de configuration&nbsp;<a href=\"https:\/\/www.postman.com\/downloads\/\">Postman<\/a>&nbsp;vous permettant interroger l\u2019api.<\/p>\n\n\n\n<p>Rest se base sur le protocole HTTP et repose sur un concept de d\u00e9coupage des donn\u00e9es en ressource. Dans notre cas nous en avons deux.<\/p>\n\n\n\n<p>Lancez l\u2019application et appelez les deux services REST.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"json\" class=\"language-json\">{\n    \"id\": 1,\n    \"name\": \"Cichlidae\",\n    \"waterType\": \"FRESH\"\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u00c9tape 2 : Setup GraphQL<\/h2>\n\n\n\n<p>Je souhaite faire \u00e9voluer mon application et mettre en place GraphQL.<\/p>\n\n\n\n<p>Il existe plusieurs librairies java :<br>\u2013 https:\/\/www.graphql-java.com\/<br>\u2013 https:\/\/www.graphql-java-kickstart.com\/spring-boot\/<br>\u2013 https:\/\/download.eclipse.org\/microprofile\/microprofile-graphql-1.0\/microprofile-graphql.html<\/p>\n\n\n\n<p>Pour ce tutoriel je choisis GraphQL Kickstart pour sa bonne int\u00e9gration \u00e0 Spring Boot.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">A) D\u00c9PENDANCES MAVEN :<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"xml\" class=\"language-xml\">&lt;!-- GraphQL --&gt;\n   &lt;dependency&gt;\n     &lt;groupId&gt;com.graphql-java-kickstart&lt;\/groupId&gt;\n     &lt;artifactId&gt;graphql-spring-boot-starter&lt;\/artifactId&gt;\n     &lt;version&gt;${graphql.version}&lt;\/version&gt;\n   &lt;\/dependency&gt;\n   &lt;!-- to embed GraphiQL tool --&gt;\n   &lt;dependency&gt;\n     &lt;groupId&gt;com.graphql-java-kickstart&lt;\/groupId&gt;\n     &lt;artifactId&gt;graphiql-spring-boot-starter&lt;\/artifactId&gt;\n     &lt;version&gt;${graphql.version}&lt;\/version&gt;\n     &lt;scope&gt;runtime&lt;\/scope&gt;\n   &lt;\/dependency&gt;<\/code><\/pre>\n\n\n\n<p>La premi\u00e8re contient l\u2019impl\u00e9mentation, GraphiQL quant \u00e0 lui est un client web dont nous parlerons plus tard.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">B) CONFIGURATION DE L\u2019APPLICATION SPRING BOOT :<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"yaml\" class=\"language-yaml\">graphql:\n  servlet:\n    mapping: \/graphql\n    enabled: true\n    corsEnabled: true\n    # if you want to @ExceptionHandler annotation for custom GraphQLErrors\n    exception-handlers-enabled: true\n    contextSetting: PER_REQUEST_WITH_INSTRUMENTATION\ngraphiql:\n  mapping: \/graphiql\n  endpoint:\n    graphql: \/graphql\n    subscriptions: \/subscriptions\n  subscriptions:\n    timeout: 30\n    reconnect: false\n  static:\n    basePath: \/\n  enabled: true<\/code><\/pre>\n\n\n\n<p>application.yaml<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">C) D\u00c9CLARATION DU SCH\u00c9MA GRAPHQL :<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">MASTERCLASS-GRAPHQL\n\u2502   .gitignore\n\u2502   pom.xml\n\u2502   README.md\n\u2514\u2500\u2500\u2500src\n    \u2514\u2500\u2500\u2500main\n        \u251c\u2500\u2500\u2500java\n        \u2502\n        \u2514\u2500\u2500\u2500resources\n            \u2502   application.yaml\n            \u2502   data.sql\n            \u2502\n            \u2514\u2500\u2500\u2500graphql\n                    fish.graphqls<\/code><\/pre>\n\n\n\n<p>Le Sch\u00e9ma est l\u2019\u00e9l\u00e9ment central de l\u2019application. GraphQL est dit d\u00e9claratif, c\u2019est \u00e0 l\u2019int\u00e9rieur de ce fichier que sont d\u00e9clar\u00e9s&nbsp; l\u2019ensemble des objets expos\u00e9s.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">D\u00e9finissons ensemble l\u2019objet Family :<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"graphql\" class=\"language-graphql\">enum WaterType {\n    SEA,\n    FRESH\n}\n\ntype Family {\n    id: ID!\n    # Name of the family\n    name: String\n    # Type of water\n    waterType: WaterType\n    \" Fish inner the family\"\n    fishs: [Fish!]\n}<\/code><\/pre>\n\n\n\n<p>Il existe un certain nombre de champs de base appel\u00e9s Scalar (ID, String, Int, \u2026). Les types tableau sont indiqu\u00e9s par des crochets []. Il est aussi possible de d\u00e9finir des validations simples, telles que la notion de champs obligatoires avec le signe \u00ab&nbsp;!&nbsp;\u00bb. Il est aussi possible de documenter votre API en utilisant \u00ab&nbsp;#&nbsp;\u00bb ou les guillemets.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">D) EN VOUS INSPIRANT DE CE QUI A \u00c9T\u00c9 FAIT PR\u00c9C\u00c9DEMMENT ET EN VOUS AIDANT DE CETTE&nbsp;<a href=\"https:\/\/graphql.org\/learn\/schema\/\">DOCUMENTATION<\/a>, D\u00c9CLAREZ LE SCH\u00c9MA DE L\u2019OBJET FISH.<\/h3>\n\n\n\n<h2 class=\"wp-block-heading\">\u00c9tape 3 : Les Query<\/h2>\n\n\n\n<p>L\u2019une des grandes forces de GraphQL, c\u2019est qu\u2019il impose un d\u00e9coupage de ses fonctionnalit\u00e9s d\u2019interrogations et de mutations (pattern&nbsp;<a href=\"https:\/\/microservices.io\/patterns\/data\/cqrs.html\">CQRS<\/a>).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">A) CR\u00c9ATION D\u2019UNE QUERY SIMPLE :<\/h3>\n\n\n\n<p>L\u2019objectif de cette t\u00e2che est d\u2019exposer en GraphQL la fonctionnalit\u00e9 getMostExpensiveFish pr\u00e9sente dans la classe MostExpensiveFish.<\/p>\n\n\n\n<p>Tout ajout de fonctionnalit\u00e9 GrapQL doit faire l\u2019objet de d\u00e9claration dans le Sch\u00e9ma.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"graphql\" class=\"language-graphql\">type Query {\n    # The most Expensive Fish\n    mostExpensiveFish: Fish\n}<\/code><\/pre>\n\n\n\n<p>Cr\u00e9ez un nouveau package par exemple \u00ab&nbsp;graphql&nbsp;\u00bb et ajoutez la classe FishQueryResolver impl\u00e9mentant l\u2019interface GraphQLQueryResolver.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">\nimport com.coxautodev.graphql.tools.GraphQLQueryResolver;\n\n@Component\npublic class FishQueryResolver implements GraphQLQueryResolver {\n\n    private FishDatabaseService fishDatabaseService;\n\n    @Autowired\n    public FishQueryResolver(FishDatabaseService fishDatabaseService) {\n        this.fishDatabaseService = fishDatabaseService;\n    }\n\n    public Fish getMostExpensiveFish() {\n        return fishDatabaseService.getMostExpensiveFish();\n    }<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">B) GRAPHIQL<\/h3>\n\n\n\n<p>Ce client web est automatiquement lanc\u00e9 par SpringBoot. Lancez l\u2019application et rendez-vous sur localhost:8080\/graphiql<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"921\" height=\"597\" src=\"http:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/GraphiQL.png\" alt=\"\" class=\"wp-image-253\" srcset=\"https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/GraphiQL.png 921w, https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/GraphiQL-300x194.png 300w, https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/GraphiQL-768x498.png 768w\" sizes=\"auto, (max-width: 921px) 100vw, 921px\" \/><\/figure>\n<\/div>\n\n\n<p><br>Ex\u00e9cutez la query :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"graphql\" class=\"language-graphql\">query{\n  mostExpensiveFish {\n    id,\n    name,\n    family {\n      waterType\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<p>En GraphQL le client est un acteur tout aussi important que la partie serveur. C\u2019est lui qui d\u00e9tient la responsabilit\u00e9 de d\u00e9finir les champs dont vous souhaitez r\u00e9cup\u00e9rer les valeurs ! Il est tout \u00e0 fait possible de le faire en Rest, mais il s\u2019agit d\u2019une impl\u00e9mentation \u00e0 r\u00e9aliser cot\u00e9 serveur, ici il s\u2019agit du comportement natif du client! Cela permet de r\u00e9duire consid\u00e9rablement la taille du payload, imaginez les gains en terme r\u00e9seau !<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">C) QUERY AVEC PARAM\u00c8TRE :<\/h3>\n\n\n\n<p>Ce type de query se d\u00e9clare de la m\u00eame fa\u00e7on, il suffit tout simplement de faire attention au mapping des arguments sch\u00e9ma \/ QueryResolver.<\/p>\n\n\n\n<p>D\u00e9clarez dans le sch\u00e9ma la m\u00e9thode findFishByName situ\u00e9e dans la classe FishDatabaseService en suivant les conseils de la&nbsp;<a href=\"https:\/\/graphql.org\/learn\/queries\/#arguments\">documentation officielle<\/a>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"graphql\" class=\"language-graphql\">query{\n fishByName(name:\"Discus\") {\n   id,\n  name\n }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">D) QUERY AVEC PARAM\u00c8TRES PLUS COMPLEXE<\/h3>\n\n\n\n<p>Pour les requ\u00eates n\u00e9cessitant d\u2019avantage de param\u00e8tres, GraphQL met \u00e0 disposition du d\u00e9veloppeur la notion d\u2019Input. Il se d\u00e9clare de la m\u00eame fa\u00e7on qu\u2019un objet classique \u00e0 la diff\u00e9rence pr\u00e8s que cet objet ne peut \u00eatre situ\u00e9 qu\u2019en param\u00e8tre.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Prenons l\u2019exemple de la&nbsp;<a href=\"https:\/\/graphql.org\/learn\/pagination\/\">pagination:<\/a><\/h4>\n\n\n\n<p>Commen\u00e7ons par cr\u00e9er l\u2019objet java :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">\npackage com.bmeynier.masterclass.spring.graphql.graphql.input;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport javax.validation.constraints.Positive;\nimport javax.validation.constraints.PositiveOrZero;\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class PaginationInput {\n    @Positive\n    private int first;\n    @PositiveOrZero\n    private int offset;\n}<\/code><\/pre>\n\n\n\n<p>Puis la d\u00e9claration dans le sch\u00e9ma :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"graphql\" class=\"language-graphql\">\ninput PaginationInput {\n    # Number of element returned\n    first: Int\n    # Index where element will be returned\n    offset: Int\n}\n...\ntype Query {\n    # Fish with pagination\n    fishWithPagination(pagination: PaginationInput): [Fish]\n}<\/code><\/pre>\n\n\n\n<p>Et dans le QueryResolver :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">public List&lt;Fish&gt; fishWithPagination(PaginationInput paginationInput) {\n     Fish first = fishDatabaseService.findByOffset(paginationInput.getOffset());\n     return fishDatabaseService.findFish(first.getId(), paginationInput.getFirst());\n }<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"graphql\" class=\"language-graphql\">query{\n fishWithPagination(pagination: {first: 2, offset: 1}) {\n  id,\n  name\n }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">E) METTEZ EN PLACE LA M\u00caME STRUCTURE POUR L\u2019INPUT CURSORINPUT.<\/h3>\n\n\n\n<h2 class=\"wp-block-heading\">\u00c9tape 4 : Query cot\u00e9 client<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">A) LES FRAGMENTS :<\/h3>\n\n\n\n<p>Nous avons vu plus haut que le client \u00e9tait responsable de la r\u00e9cup\u00e9ration des champs et qu\u2019il fallait \u00e9num\u00e9rer un \u00e0 un les champs r\u00e9cup\u00e9rables. Il est possible gr\u00e2ce aux&nbsp;<a href=\"https:\/\/graphql.org\/learn\/queries\/#fragments\">Fragments<\/a>&nbsp;de d\u00e9finir un groupement de champs que l\u2019on souhaite r\u00e9cup\u00e9rer.<\/p>\n\n\n\n<p>Lancez l\u2019application et connectez-vous \u00e0 l\u2019interface de GraphiQL.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"graphql\" class=\"language-graphql\">fragment standard on Fish {\n id,\n name\n}\n\nquery{\n fishWithCursor(cursor: { first: 2,after: 3}) {\n  ...standard,\n  price\n }\n}<\/code><\/pre>\n\n\n\n<p>D\u00e9clarez un fragment pour l\u2019entity family et utilisez le sur la query mostExpensiveFish.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">B) CLIENT PARAM\u00c9TRABLE :<\/h3>\n\n\n\n<p>Dans GraphiQL se cache un petit onglet appel\u00e9 \u00ab&nbsp;Variables&nbsp;\u00bb<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"687\" height=\"314\" src=\"http:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/GraphiQLqueryVariable.png\" alt=\"\" class=\"wp-image-254\" srcset=\"https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/GraphiQLqueryVariable.png 687w, https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/GraphiQLqueryVariable-300x137.png 300w\" sizes=\"auto, (max-width: 687px) 100vw, 687px\" \/><\/figure>\n<\/div>\n\n\n<p><br>Dans la zone query:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"graphql\" class=\"language-graphql\">query Fish($name: String){ \n    fishByName(name: $name) { id, name } \n}<\/code><\/pre>\n\n\n\n<p>Puis dans la zone query variables:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"json\" class=\"language-json\">{\"name\": \"Discus\"}<\/code><\/pre>\n\n\n\n<p>Utilisez cette technique sur la m\u00e9thode fishWithPagination.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u00c9TAPE 5 : LES RESOLVERS<\/h3>\n\n\n\n<p>Dans GraphQL kick start la notion de&nbsp;<a href=\"https:\/\/www.graphql-java-kickstart.com\/tools\/schema-definition\/#field-mapping-priority\">Resolvers<\/a>&nbsp;est extr\u00eamement importante. Le framework tente de r\u00e9soudre le mapping Sch\u00e9ma\/Code via le FieldResolver qui tentera d\u2019effectuer un mapping par le nom de champ, puis par les m\u00e9thodes commen\u00e7ant par getNAME, puis isNAME et enfin getNAME.<\/p>\n\n\n\n<p>Il est aussi possible de cr\u00e9er son propre Resolver !<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">A) CR\u00c9ER UN RESOLVER PERSONNALIS\u00c9 :<\/h3>\n\n\n\n<p>Mettez le sch\u00e9ma \u00e0 jour avec le type Purchase:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"graphql\" class=\"language-graphql\">type Purchase {\n    id:ID!\n    shopName: String\n}\n\ntype Fish {\n    id: ID!\n    \"Fish name\"\n    name: String\n    \"Optimal temperature accept by the fish\"\n    temperature: Int\n    \"Price of the fish\"\n    price: Float\n    \"Family of the fish\"\n    family: Family\n    \"Information on the purchase\"\n    purchase: Purchase\n}<\/code><\/pre>\n\n\n\n<p>Voici la classe Purchase :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">package com.bmeynier.masterclass.spring.graphql.model;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class Purchase {\n    private long id;\n    private String shopName;\n}<\/code><\/pre>\n\n\n\n<p>Notre source de donn\u00e9es sera un fichier json \u00e0 mettre dans le r\u00e9pertoire resources:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">[\n  { \"id\": 1, \"shopName\": \"BricoFish\" },\n  { \"id\": 2,  \"shopName\": \"AquaFish\" },\n  { \"id\": 3,  \"shopName\": \"FisherShop\" }\n]<\/code><\/pre>\n\n\n\n<p>Le code permettant de charger les donn\u00e9es du fichier est le suivant :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">public Purchase getPurchase(Fish fish) throws IOException {\n\n   File purchasesDatasource = new File(getClass().getClassLoader().getResource(\"purchases.json\").getFile());\n\n   Purchase[] purchaseList = new ObjectMapper().readValue(purchasesDatasource, Purchase[].class);\n\n   int randomIndex = new Random().nextInt(purchaseList.length);\n\n   return purchaseList[randomIndex];\n}<\/code><\/pre>\n\n\n\n<p>D\u00e9clarez une nouvelle classe appel\u00e9 FishResolver :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">@Component\npublic class FishResolver implements GraphQLResolver&lt;Fish&gt; {}<\/code><\/pre>\n\n\n\n<p>Compl\u00e9tez le code de la classe pour charger al\u00e9atoirement un achat.<\/p>\n\n\n\n<p>Cet exercice, en agr\u00e9geant des donn\u00e9es venant de deux sources diff\u00e9rentes (sql et fichier json), de mettre en lumi\u00e8re un cas d\u2019utilisation de GraphQL, la Gateway.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u00c9tape 6 : Les Scalars personnalis\u00e9s<\/h2>\n\n\n\n<p>Je souhaite maintenant pr\u00e9ciser la date \u00e0 laquelle j\u2019ai effectu\u00e9 mon achat.<\/p>\n\n\n\n<p>Le type Date ne fait pas parti des types scalar de base de GraphQL. Heureusement pour nous, GraphQL nous laisse la main pour cr\u00e9er nos propres types.<\/p>\n\n\n\n<p>Enrichissons d\u2019abord le sch\u00e9ma :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"graphql\" class=\"language-graphql\">scalar Date\n\ntype Purchase {\n    id:ID!\n    shopName: String\n    date: Date\n}<\/code><\/pre>\n\n\n\n<p>Mettons \u00e0 jour le fichier purchase.json avec des dates :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"json\" class=\"language-json\">[\n  { \"id\": 1, \"shopName\": \"BricoFish\", \"date\": \"2009-01-12\"},\n  { \"id\": 2, \"shopName\": \"AquaFish\", \"date\": \"2020-03-02\"  },\n  { \"id\": 3, \"shopName\": \"FisherShop\", \"date\": \"1985-12-24\"}\n]<\/code><\/pre>\n\n\n\n<p>Cette fois-ci utilisons les configurators Spring pour d\u00e9clarer notre nouveau type :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">@Configuration\npublic class FishConfiguration {\n\n    private static final String DATE_FORMAT = \"yyyy-MM-dd\";\n\n    private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT);\n\n    @Bean\n    public GraphQLScalarType getCustomDate() {\n\tGraphQLScalarType.newScalar().name(\"Date\").description(\"A date with format \" + DATE_FORMAT).coercing(new Coercing&lt;LocalDate, String&gt;() {\n\t\t public String serialize(Object input) throws CoercingSerializeException {}\n\t\t@Override\n\t\tpublic LocalDate parseValue(Object input) throws CoercingParseValueException {}\n\t\t@Override\n\t\tpublic LocalDate parseLiteral(Object input) throws CoercingParseLiteralException {}\n\n\t\t}\n\t}\n}<\/code><\/pre>\n\n\n\n<p>L\u2019\u00e9l\u00e9ment le plus important de la cr\u00e9ation d\u2019un Scalar est le Coercing, c\u2019est lui qui est en charge de serializer\/deserializer le champ.<\/p>\n\n\n\n<p>Voici le code (un peu complexe) du type Date :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">@Bean\n  public GraphQLScalarType getCustomDate() {\n      return GraphQLScalarType.newScalar().name(\"Date\").description(\"A date with format \" + DATE_FORMAT).coercing(new Coercing&lt;LocalDate, String&gt;() {\n\n          private LocalDate parseLocalDate(String s, Function&lt;String, RuntimeException&gt; exceptionMaker) {\n              try {\n                  TemporalAccessor temporalAccessor = dateFormatter.parse(s);\n                  return LocalDate.from(temporalAccessor);\n              } catch (DateTimeParseException e) {\n                  throw exceptionMaker.apply(\"Invalid RFC3339 full date value : '\" + s + \"'. because of : '\" + e.getMessage() + \"'\");\n              }\n          }\n\n          @Override\n          public String serialize(Object input) throws CoercingSerializeException {\n              TemporalAccessor temporalAccessor;\n              if (input instanceof TemporalAccessor) {\n                  temporalAccessor = (TemporalAccessor) input;\n              } else if (input instanceof String) {\n                  temporalAccessor = parseLocalDate(input.toString(), CoercingSerializeException::new);\n              } else {\n                  throw new CoercingSerializeException(\n                          \"Expected a 'String' or 'java.time.temporal.TemporalAccessor' but was '\" + input + \"'.\"\n                  );\n              }\n              try {\n                  return dateFormatter.format(temporalAccessor);\n              } catch (DateTimeException e) {\n                  throw new CoercingSerializeException(\n                          \"Unable to turn TemporalAccessor into full date because of : '\" + e.getMessage() + \"'.\"\n                  );\n              }\n          }\n\n          @Override\n          public LocalDate parseValue(Object input) throws CoercingParseValueException {\n              TemporalAccessor temporalAccessor;\n              if (input instanceof TemporalAccessor) {\n                  temporalAccessor = (TemporalAccessor) input;\n              } else if (input instanceof String) {\n                  temporalAccessor = parseLocalDate(input.toString(), CoercingParseValueException::new);\n              } else {\n                  throw new CoercingParseValueException(\n                          \"Expected a 'String' or 'java.time.temporal.TemporalAccessor' but was '\" + input + \"'.\"\n                  );\n              }\n              try {\n                  return LocalDate.from(temporalAccessor);\n              } catch (DateTimeException e) {\n                  throw new CoercingParseValueException(\n                          \"Unable to turn TemporalAccessor into full date because of : '\" + e.getMessage() + \"'.\"\n                  );\n              }\n          }\n\n          @Override\n          public LocalDate parseLiteral(Object input) throws CoercingParseLiteralException {\n              if (!(input instanceof StringValue)) {\n                  throw new CoercingParseLiteralException(\n                          \"Expected AST type 'StringValue' but was '\" + input + \"'.\"\n                  );\n              }\n              return parseLocalDate(((StringValue) input).getValue(), CoercingParseLiteralException::new);\n          }\n\n      }).build();\n  }<\/code><\/pre>\n\n\n\n<p>Ajoutez un champ date de type string dans la classe Purchase. Nous aurions pu mettre un type LocalDate, mais il aurait fallu impl\u00e9menter un serialiser\/deserialiser json ce qui n\u2019est pas l\u2019objectif de la formation.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"767\" height=\"327\" src=\"http:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/customScalar-e1585408858503.png\" alt=\"\" class=\"wp-image-255\" srcset=\"https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/customScalar-e1585408858503.png 767w, https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/customScalar-e1585408858503-300x128.png 300w\" sizes=\"auto, (max-width: 767px) 100vw, 767px\" \/><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">\u00c9tape 7 : Mutation<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">A) ET SI ON INS\u00c9RAIT DES DONN\u00c9ES ?<\/h3>\n\n\n\n<p>Pour cela impl\u00e9mentez l\u2019interface MutationResolver dans une classe appel\u00e9e FishMutationResolver.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">package com.bmeynier.masterclass.spring.graphql.graphql;\n\nimport com.coxautodev.graphql.tools.GraphQLMutationResolver;\nimport com.bmeynier.masterclass.spring.graphql.model.Family;\nimport com.bmeynier.masterclass.spring.graphql.model.WaterType;\nimport com.bmeynier.masterclass.spring.graphql.repository.FamilyRepository;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\nimport org.springframework.transaction.annotation.Transactional;\n\n@Component\npublic class FishMutationResolver implements GraphQLMutationResolver {\n\n    private final FamilyRepository familyRepository;\n\n    @Autowired\n    public FishMutationResolver(FamilyRepository familyRepository) {\n        this.familyRepository = familyRepository;\n    }\n\n    @Transactional\n    public Family createFamily(String name, WaterType waterType) {\n        return this.familyRepository.save(Family.builder().name(name).waterType(waterType).build());\n    }\n}<\/code><\/pre>\n\n\n\n<p>Ajoutez dans le sch\u00e9ma une nouvelle section Mutation :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"json\" class=\"language-json\">type Mutation {\n  createFamily(name: String, waterType: WaterType): Family\n}<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">package com.bmeynier.masterclass.spring.graphql.graphql;\n\nimport com.coxautodev.graphql.tools.GraphQLMutationResolver;\nimport com.bmeynier.masterclass.spring.graphql.model.Family;\nimport com.bmeynier.masterclass.spring.graphql.model.WaterType;\nimport com.bmeynier.masterclass.spring.graphql.repository.FamilyRepository;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\nimport org.springframework.transaction.annotation.Transactional;\n\n@Component\npublic class FishMutationResolver implements GraphQLMutationResolver {\n\n    private final FamilyRepository familyRepository;\n\n    @Autowired\n    public FishMutationResolver(FamilyRepository familyRepository) {\n        this.familyRepository = familyRepository;\n    }\n\n    @Transactional\n    public Family createFamily(String name, WaterType waterType) {\n        return this.familyRepository.save(Family.builder().name(name).waterType(waterType).build());\n    }\n}<\/code><\/pre>\n\n\n\n<p>Rien de bien diff\u00e9rent de la query, voici l\u2019appel cot\u00e9 client :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"json\" class=\"language-json\">mutation{\n createFamily(name: \"Galeaspida\",waterType: SEA){ name }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">B) AJOUTEZ UNE FONCTIONNALIT\u00c9 DE MUTATION PERMETTANT D\u2019AJOUTER UN POISSON PRENANT UN NOM DE FAMILLE. A CELA JETEZ UNE ERREUR DE TYPE&nbsp;<code>NOSUCHELEMENTEXCEPTION<\/code>&nbsp;LORSQU\u2019AUCUNE FAMILLE N\u2019EST TROUV\u00c9E.<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"json\" class=\"language-json\">createFish(name: String, temperature: Int, price : Float, familyName: String): Fish<\/code><\/pre>\n\n\n\n<p>Voici un exemple de retour d\u2019erreur :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"json\" class=\"language-json\">{\n  \"errors\": [\n    {\n      \"message\": \"Impossible to find Family azert\"\n    }\n  ],\n  \"data\": {\n    \"createFish\": null\n  }\n}<\/code><\/pre>\n\n\n\n<p>A noter qu\u2019en cas d\u2019erreur, GraphQL renvoie tout de m\u00eame les attributs qu\u2019il a r\u00e9ussi \u00e0 r\u00e9cup\u00e9rer, tout en \u00e9num\u00e9rant les erreurs qu\u2019il a rencontr\u00e9.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u00c9TAPE 8 : SUBSCRIPTIONS<\/h3>\n\n\n\n<p>GraphQL propose un syst\u00e8me d\u2019abonnement. Je souhaite impl\u00e9menter un syst\u00e8me pouvant m\u2019alerter lorsqu\u2019une nouvelle famille de poisson est ajout\u00e9e.<\/p>\n\n\n\n<p>Nous allons utiliser les EventPublisher Spring :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">package com.bmeynier.fmasterclass.spring.graphql.event;\n\nimport com.bmeynier.masterclass.spring.graphql.model.Family;\nimport org.springframework.context.ApplicationEvent;\n\npublic class FamilyCreationEvent extends ApplicationEvent {\n    private Family family;\n\n    public FamilyCreationEvent(Object source, Family family) {\n        super(source);\n        this.family = family;\n    }\n    public Family getFamily() {\n        return family;\n    }\n}<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">private ApplicationEventPublisher applicationEventPublisher;\n  private final FamilyRepository familyRepository;\n  private final FishRepository fishRepository;\n\n  @Autowired\n  public FishMutationResolver(ApplicationEventPublisher applicationEventPublisher,\n                              FamilyRepository familyRepository,\n                              FishRepository fishRepository) {\n      this.applicationEventPublisher = applicationEventPublisher;\n      this.familyRepository = familyRepository;\n      this.fishRepository = fishRepository;\n  }\n\n  @Transactional\n  public Family createFamily(String name, WaterType waterType) {\n      Family family = Family.builder().name(name).waterType(waterType).build();\n      FamilyCreationEvent customSpringEvent = new FamilyCreationEvent(this, family);\n      applicationEventPublisher.publishEvent(customSpringEvent);\n      return this.familyRepository.save(family);\n  }<\/code><\/pre>\n\n\n\n<p>Nous allons ensuite cr\u00e9er un publicateur d\u2019\u00e9v\u00e9nements sous forme de flowable :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">package com.bmeynier.masterclass.spring.graphql.service;\n\nimport com.bmeynier.masterclass.spring.graphql.event.FamilyCreationEvent;\nimport com.bmeynier.masterclass.spricng.graphql.model.Family;\nimport com.bmeynier.masterclass.spring.graphql.model.Fish;\nimport com.bmeynier.masterclass.spring.graphql.model.WaterType;\nimport io.reactivex.BackpressureStrategy;\nimport io.reactivex.Flowable;\nimport io.reactivex.Observable;\nimport io.reactivex.ObservableEmitter;\nimport io.reactivex.observables.ConnectableObservable;\nimport org.reactivestreams.Publisher;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Queue;\nimport java.util.concurrent.*;\n\n@Component\npublic class FamilyPublisher {\n\n    private Flowable&lt;Family&gt; publisher;\n\n    private Queue&lt;Family&gt; events= new ArrayBlockingQueue&lt;&gt;(30);\n\n    @Autowired\n    public FamilyPublisher() {\n        this.initSubscriber();\n    }\n\n    private void initSubscriber() {\n        Observable&lt;Family&gt; familyObservable = Observable.create(observableEmitter -&gt; {\n                    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();\n                    executorService.scheduleAtFixedRate(newFamilyTick(observableEmitter), 0, 2, TimeUnit.SECONDS);\n                }\n        );\n        ConnectableObservable&lt;Family&gt; connectableObservable = familyObservable.share().publish();\n        connectableObservable.connect();\n\n        this.publisher = connectableObservable.toFlowable(BackpressureStrategy.BUFFER);\n    }\n\n    private Runnable newFamilyTick(ObservableEmitter&lt;Family&gt; observableEmitter) {\n        return () -&gt; {\n            while(!events.isEmpty()) {\n                observableEmitter.onNext(events.poll());\n            }\n        };\n    }\n\n    @EventListener\n    public void handleSuccessful(FamilyCreationEvent event) {\n        events.add(event.getFamily());\n    }\n\n    public Publisher&lt;Family&gt; getPublisher() {\n        return publisher;\n    }\n}<\/code><\/pre>\n\n\n\n<p>Cot\u00e9 GraphQL commen\u00e7ons par mettre \u00e0 jour le sch\u00e9ma :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"json\" class=\"language-json\">type Subscription {\n\tlastFamily: Family!\n}<\/code><\/pre>\n\n\n\n<p>Puis comme pour les query\/mutation impl\u00e9menter l\u2019interface SubscriptionResolver :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">package com.bmeynier.masterclass.spring.graphql.graphql;\n\nimport com.coxautodev.graphql.tools.GraphQLSubscriptionResolver;\nimport com.bmeynier.masterclass.spring.graphql.model.Family;\nimport com.bmeynier.masterclass.spring.graphql.service.FamilyPublisher;\nimport org.reactivestreams.Publisher;t\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class FishSubscriptionResolver implements GraphQLSubscriptionResolver {\n\n    private final FamilyPublisher familyPublisher;\n\n    @Autowired\n    public FishSubscriptionResolver(FamilyPublisher familyPublisher) {\n        this.familyPublisher = familyPublisher;\n    }\n\n    public Publisher&lt;Family&gt; lastFamily(){\n        return familyPublisher.getPublisher();\n    }\n}<\/code><\/pre>\n\n\n\n<p>Pour v\u00e9rifier son fonctionnement nous allons impl\u00e9menter un client Javascript \u00e0 placer dans le r\u00e9pertoire resources\/public\/index.html.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"markup\" class=\"language-markup\">&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n&lt;head&gt;\n    &lt;meta http-equiv=\"Content-Type\" content=\"text\/html; charset=UTF-8\"&gt;\n    &lt;title&gt;Subscriptions over Web Sockets&lt;\/title&gt;\n    &lt;style&gt;\n        body {\n            font-family: \"HelveticaNeue-Light\", \"Helvetica Neue Light\", \"Helvetica Neue\", Helvetica, Arial, \"Lucida Grande\", sans-serif;\n            font-weight: 300;\n        }\n\n        .networking {\n            display: none;\n        }\n\n        .stockTicker {\n            border: solid black 1px;\n            margin: 2px;\n            min-height: 400px\n        }\n\n        .stockWrapper {\n            display: block;\n            padding: 20px;\n            font-family: \"HelveticaNeue-Light\", \"Helvetica Neue Light\", \"Helvetica Neue\", Helvetica, Arial, \"Lucida Grande\", sans-serif;\n            font-weight: 300;\n        }\n\n        .stockSymbol {\n            font-weight: 600;\n        }\n\n        .stockPrice {\n            font-weight: 600;\n            color: red;\n        }\n\n        .stockUp {\n            font-weight: 600;\n            color: green;\n        }\n    &lt;\/style&gt;\n    &lt;link rel=\"stylesheet\" href=\"https:\/\/maxcdn.bootstrapcdn.com\/bootstrap\/3.3.7\/css\/bootstrap.min.css\"&gt;\n\n    &lt;script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/jquery\/3.2.1\/jquery.js\"&gt;&lt;\/script&gt;\n    &lt;script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/jqueryui\/1.12.1\/jquery-ui.js\"&gt;&lt;\/script&gt;\n    &lt;script src=\"https:\/\/maxcdn.bootstrapcdn.com\/bootstrap\/3.3.7\/js\/bootstrap.min.js\"&gt;&lt;\/script&gt;\n\n    &lt;script&gt;\n\n        function networkBlip() {\n\n            var $networking = $('.networking');\n            if (!$networking.is(\":visible\")) {\n                $networking.show(100, function () {\n                    var $that = $(this);\n                    setTimeout(function () {\n                        $that.hide();\n                    }, 500)\n                });\n            }\n        }\n\n        function subscribeToStocks() {\n            var exampleSocket = new WebSocket(\"ws:\/\/localhost:8080\/subscriptions\");\n            networkBlip();\n\n            exampleSocket.onopen = function () {\n                networkBlip();\n                console.log(\"web socket opened\");\n\n                var query = 'subscription LastFamilySubscription \\{ \\n' +\n                    '    lastFamily {' +\n                    '       id ' +\n                    '       name ' +\n                    '     }' +\n                    '}';\n                var graphqlMsg = {\n                    query: query,\n                    variables: {}\n                };\n                exampleSocket.send(JSON.stringify(graphqlMsg));\n            };\n\n            var STOCK_CODES_UPDATES = {};\n\n            exampleSocket.onmessage = function (event) {\n                networkBlip();\n\n                var data = JSON.parse(event.data);\n                console.log(data);\n            };\n        }\n\n        window.addEventListener(\"load\", subscribeToStocks);\n    &lt;\/script&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n\n&lt;div class=\"jumbotron text-center\"&gt;\n    &lt;h2&gt;graphql-java Subscriptions&lt;\/h2&gt;\n    &lt;p&gt;An example of graphql-java subscriptions sending continuous updates over websockets&lt;\/p&gt;\n&lt;\/div&gt;\n\n\n&lt;div class=\"container\"&gt;\n    &lt;div class=\"row\"&gt;\n        &lt;div class=\"col-sm-1\"&gt;\n            &lt;img src=\"https:\/\/avatars1.githubusercontent.com\/u\/14289921?s=200&amp;v=4\"\n                 class=\"img-thumbnail\" alt=\"graphql-java\" width=\"400\" height=\"400\"&gt;\n        &lt;\/div&gt;\n        &lt;div class=\"col-sm-3\"&gt;\n            &lt;h3&gt;Explanation&lt;\/h3&gt;\n            &lt;p&gt;This demonstrates the use of graphql subscriptions and web sockets to send a stream of imagined stock price\n                updates to this page.&lt;\/p&gt;\n            &lt;p&gt;The updates are continuously sent from a server side publish and subscribe system (RxJava in this case) and\n                pushed\n                down to the browser client while applying graphql shapes to the subscription data&lt;\/p&gt;\n            &lt;p&gt;The graphql query used in this example is :&lt;\/p&gt;\n            &lt;pre&gt;\n\n            &lt;\/pre&gt;\n        &lt;\/div&gt;\n        &lt;div class=\"col-sm-8\"&gt;\n            &lt;h3&gt;Stock Price Updates&lt;\/h3&gt;\n            &lt;div class=\"stockTicker\"&gt;Pending subscription...&lt;\/div&gt;\n            &lt;div class=\"networking\"&gt;&lt;\/div&gt;\n        &lt;\/div&gt;\n    &lt;\/div&gt;\n&lt;\/div&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n\n\n\n<p>La connexion \u00e0 l\u2019adresse localhost:8080\/index.html initialisera le client. Lancez la console de d\u00e9veloppement pour y voir plus clair. Il s\u2019agit d\u2019une communication web-socket standard avec initialisation de la conversation avec une requ\u00eate http 101. En parall\u00e8le lancez GraphiQL et ajoutez une famille.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"357\" src=\"http:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/subscription-e1585422118584-1024x357.png\" alt=\"\" class=\"wp-image-266\" srcset=\"https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/subscription-e1585422118584-1024x357.png 1024w, https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/subscription-e1585422118584-300x105.png 300w, https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/subscription-e1585422118584-768x268.png 768w, https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/subscription-e1585422118584.png 1142w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n<\/div>\n\n\n<p> <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u00c9tape 9 : Les outils<\/h2>\n\n\n\n<p>Kick start facilite l\u2019int\u00e9gration d\u2019outils de l\u2019\u00e9cosyst\u00e8me GraphQL.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><a href=\"https:\/\/altair.sirmuel.design\/\">ALTAIR<\/a>&nbsp;ET&nbsp;<a href=\"https:\/\/github.com\/prisma-labs\/graphql-playground\">PLAYGROUND<\/a><\/h3>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"417\" src=\"http:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/altair-e1585480738405-1024x417-1.png\" alt=\"\" class=\"wp-image-256\" srcset=\"https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/altair-e1585480738405-1024x417-1.png 1024w, https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/altair-e1585480738405-1024x417-1-300x122.png 300w, https:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/altair-e1585480738405-1024x417-1-768x313.png 768w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n<\/div>\n\n\n<p>Il s\u2019agit de sur-couche \u00e0 GraphiQL, ils ajoutent un certain nombre de fonctionnalit\u00e9s telles que la gestion des onglets, une meilleure int\u00e9gration des subscriptions ou encore une personnalisation de l\u2019interface. Ces outils sont \u00e9galement disponibles en client lourd.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><a href=\"https:\/\/github.com\/APIs-guru\/graphql-voyager\">VOYAGER<\/a><\/h3>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"900\" height=\"541\" src=\"http:\/\/baptiste-meynier.com\/wp-content\/uploads\/2020\/11\/demo-gif.gif\" alt=\"\" class=\"wp-image-257\"\/><\/figure>\n<\/div>\n\n\n<p>Et pour finir un outil de consultation du Graph.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>PR\u00c9-REQUIS: Vous devrez disposer d\u2019un jdk 8, d\u2019un IDE et si possible du plugin lombok. Vous trouverez l\u2019int\u00e9gralit\u00e9 du code sur la branche&nbsp;master. Chacune des \u00e9tapes de ce tutoriel dispose d\u2019une branche que vous pourrez checkouter en cas de souci. Depuis tout petit j\u2019ai toujours \u00e9t\u00e9 passionn\u00e9 d\u2019aquariophilie. J\u2019ai impl\u00e9ment\u00e9 une petite application me permettant &hellip;<\/p>\n","protected":false},"author":1,"featured_media":263,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[7],"tags":[],"class_list":["post-249","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-autre"],"_links":{"self":[{"href":"https:\/\/baptiste-meynier.com\/index.php\/wp-json\/wp\/v2\/posts\/249","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/baptiste-meynier.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/baptiste-meynier.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/baptiste-meynier.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/baptiste-meynier.com\/index.php\/wp-json\/wp\/v2\/comments?post=249"}],"version-history":[{"count":8,"href":"https:\/\/baptiste-meynier.com\/index.php\/wp-json\/wp\/v2\/posts\/249\/revisions"}],"predecessor-version":[{"id":1850,"href":"https:\/\/baptiste-meynier.com\/index.php\/wp-json\/wp\/v2\/posts\/249\/revisions\/1850"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/baptiste-meynier.com\/index.php\/wp-json\/wp\/v2\/media\/263"}],"wp:attachment":[{"href":"https:\/\/baptiste-meynier.com\/index.php\/wp-json\/wp\/v2\/media?parent=249"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/baptiste-meynier.com\/index.php\/wp-json\/wp\/v2\/categories?post=249"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/baptiste-meynier.com\/index.php\/wp-json\/wp\/v2\/tags?post=249"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}