GraphQL API (CRUD) en marcha

imagen


Hola a todos! Hay muchos artículos sobre GraphQL en Habr, pero después de leerlos, descubrí que todos omiten un lenguaje tan maravilloso como Go. Hoy intentaré corregir este malentendido. Para hacer esto, escribiremos una API en Go usando GraphQL.


En pocas palabras: GraphQL es un lenguaje de consulta para construir una API que describe en qué forma solicitar y devolver datos (información más detallada sobre el recurso oficial graphql.imtqy.com y en el concentrador )


Puede argumentar que GraphQL o REST es mejor aquí.


Tendremos la API clásica: CRUD (Crear, Leer, Actualizar, Eliminar) agregar, recibir, editar y eliminar productos en la tienda en línea.
En el lado del servidor, utilizaremos la implementación lista de GraphQL graphql-go


Primero necesitas descargar graphql-go, esto se puede hacer con el comando


go get github.com/graphql-go/graphql 

A continuación, describimos la estructura del producto (en forma simplificada)


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

ID - identificador único, Name - nombre, Info - información del producto, Price - precio


Lo primero que debe hacer es llamar al método Do , que acepta un esquema de datos y parámetros de consulta como parámetros de entrada. Y nos devolverá los datos resultantes (para su posterior transmisión al cliente)


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

Código completo
 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 - esquema de datos, RequestString - valor del parámetro de cadena de consulta, en nuestro caso, valor de query


Esquema


Un esquema acepta dos tipos de datos raíz: Query : datos inmutables, Mutation : datos mutables


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

Consulta


Query se usa para leer (y solo leer) datos. Usando Query especificamos qué datos debe devolver el servidor.
Escribiremos una implementación del tipo de datos Query , en nuestro caso contendrá campos con información sobre un solo producto (producto) y una lista de productos (lista)


 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 }, }, }, }) 

El tipo queryType contiene el Name y los Fields requeridos, así como la Description opcional (utilizada para la documentación)
A su vez, el campo Fields también contiene el campo Type requerido y los Args opcionales Args , Resolve y Description


Args (argumentos)


Argumentos: una lista de parámetros transferidos del cliente al servidor y que afectan el resultado de los datos devueltos. Los argumentos están vinculados a un campo específico. Y los argumentos se pueden pasar tanto en Query como en Mutation .


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

En este caso, el argumento id para el campo del product con un valor de 1 indica que es necesario devolver el producto con el identificador especificado.
Para la list se omiten list argumentos, pero en una aplicación real puede ser, por ejemplo: limit y offset .


Resolver (reconocimiento)


Toda la lógica de trabajar con datos (por ejemplo, consultas de bases de datos, procesamiento y filtrado) está en los reconocedores, son ellos quienes devuelven los datos que se transmitirán al cliente como respuesta a la solicitud.


Tipo


GraphQL usa su sistema de tipos para describir datos. Puede usar los tipos básicos String , Int , Float , Boolean y los suyos (personalizados). Para nuestro ejemplo, necesitaremos un tipo de Product personalizado, que describirá todas las propiedades del producto.


 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, }, }, }, ) 

Para cada campo, se especifica el tipo base, en este caso graphql.Int , graphql.String , graphql.Float .
El número de campos anidados no está limitado, por lo que puede implementar un sistema gráfico de cualquier nivel.


Mutación


Las mutaciones son estos datos mutables, que incluyen: adición, edición y eliminación. De lo contrario, las mutaciones son muy similares a las consultas regulares: también toman argumentos de Args y devuelven datos de Args como respuesta a la consulta.


Escribamos mutaciones para nuestros productos.
  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 }, }, }, }) 

Todo similar a queryType . Solo hay un tipo de característica pequeña graphql.NewNonNull(graphql.Int) , que nos dice que este campo no puede estar vacío (similar a NOT NULL en MySQL)


Eso es todo. Ahora tenemos una API Go CRUD simple para trabajar con productos. No usamos la base de datos para este ejemplo, pero vimos cómo crear un modelo de datos y manipularlos usando mutaciones.


Ejemplos


Si descargó la fuente a través de


 go get github.com/graphql-go/graphql 

solo ve al directorio con un ejemplo


 cd examples/crud 

y ejecuta la aplicación


 go run main.go 

Puede usar las siguientes consultas:
Obtener producto por ID
http://localhost:8080/product?query={product(id:1){name,info,price}}


Obtener una lista de productos
http://localhost:8080/product?query={list{id,name,info,price}}


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


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


Eliminar un producto por id
http://localhost:8080/product?query=mutation+_{delete(id:1){id,name,info,price}}


Si usa REST, preste atención a GraphQL como una posible alternativa. Sí, a primera vista parece más difícil, pero vale la pena comenzar y en un par de días dominarás esta tecnología. Al menos será útil.

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


All Articles