Интегрируйте статически типизированные API с GraphQL
В этом посте вы узнаете, как реализовать сервер GraphQL с использованием ent
и gqlgen
.
Пример репо
Вот ссылка на окончательную кодовую базу:
Зависимости
Требуемые зависимости следующие:
- Go v1.17.2
- Эхо
- энт/энт
- 99designs/gqlgen
Что такое Энт?
ent — это ORM-фреймворк для Go, который упрощает поддержку приложений с базой данных и позволяет нам:
- Выполняйте запросы и легко просматривайте любую структуру графа
- Используйте 100% статически типизированный API посредством генерации кода
- Используйте несколько драйверов хранилища — MySQL, PostgreSQL, SQLite и Gremlin.
- Используйте GraphQL с
gqlgen
Что касается статически типизированных API, sqlboiler
имеет аналогичную функциональность, но разница в том, что ent
— это инструмент, основанный на схеме, который создает базу данных и типизированный API из файла схемы. sqlboilder
— это ORM, ориентированный на базу данных, который создает типизированный API из базы данных.
Обзор реализации
Этапы реализации следующие:
- Установить зависимости
- Настроить эхо-сервер
- Настроить
- Настроить
gqlgen
Установить зависимости
В этом посте требуются некоторые зависимости, поэтому сначала установите их:
go get github.com/labstack/echo/v4 go get github.com/labstack/echo/v4/[email protected] go get github.com/go-sql-driver/mysql go install entgo.io/ent/cmd/ent
Настроить эхо-сервер
Чтобы быстро начать, мы настроим сервер с помощью пакета echo.
Создайте main.go
и напишите код:
И вы можете увидеть страницу по адресу http://localhost:8080:
Настроить
Далее мы настроим ent
.
Создать схему пользователя
В этом посте мы создадим таблицу users
в нашей базе данных.
Перед этим нам нужно создать файл схемы с именем User
:
ent init User
Сгенерированные файлы должны выглядеть так:
ent ├── generate.go └── schema └── user.go
user.go
имеет схему, включающую Fields
и Edges
:
Fields
— Атрибуты узла. Это эквивалентно столбцу в базе данных, напримерname
,age
иcreated_at
.Edges
—Отношения сущностей. Это позволяет вам определить отношение каждой таблицы, напримерO2O
(один к одному),O2M
(один ко многим) иM2M
(многие ко многим).
Итак, мы установим name
, age
и created_at
в Fields
следующим образом:
После добавления полей создайте активы на основе приведенной выше схемы, чтобы они работали в вашем проекте.
Запустите go generate
в корневом проекте:
go generate ./ent
Это создаст файлы следующим образом:
ent ├── client.go ├── config.go ├── context.go ├── ent.go ├── enttest │ └── enttest.go ├── generate.go ├── hook │ └── hook.go ├── migrate │ ├── migrate.go │ └── schema.go ├── mutation.go ├── predicate │ └── predicate.go ├── runtime │ └── runtime.go ├── runtime.go ├── schema │ └── user.go ├── tx.go ├── user │ ├── user.go │ └── where.go ├── user.go ├── user_create.go ├── user_delete.go ├── user_query.go └── user_update.go
Этот актив включает в себя:
- Код, который работает с CRUD в таблице (
user_create.go
,user_delete.go
,user_query.go
иuser_update.go
) - Пакет миграции для SQL
- Сущностный объект
- Пакет ловушек для добавления промежуточного программного обеспечения для мутаций.
- и так далее
Таким образом, с помощью этого актива вы можете безопасно манипулировать таблицами с сущностями, которые имеют статические типы.
Миграция базы данных
ent
также обеспечивает поддержку миграции базы данных для обеспечения согласованности схемы в вашем проекте.
Чтобы запустить миграцию, напишите код в инициализации приложения.
Напишите логику миграции в main.go
:
После завершения инициализации вы увидите, что таблица users
успешно создана.
По умолчанию field.Time
использует тип TIMESTAMP
в MySQL, который имеет диапазон от 1970–01–01 00:00:01 UTC
до 2038–01–19 03:14:07 UTC
.
Если TIMESTAMP
не работает для вашего приложения, вы можете изменить тип на DATETIME
следующим образом:
Создать пользователя
Теперь мы готовы создать пользователя в нашей таблице.
Как упоминалось выше, у нас уже были методы, которые имеют CRUD API для таблицы.
Позвоним Create
в ent/user_create.go
:
Когда вы отправляете запрос POST на http://localhost:8080/users
, это создаст пользователя в таблице users
и впоследствии ответит данными пользователя:
Запросить пользователя
Чтобы получить пользовательские данные из пакета ent
, вы можете использовать Query()
следующим образом:
Это ответит всем пользователям из таблицы:
Вы можете выбрать частичные объекты с помощью функции Select
следующим образом:
Поле age
исключается из ответа следующим образом:
Вы также можете использовать Where
следующим образом:
Функция Only
возвращает один пользовательский объект, а когда пользовательские объекты не найдены, возвращает NotFoundError
.
Когда SQL возвращает несколько пользователей:
us, err := client.User. Query(). Where(user.AgeEQ(21)). Only(c.Request().Context())
ent
предупредит, что запись не одиночная.
user not singular
Создать край
Далее мы создадим еще один объект и объявим его ребро в схеме.
Во-первых, давайте создадим объект с именем Article
.
ent init Artcile
И он сгенерирует article.go
в папке схемы.
ent ├── schema │ ├── article.go │ └── user.go
Затем мы добавим несколько полей, например:
Предположим, что у User
может быть несколько статей, а у статьи есть только один пользователь, поэтому отношение будет one-to-many
.
Итак, давайте создадим ребро от User
до Artcie
.
Сначала добавьте ребро articles
к схеме User
в ent/schema/user.go
:
И запустите его:
go generate ./ent
Когда вы запустите сервер, вы увидите, что таблица articles
создана:
Итак, давайте создадим статью и добавим ее в User
.
Мы продолжаем использовать конечную точку, /users
в main.go
, и помещаем такой код:
Функция AddArticles
была сгенерирована путем добавления ребра в схему, поэтому мы можем использовать ее для добавления статьи пользователю.
Отправив запрос на http://localhost:8080/users
, вы увидите, что запись создана:
Обратный край
Теперь, когда мы можем получить доступ к объекту Article
через объект User
, но в некоторых случаях вы хотите получить пользователя из Article
. Для этого ent
предоставляет функцию edge.From
.
Итак, давайте добавим обратное ребро с именем users
к схеме Article
в ent/schema/artcile.go
:
Поскольку схема User
может иметь более одной ссылки помимо Article
, метод Ref
описывает, на какое ребро пользовательской схемы ссылается.
И запустить генерацию:
go generate ./ent
Итак, давайте запросим пользователя из объекта статьи:
В журнале консоли вы можете увидеть фактическое выполнение SQL:
SELECT DISTINCT `users`.`id`, `users`.`name`, `users`.`age`, `users`.`created_at` FROM `users` JOIN (SELECT `articles`.`user_articles` FROM `articles` WHERE `articles`.`id` = ?) AS `t1` ON `users`.`id` = `t1`.`user_articles` LIMIT 2 args=[1]
И это отвечает на пользовательские данные так:
Нетерпеливая загрузка
ent
предоставляет возможность запрашивать объекты с их ассоциациями через Eager loading
.
Нетерпеливая загрузка позволяет запрашивать более одной ассоциации. Например, мы можем включить в пользовательские данные такие статьи:
И ответ включает данные о статьях в edges
:
Выполненный журнал выглядит следующим образом:
SELECT DISTINCT `users`.`id`, `users`.`name`, `users`.`age`, `users`.`created_at` FROM `users` WHERE `users`.`id` = ? LIMIT 2 args=[7] SELECT DISTINCT `articles`.`id`, `articles`.`title`, `articles`.`description`, `articles`.`user_articles` FROM `articles` WHERE `user_articles` IN (?) args=[7]
Пользовательский столбец внешнего ключа
При создании ребра ent
по умолчанию автоматически создает столбец внешнего ключа.
Например, при создании Article
ent
создал столбец user_articles
как внешний ключ.
Чтобы настроить это имя, вы можете использовать StorageKey
в ent/schema/user.go
следующим образом:
И запустите go generate ./ent
, он добавит столбец user_id
в таблицу articles
:
ПРИМЕЧАНИЕ: ent делает внешний ключ обнуляемым по умолчанию. После объединения PR вы можете установить NOT NULL
в качестве внешнего ключа в качестве требуемого края.
Настроить GraphQL
Далее мы подключим ent
к GraphQL, используя gqlgen
.
Настроить gqlgen
Во-первых, давайте установим gqlgen:
go get github.com/99designs/gqlgen
Чтобы настроить пакет, выполните эту команду:
go run github.com/99designs/gqlgen init
И это создаст следующие макеты в корневом проекте:
├── gqlgen.yml ├── graph │ ├── generated │ │ └── generated.go │ ├── model │ │ └── models_gen.go │ ├── resolver.go │ ├── schema.graphqls │ └── schema.resolvers.go
gqlgen.yml
— Конфиг-файл для управления сгенерированным файломgraph/generated
— Пакет для выполнения (автогенерируется)model/models_gen.go
— Пакет для всех графовых моделей (автогенерируется)resolver.go
— корень преобразователя графаschema.graphqls
— файл схемы, который вы можете настроить по своему усмотрениюschema.resolvers.go
— Реализация резолвера (сгенерирована автоматически)
gqlgen.yml
необходимо изменить на ent, поэтому вставьте код:
autobind
ищет любые имена типов, совпадающие в папке ent
. В этом случае в резолвере будет использоваться ent.User
.
Далее мы изменим schema.graphqls
на user.graphqls
и вставим такой код:
И удалите существующий преобразователь, schema.resolvers.go
:
rm -r ./graph/schema.resolvers.go
Запустите генерацию кода gqlgen
:
go run github.com/99designs/gqlgen
Итак, каталог проекта должен выглядеть так:
graph ├── generated │ └── generated.go ├── model │ └── models_gen.go ├── resolver.go ├── user.graphqls └── user.resolvers.go
Подключить ent к gqlgen
И расширение GraphQL для ent
:
go get entgo.io/contrib/entgql
Чтобы использовать gqlgen
в проекте ent, в нашем проекте необходимо включить расширение ent.
Для этого создайте новый файл с именем ent/entc.go
и добавьте следующий код:
Затем откройте файл ent/generate.go
и измените код на:
package ent //go:generate go run -mod=mod entc.go
Запустите кодеген:
go generate ./ent
Итак, выполнив эту команду, в наш проект добавятся некоторые надстройки.
Наконец, чтобы использовать преобразователь ent.Client
в gelgen
, откройте graph/resolver.go
и передайте его в схему как дополнительную зависимость:
Запустите сервер GraphQL
Теперь, когда мы готовы запустить сервер GraphQL.
Давайте изменим main.go
для настройки сервера GraphQL.
И откройте http://localhost:8080/playground
:
Запросить пользователя
Так как преобразователь для пользователя еще не реализован, давайте добавим преобразователь в graph/user.resolvers.go
:
Запустите запрос на игровой площадке, и он должен вернуть данные пользователя:
Заключение
Вот и все! Мы реализовали сервер GraphQL, используя пакеты ent
и gqlgen
. Как видите, ent
предоставляет нам действительно полезный API со строго типизированной базой данных.
Gorm был стандартным инструментом ORM для Go и предоставляет полнофункциональный пакет. Что касается статических типов, то gorm берет интерфейс и использует внутреннее отражение, поэтому иногда он теряет свои типы и возникает ошибка времени выполнения.
С другой стороны, ent
использует codegen для генерации типов и API, чтобы уведомлять нас о возможных ошибках во время компиляции.
Если вам нужно использовать статически типизированный API и интеграцию с GraphQL, ent будет одним из вариантов для вас.