GraphQL Microservice with Apollo

使用 GraphQL 獲取 Medium 最新文章列表

Michael Hsu
9 min readAug 18, 2017
Microservice for fetching the latest posts of Medium with GraphQL. [Link]

星期三參加了由優拓主辦的 GraphQL Taiwan 社群小聚 #1,沒想到蠻多公司都有在使用,各種資源都蠻到位的,觀望很久也躍躍欲試,大大們都鋪了幾年的路了,還不上車嗎?現場跟其他工程師交流,也有跟 MediaTek 情況蠻類似的公司,專案由前後端分離的團隊個別負責,要導入 GraphQL 早已經不是技術上的問題,人力工作分配影響更廣。

這篇文章主要從前端角度驗證,能不能在既有系統下,Client Side、API Server 中間再多增加一層 GraphQL Server,而我 DEMO 上拿 Medium API 來使用,架構大致如下圖所示:

GraphQL layer that integrates existing systems. (How to GraphQL)

Medium API

用 Medium 撰寫文章很方便,但就是不知道為什麼官方都沒提供獲取文章列表的正常 API,目前有兩個方向:

  1. RSS XML:但 Parse XML 有一點麻煩,而且我只要要列表 Title 以及 URL 就好。範例: https://medium.com/feed/@evenchange4
  2. JSON String:其實是有返回 JSON 的隱藏 API,但應該是為了JSON Hijacking 安全性問題,開頭多塞了 字串,並且限制不支持 CORS 資源共享。但事實上文章列表本來就只要拿 Public 的,所以我在 Server Side 把格式做了簡單的轉換。範例:https://medium.com/@evenchange4/latest?format=json&limit=10。

GraphQL Schema

因為是第一次開發 GraphQL Server,問了其他開發者建議直接用 Facebook 的 Graphql-JS 來使用,寫起來像是一個個 Object 的定義,為了感受一下結構,挑了有名氣的真的假的 Rumors-api Open Source 來看,剛開始看眼花繚亂的,但因為我要做的 Side Project 其實非常的簡單,所以最後拿 Apollo 的 GraphQL-Tools 來定義 Schema,用起來簡潔多了。

GraphQL-Tools 只要定義好兩件事情:Type 與 Resolver。

1. Type

其實就是一個 String,用來表示 GraphQL 型別的語言。Medium 文章的定義如下 typeDefs 所示,其中還可以自己宣告 type 或是用 scalar 來擴充基本型態:

const typeDefs = `
scalar Timestamp
type Content {
subtitle: String
}
type Post {
id: ID!
subtitle: String!
content: Content!
firstPublishedAt: Timestamp!
}
`;

2. Resolver

定義取值的函示,可以返回 Promise 或是 Value。實際 Query posts 的時候會向 Medium 打 API,接到返回的 Response 來做後續處理。又或是想把巢狀的資料扁平化,新增一個 subtitle 欄位,就是返回一個 Value:

const resolvers = {
Query: {
posts: (obj, { username }, context) =>
API.getPost(username).then(...),
},
Post: {
subtitle: R.path(['content', 'subtitle']),
},
};

最後,透過 GraphQL-Tools makeExecutableSchema 來生成 schema。也因為GraphQL 是強型別,所以測試的時候可以把 Type 定義的資料都 Mock 掉,例如預設 String,測試時就會產生 "Hello World"

const { makeExecutableSchema } = require('graphql-tools');
const schema = makeExecutableSchema({ typeDefs, resolvers });
// Mock for test
const { addMockFunctionsToSchema } = require('graphql-tools');
addMockFunctionsToSchema({ schema });

Apollo Server

小聚議題上有提到,Apollo 維護了數種 Server Framework 的 Plugin,同樣的接口真的很方便。我挑選非常輕量的 Zeit Micro 來實作,因為範例實在太精簡了,文檔一字不刪直接貼過來:

import { microGraphiql, microGraphql } from "graphql-server-micro";
import micro, { send } from "micro";
import { get, post, router } from "microrouter";
import schema from "./schema";

const graphqlHandler = microGraphql({ schema });
const graphiqlHandler = microGraphiql({ endpointURL: "/graphql" });

const server = micro(
router(
get("/graphql", graphqlHandler),
post("/graphql", graphqlHandler),
get("/graphiql", graphiqlHandler),
(req, res) => send(res, 404, "not found"),
),
);

server.listen(3000);

其中 schema 即為上個段落最終生成的結果,這邊還做了另一件事是快速 Setup GraphiQL 作為開發使用。

Per-request Memory Cache

小聚最後一個議程 Daniel Tseng 介紹的 Dataloader,可以幫助 GraphQL 提升效能。設定上可以在 microGraphql 中進行傳遞,如此一來每個 Resolver 都能透過 context 來共享同一份 Dataloader Instance 來達到 Memory Cache 目的,記得把原本 Object 參數改為 Function 使用:

const graphqlHandler = microGraphql(() => ({
schema,
// Per-request basis
context: {
loader: new DataLoader(...),
},
}));
Query: {
posts: (obj, { username }, context) =>
context.loader.load(username).then(...),
},

Fetch to Apollo-Fetch

議程上也有提到 Apollo Client 的輕量套件,封裝的跟原本 Fetch 很像,使用起來很簡單,但比較複雜的前端架構下次可能就會試試看用 React-Apollo 來管理了:

const uri = 'https://micro-medium-api.now.sh/graphql';
const apolloFetch = createApolloFetch({ uri });
apolloFetch({
query: `query PostQuery($username: String!, $limit: Int!){
posts(username: $username, limit: $limit) {
title
firstPublishedAt
url
content {
subtitle
}
}
}`,
variables: `{
"username": "evenchange4",
"limit": 100
}`,
})
.then({ data } => { this.setState(...) })
.catch(error => { ... });

後記

直接打別人的 API 來封裝實在也不太好,所以後來還補上了 Rate-limit 的 Middleware 來限制每個 IP 一秒只能打一次 Request,有興趣可以去看看 GitHub Source Code。

很久沒寫後端 Server 了,因為 GraphQL 實在太紅,後端相關的 Framework/Library 都想試著玩玩看,一開始還搞不太清楚套件彼此間的關係。透過這樣一個 Side Project 來簡易的驗證 GraphQL Server 的可行性,希望有朝一日也能運用在公司專案上!

Further Readings

  1. Microservice 產品交付 — Dockerize 與 Zeit JavaScript 跨平台解決方案
  2. 使用 CircleCI 2.0 Workflows 挑戰三倍速

References

  1. GraphQL Taiwan Meetup 001 — GraphQL
  2. GraphQL Taiwan Meetup 001 — Dataloader

*完整的專案放在 Evenchange4/micro-medium-api,如果你喜歡這系列文章,關於 Michael 在 OSS 專案開發心得,別忘了可以點個 👏 讓我知道喔!

Thanks to Lynn Lin and Daniel Tseng.

--

--