使用ApolloServer探索GraphQL的Subscription功能

首先

这篇文章是上一篇关于介绍GraphQL概述,并确认Query和Mutation的操作的续篇。本文将讨论GraphQL的订阅功能。

“Subscription(订阅)是一个熟悉的词汇,直译为“购买”,在GraphQL中指的是在Mutation操作数据时,客户端收到来自服务器发出的变更信息,可用于聊天应用程序、通知功能、登录认证等。推荐这里的解释,非常易懂。”

这一次,我们将使用Apollo来替代之前建立的Express-GraphQL服务器,搭建GraphQL服务器,并检查Subscription的运作情况。

“Apollo是什么意思?”

Apollo是一个用于构建数据图的平台,它是一个连接前端应用程序客户端(如React或iOS应用程序)与后端服务无缝通信的通信层。

通过位于客户端和服务器之间的Apollo服务器,GraphQL数据交互变得更加流畅。

download.png

公式文件

准备好

创建项目后,通过npm安装apollo-server。

mkdir graphql-server
cd graphql-server
mkdir resolver
touch index.js db.js resolver/{Query.js,Mutation.js,Subscription.js}
npm init
npm install apollo-server apollo-server-express --save

为了使示例代码更易读

发布-订阅模式

PubSub就像一个工厂,通过Subscription创建通知事件。

const { PubSub } = require('apollo-server');

const pubsub = new PubSub();

高翘了。

使用称为gql的标记模板文字面量函数,可以将与GraphQL相关的定义表示为JavaScript字符串。

const typeDefs = gql`
  type Subscription {
    postAdded: Post
  }

  type Query {
    posts: [Post]
  }

  type Mutation {
    addPost(author: String, comment: String): Post
  }

  type Post {
    author: String
    comment: String
  }
`

Resolver参数

在解析函数中,有四个固定参数扮演着特定的角色。

引数(昇順)説明parent親リゾルバからの戻り値。argsフィールドに提供されたすべてのGraphQL引数を含むオブジェクトcontext特定の操作に対して実行されるすべてのリゾルバ間で共有されます。これを使用して、認証情報やデータソースへのアクセスなどの操作ごとの状態を共有します。infoフィールド名、ルートからフィールドへのパスなど、操作の実行状態に関する情報

代码介绍和解释

请写下代码到已创建的文件中。
由于篇幅原因,我们会省略对于Query和Mutation的概述,初次接触GraphQL的朋友们可以从这篇文章开始阅读!?

首先,准备一个用作数据库替代的JavaScript文件。

const posts = [{
    id: '1',
    title: 'こころ',
    author: '夏目漱石'
}, {
    id: '2',
    title: '舞姫',
    author: '森鴎外'
}, {
    id: '3',
    title: '羅生門',
    author: '芥川龍之介'
}]

const db = {
    posts,
}

module.exports  = db;

接下来,我们将依次准备包含Query、Mutation和本次主题的Subscription的解析器函数所在的文件。

const Query = {
    posts(parent, args, { db }, info) {
       //クエリを書いた時に引数が「ない」時
       //模擬データベースの内容を全て表示
        if (!args.query) {
            return db.posts
       //クエリを書いた時に引数が「ある」時
       //引数とtitle or authorが一致したものだけを表示
        }else{
            return db.posts.filter((post) => {
            const isTitleMatch = post.title.toLowerCase().includes(args.query.toLowerCase())
            const isAuthorMatch = post.author.toLowerCase().includes(args.query.toLowerCase())
            return isTitleMatch || isAuthorMatch
        })
    }
    }
}

module.exports  = Query

这是一个查询解析函数。根据参数的有无进行条件分支。通过JavaScript的详细条件编写,GraphQL的优势之一是能够只获取所需的必要信息。

const Mutation = {
    createPost(parent, args, { db, pubsub }, info) {
        const postNumTotal = String(db.posts.length + 1)
        const post = {
            id: postNumTotal,
            ...args.data
        }

        //データベース更新
        db.posts.push(post)
        //Subscription着火
        pubsub.publish('post', { 
                post: {
                    mutation: 'CREATED',
                    data: post
                }
             })
        return post
    },
    updatePost(parent, args, { db, pubsub }, info) {
        const { id, data } = args
        const post = db.posts.find((post) => post.id === id)
        if (!post) {
            throw new Error('Post not found')
        }

        if (typeof data.title === 'string' && typeof data.author === 'string') {
            //データベース更新
            post.title = data.title
            post.author = data.author
            console.log(post)
            //Subscription着火
            pubsub.publish('post', {
            post: {
                mutation: 'UPDATED',
                data: post
            }
        })
        }

        return post
    },
    deletePost(parent, args, { db, pubsub }, info) {
        const post = db.posts.find((post) => post.id === args.id)
        const postIndex = db.posts.findIndex((post) => post.id === args.id)

        if (postIndex === -1) {
            throw new Error('Post not found')
        }
        //データベース更新
        db.posts.splice(postIndex, 1)
        //Subscription着火
        pubsub.publish('post', {
                post: {
                    mutation: 'DELETED',
                    data: post
                }
            })
        return post
    },
}

module.exports  = Mutation

在Mutation的解析器函数中,同时进行了数据库的更新和启动Subscription。

const Subscription = {
    post: {
        subscribe(parent, args, { pubsub }, info) {
            return pubsub.asyncIterator('post')
        }
    }
}

module.exports = Subscription

这是一个订阅的解析器函数。
它使用pubsub.asyncIterator来异步监听订阅的事件。

因为说明的关系,这是最后的部分,包括了模式定义和服务器启动文件。

const  {ApolloServer,PubSub,gql} = require('apollo-server');
const db = require('./db')
const Query = require('./resolver/Query')
const Mutation = require('./resolver/Mutation')
const Subscription = require('./resolver/Subscription')

//スキーマ定義
const typeDefs = gql`
type Query {
  posts(query: String): [Post!]!
}

type Mutation {
  createPost(data: CreatePostInput!): Post!
  deletePost(id: ID!): Post!
  updatePost(id: ID!, data: UpdatePostInput!): Post!
}

# Subscription
type Subscription {
  post: PostSubscriptionPayload!
}

input CreatePostInput {
  title: String!
  author: String!
}

input UpdatePostInput {
  title: String
  author: String!
}

type Post {
  id: ID!
  title: String!
  author: String!
}

######################
# Subscriptionで利用
######################

enum MutationType {
  CREATED
  UPDATED
  DELETED
}

# Subscriptionのフィールド
type PostSubscriptionPayload {
  mutation: MutationType!
  data: Post!
}

`
//PubSubのインスタンスを作成,Subscriptionが利用可能に!
const pubsub = new PubSub()

const server = new ApolloServer({
    typeDefs: typeDefs,
    resolvers: {
        Query,
        Mutation,
        Subscription,
    },
    context: {
        db,
        pubsub
    }
})

server.listen().then(({ url, subscriptionsUrl }) => {
    console.log(`? Server ready at ${url}`);
    console.log(`? Subscriptions ready at ${subscriptionsUrl}`);
  });

在搭建服务器时,指定了模式(schema),解析器(resolver),消息发布与订阅(PubSub)等作为参数。
指定的参数在查询(Query),变更(Mutation)和订阅(Subscription)的各个处理中被使用。

当准备好之后,通过终端启动。

node index.js
? Server ready at http://localhost:4000/
? Subscriptions ready at ws://localhost:4000/graphql

我会像上次一样在GraphQL IDE中进行测试。

要使用Subscription,您需要在编写查询之后,点击一个类似YouTube播放按钮的箭头。
当它变成像照片中的红色暂停按钮一样的时候,表示Subscription成功启动。

スクリーンショット 2020-10-15 23.00.31.png
スクリーンショット 2020-10-15 23.00.52.png
スクリーンショット 2020-10-15 23.01.03.png

我会确认数据的更新和删除。

スクリーンショット 2020-10-15 23.01.53.png
スクリーンショット 2020-10-15 23.01.59.png
スクリーンショット 2020-10-15 23.02.11.png

正在进行订阅,确认数据删除的操作(正在连续进行更新和删除)。

スクリーンショット 2020-10-15 23.02.24.png

我们通过订阅成功确认了通过突变进行数据更新!!??

用中文进行自然改写:
最后

尽管相对于Query和Mutation来说,Subscription理解起来需要花费更多的时间,但通过编写实际的代码并在GraphQL IDE中进行测试,我成功地将其融入自己的理解中。
在下一篇文章中,我将使用Vue作为前端,并在实际应用程序中使用GraphQL!

好的,再见!?

bannerAds