iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 20
0

前言

前一篇稍微的介紹了 GraphQL 裡面的 Schema 以及 Type,這一篇將會進一步嘗試更複雜的 Schema,介紹一些 Query 的變化,以及 GraphQL 的一些特色。

準備簡單的資料

延續上一篇的內容,把 GraphQLSchema.js 裡面的 UserPost 兩種 Type 定義好,並讓完整的清單能在 Query 上面找得到:

exports.schema = buildSchema(`
  type User {
    id: ID!
    name: String!
  }

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

  type Query {
    users: [User!]!
    posts: [Post!]!
  }
`);

接著我們用最簡單的方式來把資料存在對應的 JavaScript 變數中:

const usersById = {
  1: {
    id: 1,
    name: 'chentsulin',
  },
};

const postsById = {
  18: {
    id: 18,
    authorId: 1,
    title: 'Day 18:GraphQL 入門 Part I - 從 REST 到 GraphQL',
    body: 'Facebook 在 2012 年開始在公司內部使用 GraphQL,而在 2015 年 7 月開源...',
  },
  19: {
    id: 19,
    authorId: 1,
    title: 'Day 19:GraphQL 入門 Part II - 實作 Schema & Type',
    body: '前一篇講了 REST 的一些缺點,還有 GraphQL 如何解決這些問題...',
  },
};

有了資料後再改寫 rootValuedefaultFieldResolver 能處理 userspost 這兩個 Key:

exports.rootValue = {
  users: () => Object.keys(usersById).map(id => usersById[id]),
  posts: () => Object.keys(postsById).map(id => postsById[id]),
};

就能在 GraphiQL 用以下的 Query 來看到結果:

query {
  users {
    id
    name
  }
  posts {
    id
    title
    body
  }
}

前面的 query 可以省略不寫,所以我們接下來幾乎都會省略:

{
  users {
    id
    name
  }
  posts {
    id
    title
    body
  }
}

巢狀資源

GraphQL 的強項之一就是處理關聯的複雜度,而前面的範例其實還沒有真的實作到關聯的部分,雖然眼尖的讀者可能會發現,我在 Post 的資料上面留下了 authorId 這個屬性。

而 GraphQL 完全不限制拿資料的方式,不管是要從變數、SQL、NoSQL、RESTful API 拿,都是可行的。也必須特別強調,GraphQL 的 Type 不一定要跟資料庫表格有明確的對應。

這次我們修改一下 Schema,在 UserPost 之間建立一個一對多的關聯,可以查 User 的所有 posts,也可以查 Postauthor

type User {
  id: ID!
  name: String!
  posts: [Post!]!
}

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

針對 Schema 上面所定義的屬性 postsauthor,已經不是用原始的 JavaScript 物件就能表達的了,所以必須宣告特別的 class 來處理 Resolve 的邏輯,下方的 GraphQLUser.postsGraphQLPost.author 先留空沒關係:

class GraphQLUser {
  constructor({ id, name }) {
    this.id = id;
    this.name = name;
  }

  posts() {
    // ...待實作
  }
}

class GraphQLPost {
  constructor({ id, authorId, title, body }) {
    this.id = id;
    this.authorId = authorId;
    this.title = title;
    this.body = body;
  }

  author() {
    // ...待實作
  }
}

接著把原本 Resolve 的一般物件替換成用 class 建構出來的物件:

exports.rootValue = {
  users: () => Object.keys(usersById).map(
    id => new GraphQLUser(usersById[id])
  ),
  posts: () => Object.keys(postsById).map(
    id => new GraphQLPost(postsById[id])
  ),
};

如此一來應該不會破壞原本的任何功能,因為在 GraphQLUser 上一樣能取到 idname

再來要實作 GraphQLPost.author 的 Resolve 邏輯,利用 authorId 去把資料取回來並回傳 GraphQLUser

class GraphQLPost {
  //...

  author() {
    return new GraphQLUser(usersById[this.authorId]);
  }
}

GraphQLUser.posts 的 Resolve 邏輯則複雜一點,要用 id 過濾出屬於 User 的文章:

class GraphQLUser {
  //...

  posts() {
    return Object.keys(postsById)
      .map(id => new GraphQLPost(postsById[id]))
      .filter(post => post.authorId === this.id);
  }
}

完成這個步驟就已經算是大功告成,可以開始嘗試最恐怖的巢狀 Query:

{
  users {
    name
    posts {
      title
      author {
        name
        posts {
          title
          author {
            id
            name
          }
        }
      }
    }
  }
}

很順的拿回了結果!這就是 GraphQL 最令人驚艷的地方。

參數

如同 REST 的 GET 一樣,在使用 Query 時難免還是需要設定參數,而這在 GraphQL 是透過在 Key 後面放置 () 來傳遞,例如:

{
  user(id: 1) {
    name
  }
}

實作上也非常簡單,如下方這樣去定義把 id: ID! 傳遞給 user 的 Resolving Function:

type Query {
  users: [User!]!
  posts: [Post!]!
  user(id: ID!): User
}

就可以在第一個參數拿到 id

exports.rootValue = {
  // ...
  user: ({ id }) => usersById[id] ? new GraphQLUser(usersById[id]) : null,
};

在 GraphiQL 上應該能得到以下結果:

{
  user(id: 1) {
    name
    posts {
      title
    }
  }
}

Client

GraphQL 是個剛起步的生態系,雖然套件的完善程度可能遠不及 REST 的千分之一,但也算是發展的非常迅速,在這幾篇中使用到的 GraphiQL 當然也是其中之一。

Lokka 是個小型的 GraphQL Client,只有簡單的 Query、Mutation、Fragemt 的功能以及非常陽春的 Cache 功能,不過也是最好上手的,彈性高。

Apollo Client 則是中型的 GraphQL Client,支援 ReactVueAngular 2 等等的框架。它是由目前 GraphQL 最大的社群組織 - Apollo 所維護,這個社群組織囊跨前後端以及工具的開發,並擁抱開源,他們最早的架構設計圖稿也能在 Github 上看到。

Relay 則是由 Facebook 所開發目前最有野心的一個 GraphQL Client,除了跟 React 密切的結合外,還幫忙處理了 Cache、Fragment Composition、Pagination、錯誤處理、Optimistic Updates 等等最困難的問題。

結語

跟以往開發不同的是,在寫 GraphQL 時最多的時間會花在定義Schema 跟 Type,雖然乍看之下定義會花掉很多很多的時間,但定義完後就能隨便查詢的人怎麼去使用它,並盡其所能地去利用系統中所有能查詢的資料,這還算是值得的。至於如果效能上的擔憂,建議看一下 Facebook 內部搭配 GraphQL 所使用的機制 DataLoader,應該能解除你的疑慮。


上一篇
Day 19:GraphQL 入門 Part II - 實作 Schema & Type
下一篇
Day 21:GraphQL 入門 Part IV - Mutation
系列文
使用 Modern Web 技術來打造 Native App30

尚未有邦友留言

立即登入留言