微信公众号:虎珀
感谢关注。问题或建议,请公众号留言

目录

什么是 GraphQLGraphQL Schema 重试    错误原因 GraphQL SDL 重试    错误原因 模式 schema对象 type方法 queryResolver 解析器代码 Python 实现测试客户端GraphQL 优点GraphQL 缺点附加成本过度查询安全问题参考每日一句

什么是 GraphQL

GraphQL 是一种用于 API 的查询语言。根据预先定义的 Schema 对数据执行查询。

GraphQL 不绑定任何特定的数据库或存储引擎。仅在现有数据的基础上,额外增加查询、过滤数据的功能。

GraphQL 最大的特点就是 “需要什么,得到什么。

图片

上图,左侧为查询,右侧为响应。

随着查询字段变化,响应随之更改。

这极大提高了开发效率,客户端可以按需获取数据并且服务端不用修改代码。

GraphQL Schema 重试    错误原因

GraphQL Schema 是类型、字段、方法的集合。

它们构成了我们可以在 GraphQL server 中对数据进行的所有操作。

GraphQL 有自己的语言(GraphQL Schema Definition Language),简称 SDL。 重试    错误原因

SDL 用于编写 Schema。

Schema 包含

  • 定义数据类型,包含请求参数、返回值

  • 定义功能函数

下面来看如何定义

GraphQL SDL 重试    错误原因

SDL 是 GraphQL Schema 自己的语言,称为模式定义语言。

让我们看一个完整的 Schema SDL 例子

schema.graphql 文件如下

<span> 1</span><span>schema</span>&nbsp;{<br><span> 2</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>query</span>:&nbsp;Query<br><span> 3</span>}<br><span> 4</span><br><span> 5</span><span>type</span>&nbsp;<span>Post</span>&nbsp;{<br><span> 6</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>id</span>:&nbsp;ID!<br><span> 7</span>&nbsp;&nbsp;&nbsp;&nbsp;title:&nbsp;String!<br><span> 8</span>&nbsp;&nbsp;&nbsp;&nbsp;description:&nbsp;String!<br><span> 9</span>&nbsp;&nbsp;&nbsp;&nbsp;created_at:&nbsp;String!<br><span>10</span>}<br><span>11</span><br><span>12</span><span>type</span>&nbsp;<span>PostsResult</span>&nbsp;{<br><span>13</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>success</span>:&nbsp;Boolean!<br><span>14</span>&nbsp;&nbsp;&nbsp;&nbsp;errors:&nbsp;[String]<br><span>15</span>&nbsp;&nbsp;&nbsp;&nbsp;posts:&nbsp;[Post]<br><span>16</span>}<br><span>17</span><br><span>18</span><span>type</span>&nbsp;<span>Query</span>&nbsp;{<br><span>19</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>listPosts</span>:&nbsp;PostsResult!<br><span>20</span>}<br>

模式 schema

<span>1</span><span>schema</span>&nbsp;{<br><span>2</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>query</span>:&nbsp;Query<br><span>3</span>}<br>

schema: query 为查询,Query 为方法集合对象。GraphQL 有三种模式

  • query: 查询数据

  • mutation: 修改数据

  • subscription: 订阅数据

对象 type

<span> 1</span><span>type</span>&nbsp;<span>Post</span>&nbsp;{<br><span> 2</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>id</span>:&nbsp;ID!<br><span> 3</span>&nbsp;&nbsp;&nbsp;&nbsp;title:&nbsp;String!<br><span> 4</span>&nbsp;&nbsp;&nbsp;&nbsp;description:&nbsp;String!<br><span> 5</span>&nbsp;&nbsp;&nbsp;&nbsp;created_at:&nbsp;String!<br><span> 6</span>}<br><span> 7</span><br><span> 8</span><span>type</span>&nbsp;<span>PostsResult</span>&nbsp;{<br><span> 9</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>success</span>:&nbsp;Boolean!<br><span>10</span>&nbsp;&nbsp;&nbsp;&nbsp;errors:&nbsp;[String]<br><span>11</span>&nbsp;&nbsp;&nbsp;&nbsp;posts:&nbsp;[Post]<br><span>12</span>}<br>

type Post 定义 Post 对象,有 4 个字段。id、title、description、created_at 重试    错误原因
type PostsResult 定义 PostsResult 对象,内部嵌套了 Post 对象。

GraphQL 中可用的标量类型有 String 、 Int 、 Float 、 Boolean 和 ID 。

这些类似于任何编程语言中的标量类型。方括号( [] )表示 List 类型。

方法 query

<span>1</span><span>type</span>&nbsp;<span>Query</span>&nbsp;{<br><span>2</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>listPosts</span>:&nbsp;PostsResult!<br><span>3</span>}<br>

type Query 定义 query 方法

listPosts:定义 listPosts 方法,无参,返回值为 PostsResult。

类型后用感叹号(!)表示必需字段(非空字段)。

Resolver 解析器

在 GraphQL 中, Resolver 是用于处理 GraphQL 查询的函数。

Resolver 用于连接 GraphQL Schema 和真实的数据源,并根据查询请求返回相应的数据。

Resolver 可以是同步的,也可以是异步的,以支持各种数据源和数据处理操作,例如数据库查询、 RPC 调用等。

以 python 代码为例,我们定义了 listPosts,无额外参数,返回 PostsResult。

<span> 1</span><span><span>def</span>&nbsp;<span>list_posts_resolver</span><span>(obj,&nbsp;info)</span>:</span><br><span> 2</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>try</span>:<br><span> 3</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;posts&nbsp;=&nbsp;[post.to_dict()&nbsp;<span>for</span>&nbsp;post&nbsp;<span>in</span>&nbsp;Post.query.all()]<br><span> 4</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<br><span> 5</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"success"</span>:&nbsp;<span>True</span>,<br><span> 6</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"posts"</span>:&nbsp;posts<br><span> 7</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span> 8</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>except</span>&nbsp;Exception&nbsp;<span>as</span>&nbsp;error:<br><span> 9</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<br><span>10</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"success"</span>:&nbsp;<span>False</span>,<br><span>11</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"errors"</span>:&nbsp;[str(error)]<br><span>12</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span>13</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;payload<br>

代码 Python 实现

以下为 Python 使用 GraphQL 完整示例。

代码依赖

  • flask: web server 重试    错误原因

  • flask_cors: 跨域 重试    错误原因

  • flask_sqlalchemy: 数据库访问

  • ariadne: GraphQL 框架 重试    错误原因

安装

<span>1</span>pip&nbsp;install&nbsp;flask&nbsp;ariadne&nbsp;flask-sqlalchemy&nbsp;flask-cors<br>
<span>  1</span><span>from</span>&nbsp;datetime&nbsp;<span>import</span>&nbsp;date<br><span>  2</span><span>from</span>&nbsp;flask&nbsp;<span>import</span>&nbsp;Flask<br><span>  3</span><span>from</span>&nbsp;flask_cors&nbsp;<span>import</span>&nbsp;CORS<br><span>  4</span><span>from</span>&nbsp;flask_sqlalchemy&nbsp;<span>import</span>&nbsp;SQLAlchemy<br><span>  5</span><span>from</span>&nbsp;ariadne&nbsp;<span>import</span>&nbsp;load_schema_from_path,&nbsp;make_executable_schema,&nbsp;\<br><span>  6</span>&nbsp;&nbsp;&nbsp;&nbsp;graphql_sync,&nbsp;snake_case_fallback_resolvers,&nbsp;ObjectType,&nbsp;convert_kwargs_to_snake_case<br><span>  7</span><span>from</span>&nbsp;flask&nbsp;<span>import</span>&nbsp;request,&nbsp;jsonify<br><span>  8</span><br><span>  9</span>app&nbsp;=&nbsp;Flask(__name__)<br><span> 10</span>CORS(app)<br><span> 11</span><br><span> 12</span><span>#&nbsp;数据源为&nbsp;MySQL,db&nbsp;配置需修改</span><br><span> 13</span>app.config[<span>"SQLALCHEMY_DATABASE_URI"</span>]&nbsp;=&nbsp;<span>"mysql+pymysql://root:passwd@localhost/graphql"</span><br><span> 14</span>app.config[<span>"SQLALCHEMY_TRACK_MODIFICATIONS"</span>]&nbsp;=&nbsp;<span>False</span><br><span> 15</span>db&nbsp;=&nbsp;SQLAlchemy(app)<br><span> 16</span><br><span> 17</span><span>#&nbsp;定义&nbsp;db&nbsp;数据模型</span><br><span> 18</span><span><span>class</span>&nbsp;<span>Post</span><span>(db.Model)</span>:</span><br><span> 19</span>&nbsp;&nbsp;&nbsp;&nbsp;id&nbsp;=&nbsp;db.Column(db.Integer,&nbsp;primary_key=<span>True</span>)<br><span> 20</span>&nbsp;&nbsp;&nbsp;&nbsp;title&nbsp;=&nbsp;db.Column(db.String(<span>100</span>))<br><span> 21</span>&nbsp;&nbsp;&nbsp;&nbsp;description&nbsp;=&nbsp;db.Column(db.String(<span>1000</span>))<br><span> 22</span>&nbsp;&nbsp;&nbsp;&nbsp;created_at&nbsp;=&nbsp;db.Column(db.Date)<br><span> 23</span><br><span> 24</span>&nbsp;&nbsp;&nbsp;&nbsp;<span><span>def</span>&nbsp;<span>to_dict</span><span>(self)</span>:</span><br><span> 25</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;{<br><span> 26</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"id"</span>:&nbsp;self.id,<br><span> 27</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"title"</span>:&nbsp;self.title,<br><span> 28</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"description"</span>:&nbsp;self.description,<br><span> 29</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"created_at"</span>:&nbsp;str(self.created_at.strftime(<span>'%d-%m-%Y'</span>))<br><span> 30</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span> 31</span><br><span> 32</span><br><span> 33</span><span>#&nbsp;定义多个&nbsp;resolver,数据源为&nbsp;MySQL</span><br><span> 34</span><span><span>def</span>&nbsp;<span>list_posts_resolver</span><span>(obj,&nbsp;info)</span>:</span><br><span> 35</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>try</span>:<br><span> 36</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;posts&nbsp;=&nbsp;[post.to_dict()&nbsp;<span>for</span>&nbsp;post&nbsp;<span>in</span>&nbsp;Post.query.all()]<br><span> 37</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<br><span> 38</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"success"</span>:&nbsp;<span>True</span>,<br><span> 39</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"posts"</span>:&nbsp;posts<br><span> 40</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span> 41</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>except</span>&nbsp;Exception&nbsp;<span>as</span>&nbsp;error:<br><span> 42</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<br><span> 43</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"success"</span>:&nbsp;<span>False</span>,<br><span> 44</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"errors"</span>:&nbsp;[str(error)]<br><span> 45</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span> 46</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;payload<br><span> 47</span><br><span> 48</span><br><span> 49</span><span>@convert_kwargs_to_snake_case</span><br><span> 50</span><span><span>def</span>&nbsp;<span>get_post_resolver</span><span>(obj,&nbsp;info,&nbsp;id)</span>:</span><br><span> 51</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>try</span>:<br><span> 52</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;post&nbsp;=&nbsp;Post.query.get(id)<br><span> 53</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<br><span> 54</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"success"</span>:&nbsp;<span>True</span>,<br><span> 55</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"post"</span>:&nbsp;post.to_dict()<br><span> 56</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span> 57</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>except</span>&nbsp;AttributeError:&nbsp;&nbsp;<span>#&nbsp;not&nbsp;found</span><br><span> 58</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<br><span> 59</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"success"</span>:&nbsp;<span>False</span>,<br><span> 60</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"errors"</span>:&nbsp;[<span>"Post&nbsp;item&nbsp;matching&nbsp;{id}&nbsp;not&nbsp;found"</span>]<br><span> 61</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span> 62</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;payload<br><span> 63</span><br><span> 64</span><br><span> 65</span><span>@convert_kwargs_to_snake_case</span><br><span> 66</span><span><span>def</span>&nbsp;<span>create_post_resolver</span><span>(obj,&nbsp;info,&nbsp;title,&nbsp;description)</span>:</span><br><span> 67</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>try</span>:<br><span> 68</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;today&nbsp;=&nbsp;date.today()<br><span> 69</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;post&nbsp;=&nbsp;Post(<br><span> 70</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;title=title,&nbsp;description=description,&nbsp;created_at=today.strftime(<span>"%Y-%m-%d"</span>)<br><span> 71</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)<br><span> 72</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;db.session.add(post)<br><span> 73</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;db.session.commit()<br><span> 74</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<br><span> 75</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"success"</span>:&nbsp;<span>True</span>,<br><span> 76</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"post"</span>:&nbsp;post.to_dict()<br><span> 77</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span> 78</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>except</span>&nbsp;ValueError:&nbsp;&nbsp;<span>#&nbsp;date&nbsp;format&nbsp;errors</span><br><span> 79</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<br><span> 80</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"success"</span>:&nbsp;<span>False</span>,<br><span> 81</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"errors"</span>:&nbsp;[<span>f"Incorrect&nbsp;date&nbsp;format&nbsp;provided.&nbsp;Date&nbsp;should&nbsp;be&nbsp;in&nbsp;"</span><br><span> 82</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>f"the&nbsp;format&nbsp;dd-mm-yyyy"</span>]<br><span> 83</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span> 84</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;payload<br><span> 85</span><br><span> 86</span><br><span> 87</span><span>@convert_kwargs_to_snake_case</span><br><span> 88</span><span><span>def</span>&nbsp;<span>update_post_resolver</span><span>(obj,&nbsp;info,&nbsp;id,&nbsp;title,&nbsp;description)</span>:</span><br><span> 89</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>try</span>:<br><span> 90</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;post&nbsp;=&nbsp;Post.query.get(id)<br><span> 91</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>if</span>&nbsp;post:<br><span> 92</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;post.title&nbsp;=&nbsp;title<br><span> 93</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;post.description&nbsp;=&nbsp;description<br><span> 94</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;db.session.add(post)<br><span> 95</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;db.session.commit()<br><span> 96</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<br><span> 97</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"success"</span>:&nbsp;<span>True</span>,<br><span> 98</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"post"</span>:&nbsp;post.to_dict()<br><span> 99</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span>100</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>except</span>&nbsp;AttributeError:<br><span>101</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<br><span>102</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"success"</span>:&nbsp;<span>False</span>,<br><span>103</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"errors"</span>:&nbsp;[<span>"item&nbsp;matching&nbsp;id&nbsp;{id}&nbsp;not&nbsp;found"</span>]<br><span>104</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span>105</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;payload<br><span>106</span><br><span>107</span><br><span>108</span><span>@convert_kwargs_to_snake_case</span><br><span>109</span><span><span>def</span>&nbsp;<span>delete_post_resolver</span><span>(obj,&nbsp;info,&nbsp;id)</span>:</span><br><span>110</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>try</span>:<br><span>111</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;post&nbsp;=&nbsp;Post.query.get(id)<br><span>112</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;db.session.delete(post)<br><span>113</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;db.session.commit()<br><span>114</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<span>"success"</span>:&nbsp;<span>True</span>,&nbsp;<span>"post"</span>:&nbsp;post.to_dict()}<br><span>115</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>except</span>&nbsp;AttributeError:<br><span>116</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<br><span>117</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"success"</span>:&nbsp;<span>False</span>,<br><span>118</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"errors"</span>:&nbsp;[<span>"Not&nbsp;found"</span>]<br><span>119</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span>120</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;payload<br><span>121</span><br><span>122</span><br><span>123</span><span>#&nbsp;定义&nbsp;GraphQL&nbsp;操作</span><br><span>124</span>query&nbsp;=&nbsp;ObjectType(<span>"Query"</span>)<br><span>125</span>mutation&nbsp;=&nbsp;ObjectType(<span>"Mutation"</span>)<br><span>126</span><br><span>127</span>query.set_field(<span>"listPosts"</span>,&nbsp;list_posts_resolver)<br><span>128</span>query.set_field(<span>"getPost"</span>,&nbsp;get_post_resolver)<br><span>129</span>mutation.set_field(<span>"createPost"</span>,&nbsp;create_post_resolver)<br><span>130</span>mutation.set_field(<span>"updatePost"</span>,&nbsp;update_post_resolver)<br><span>131</span>mutation.set_field(<span>"deletePost"</span>,&nbsp;delete_post_resolver)<br><span>132</span><br><span>133</span><span>#&nbsp;加载&nbsp;Schema</span><br><span>134</span>type_defs&nbsp;=&nbsp;load_schema_from_path(<span>"./schema/schema.graphql"</span>)<br><span>135</span>schema&nbsp;=&nbsp;make_executable_schema(<br><span>136</span>&nbsp;&nbsp;&nbsp;&nbsp;type_defs,&nbsp;query,&nbsp;mutation,&nbsp;snake_case_fallback_resolvers<br><span>137</span>)<br><span>138</span><br><span>139</span><br><span>140</span><span>@app.route("/graphql",&nbsp;methods=["GET"])</span><br><span>141</span><span><span>def</span>&nbsp;<span>graphql_playground</span><span>()</span>:</span><br><span>142</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>"&lt;h1&gt;Hello,&nbsp;GraphQL&lt;/h1&gt;"</span>,&nbsp;<span>200</span><br><span>143</span><br><span>144</span><br><span>145</span><span>@app.route("/graphql",&nbsp;methods=["POST"])</span><br><span>146</span><span><span>def</span>&nbsp;<span>graphql_server</span><span>()</span>:</span><br><span>147</span>&nbsp;&nbsp;&nbsp;&nbsp;data&nbsp;=&nbsp;request.get_json()<br><span>148</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>#&nbsp;同步查询</span><br><span>149</span>&nbsp;&nbsp;&nbsp;&nbsp;success,&nbsp;result&nbsp;=&nbsp;graphql_sync(<br><span>150</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;schema,<br><span>151</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;data,<br><span>152</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;context_value=request,<br><span>153</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;debug=app.debug<br><span>154</span>&nbsp;&nbsp;&nbsp;&nbsp;)<br><span>155</span>&nbsp;&nbsp;&nbsp;&nbsp;status_code&nbsp;=&nbsp;<span>200</span>&nbsp;<span>if</span>&nbsp;success&nbsp;<span>else</span>&nbsp;<span>400</span><br><span>156</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;jsonify(result),&nbsp;status_code<br><span>157</span><br><span>158</span><br><span>159</span><span>if</span>&nbsp;__name__&nbsp;==&nbsp;<span>'__main__'</span>:<br><span>160</span>&nbsp;&nbsp;&nbsp;&nbsp;app.run(port=<span>4000</span>)<br>

测试客户端

Apollo Sandbox Explorer 提供了一个方便的 client 使用

创建 CreateNewPost 重试    错误原因

图片

查询 ListPosts 重试    错误原因

图片

GraphQL 优点

GraphQL 具有以下优点:

  • 强大的查询能力:允许客户端精确地获取所需的数据,避免了过度获取和数据冗余。同时减少服务端变动。

  • 清晰的 API 定义:通过 GraphQL 的模式定义,清晰地描述了服务器提供的数据结构和功能。

  • 易于扩展:添加新的字段或类型不会影响现有客户端,因为客户端可以根据需要动态地获取数据。

  • 高效的数据传输:只传输客户端所需的数据,减少了网络带宽的使用。例如,在一个电子商务应用中,客户端可以通过 GraphQL 查询特定产品的名称、价格、描述和库存数量,而不是获取整个产品表的数据。

  • 强大的错误处理:明确的错误反馈有助于客户端快速解决问题。例如,如果某个字段不存在或无权访问,GraphQL 会返回明确的错误信息。

  • 提高开发效率:使得客户端和服务器的开发更加高效和协作。

GraphQL 缺点

附加成本

理解和掌握 GraphQL 的概念和语法需要一定的学习成本。

并且构建高效的 GraphQL 架构需要仔细设计模式和解析器。

过度查询

GraphQL 为了灵活性而牺牲了一定的性能。

通常我们需要将数据全部查出来而不管是否需要,通过 GraphQL 来进行数据转换,过滤。

这就会导致过度查询。

好在我们可以自行解析请求 Schema 来获取所需字段,按字段来查询真实数据源。

<span> 1</span><span><span>def</span>&nbsp;<span>list_posts_resolver</span><span>(obj,&nbsp;info)</span>:</span><br><span> 2</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>#&nbsp;拿到客户端所需要的字段</span><br><span> 3</span>&nbsp;&nbsp;&nbsp;&nbsp;fields&nbsp;=&nbsp;info.return_type.fields&nbsp;&nbsp;<span>#&nbsp;举例说明,未经过测试</span><br><span> 4</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>try</span>:<br><span> 5</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;posts&nbsp;=&nbsp;[post.to_dict()&nbsp;<span>for</span>&nbsp;post&nbsp;<span>in</span>&nbsp;Post.query.where(fields=fields)]<br><span> 6</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<br><span> 7</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"success"</span>:&nbsp;<span>True</span>,<br><span> 8</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"posts"</span>:&nbsp;posts<br><span> 9</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span>10</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>except</span>&nbsp;Exception&nbsp;<span>as</span>&nbsp;error:<br><span>11</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;payload&nbsp;=&nbsp;{<br><span>12</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"success"</span>:&nbsp;<span>False</span>,<br><span>13</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>"errors"</span>:&nbsp;[str(error)]<br><span>14</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><span>15</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;payload<br>

安全问题

GraphQL 的灵活性也来带了一定的安全问题。我们需要详细设计,避免将不能暴露给用户的字段泄漏。

参考

GraphQL官方文档

Apollo Sandbox Explorer 重试    错误原因

Apollo GraphQL tutorials 重试    错误原因

每日一句

写作就是思考,思考很难。