关于如何使用 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来定义架构

    1. 在@field函数中使用定义的NoopResolver.

 

    1. 准备一个用于汇总查询的类型

 

    定义一个用于定义字段的类型
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
];
スクリーンショット 2022-11-30 23.16.03.png

最后

灯塔相当好用…!
我们团队进行了相当多的验证,
它不仅提供了丰富的方便指令,而且几乎可以实现我们所需要的加载操作。
还可以很好地解决N+1问题。
https://lighthouse-php.com/master/performance/n-plus-one.html#the-n-1-query-problem

虽然还有很多地方没有介绍到,但对于想在Laravel上构建GraphQL服务器的人来说,我推荐这个!

明天是由kurifumi撰写的文章!请期待哦!

广告
将在 10 秒后关闭
bannerAds