GraphQL API (CRUD) für unterwegs

Bild


Hallo allerseits! Es gibt viele Artikel über GraphQL auf Habr, aber nachdem ich sie durchgesehen hatte, stellte ich fest, dass sie alle eine so wunderbare Sprache wie Go umgehen. Heute werde ich versuchen, dieses Missverständnis zu korrigieren. Dazu schreiben wir eine API auf Go mit GraphQL.


Kurz gesagt: GraphQL ist eine Abfragesprache zum Erstellen einer API, die beschreibt, in welcher Form Daten angefordert und zurückgegeben werden sollen (detailliertere Informationen zur offiziellen Ressource graphql.imtqy.com und zum Hub ).


Sie können argumentieren, dass GraphQL oder REST hier besser ist.


Wir werden die klassische API haben: CRUD (Erstellen, Lesen, Aktualisieren, Löschen) Hinzufügen, Empfangen, Bearbeiten und Löschen von Produkten im Online-Shop.
Auf der Serverseite werden wir die fertige Implementierung von GraphQL graphql-go verwenden


Zuerst müssen Sie graphql-go herunterladen, dies kann mit dem Befehl erfolgen


go get github.com/graphql-go/graphql 

Als nächstes beschreiben wir die Struktur des Produkts (in vereinfachter Form)


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

ID - eindeutige Kennung, Name - Name, Info - Produktinformationen, Price - Preis


Als erstes müssen Sie die Do Methode aufrufen, die ein Datenschema und Abfrageparameter als Eingabeparameter akzeptiert. Die resultierenden Daten werden an uns zurückgesandt (zur weiteren Übermittlung an den Kunden).


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

Vollständiger Code
 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 - Datenschema, RequestString - Wert des RequestString , in unserem Fall query


Schema


Ein Schema akzeptiert zwei Stammdatentypen: Query - unveränderliche Daten, Mutation - veränderbare Daten


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

Abfrage


Query wird zum Lesen (und nur zum Lesen) von Daten verwendet. Mit Query legen wir fest, welche Daten der Server zurückgeben soll.
Wir werden eine Implementierung des Query schreiben. In unserem Fall enthält sie Felder mit Informationen zu einem einzelnen Produkt (Produkt) und einer Warenliste (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 }, }, }, }) 

Der Typ queryType enthält den erforderlichen Name und die erforderlichen Fields sowie die optionale Description (zur Dokumentation verwendet).
Das Feld Fields enthält wiederum das erforderliche Feld Type und die optionalen Args , Resolve und Description


Argumente (Argumente)


Argumente - Eine Liste von Parametern, die vom Client auf den Server übertragen werden und sich auf das Ergebnis der zurückgegebenen Daten auswirken. Argumente sind an ein bestimmtes Feld gebunden. Die Argumente können sowohl in Query als auch in Mutation .


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

In diesem Fall gibt das id Argument für das product mit dem Wert 1 an, dass das Produkt mit dem angegebenen Bezeichner zurückgegeben werden muss.
Für die list Argumente weggelassen, aber in einer realen Anwendung können dies beispielsweise sein: limit und offset .


Auflösen (Anerkennung)


Die gesamte Logik für die Arbeit mit Daten (z. B. Datenbankabfragen, Verarbeitung und Filterung) liegt in den Erkennern. Sie geben die Daten zurück, die als Antwort auf die Anforderung an den Client übertragen werden.


Typ


GraphQL verwendet sein Typsystem zur Beschreibung von Daten. Sie können sowohl die Grundtypen String , Int , Float , Boolean als auch Ihre eigenen (benutzerdefinierten) Typen verwenden. Für unser Beispiel benötigen wir einen benutzerdefinierten Product , der alle Eigenschaften des Produkts beschreibt


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

Für jedes Feld wird der Basistyp angegeben, in diesem Fall graphql.Int , graphql.String , graphql.Float .
Die Anzahl der verschachtelten Felder ist nicht begrenzt, sodass Sie ein Diagrammsystem jeder Ebene implementieren können.


Mutation


Mutationen sind diese veränderlichen Daten, die Folgendes umfassen: Hinzufügen, Bearbeiten und Löschen. Ansonsten sind Mutationen regulären Abfragen sehr ähnlich: Sie verwenden auch Args Argumente und geben Resolve Daten als Antwort auf die Abfrage zurück.


Schreiben wir Mutationen für unsere Produkte.
  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 }, }, }, }) 

Alles ähnlich wie queryType . Es gibt nur einen kleinen Feature-Typ graphql.NewNonNull(graphql.Int) , der besagt, dass dieses Feld nicht leer sein darf (ähnlich wie NOT NULL in MySQL).


Das ist alles. Jetzt haben wir eine einfache Go CRUD-API für die Arbeit mit Waren. Wir haben die Datenbank für dieses Beispiel nicht verwendet, aber wir haben uns angesehen, wie ein Datenmodell erstellt und mithilfe von Mutationen bearbeitet wird.


Beispiele


Wenn Sie die Quelle über heruntergeladen haben


 go get github.com/graphql-go/graphql 

Gehen Sie einfach mit einem Beispiel in das Verzeichnis


 cd examples/crud 

und führen Sie die Anwendung aus


 go run main.go 

Sie können die folgenden Abfragen verwenden:
Produkt per ID abrufen
http://localhost:8080/product?query={product(id:1){name,info,price}}


Eine Liste der Produkte erhalten
http://localhost:8080/product?query={list{id,name,info,price}}


Neues Produkt hinzufügen
http://localhost:8080/product?query=mutation+_{create(name:"Tequila",info:"Strong alcoholic beverage",price:999){id,name,info,price}}


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


Produkt nach ID entfernen
http://localhost:8080/product?query=mutation+_{delete(id:1){id,name,info,price}}


Wenn Sie REST verwenden, achten Sie auf GraphQL als mögliche Alternative. Ja, auf den ersten Blick scheint es schwieriger zu sein, aber es lohnt sich zu beginnen und in ein paar Tagen werden Sie diese Technologie beherrschen. Zumindest wird es nützlich sein.

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


All Articles