API do GraphQL (CRUD) on Go

imagem


Olá pessoal! Existem muitos artigos sobre o GraphQL no Habr, mas, depois de analisá-los, descobri que todos eles ignoram uma linguagem maravilhosa como o Go. Hoje vou tentar corrigir esse mal-entendido. Para fazer isso, escreveremos uma API on Go usando o GraphQL.


Em poucas palavras: GraphQL é uma linguagem de consulta para a criação de uma API que descreve de que forma solicitar e retornar dados (informações mais detalhadas sobre o recurso oficial graphql.imtqy.com e o hub )


Você pode argumentar que GraphQL ou REST é melhor aqui.


Teremos a API clássica: CRUD (criar, ler, atualizar, excluir) adicionar, receber, editar e excluir produtos na loja online.
No lado do servidor, usaremos a pronta implementação do GraphQL graphql-go


Primeiro você precisa baixar o graphql-go, isso pode ser feito com o comando


go get github.com/graphql-go/graphql 

A seguir, descrevemos a estrutura do produto (de forma simplificada)


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

ID - identificador exclusivo, Name - nome, Info - informações sobre o produto, Price - preço


A primeira coisa a fazer é chamar o método Do , que aceita um esquema de dados e consulta parâmetros como parâmetros de entrada. E ele retornará os dados resultantes para nós (para posterior transmissão ao 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 - Schema dados, RequestString - valor do parâmetro string de consulta, no nosso caso, valor da query


Esquema


Um esquema aceita dois tipos de dados raiz: Query - dados imutáveis, Mutation - dados mutáveis


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

Consulta


Query é usada para ler (e apenas ler) dados. Usando Query especificamos quais dados o servidor deve retornar.
Escreveremos uma implementação do tipo de dados Query , no nosso caso, conterá campos com informações sobre um único produto (produto) e uma lista de mercadorias (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 }, }, }, }) 

O tipo queryType contém o Name e os Fields necessários, bem como a Description opcional (usada para a documentação)
Por sua vez, o campo Fields também contém o campo Type obrigatório e os campos Args , Resolve e Description opcionais


Args (argumentos)


Argumentos - uma lista de parâmetros transferidos do cliente para o servidor e afetando o resultado dos dados retornados. Os argumentos estão vinculados a um campo específico. E os argumentos podem ser passados ​​na Query e na Mutation .


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

Nesse caso, o argumento id para o campo do product com o valor 1 indica que é necessário retornar o produto com o identificador especificado.
Para a list argumentos são omitidos, mas em um aplicativo real, pode ser, por exemplo: limit e offset .


Resolução (reconhecimento)


Toda a lógica de trabalhar com dados (por exemplo, consultas de banco de dados, processamento e filtragem) está nos reconhecedores, são eles que retornam os dados que serão transmitidos ao cliente como resposta à solicitação.


Tipo


O GraphQL usa seu sistema de tipos para descrever dados. Você pode usar os tipos básicos String , Int , Float , Boolean e seus próprios (personalizados). Para o nosso exemplo, precisaremos de um Product personalizado, que descreva todas as propriedades do produto


 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, o tipo de base é especificado, neste caso, graphql.String , graphql.Float , graphql.Float .
O número de campos aninhados não é limitado, portanto, você pode implementar um sistema gráfico de qualquer nível.


Mutação


Mutações são esses dados mutáveis, que incluem: adição, edição e exclusão. Caso contrário, as mutações são muito semelhantes às consultas regulares: elas também Args argumentos de Args e retornam dados de Resolve como resposta à consulta.


Vamos escrever mutações para nossos produtos.
  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 }, }, }, }) 

Tudo semelhante ao queryType . Existe apenas um pequeno tipo de recurso graphql.NewNonNull(graphql.Int) , que indica que esse campo não pode estar vazio (semelhante a NOT NULL no MySQL)


Só isso. Agora, temos uma API Go CRUD simples para trabalhar com mercadorias. Não usamos o banco de dados para este exemplo, mas vimos como criar um modelo de dados e manipulá-los usando mutações.


Exemplos


Se você baixou a fonte através do


 go get github.com/graphql-go/graphql 

basta ir ao diretório com um exemplo


 cd examples/crud 

e execute o aplicativo


 go run main.go 

Você pode usar as seguintes consultas:
Obtendo produto por ID
http://localhost:8080/product?query={product(id:1){name,info,price}}


Obtendo uma lista de produtos
http://localhost:8080/product?query={list{id,name,info,price}}


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


Edição do produto
http://localhost:8080/product?query=mutation+_{update(id:1,price:195){id,name,info,price}}


Removendo um Produto por ID
http://localhost:8080/product?query=mutation+_{delete(id:1){id,name,info,price}}


Se você usa o REST, preste atenção ao GraphQL como uma possível alternativa. Sim, à primeira vista, parece mais difícil, mas vale a pena começar e em alguns dias você dominará essa tecnologia. Pelo menos será útil.

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


All Articles