参考资料
GraphQL是由Facebook开发并开源的一种新的API标准,它提供了一种比REST更有效、更强大和更灵活的替代方案。GraphQL是数据库无关的,可以理解为GraphQL是基于API之上的一层封装,目的是为了更好,更灵活的适用于业务的需求变化
GraphQL相比REST API 的好处如下
REST API | GraphQL | |
---|---|---|
灵活性 | 接口灵活性差、接口操作流程繁琐 | 声明式数据获取,数据查询流程简洁 |
拓展性 | 不断编写新接口(依赖于服务端) | 一个微服务仅暴露一个 GraphQL 层,消除了服务器对数据格式的硬性规定 |
强依赖 | 基于HTTP协议,不能灵活选择网络协议 | 传输层无关、数据库技术无关,获得更灵活的技术栈选择 |
REST和GraphQL数据的请求过程的区别
图片来自 RESTful真垃圾?试试 GraphQL?
与REST多个endpoint不同,每一个的 GraphQL 服务其实对外只提供了一个用于调用内部接口的端点,所有的请求都访问这个暴露出来的唯一端点。
多个请求变成了一个请求的不同字段,从原有的分散式请求变成了集中式的请求,因此GraphQL又可以被看成是图数据库的形式
使用GraphQL的步骤
下图是GraphQL 应用的基本架构,客户端只和 GraphQL 层进行 API 交互
关于GraphQL支持的数据操作
关于GraphQL的图表模式(Schema)
schema即GraphQL的语法,指定了数据类型(type)的定义和支持
type类似golang中的结构体
类型修饰符。类似于java修饰符,控制数据类型的定义和表现
关于GraphQL接入架构
AWS AppSync是一个完全托管的 GraphQL 服务,包含实时订阅、离线编程和同步、企业级安全特性以及细粒度的授权控制
来看看chatgpt如何解释appsync和GraphQL的关系
AWS AppSync is a managed service by Amazon Web Services (AWS) that provides a serverless GraphQL API to interact with data sources like AWS DynamoDB, AWS Lambda, HTTP data sources, and more. On the other hand, GraphQL is a query language for APIs, which can be used with various backend technologies, including AWS AppSync.
One major difference between GraphQL and AWS AppSync is that AWS AppSync includes additional functionality beyond the core GraphQL specification. For example, it supports real-time data synchronization and offline capabilities through its built-in support for AWS DynamoDB, which allows clients to interact with data even when they are offline.
Another difference is that AWS AppSync is tightly integrated with other AWS services, allowing developers to easily build and deploy GraphQL APIs that interact with other AWS services like Amazon S3, Amazon Cognito, and AWS Lambda.
从官方文档我们也能看到appsync提供的额外功能
下面做一下快速入门的示例
从现有 Amazon DynamoDB 表导入以创建实时和脱机 GraphQL API
官方给了一个快读启动的项目demo
https://github.com/amazon-archives/aws-mobile-appsync-events-starter-react#readme
This is a Starter React application for using the Sample app in the AWS AppSync console when building your GraphQL API
示例程序会自动创建两张dynamodb table作为数据源
在控制台测试createevent
拉取示例程序,将aws-export.js
文件中的内容替换为以下
$ cat src/aws-exports.js
const awsmobile = {
"aws_appsync_graphqlEndpoint": "https://2xxxxxxxxca.appsync-api.cn-north-1.amazonaws.com.cn/graphql",
"aws_appsync_region": "cn-north-1",
"aws_appsync_authenticationType": "API_KEY",
"aws_appsync_apiKey": "da2-xxxxxxxxxxxxxxxxxxxwm",
};
启动服务,之前在控制台测试的数据My First Event
已经同步到页面上
$ npm run start
You can now view aws-mobile-appsync-events-starter-react in the browser.
Local: http://localhost:3000
On Your Network: http://172.31.18.4:3000
Note that the development build is not optimized.
To create a production build, use npm run build.
查看dynamodb数据写入
关于入门示例的内容,之后详细分析。作者接触过一点vue,硬着头皮看看这个react项目
https://github.com/amazon-archives/aws-mobile-appsync-events-starter-react/blob/master/README.md
入口文件
registerServiceWorker
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
查看APP文件,忽略样式表部分
加载路由等依赖
导入aws配置文件appSyncConfig
,这是app连接appsync的配置,aws_appsync_apiKey
类似于密钥
Apollo 是在应用中使用 GraphQL 的一套工具,apollo在vue中的用法可以借鉴一下
还提供了一个示例的vue项目解释apollo的用法
注册了一个全局的appsync客户端client
之后我们主要关注AllEvents,NewEvent和ViewEvent组件即可
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import "semantic-ui-css/semantic.min.css";
import 'react-datepicker/dist/react-datepicker.css';
import appSyncConfig from "./aws-exports";
import { ApolloProvider } from "react-apollo";
import AWSAppSyncClient, { defaultDataIdFromObject } from "aws-appsync";
import { Rehydrated } from "aws-appsync-react";
import './App.css';
import AllEvents from './Components/AllEvents';
import NewEvent from './Components/NewEvent';
import ViewEvent from './Components/ViewEvent';
const Home = () => (
<div className="ui container">
<AllEvents />
</div>
);
// 注册路由,分别对应主页,查看enent和创建event的组件
const App = () => (
<Router>
<div>
<Route exact={true} path="/" component={Home} />
<Route path="/event/:id" component={ViewEvent} />
<Route path="/newEvent" component={NewEvent} />
</div>
</Router>
);
// 创建appsync客户端
const client = new AWSAppSyncClient({
url: appSyncConfig.aws_appsync_graphqlEndpoint,
region: appSyncConfig.aws_appsync_region,
auth: {
type: appSyncConfig.aws_appsync_authenticationType,
apiKey: appSyncConfig.aws_appsync_apiKey,
},
cacheOptions: {
dataIdFromObject: (obj) => {
let id = defaultDataIdFromObject(obj);
if (!id) {
const { __typename: typename } = obj;
switch (typename) {
case 'Comment':
return `${typename}:${obj.commentId}`;
default:
return id;
}
}
return id;
}
}
});
const WithProvider = () => (
<ApolloProvider client={client}>
<Rehydrated>
<App />
</Rehydrated>
</ApolloProvider>
);
export default WithProvider;
查看allevent组件
// src/Components/AllEvents.js
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { graphql, compose, withApollo } from "react-apollo";
import QueryAllEvents from "../GraphQL/QueryAllEvents";
import MutationDeleteEvent from "../GraphQL/MutationDeleteEvent";
import moment from "moment";
class AllEvents extends Component {
state = {
busy: false,
}
static defaultProps = {
events: [],
deleteEvent: () => null,
}
async handleDeleteClick(event, e) {
e.preventDefault();
if (window.confirm(`Are you sure you want to delete event ${event.id}`)) {
const { deleteEvent } = this.props;
await deleteEvent(event);
}
}
handleSync = async () => {
const { client } = this.props;
const query = QueryAllEvents;
// import gql from "graphql-tag";
// export default gql(`
// query {
// listEvents(limit: 1000) {
// items {
// id
// name
// where
// when
// description
// comments {
// items {
// commentId
// }
// }
// }
// }
// }
this.setState({ busy: true });
// 执行查询操作
await client.query({
query,
fetchPolicy: 'network-only',
});
this.setState({ busy: false });
}
renderEvent = (event) => (
...
}
export default withApollo(compose(
graphql(
QueryAllEvents,
{
options: {
fetchPolicy: 'cache-first',
},
props: ({ data: { listEvents = { items: [] } } }) => ({
events: listEvents.items
})
}
),
graphql(
MutationDeleteEvent,
{
options: {
update: (proxy, { data: { deleteEvent } }) => {
const query = QueryAllEvents;
const data = proxy.readQuery({ query });
data.listEvents.items = data.listEvents.items.filter(event => event.id !== deleteEvent.id);
proxy.writeQuery({ query, data });
}
},
props: (props) => ({
deleteEvent: (event) => {
return props.mutate({
variables: { id: event.id },
optimisticResponse: () => ({
deleteEvent: {
...event, __typename: 'Event', comments: { __typename: 'CommentConnection', items: [] }
}
}),
});
}
})
}
)
)(AllEvents));
在newevent也同样使用graphql封装graphql查询,并监听
export default graphql(
MutationCreateEvent,
// import gql from "graphql-tag";
// export default gql(`
// mutation($name: String! $when: String! $where: String! $description: String!) {
// createEvent(
// name: $name
// when: $when
// where: $where
// description: $description
// ) {
// id
// name
// where
// when
// description
// comments {
// items {
// commentId
// }
// }
// }
// }`);
{
props: (props) => ({
createEvent: (event) => {
return props.mutate({
update: (proxy, { data: { createEvent } }) => {
// Update QueryAllEvents
const query = QueryAllEvents;
const data = proxy.readQuery({ query });
data.listEvents.items = [...data.listEvents.items.filter(e => e.id !== createEvent.id), createEvent];
proxy.writeQuery({ query, data });
// Create cache entry for QueryGetEvent
const query2 = QueryGetEvent;
const variables = { id: createEvent.id };
const data2 = { getEvent: { ...createEvent } };
proxy.writeQuery({ query: query2, variables, data: data2 });
},
variables: event,
optimisticResponse: () => ({
createEvent: {
...event, id: uuid(), __typename: 'Event', comments: { __typename: 'CommentConnection', items: [] }
}
}),
})
}
})
}
)(NewEvent);
由此可知,前端框架通过apollo工具解析gql语句,进行graphql风格的增删改查请求,而appsync客户端被apollo调用,因此我们可以只关注appsync的客户端请求即可