API GraphQL (CRUD) sur Go

image


Bonjour à tous! Il y a beaucoup d'articles sur GraphQL sur Habr, mais après les avoir parcourus, j'ai trouvé qu'ils contournaient tous un langage aussi merveilleux que Go. Aujourd'hui, je vais essayer de corriger ce malentendu. Pour ce faire, nous allons écrire une API sur Go à l'aide de GraphQL.


En bref: GraphQL est un langage de requête pour construire une API qui décrit sous quelle forme demander et renvoyer des données (des informations plus détaillées sur la ressource officielle graphql.imtqy.com et sur le hub )


Vous pouvez affirmer que GraphQL ou REST est mieux ici.


Nous aurons l'API classique: CRUD (créer, lire, mettre à jour, supprimer) ajouter, recevoir, modifier et supprimer des produits dans la boutique en ligne.
Côté serveur, nous utiliserons l'implémentation prête de GraphQL graphql-go


Vous devez d'abord télécharger graphql-go, cela peut être fait avec la commande


go get github.com/graphql-go/graphql 

Ensuite, nous décrivons la structure du produit (sous forme simplifiée)


 type Product struct { ID int64 `json:"id"` Name string `json:"name"` Info string `json:"info,omitempty"` Price float64 `json:"price"` } 

ID - identifiant unique, Name - nom, Info - informations sur le produit, Price - prix


La première chose à faire est d'appeler la méthode Do , qui accepte un schéma de données et des paramètres de requête comme paramètres d'entrée. Et il nous renverra les données résultantes (pour une transmission ultérieure au client)


 result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) 

Code complet
 func executeQuery(query string, schema graphql.Schema) *graphql.Result { result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) > 0 { fmt.Printf("errors: %v", result.Errors) } return result } func main() { http.HandleFunc("/product", func(w http.ResponseWriter, r *http.Request) { result := executeQuery(r.URL.Query().Get("query"), schema) json.NewEncoder(w).Encode(result) }) http.ListenAndServe(":8080", nil) } 

Schema - schéma de données, RequestString - valeur du paramètre de chaîne de requête, dans notre cas, valeur de query


Schéma


Un schéma accepte deux types de données racine: Query - données immuables, Mutation - données mutables


 var schema, _ = graphql.NewSchema( graphql.SchemaConfig{ Query: queryType, Mutation: mutationType, }, ) 

Requête


Query est utilisée pour lire (et uniquement lire) les données. À l'aide de Query nous spécifions les données que le serveur doit retourner.
Nous allons écrire une implémentation du type de données Query , dans notre cas, elle contiendra des champs avec des informations sur un seul produit (produit) et une liste de marchandises (liste)


 var queryType = graphql.NewObject( graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ /*    ID http://localhost:8080/product?query={product(id:1){name,info,price}} */ "product": &graphql.Field{ Type: productType, Description: "Get product by id", //   ,    Args: graphql.FieldConfigArgument{ //       id "id": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { id, ok := p.Args["id"].(int) if ok { //    ID for _, product := range products { if int(product.ID) == id { return product, nil } } } return nil, nil }, }, /*    http://localhost:8080/product?query={list{id,name,info,price}} */ "list": &graphql.Field{ Type: graphql.NewList(productType), Description: "Get product list", Resolve: func(params graphql.ResolveParams) (interface{}, error) { return products, nil }, }, }, }) 

Le type queryType contient le Name et les Fields requis, ainsi que la Description facultative (utilisée pour la documentation)
À son tour, le champ Fields contient également le champ Type requis et les Args optionnels Args , Resolve et Description


Args (arguments)


Arguments - une liste de paramètres transférés du client au serveur et affectant le résultat des données renvoyées. Les arguments sont liés à un champ spécifique. Et les arguments peuvent être passés à la fois dans Query et Mutation .


 ?query={product(id:1){name,info,price}} 

Dans ce cas, l'argument id du champ product avec une valeur de 1 indique qu'il est nécessaire de renvoyer le produit avec l'identifiant spécifié.
Pour la list arguments sont omis, mais dans une application réelle, cela peut être, par exemple: limit et offset .


Résoudre (reconnaissance)


Toute la logique de travail avec les données (par exemple, les requêtes de base de données, le traitement et le filtrage) est dans les reconnaisseurs, ce sont eux qui renvoient les données qui seront transmises au client en réponse à la requête.


Tapez


GraphQL utilise son système de types pour décrire les données. Vous pouvez utiliser les types de base String , Int , Float , Boolean et les vôtres (personnalisés). Pour notre exemple, nous aurons besoin d'un Product type personnalisé, qui décrira toutes les propriétés du produit


 var productType = graphql.NewObject( graphql.ObjectConfig{ Name: "Product", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.Int, }, "name": &graphql.Field{ Type: graphql.String, }, "info": &graphql.Field{ Type: graphql.String, }, "price": &graphql.Field{ Type: graphql.Float, }, }, }, ) 

Pour chaque champ, le type de base est spécifié, dans ce cas graphql.Int , graphql.String , graphql.Float .
Le nombre de champs imbriqués n'est pas limité, vous pouvez donc implémenter un système graphique de n'importe quel niveau.


Mutation


Les mutations sont ces données mutables, qui comprennent: l'ajout, l'édition et la suppression. Sinon, les mutations sont très similaires aux requêtes régulières: elles prennent également des arguments Args et renvoient des données Resolve en réponse à la requête.


Écrivons des mutations pour nos produits.
  var mutationType = graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{ /*    http://localhost:8080/product?query=mutation+_{create(name:"Tequila",info:"Alcohol",price:99){id,name,info,price}} */ "create": &graphql.Field{ Type: productType, Description: "Create new product", Args: graphql.FieldConfigArgument{ "name": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), //     }, "info": &graphql.ArgumentConfig{ Type: graphql.String, //    }, "price": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Float), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { rand.Seed(time.Now().UnixNano()) product := Product{ ID: int64(rand.Intn(100000)), //   ID Name: params.Args["name"].(string), Info: params.Args["info"].(string), Price: params.Args["price"].(float64), } products = append(products, product) return product, nil }, }, /*    id http://localhost:8080/product?query=mutation+_{update(id:1,price:195){id,name,info,price}} */ "update": &graphql.Field{ Type: productType, Description: "Update product by id", Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Int), }, "name": &graphql.ArgumentConfig{ Type: graphql.String, }, "info": &graphql.ArgumentConfig{ Type: graphql.String, }, "price": &graphql.ArgumentConfig{ Type: graphql.Float, }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { id, _ := params.Args["id"].(int) name, nameOk := params.Args["name"].(string) info, infoOk := params.Args["info"].(string) price, priceOk := params.Args["price"].(float64) product := Product{} for i, p := range products { //     if int64(id) == p.ID { if nameOk { products[i].Name = name } if infoOk { products[i].Info = info } if priceOk { products[i].Price = price } product = products[i] break } } return product, nil }, }, /*    id http://localhost:8080/product?query=mutation+_{delete(id:1){id,name,info,price}} */ "delete": &graphql.Field{ Type: productType, Description: "Delete product by id", Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Int), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { id, _ := params.Args["id"].(int) product := Product{} for i, p := range products { if int64(id) == p.ID { product = products[i] //     products = append(products[:i], products[i+1:]...) } } return product, nil }, }, }, }) 

Tous similaires à queryType . Il n'y a qu'un seul petit type d' graphql.NewNonNull(graphql.Int) , qui nous dit que ce champ ne peut pas être vide (similaire à NOT NULL dans MySQL)


C’est tout. Nous avons maintenant une API Go CRUD simple pour travailler avec des marchandises. Nous n'avons pas utilisé la base de données pour cet exemple, mais nous avons examiné comment créer un modèle de données et les manipuler à l'aide de mutations.


Des exemples


Si vous avez téléchargé la source via


 go get github.com/graphql-go/graphql 

allez dans le répertoire avec un exemple


 cd examples/crud 

et exécutez l'application


 go run main.go 

Vous pouvez utiliser les requêtes suivantes:
Obtenir un produit par ID
http://localhost:8080/product?query={product(id:1){name,info,price}}


Obtenir une liste de produits
http://localhost:8080/product?query={list{id,name,info,price}}


Ajouter un nouveau produit
http://localhost:8080/product?query=mutation+_{create(name:"Tequila",info:"Strong alcoholic beverage",price:999){id,name,info,price}}


Edition de produits
http://localhost:8080/product?query=mutation+_{update(id:1,price:195){id,name,info,price}}


Suppression d'un produit par identifiant
http://localhost:8080/product?query=mutation+_{delete(id:1){id,name,info,price}}


Si vous utilisez REST, faites attention à GraphQL comme alternative possible. Oui, à première vue, cela semble plus difficile, mais cela vaut la peine de commencer et dans quelques jours, vous maîtriserez cette technologie. Au moins, ce sera utile.

Source: https://habr.com/ru/post/fr418203/


All Articles