實務上可能不想接收所有 Subscription,而是只想訂閱有興趣內容,此時可如 Query 對 Subscription 傳入 Argument,Apollo GraphQL 將使用 Subscription Filter 處理。
Version
macOS Catalina 10.15.2
WebStorm 2019.3.1
Node 12.4.0
Apollo GraphQL 2.9.6
Apollo GraphQL
src/index.js
import { ApolloServer, gql, PubSub, withFilter } from 'apollo-server'
let data = [
{ title: 'FP in JavaScript', price: 100 },
{ title: 'RxJS in Action', price: 200 },
]
let pubsub = new PubSub()
let typeDefs = gql`
type Query {
books: [Book]
}
type Mutation {
addBook(book: BookInput!): Book
}
type Subscription {
newBook(title: String!): Book
}
type Book {
title: String
price: Int
}
input BookInput {
title: String!
price: Int!
}
`
let books = () => data
let addBook = (_, { book }, { pubsub }) => {
data.push(book)
pubsub.publish('bookAdded', {
newBook: book,
payload: book.title
})
return book
}
let newBook = {
subscribe: withFilter(
(_, __, { pubsub }) => pubsub.asyncIterator('bookAdded'),
({ payload }, { title }) => payload === title
)
}
let resolvers = {
Query: {
books
},
Mutation: {
addBook
},
Subscription: {
newBook
}
}
let context = () => ({ pubsub })
new ApolloServer({ typeDefs, resolvers, context })
.listen()
.then(({ url }) => `GraphQL Server ready at ${ url }`)
.then(console.log)
第 1 行
import { ApolloServer, gql, PubSub, withFilter } from 'apollo-server'
若要使用 subscription,除了 import ApolloServer
與 gql
外,還需要 PubSub
。
withFilter()
則是 subscription filter 所使用,用來過濾 event 所傳來資料,只將 user 所關注資料回傳。
第 3 行
let data = [
{ title: 'FP in JavaScript', price: 100 },
{ title: 'RxJS in Action', price: 200 },
]
原始只有 2 筆資料,第 3 筆將由 mutation 新增並通知 subscription。
第 8 行
let pubsub = new PubSub()
PubSub
為 factory class,實作了 PubSubEngine
interface,稍後會以 publish()
發布 event。
11 行
type Query {
books: [Book]
}
type Mutation {
addBook(book: BookInput!): Book
}
type Subscription {
newBook(title: String!): Book
}
type Book {
title: String
price: Int
}
input BookInput {
title: String!
price: Int!
}
在 schema 宣告 books
query、addBook
mutation 與 newBook
subscription,值得注意的是 subscription 宣告方式與 query 與 mutation 並無差異,且多了 title
argument。
54 行
let resolvers = {
Query: {
books
},
Mutation: {
addBook
},
Subscription: {
newBook
}
}
在 resolvers
宣告 books
query、addBook
mutation 與 newBook
subscription,值得注意的是 subscription 宣告方式與 query 與 mutation 並無差異。
34 行
let books = () => data
實踐 books
query,目前只是回傳所有資料。
36 行
let addBook = (_, { book }, { pubsub }) => {
data.push(book)
pubsub.publish('bookAdded', {
newBook: book,
payload: book.title
})
return book
}
實踐 addBook
mutation,重點在 mutation 內使用 pubsub.publish()
發布 bookAdded
event。
pubsub
直接從 context
argument 解構,稍後會建立 context。
publish()
的第一個 argument 為 event 名稱。
第二個 argument 為要發到 event 的資料:
newBook
為 subscription 名稱,book
為要傳給 subscription 的資料payload
為使用 subscription filter 時的 argument 名稱,book.title
為傳給 filter 資料,若不使用 filter 可省略不傳payload
47 行
let newBook = {
subscribe: withFilter(
(_, __, { pubsub }) => pubsub.asyncIterator('bookAdded'),
({ payload }, { title }) => payload === title
)
}
實踐 newBook
subscription,實作 subscribe()
時特別使用 withFilter()
higher order function。
withFilter()
第一個 argument 為 simple subscription 的 subscribe()
,由 context
argument 直接解構出 pubsub
,再由 pubsub.asyncIterator()
訂閱 bookAdded
event。
第二個 argument 相當於 subscription filter 的 predicate,回傳 true
或 false
,由 第一個 argument 解構出從 addBook
mutation 傳來的 payload
,再由第二個 argument 解構出從 newBook
subscription 傳來的 title
。
66 行
let context = () => ({ pubsub })
new ApolloServer({ typeDefs, resolvers, context })
.listen()
.then(({ url }) => `GraphQL Server ready at ${ url }`)
.then(console.log)
由於各 resolver 都要使用 pubsub
,因此特別將 pubsub
放到 context 內。
在 context()
內回傳包含 pubsub
的 object,然後再 new ApolloServer
時傳入。
GraphQL Playground
Query
query {
books {
title
price
}
}
執行 books
query 回傳目前所有資料,共兩筆。
Subscription
subscription {
newBook(title: "Speaking JavaScript") {
title
price
}
}
執行 newBook
subscription,且只有當 title
為 Speaking JavaScript
才通知,由於還沒被觸發,因此處在 listening 狀態。
Mutation
mutation {
addBook(book: {
title: "JavaScript Ninja",
price: 400
}) {
title
price
}
}
執行 addBook
mutation,新增 title
為 JavaScript Ninja
的 book。
Subscription
subscription {
newBook(title: "Speaking JavaScript") {
title
price
}
}
可發現 newBook
subscription 並沒有被觸發,因為 title
並不是 Speaking JavaScript
。
Mutation
mutation {
addBook(book: {
title: "Speaking JavaScript",
price: 500
}) {
title
price
}
}
執行 addBook
mutation,新增 title
為 Speaking JavaScript
的 book,此 mutation 將觸發 newBook
subscription。
Subscription
subscription {
newBook(title: "Speaking JavaScript") {
title
price
}
}
原本 newBook
subscription 由 listening 得到回傳資料。
Conclusion
- Subscription filter 關鍵在於
withFilter()
higher order function,第一個 argument 與普通 subscription 無異,目的只是訂閱 event,第二個 argument 則是 predicate,可比對 event 傳來的資料與 subscription 傳來的資料 - Subscription 會在 resolver 內使用
pubsub
,因此特別適合將pubsub
放在 context 內讓多個 resolver 共用
Sample Code
完整範例可在我的 GitHub 上找到
Reference
Apollo, Subscription Filters
Apollo, Adding Subscriptions To Schema
Shadaj Laddad, Tutorial: GraphQL Subscriptions on the Server
Alex Banks & Eve Porcello, Learning GraphQL