关于如何使用 Lighthouse(GraphQL服务器)的具体方法
这篇文章是2022年ururu Advent Calendar活动的第一篇文章。
首先
我将写一篇关于我目前负责的项目后端如何使用GraphQL服务器Lighthouse的具体方法的文章。
我希望能够遇到像下面这样的人。
-
- これからLighthouseを導入するための材料を集めている
- Lighthouseを具体的にどう利用しているのか知りたい
Lighthouse公式文档
https://lighthouse-php.com/
我是通过学习以下链接中的GraphQL基础知识来了解的:
https://www.oreilly.co.jp/books/9784873118932/
各种版本 (Gè
-
- PHP:8.1
-
- Laravel:9.0
-
- lighthouse:5.55
- dd-trace:0.74.0
引入Lighthouse
请参考以下链接:
https://lighthouse-php.com/master/getting-started/installation.html#install-via-composer
詳細细节不多提。
将模式的分割进行概括
所有内容也可以整理在schema.graphql中,但是可能会变得非常复杂,所以我们将其分开。
目录结构
我们按照以下的结构进行构建。
我们将文件按照每个基本模型分开。
src/
├ graphql/
│ ├ models/
│ │ ├ user.graphql
│ │ ├ company.graphql
│ │ ├ ...
│ │ └ prefecture.graphql
│ └ schema.graphql
...
如何编写模式(Schema)
模式. graphql
为了能够读取models文件夹中的所有文件,我们将进行相应的操作。
type Query
type Mutation
#import **/*.graphql
model文件夹下的文件们
在中文中,它可以这样表达:扩展类型Query,扩展类型Mutation进行定义。
extend type Query {
"hugahuga"
hoge: Hoge! @paginate(defaultCount: 10)
}
extend type Mutation {
"hugehuge"
update(id: Int!, input: HugeInput @spread): Huge @update
}
...
防止查询名称枯竭
这是一个普适的查询名称,比如”all”、”find”等,但是如果不经过任何改动就构建模式,它只能使用一次。
即使改变名称以避免重复,如”userAll”或”companyAll”,也不是很好。
更好的方式是使用”user的all查询”或”company的all查询”这样的命名,更容易使用。
因此,我们通过在类型中指定查询来避免这个问题,并将业务相关的查询进行整合。
准备一个什么都不做的解决方案。
我們將準備一個返回空陣列的Noop解析程式。
<?php
namespace Modules\GraphQL\Queries;
/**
* Class NoopResolver
*/
class NoopResolver
{
/**
*
* @param null $_
* @param array<string, mixed> $args
*/
public function __invoke($_, array $args): array
{
return [];
}
}
使用NoopResolver来定义架构
-
- 在@field函数中使用定义的NoopResolver.
-
- 准备一个用于汇总查询的类型
- 定义一个用于定义字段的类型
extend type Query {
"【Query】ユーザー"
user: UserQuery! @field(resolver: "NoopResolver")
}
"【Type】ユーザー"
type User {
"ID"
id: Int!
"名前"
name: String!
"有効フラグ"
active: Boolean!
"作成日時"
createdAt: DateTime @rename(attribute: "created_at")
"更新日時"
updatedAt: DateTime @rename(attribute: "updated_at")
}
"【Query】ユーザー"
type UserQuery {
"ユーザー全件取得"
all: [User!]! @paginate(defaultCount: 10)
}
extend type Query {
"【Query】会社"
company: CompanyQuery! @field(resolver: "NoopResolver")
}
"【Type】会社"
type Company {
"ID"
id: Int!
"名前"
name: String!
"住所"
address: String!
"作成日時"
createdAt: DateTime @rename(attribute: "created_at")
"更新日時"
updatedAt: DateTime @rename(attribute: "updated_at")
}
"【Query】会社"
type CompanyQuery {
"会社全件取得"
all: [Company!]! @paginate(defaultCount: 10)
}
通过这个方法,所有查询都可以不冲突地调用,具体如下。
{
user {
all {
data {
id
name
}
}
}
company {
all {
data {
id
name
}
}
}
}
经常使用的三种指令和描述方法
我将向您介绍经常使用的三个选择。
本地范围
公式如下:
https://lighthouse-php.com/5/eloquent/getting-started.html#local-scopes
在需要对特定数据进行筛选的模型中使用。
在关联数据中进行筛选和提取时非常有用。
使用方法
在希望将用户分类为活跃用户和非活跃用户的情况下,试着使用这个。
定义一个模型以`scope`作为开始的函数,并且在其中的处理中编写查询语句。
<?php
...省略
class User
{
...省略
/**
* @param Builder $query
*
* @return Builder
*/
public function scopeActive(Builder $query): Builder
{
return $query->where('active', true);
}
/**
* @param Builder $query
*
* @return Builder
*/
public function scopeInActive(Builder $query): Builder
{
return $query->where('active', false);
}
}
在使用模式时,为所有指令定义作用域。
extend type Query {
"【Query】ユーザー"
user: UserQuery! @field(resolver: "NoopResolver")
}
"【Type】ユーザー"
type User {
"ID"
id: Int!
"アクティブフラグ"
active: Boolean!
"名前"
name: String!
}
"【Query】ユーザー"
type UserQuery {
"アクティブユーザー取得"
allActiveUser: [User!]! @all(scopes: ["active"])
"非アクティブユーザー取得"
allInActiveUser: [User!]! @all(scopes: ["inActive"])
}
可以使用下面的查询获取各种数据。
{
user {
allActiveUser {
data {
id
active
name
}
}
allInActiveUser {
data {
id
active
name
}
}
}
}
用汉语进行适当改述。仅提供一个选项:
@方法
如果想要为每个提取的数据添加计算结果等字段时,可以使用该功能。
公式文档
https://lighthouse-php.com/5/api-reference/directives.html#method
使用方法
作为一个简单的示例,我们尝试添加一个根据活动状态而改变的字段来返回文字。
在模型中定义一个函数
... 省略
/**
* @return string
*/
public function isActive(): string
{
if ($this->active) {
return 'アクティブ';
}
return '非アクティブ';
}
将其添加到字段中
extend type Query {
"【Query】ユーザー"
user: UserQuery! @field(resolver: "NoopResolver")
}
"【Type】ユーザー"
type User {
"ID"
id: Int!
"アクティブ文言"
isActive: String! @method(name: "isActive")
"名前"
name: String!
}
"【Query】ユーザー"
type UserQuery {
"ユーザー全件取得"
all: [User!]! @all
}
利用@hasMany指令的relation。
在某种情况下,我们希望通过scopes对关联数据进行筛选,并且同时改变字段名称时可以使用这种方法。
使用方法
尝试根据公司所拥有的用户,将用户分为活跃用户和非活跃用户。
定义一个关于公司模型的关系。
... 省略
/**
* @return HasMany
*/
public function users(): HasMany
{
return $this->hasMany(User::class);
}
在用户的模型中定义范围。(与前一章中定义的内容相同)
...省略
/**
* @param Builder $query
*
* @return Builder
*/
public function scopeActive(Builder $query): Builder
{
return $query->where('active', true);
}
/**
* @param Builder $query
*
* @return Builder
*/
public function scopeInActive(Builder $query): Builder
{
return $query->where('active', false);
}
}
由于Lighthouse的特性,我们认为字段名与关系函数名相等,因此需要使用relation选项,直接指定函数名来定义字段。
extend type Query {
"【Query】会社"
company: CompanyQuery! @field(resolver: "NoopResolver")
}
"【Type】会社"
type Company {
"ID"
id: Int!
"名前"
name: String!
"住所"
address: String!
"作成日時"
createdAt: DateTime @rename(attribute: "created_at")
"更新日時"
updatedAt: DateTime @rename(attribute: "updated_at")
"アクティブなユーザー"
activeUsers: [User!]! @hasMany(scopes: ["active"], relation: "users")
"非アクティブなユーザー"
inActiveUsers: [User!]! @hasMany(scopes: ["inActive"], relation: "users")
}
"【Query】会社"
type CompanyQuery {
"会社全件取得"
all: [Company!]! @paginate(defaultCount: 10)
}
想要测量查询性能。
GraphQL在只有一个端点的情况下,很难对性能进行测量。
我们通过将其与Datadog的APM进行标记和整合,使得可以对每个查询进行聚合统计。
做法
关于DatadogAPM的集成,请参考下面的链接。
详细信息略过,具体内容请参考:https://docs.datadoghq.com/ja/tracing/trace_collection/dd_libraries/php/?tab=%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A
在src/app/Http/Middleware目录下创建Datadog.php。
从发送查询时的Request中获取operationName,并进行标记。
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use DDTrace\GlobalTracer;
class Datadog
{
/**
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$response = $next($request);
$span = GlobalTracer::get()->getActiveSpan();
if ($span === null) {
return $response;
}
$operationName = $request->toArray();
if ($operationName) {
$span->setTag('operationName', $operationName["operationName"]);
}
return $response;
}
}
将上述创建的类放入src/config/lighthouse.php中的$middleware中。
<?php
$middleware = [
...省略
\App\Http\Middleware\Datadog::class
];

最后
灯塔相当好用…!
我们团队进行了相当多的验证,
它不仅提供了丰富的方便指令,而且几乎可以实现我们所需要的加载操作。
还可以很好地解决N+1问题。
https://lighthouse-php.com/master/performance/n-plus-one.html#the-n-1-query-problem
虽然还有很多地方没有介绍到,但对于想在Laravel上构建GraphQL服务器的人来说,我推荐这个!
明天是由kurifumi撰写的文章!请期待哦!