Главная » Хабрахабр » GraphQL API (CRUD) на Go

GraphQL API (CRUD) на Go

image

О GraphQL много статей на Хабре, но пробежавшись по ним обнаружил, что все они обходят стороной такой замечательный язык как Go. Всем привет! Для этого напишем API на Go с использованием GraphQL. Сегодня попробую исправить это недоразумение.

Если совсем коротко: GraphQL это язык запросов для построения API, который описывает в каком виде запрашивать и возвращать данные (более подробная информация на официальном ресурсе graphql.github.io и на хабре)

Поспорить о том, что лучше GraphQL или REST можно тут

У нас будет классическое API: CRUD (Create, Read, Update, Delete) добавление, получение, редактирование и удаление товаров в интернет магазине.
На стороне сервера будем использовать готовую реализацию GraphQL graphql-go

Для начала необходимо скачать graphql-go, это можно сделать командой

go get github.com/graphql-go/graphql

Далее, опишем структуру товара (в упрощенном виде)

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

ID — уникальный идентификатор, Name — название, Info — информация о товаре, Price — цена

А вернет нам результирующие данные (для дальнейшей передачи на клиент) Первое, что необходимо сделать, это вызвать метод Do, который в качестве входных параметров принимает схему данных и параметры запроса.

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

Полный код

func executeQuery(query string, schema graphql.Schema) *graphql.Result ) 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 — схема данных, RequestString — значение параметра строки запроса, в нашем случае значение query

Schema (Схема)

Схема принимает два корневых типа данных: Query — неизменяемые данные, Mutation — изменяемые данные

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

Query (Запросы)

С помощью Query мы указываем какие данные должен вернуть сервер.
Напишем реализацию типа данных Query, в нашем случае он будет содержать поля с получением информации о единичном товаре (product) и списке товаров (list) Query служит для чтения (и только чтения) данных.

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

Тип queryType содержить обязательные поля Name и Fields, а также необязательное Description (используется для документации)
В свою очередь поле Fields также содержит обязательное поле Type и не обязательные поля Args, Resolve и Description

Args (Аргументы)

Аргументы привязаны к конкретному полю. Аргументы — список параметров, переданных с клиента на сервер и влияющие на результат возвращаемых данных. Причем аргументы можно передать как в Query так и в Mutation.

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

В данном случае аргумент id для поля product со значением 1, говорит о том, что необходимо вернуть товар с указанным идентификатором.
Для list аргументы опущены, но в реальном приложении это могут быть, к примеру: limit и offset.

Resolve (Распознаватели)

Вся логика работы с данными (например запросы к БД, обработка и фильтрация) находится в распознователях, именно они возвращают данные, которые будут переданы на клиент в качестве ответа на запрос.

Type (Система типов)

Можно использовать как базовые типы String, Int, Float, Boolean, так и собственные (пользовательские). GraphQL использует свою систему типов для описания данных. Для нашего примера понадобится пользовательский тип Product, который будет описывать все свойства продукта

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

Int, graphql. Для каждого поля указан базовый тип, в данном случае это graphql. Float.
Кол-во вложенных полей не ограничено, благодаря чему можно реализовывать систему графов любого уровня. String
, graphql.

Mutation (Мутации)

Во всем остальном мутации очень похожи на обычные запросы: они также принимают аргументы Args и возвращают данные Resolve в качестве ответа на запрос. Мутации эти изменяемые данные, к которым относится: добавление, редактирование и удаление.

Давайте напишем мутации для наших товаров

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

Есть только одна маленькая особенность тип graphql. Все по аналогии с queryType. Int), который сообщает нам, что данное поле не может быть пустым (похоже на NOT NULL в MySQL) NewNonNull(graphql.

Теперь у нас есть простое CRUD API на Go для работы с товарами. Все. Мы не использовали базу данных для этого примера, но мы рассмотрели как создать модель данных и манипулировать ими с помощью мутаций.

Примеры

Если вы скачали исходники через

go get github.com/graphql-go/graphql

достаточно перейти в директорию с примером

cd examples/crud

и запустить приложение

go run main.go

Вы можете использовать следующие запросы:
Получение продукта по ID
http://localhost:8080/product?query={product(id:1){name,info,price}}

Получение списка продуктов
http://localhost:8080/product?query={list{id,name,info,price}}

Добавление нового продукта
http://localhost:8080/product?query=mutation+_{create(name:"Tequila",info:"Strong alcoholic beverage",price:999){id,name,info,price}}

Редактирование продукта
http://localhost:8080/product?query=mutation+_{update(id:1,price:195){id,name,info,price}}

Удаление продукта по id
http://localhost:8080/product?query=mutation+_{delete(id:1){id,name,info,price}}

Да, с первого взгляда это кажется сложнее, но стоит начать и за пару дней вы освоите данную технологию. Если вы используете REST стоит обратить внимание на GraphQL как на возможную альтернативу. Как минимум это будет полезно.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Из песочницы] WiX.Py: cобираем MSI пакет «в три строчки»

Хотите собирать инсталлер, описывая его простыми и понятными терминами, в несколько строк? Нет времени и желания изучать километровые файлы WiX, чтобы собрать MSI инсталлер для своего проекта, погружаясь при этом в бездны MSDN? Ну тогда вам под кат! Есть клиническая ...

[Из песочницы] Определяем спелость арбуза с помощью Keras: полный цикл, от идеи до программы на Google Play

С чего все началось Все началось с Эппл Маркета — я обнаружил, что у них есть программа, позволяющая определить спелость арбуза. Программа… странная. Чего стоит, хотя бы, предложение постучать по арбузу не костяшками пальцев, а… телефоном! Тем не менее, мне ...