尝试在Apollo上进行优雅的API实现(GraphQL)

首先,

这次的样本已经准备好了,包括Apollo Server和Apollo Client的示例。

我以前写过这样一篇文章。
使用React(+Redux)和gRPC实现的Clean Architecture + 微服务架构
就BFF前端API部分而言,我认为通过阅读下一篇文章,可以用GraphQL来实现客户端通信,这样会更灵活、更强大,所以我尝试了一下。
我想向前端工程师们宣传Apollo Client。

请参考下面的内容,了解REST API和GraphQL之间的区别。
“GraphQL是一种改变应用程序开发流程的技术,与REST有所不同。”

引入GraphQL的好处大致如下。

    • APIのインタフェース定義(送信パラメータのデータ型定義)ができる(それにともない、データの型定義によるAPIパラメータのデータ型バリデーションチェックができる)

 

    • データ取得条件に合わせてGETエンドポイントを大量に作成しなくて済む(GraphQLスキーマ単位になる)

 

    • APIのバージョン管理ができる

 

    APIレスポンスのキャッシュが容易

以下是反面的缺点。

    • 日本語の記事が少ない

 

    GraphQLインタフェース定義を学習するコストがかかる

Apollo Client有一个名为Link的功能来管理通信后的数据,因此被期望作为Redux的替代品。

Apollo引入GraphQL

Apollo是一个用于GraphQL前端和后端的库。
需要在后端使用Apollo Server,在前端使用Apollo Client。
此外,还附带了一个名为GraphiQL的可视化编辑器工具,可以轻松进行API的操作验证。

后端(Apollo Server)

支持各种NodeJS框架。

    • express

 

    • koa

 

    • hapi

 

    • restify

 

    • lambda

 

    • micro

 

    • azure-functions

 

    adonis

由于本次实现采用 Express,因此我们需要使用 npm 安装支持 Express 的 Apollo。

npm install --save apollo-server-express graphql-tools graphql express body-parser

以下是最小的样本实现。

const express = require('express')
const bodyParser = require('body-parser')
const { graphqlExpress, graphiqlExpress } = require('apollo-server-express')
const { makeExecutableSchema } = require('graphql-tools')
const app = express()

process.on('uncaughtException', (err) => console.error(err))
process.on('unhandledRejection', (err) => console.error(err))

app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json())


// GraphQLスキーマ定義
const typeDefs = `
  """
  type Query (必須)
  """
  type Query { books: [Book] }

  """
  返却するデータ構造
  """
  type Book { title: String, author: String }
`

// ダミーデータ
const books = [
  {
    title: 'Harry Potter and the Sorcerer\'s stone',
    author: 'J.K. Rowling',
  },
  {
    title: 'Jurassic Park',
    author: 'Michael Crichton',
  },
]

// resolvers
const resolvers = {
  Query: { books: () => books },
}

// スキーマ生成
const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
})

// GraphQLエンドポイント
app.use('/graphql', bodyParser.json(), graphqlExpress({ schema }))

// GraphiQL:GraphQLクエリのvisual editor
// TODO: 本番デプロイ時はアクセス出来ないようにする
app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }))

app.listen(5000, () => {
  console.log('Access to http://localhost:5000')
})

请使用以下命令执行。

$ node server.js

当您访问http://localhost:5000/graphiql时,将显示GraphiQL。您可以编写查询并通过点击”▶”按钮来执行,这样您就可以获取虚拟数据。

query {
  books {
      title,
      author,
  }
}
スクリーンショット 2018-06-10 15.07.16.png

GraphQL 的架构定义

以下的文章提供了詳細信息:
《GraphQL入門- 讓您想要使用GraphQL》的文章將細節整理得很好。

在中文中,获取数据使用Query,更新数据使用Mutation。
在模式定义中,定义type Query或type Mutation是必需的。
模式定义如下所示。

const typeDefs = `

  type Query {
    フィールド名(引数): 返却データ型
  }

  type Mutation {
    フィールド名(引数): 返却データ型
  }

}
`

// resolvers
const resolvers = {
  Query: {
    フィールド名: (引数) => 返却データ,
  },
  Mutation: {
    フィールド名: (引数) => 返却データ,
  },
}

数据的基本类型如下所示。

    • Int: 32bit整数型

 

    • Float: 浮動小数型

 

    • String: UTF-8 文字列型

 

    • Boolean: true もしくは false

 

    ID: ユニークなスカラー値、キャッシュに使われる。Stringをシリアライズ(直列化)したデータで保存されている

此外,您可以为任意对象单元定义数据类型。以下是定义Book类型的示例。如果要将参数设置为必需,请在数据类型末尾添加!。

type Query { 
  books: [Book]!,
}

type Book { title: String, author: String }

请参考GraphQL的官方文档:Schemas and Types,以获取更详细的信息。

當定義多個方法時,將方法添加到type Query或type Mutation的區塊內。另外,用”””括起來則成為註解。

const typeDefs = `
  type Query { 
    books: [Book],
    items: [Item],
  }

  """
  返却するデータ構造
  """
  type Book { title: String, author: String }
  type Item { title: String }
`

// resolvers
const resolvers = {
  Query: {
    books: () => books,
    items: () => items,
  },
}

如果需要传递参数,请按照以下方式书写。
解析函数签名
参数将传递给args。


// GraphQLスキーマ定義
const typeDefs = `
  type Query { 
    books(author: String): [Book],
  }

  type Book { title: String, author: String }
`

// resolvers
const resolvers = {
  Query: {
    books: (obj, args, context, info) => {
      const author = args.author
      return books.filter(book => book.author === author)
    },
  },
}

在GraphiQL中创建以下查询。

query ($author: String!){
  books(author: $author) {
      title,
      author,
  }
}

在查询变量中,指定以下搜索条件参数。
JSON的键将存储在$author中。

{
  "author": "Michael Crichton"
}
スクリーンショット 2018-06-10 19.01.59.png

前端(Apollo Client)

首先,我們將從React + Apollo Client嘗試通信至Apollo Server。然後,我們將下載Apollo Client。

npm install --save apollo-boost react-apollo graphql-tag graphql

您可以使用ApolloClient来生成ApolloProvider并通过它来包裹应用程序组件,以便在内部可以使用GraphQL。

/*globals module: false */
import React  from 'react'
import ReactDOM from 'react-dom'
import { hot } from 'react-hot-loader'
import { ApolloProvider } from 'react-apollo'
import ApolloClient from 'apollo-boost'
import App from './App'

const client = new ApolloClient({
  uri: 'http://localhost:5050/graphql',
})


const render = () => {
  ReactDOM.render(
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
    ,
    document.getElementById('root'),
  )
}

// Webpack Hot Module Replacement API
hot(module)(render)

render()

这是App.js。在qql中编写查询。
通过Query组件来实际调用API。
API的结果将会返回{loading, error, data}。
为了方便使用,我创建了一个名为graphQL的HOC来进行包装。

import React from 'react'
import gql from 'graphql-tag'
import { Query } from 'react-apollo'

// GraphQLクエリ
const query = gql`
query ($author: String!){
    books(author: $author) {
        title,
      author,
  }
}
`

// HOC
const graphQL = (param) => (WrappedComponent) => (props) => (
  <Query
    query={query}
    variables={param}>
    {({loading, error, data}) => <WrappedComponent {...props} loading={loading} error={error} data={data} />}
  </Query>
)


class App extends React.Component {

  render () {
    const { loading, error, data } = this.props
    if (loading) return <p>Loading...</p>
    if (error) return <p>Error...</p>

    return (<div>
      {data.books.map(book =>
        <div key={book.title}>
          <h4>{book.title}</h4>
          <span>{book.author}</span>
        </div>
      )}
    </div>)
  }
}

export default graphQL({
  author: 'Michael Crichton',
})(App)
スクリーンショット 2018-06-11 4.30.16.png

我們可以像替代 Redux 的 reducer 和 react-redux 的 connect 這樣做。

以下是一个学习Apollo Client + React的参考。

参考资料:Apollo Client + React入门指南。

Apollo Client能否替代Redux?

在参考文章中,有一篇专栏说GraphQL是否替代了Redux,
Apollo Client交互与GraphQL通信的结果状态管理,包括服务器端渲染等与通信相关的部分(比如redux-thunk和redux-saga)可以替代Redux。(主要涉及到react-redux的connect和reducer)
相反,对于通信以外的应用程序数据(例如保持画面转换状态的react-router-redux等),我觉得管理机制较为薄弱(或者说不存在?)。(apollo-link-state似乎只处理通信相关)
基于上述情况,目前似乎无法摒弃Redux,需要两个本地存储,这可能是一种反模式。(相反,如果这个问题解决了,我很想积极使用)
从路由来看,貌似可以将history传递给props?
基于GraphQL的设计思想本身非常好,我认为与Rest API相比,它使通信更加智能。

广告
将在 10 秒后关闭
bannerAds