timzaak / blog

8 stars 1 forks source link

Graphql 使用问题记录 #4

Closed timzaak closed 6 years ago

timzaak commented 7 years ago

分页

由于 GraphQL 不支持 interface 泛型,无法做到一个好的分页抽象、节约代码。不过可以参考官方标准来做批量查找API设计: http://graphql.org/learn/pagination

批量表关联

主要通过 Fetch DeferredResolverAPI 解决关联表批处理问题,DeferredValue 可解决 重复查询

timzaak commented 6 years ago

use GraphQL as Restful API

先来看两个相关项目: graphql-backend-starter foodie

带来的问题:

后端

  1. 请求路由和 Restful API 不一致导致 Play 代码组织结构的变化

    DI 注入 异常处理 用户登陆验证\权限验证 错误返回格式 更严格的数据接口返回量限制

  2. N+1 问题 成为更隐蔽更需规避的问题
  3. 更严格的接口参数设计 例如 retention/funnel actions
  4. 建模方式的变化 DDD 领域驱动设计 Graph Database
  5. 对 Http Proxy 和 微服务Route 造成影响
  6. 性能?

    性能瓶颈在哪里?

  7. Postman do not support GraphQL

前端

import { print } from 'graphql/language/printer';
export default function createNetworkInterface({ uri }) {
  return {
    query(request){
      const token = reduxStore.getState().account.token
      const textBody = JSON.stringify({
        ...request,
        query:print(request.query),
      })
      const headers = {
        Accept: '*/*',
        'Content-Type': 'application/json',
      }
      if (token) {
        headers.auth = token
      }
      return fetch(uri, {
        method: 'POST',
        body: textBody,
        headers,
      }).then(response => {
        const json = response.json()
        console.log(`request:${request.operationName} ${JSON.stringify(request.variables||{})} ${token} ==>${JSON.stringify(json)}`)
        return json
      })
    },
  }
}
import React from 'react'
import {
  View
} from 'react-native'
import styled from 'styled-components/native'
import theme from '../../theme'
import Container from '../../component/container'
import moment from 'moment'
import myCommentQL from '../../graphql/comment/myComment'
import {
  graphql,
} from 'react-apollo'

const FlatList = styled.FlatList`
  flex:1
  marginBottom:${theme.tab_bar_height}
`
const ItemContainer = styled.View`
  paddingHorizontal:${theme.h_spacing_md}
  borderBottomWidth:1
  borderColor:${theme.border_color_base}
`

const Name = styled.Text`
  marginTop:${theme.v_spacing_md}
  fontSize:${theme.font_size_display_md}
  color:${theme.SECOND_FONT}

`
const Content = styled.Text`
  marginVertical:${theme.v_spacing_md}
  fontSize:${theme.font_size_display_sm}

  marginHorizontal:${theme.h_spacing_md}
  color:${theme.FIRST_FONT}
`
const Time = styled.Text`
  alignSelf:flex-end
  fontSize:${theme.font_size_display_sm}
  color:${theme.THIRD_FONT}
  marginBottom:${theme.v_spacing_sm}
`

const NoContentTip = styled.Text`
  marginTop:${theme.v_spacing_md}
  fontSize:${theme.font_size_display_md}
  color:${theme.color_text_disabled}
`

const Comment = React.createClass({
  _renderItem({ item }){
    const { content, fromId, time } = item;
    const direction = fromId == 1 ? 'left' : 'right'
    const timeString = moment(time).format("MM-DD HH:mm")
    const name = fromId == 1 ? '作者' : '你';
    return (
      <ItemContainer>
        <Name direction={direction}>{name}</Name>
        <Content>{content}</Content>
        <Time direction={direction}>{timeString}</Time>
      </ItemContainer>
    )
  },
  _keyExtractor(item, index){
    return item.time + index
  },
  render(){
    const { loading, comment, error } = this.props;
    if (error) {
      return (<View/>);
    }

    const data = loading ? [] : (comment ? (comment.myComments.list || []) : []);
    if(!loading&& !data.length){
      return (
      <Container
        style={{alignItems:'center'}}
      >
          <NoContentTip>还没有信息哟</NoContentTip>
      </Container>)
    }
    return (
      <Container>
        <FlatList
          data={data}
          renderItem={this._renderItem}
          keyExtractor={this._keyExtractor}
          refreshing={loading}
        />
      </Container>
    );
  },
});

const withData = graphql(myCommentQL, {
  options: props => ({
    variables: {
      page: 1,
      pageSize: 100,
    },
    fetchPolicy: 'network-only',
  }),
  props (result) {
    const { data: { loading, comment, error } } = result
    return {
      loading,
      comment,
      error,
    }
  }
})

export default withData(Comment)
import {
  gql,
} from 'react-apollo'

export default gql`
query myComment($pageSize:Int!,$page:Int!) {
    comment {
        myComments(pageSize:$pageSize,page:$page){
            totalCount
            list{
                id
                fromId
                toId
                content
                time
            }
        }
    }
}`

如上,编程范式会产生略微的变化

带来的好处

  1. 更规范的返回格式
  2. 拥有类型的前后交互协议,减少前后沟通成本。(webStorm 插件,帮助检测schema 变化)
  3. 强制后台写 接口Schema ,文档自动生成支持 graphql-docs
  4. 更少的流量损耗( 前端制定返回值)
  5. 绑定数据来源到具体的 component (资源统一管理怎么办?)

GraphQL 不适合的场景

  1. 修改大于查询
  2. 使用 Node Edge 无法建模的应用场景

GraphQL more than Restful API

relay foodie

GraphQL Pagination design

在 Schema 设计中,关联关系设计是比较麻烦的。通过 Connection(Node and Edge)统一抽象 pagination,可获得良好的API。

explaining-graphql-connections

GraphQL + Relay + React 算是一整套的 Facebook 前端Web生态。Relay 帮忙解决数据缓存,请求优化以及组件化数据等问题, 但其本身设计的过于复杂。导致 Apollo Client 的出现,另外,其功能也和 Redux 部分重复甚至冲突。目前社区在等待 Relay 的简易版本出现。

Sangrid GraphQL 框架

对框架整体API的设计,个人特别特服气。作者也是一个特别 nice 的人。可以说一己之力推动了 graphQL 在 scala 生态下的落地。

GraphCool 开发框架

基于Sangrid + Akka + MariaDB 为 JS 开发者 做的一套一站式开发工具。通过写Schema 和 Resolve 去处理请求。

强烈推荐阅读源码。

  1. 代码覆盖后端的方方面面,很多可以参考甚至可以拿来用的代码。
  2. 代码通俗易懂,组织结构精良。

对 GraphQL 最看重的

  1. 静态交互协议,提前预知问题
  2. 减少重复 API 接口设计并进行规范
  3. 文档自动生成

对 GraphQL 还待解决的

  1. Relay 的复杂性和扩展性
  2. Apollo Client 拓展性(Apollo 2.0 出来后,得到完美解决)
  3. 本地存储和 Server 端存储的同步性

GraphQL 类似者

  1. naptime Coursera 通过封装来规避掉手写 route
  2. falcor netflix 通过 Json 定义结构格式

后记

最近看到 Apollo 2.0 出来了。感觉资源的统一管理有戏。对 GraphQL 的前景充满信心。

timzaak commented 6 years ago

最近有个比较极端的想法: 前端在提交给测试前的3个小时,不允许对接后端接口。 前后交互协议通过 GraphQL 来做。 GraphQL 自带全接口 schema 检查,测试数据自动生成。简直完美!