Руководство по тому, как ступить на яму GraphQL в Go

Go
Руководство по тому, как ступить на яму GraphQL в Go

1.REST vs GraphQL

REST — это очень популярное соглашение о взаимодействии между внешним и внутренним интерфейсом.В соответствии с этим соглашением внешний интерфейс фокусируется на странице при взаимодействии с внутренними данными, а серверный — на предоставлении интерфейсов API. Проблемы, возникающие при разработке RESTful API:

  • Расширяемость: по мере того, как количество API продолжает расти, интерфейс RESTful API будет становиться все более и более раздутым.
  • Недоступно по запросу: интерфейс, который возвращает идентификатор, имя, возраст, город, адрес и адрес электронной почты. Если вы получаете только некоторую информацию, такую ​​как имя и возраст, вы должны вернуть всю информацию об интерфейсе, а затем извлечь из нее то, что вам нужно.Недостаток в том, что это не только увеличит объем передачи по сети, но и сделает неудобным для клиента обработку данных.
  • Проблемы с RESTful API: например, обеспечение того, чтобы параметры, предоставленные клиентом, были типобезопасными, как генерировать документацию API из кода и т. д.
  • Один запрос не может получить все необходимые ресурсы: Например, если клиенту необходимо отобразить содержимое статьи, а также комментарии и информацию об авторе, ему необходимо вызвать интерфейс статей, комментариев и пользователей.Недостатком является то, что обслуживание службы затруднено, а время отклика больше..
    RESTful API обычно состоят из нескольких конечных точек, каждая из которых представляет ресурс. Поэтому, когда клиенту требуется несколько ресурсов, ему необходимо инициировать несколько запросов к RESTful API для получения необходимых данных.

GraphQL с открытым исходным кодом Facebook, который практикуется в крупных компаниях, таких как Twitter и GitHub, представляет собой язык запросов данных, который обеспечивает следующие свойства:

  • Запросите больше или меньше ваших данных: запросы GraphQL всегда получают именно те данные, которые вам нужны, ни больше, ни меньше, поэтому возвращаемые результаты предсказуемы.
  • Получение нескольких ресурсов одним запросом: запросы GraphQL могут не только получать свойства ресурсов, но и выполнять дальнейшие запросы по ресурсам, поэтому GraphQL может получить все данные, необходимые вашему приложению, одним запросом.
  • описать все возможные системы типов: GraphQL API составлен на основе типов и полей.Типы используются для обеспечения того, чтобы приложения запрашивали только возможные типы, предоставляя при этом четкие вспомогательные сообщения об ошибках.
  • Используйте существующие данные и код: GraphQL позволяет всему вашему приложению совместно использовать набор API-интерфейсов, что позволяет лучше использовать существующие данные и код с помощью API-интерфейсов GraphQL. Механизмы GraphQL реализованы на нескольких языках. GraphQL не ограничивается конкретной базой данных и может использовать существующие данные, код и даже подключаться к сторонним API.
  • Эволюция API без управления версиями: Добавляйте поля и типы в GraphQL API, не затрагивая существующие запросы. Старые поля можно удалить и скрыть от инструментов.

2. Введение в GraphQL

Определение, данное на официальном сайте: «GraphQL — это одновременноЯзык запросов для APIтакжеСреда выполнения, удовлетворяющая вашему запросу данных. GraphQL предоставляет наборПолное описание, которое легко понять, чтобы клиент могПолучите именно те данные, которые ему нужны, без какой-либо избыточности, что упрощает развитие API с течением времени и создание мощных инструментов для разработчиков».

  • Разве API не для звонков? Да, именно в этом сила GraphQL, цитата из официальной документацииask exactly what you want.
  • По сути, GraphQL — это язык запросов.
  • Приведенные выше определения абстрактны и трудны для понимания, но попрактиковавшись в использовании GraphQL, вы сможете понять их более глубоко.
  • Данные иерархичны по своей природе, а также являются графом взаимосвязей. Основная цель GraphQL — выражать такие графики.

В GraphQL для достижения описанных выше функций путем определения Schema и объявления Type вам необходимо научиться:

  • Абстракция модели данных описывается типом, тогда как определить Тип?
  • Логика получения данных из интерфейса описывается схемой, то как определить схему?

2.1 Как определить тип

Абстракция модели данных описывается Типом, каждый Тип состоит из нескольких Полей, и каждое Поле указывает на определенный Тип.

Тип GraphQL можно просто разделить на два типа, один из которыхскалярный тип, другойтип объекта.

2.1.1 scalar type

Встроенные скаляры в GraphQL включают String, Int, Float, Boolean, Enum, а скаляры — это наименьшие частицы в системе типов GraphQL.

2.1.2 object type

Только скаляров недостаточно для абстрагирования некоторых сложных моделей данных, нужно использовать объектные типы. Типы объектов используются для создания формы модели данных в GraphQL, и в то же время можно объявить внутреннюю связь между различными моделями (один ко многим, один к одному или многие ко -многие).

Отображение модели один на один:

type Article {
	id: ID
	text: String
	isPublished: Boolean
	author: User
}

Приведенный выше код объявляет тип статьи, который имеет три поля, а именно id (тип ID), текст (тип String), isPublished (логический тип) и автор (новый тип объекта User) Тип пользователя объявляется следующим образом:

type User {
	id: ID
	name: String
}

2.1.3 Type Modifier

Модификаторы типа, в настоящее время существует два типа модификаторов типа, а именноListиRequired , синтаксис [Type] и Type! соответственно, их можно использовать в комбинации:

  • [Тип]! : Сам список обязателен, но внутренние элементы могут быть пустыми
  • [Тип!] : Сам список может быть пустым, но его внутренние элементы обязательны.
  • [Тип!]! : Требуются как сам список, так и внутренние элементы

2.2 Как определить схему

схема используется для описанияИнтерфейс для получения логики данных, Query используется в GraphQL для абстрагирования логики запроса данных, которая делится на три типа, а именнозапрос, мутация, подписка. Интерфейс API можно разделить на четыре категории: CRUD (создание, получение, изменение, удаление), запрос может охватывать функцию R (получение), а мутация может охватывать функцию (CUD создание, изменение, удаление).

Уведомление: запрос относится конкретно к запросу в GraphQL (включая три типа), а запрос относится к типу запроса в GraphQL (относится только к типу запроса).

2.2.1 Query

  • запрос: при получении данных выберите тип запроса
  • мутация (изменение): при попытке изменить данные выберите тип мутации
  • подписка (подписка): когда вы хотите, чтобы данные изменились, вы можете отправить сообщение, используя тип подписки (предлагаемый для текущих все более популярных приложений реального времени).

Используя Article в качестве модели данных, напишите интерфейс CURD с точки зрения REST и GraphQL соответственно.

  • Интерфейс отдыха
GET  /api/v1/articles/
GET /api/v1/article/:id/
POST /api/v1/article/
DELETE /api/v1/article/:id/
PATCH /api/v1/article/:id/
  • GraphQL Query
query  {
        articles():[Article!]!
        article(id: Int!): Article!
}
mutation {
        createArticle(): Article!
        updateArticle(id: Int): Article!
        deleteArticle(id: Int): Article!
}

Уведомление:

  • GraphQL — это запрос, мутация и подписка, которые делят функции по типу, а возвращаемый тип данных должен быть явно объявлен.

2.2.2 Resolver

Приведенное выше описание не объясняет, как вернуть логику данных связанных операций (запрос, мутация, подписка). Все здесь представляет более основную концепциюРезольвер (разрешающая функция).

В GraphQL такое соглашение есть по умолчанию, запрос (в том числе запрос, мутация, подписка) и соответствующий резолвер имеют одно и то же имя, например, aboutarticles(): [Articles!]!Для этого запроса имя его Резолвера должно быть названо article.

Возьмите объявленный запрос статей в качестве примера, чтобы объяснить внутренний рабочий механизм GraphQL.:

Query {
	articles {
		id
		author {
			name
		}
		comments {
			id
			desc
			author
		}
	}
}

Проанализируйте следующим образом:

  1. Сначала выполняется первый синтаксический анализ, и текущий типqueryтип, а имя Resolverarticles.
  2. Следующим шагом является использование распознавателя статей для получения проанализированных данных, и первый уровень синтаксического анализа завершен.
  3. Следующим шагом является выполнение синтаксического анализа второго уровня для возвращаемого значения синтаксического анализа первого уровня.Текущие статьи содержат три подзапроса, которые являются идентификатором, автором и комментариями соответственно.
  • id является скалярным типом в типе Author, и синтаксический анализ завершается.
  • Автором является тип объекта Пользователь в типе статей Попробуйте использовать распознаватель пользователя для получения данных Текущее поле анализируется.
  • Следующим шагом является выполнение синтаксического анализа третьего уровня для возвращаемого значения синтаксического анализа второго уровня.Текущий автор также содержит запрос, имя является скалярным типом, и синтаксический анализ заканчивается.
  • Анализ комментариев такой же, как и выше.

Резюме Общий процесс синтаксического анализа GraphQL состоит в том, чтобы попытаться использовать его Resolver для получения значения после обнаружения запроса, а затем проанализировать возвращаемое значение.Этот процесс является рекурсивным до тех пор, пока все анализируемые типы полей не будут Scalar Type (скалярный тип). Весь процесс синтаксического анализа можно представить как очень длинную цепочку резольвера. GraphQL часто используется в качестве промежуточного уровня в реальном использовании.Сбор данных инкапсулируется Resolver.Реализация внутреннего сбора данных может быть основана на RPC, REST, WS, SQL и других различных методах.

3. Пример GraphQL

В следующем разделе будет показаноgraphql-goПримеры управления пользователями, реализованные библиотекой, включая функции получения всей информации о пользователе, получения конкретной информации о пользователе, изменения имен пользователей, удаления пользователей и как создавать функции типа перечисления "полный код здесь":

3.1 Содержимое сгенерированного файла схемы следующее:

//mutation 操作可完成C(创建)、U(更新)、D(删除)
type Mutation {
  """[用户管理] 修改用户名称""" //操作注释信息
  changeUserName(
    """用户ID""" //参数注释信息,必传一个Int类型的值
    userId: Int!
​
    """用户名称"""
    userName: String!
  ): Boolean
​
  """[用户管理] 创建用户"""
  createUser(
    """用户名称"""
    userName: String!
​
    """用户邮箱"""
    email: String!
​
    """用户密码"""
    pwd: String!
​
    """用户联系方式"""
    phone: Int
  ): Boolean
  
  """[用户管理] 删除用户"""
  deleteUser(
    """用户ID"""
    userId: Int!
  ): Boolean
}
//query 操作,可完成 R(查询)
type Query {
  """[用户管理] 获取指定用户的信息"""
  UserInfo(
    """用户ID"""
    userId: Int!
  ): userInfo
​
  """[用户管理] 获取全部用户的信息"""
  UserListInfo: [userInfo]!
}
//object type 说明
"""用户信息描述"""
type userInfo {
  """用户email"""
  email: String //字段说明
​
  """用户名称"""
  name: String
​
  """用户手机号"""
  phone: Int
​
  """用户密码"""
  pwd: String
​
  """用户状态"""
  status: UserStatusEnum
​
  """用户ID"""
  userID: Int
}
//枚举类型在schema文件中的展示
"""用户状态信息"""
enum UserStatusEnum {
  """用户可用"""
  EnableUser
​
  """用户不可用"""
  DisableUser
}
​

Уведомление

  • Существует относительно немного примеров GraphQL на основе golang.
  • Схема GraphQL может быть сгенерирована автоматически, а конкретная операция может быть просмотренаgraphq-cliДокументация, шаги примерно включают установку пакета npm, установку инструмента graphql-cli и изменение файлов конфигурации (Здесь нужно указать адрес, по которому сервис открыт для внешнего мира.) ,воплощать в жизньgraphql get-schemaЗаказ.

3.2 Определение типа объекта GraphQL

type UserInfo struct {
    UserID uint64               `json:"userID"`
    Name   string               `json:"name"`
    Email  string               `json:"email"`
    Phone  int64                `json:"phone"`
    Pwd    string               `json:"pwd"`
    Status model.UserStatusType `json:"status"`
}
//这段内容是如何使用 GraphQL 定义枚举类型
var UserStatusEnumType = graphql.NewEnum(graphql.EnumConfig{
    Name:        "UserStatusEnum",
    Description: "用户状态信息",
    Values: graphql.EnumValueConfigMap{
        "EnableUser": &graphql.EnumValueConfig{
            Value:       model.EnableStatus,
            Description: "用户可用",
        },
        "DisableUser": &graphql.EnumValueConfig{
            Value:       model.DisableStatus,
            Description: "用户不可用",
        },
    },
})
//定义 object type, 前端可以按需获取该类型中包含的字段
var UserInfoType = graphql.NewObject(graphql.ObjectConfig{
    Name:        "userInfo",
    Description: "用户信息描述",
    Fields: graphql.Fields{
        "userID": &graphql.Field{
            Description: "用户ID",
            Type:        graphql.Int,
        },
        "name": &graphql.Field{
            Description: "用户名称",
            Type:        graphql.String,
        },
        "email": &graphql.Field{
            Description: "用户email",
            Type:        graphql.String,
        },
        "phone": &graphql.Field{
            Description: "用户手机号",
            Type:        graphql.Int,
        },
        "pwd": &graphql.Field{
            Description: "用户密码",
            Type:        graphql.String,
        },
        "status": &graphql.Field{
            Description: "用户状态",
            Type:        UserStatusEnumType,
        },
    },
})

3.3 Определение запроса и мутации

var MutationType = graphql.NewObject(graphql.ObjectConfig{
    Name: "Mutation",
    Fields: graphql.Fields{
        "createUser": &graphql.Field{
            Type:        graphql.Boolean,
            Description: "[用户管理] 创建用户",
            Args: graphql.FieldConfigArgument{
                "userName": &graphql.ArgumentConfig{
                    Description: "用户名称",
                    Type:        graphql.NewNonNull(graphql.String),
                },
                "email": &graphql.ArgumentConfig{
                    Description: "用户邮箱",
                    Type:        graphql.NewNonNull(graphql.String),
                },
                "pwd": &graphql.ArgumentConfig{
                    Description: "用户密码",
                    Type:        graphql.NewNonNull(graphql.String),
                },
                "phone": &graphql.ArgumentConfig{
                    Description: "用户联系方式",
                    Type:        graphql.Int,
                },
            },
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                userId, _ := strconv.Atoi(GenerateID())
                user := &model.User{
                  //展示如何解析传入的参数,传入参数必须符合断言
                    Name: p.Args["userName"].(string),
                    Email: sql.NullString{
                        String: p.Args["email"].(string),
                        Valid:  true,
                    },
                    Pwd:    p.Args["pwd"].(string),
                    Phone:  int64(p.Args["phone"].(int)),
                    UserID: uint64(userId),
                    Status: int64(model.EnableStatus),
                }
                ......
                return true, nil
​
            },
        },
    
    },
})
​
var QueryType = graphql.NewObject(graphql.ObjectConfig{
    Name: "Query",
    Fields: graphql.Fields{
        "UserListInfo": &graphql.Field{
            Description: "[用户管理] 获取指定用户的信息",
            //定义了非空的 list 类型
            Type:        graphql.NewNonNull(graphql.NewList(UserInfoType)),
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                users, err := model.GetUsers()
                if err != nil {
                    log.WithError(err).Error("[query.UserInfo] invoke InserUser() failed")
                    return false, err
                }
                usersList := make([]*UserInfo, 0)
                for _, v := range users {
                    userInfo := new(UserInfo)
                    userInfo.Name = v.Name
                    userInfo.Email = v.Email.String
                    userInfo.Phone = v.Phone
                    userInfo.Pwd = v.Pwd
                    userInfo.Status = model.UserStatusType(v.Status)
                    usersList = append(usersList, userInfo)
​
                }
                return usersList, nil
​
            },
        },
    },
})
​

Уведомление:

  • Здесь показаны только некоторые примеры.
  • Здесь автор только перечисляет определения типов запросов и мутаций.

3.4 Как определить основную функцию службы

​
func main() {
    ......
    //new graphql schema
    schema, err := graphql.NewSchema(
        graphql.SchemaConfig{
            Query:    object.QueryType,
            Mutation: object.MutationType,
        },
    )
​
    //此次从 http 请求的 header中获取 user_id 的值,然后通过 context 向后续操作传递
    http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
        ctx := context.Background()
        //read user_id from gateway
        userIDStr := r.Header.Get("user_id")
        if len(userIDStr) > 0 {
            userID, err := strconv.Atoi(userIDStr)
            if err != nil {
                w.WriteHeader(http.StatusBadRequest)
                w.Write([]byte(err.Error()))
                return
            }
            ctx = context.WithValue(ctx, "ContextUserIDKey", userID)
        }
        h.ContextHandler(ctx, w, r)
​
    })
    log.Fatal(http.ListenAndServe(svrCfg.Addr, nil))
}
​

4. Резюме

В процессе практики GraphQL автор обнаружил следующие проблемы:

  • В дополнение к официальной версии Node.js от Facebook, которая лучше поддерживается, в других версиях меньше документации и практик.
  • graphql-goналичие тестов в библиотекеn + 1Проблема, рекомендуется использовать библиотеку graphql-go под граф-сусликами, т.к. она четко прописана в Featuresparallel execution of resolvers.
  • Rest и GraphQL являются внешними интерфейсами системы, поддерживаемыми сервером, и они могут сосуществовать.
  • GraphQL более подвержен атакам типа «отказ в обслуживании», и его следует использовать с особой осторожностью.
  • Преимущества GraphQL в основном заключаются в эффективности разработки внешнего интерфейса, но для реализации требуется полное сотрудничество сервера.

Если это небольшая компания без технической нагрузки, то вполне понятно рассмотреть возможность использования GraphQL по особенностям бизнеса с частой сменой интерфейса, но если это крупная компания, то она уже использует RESTful API для всех внешних целей, и не использует GraphQL на основе учета трудозатрат.Также разумно.Будь то GraphQL или RESTful API, лучше выбрать правильный в соответствии с местными условиями.. Хотя GraphQL подходит для такой компании, как Facebook, маловероятно, что у всех других компаний такие же потребности бизнеса, как у Facebook.

Когда автор впервые столкнулся с GraphQL, неизбежно возникли недоразумения, пожалуйста, укажите.

5. Ссылки