GraphQL是一种用于API的查询语言,它使客户能够准确地询问他们需要的数据并准确地接收这些数据,仅此而已。这样,单个查询就可以获取渲染组件所需的所有数据。
(相比之下,一个REST API必须触发多次往返以从不同端点的多个资源中获取数据,这可能会变得非常慢,尤其是在移动设备上。)
尽管GraphQL(意为 “Graph Query Language”)使用图数据模型来表示数据,但GraphQL服务器不一定需要使用图作为数据结构来解析查询,而是可以使用任何需要的数据结构。该图只是一个心理模型,而不是实际实现。
GraphQL项目在其网站graphql.org上声明了这一点:
Graph是对许多现实世界现象进行建模的强大工具,因为它们类似于我们的自然心理模型和对潜在过程的口头描述。使用GraphQL,您可以通过定义模式将业务领域建模为图形;在您的架构中,您定义不同类型的节点以及它们如何相互连接/关联。在客户端,这会创建一个类似于面向对象编程的模式:引用其他类型的类型。在服务器上,由于GraphQL只定义了接口,你可以自由地将它与任何后端(新的或旧的!)一起使用。
这是个好消息,因为处理图或树(它们是图的子集)并非易事,并且可能导致解决查询的指数或对数时间复杂度(即解决查询所需的时间可能会增加几个订单)查询的每个新输入的数量级)。
在本文中,我们将描述PoP在PHP GraphQL中的GraphQL服务器的架构设计,它使用组件作为数据结构而不是图。该服务器的名字来源于PoP,它是在PHP中构建组件的库,它是基于该库的。
本文分为5个部分,解释:
1.什么是组件
每个网页的布局都可以使用组件来表示。组件只是一组代码(例如HTML、JavaScript和CSS)组合在一起以创建一个自治实体,该实体可以包装其他组件以创建更复杂的结构,并且自身也可以被其他组件包装。每个组件都有一个用途,可以是非常基本的东西,例如链接或按钮,也可以是非常复杂的东西,例如轮播或拖放图像上传器。
通过组件构建站点类似于玩乐高。例如,在下图中的网页中,简单的组件(链接、按钮、头像)被组合成更复杂的结构(小工具、部分、侧边栏、菜单)一直到顶部,直到我们获得网页:
页面是一个wrapping组件的组件,如方框所示
组件可以在客户端(例如JS库Vue和React,或CSS组件库Bootstrap和Material-UI)和服务器端以任何语言实现。
2. PoP的工作原理
PoP描述了一种基于服务器端组件模型的架构,并通过组件模型库在PHP中实现。
在以下部分中,术语“组件”和“模块”可互换使用。
组件层次结构
所有模块相互wrapping的关系,从最顶层的模块一直到最后一层,称为组件层次结构。这种关系可以通过服务器端的关联数组(key
=>property
)来表示,其中每个模块将其名称声明为关键属性,并将其内部模块声明为属性"modules"
。
PHP数组中的数据也可以直接在客户端使用,编码为JSON对象。
组件层次结构如下所示:
‘module-level0’ => [
“modules” => [
‘module-level1’ => [
“modules” => [
‘module-level11’ => [
“modules” => […]
],
‘module-level12’ => [
“modules” => [
‘module-level121’ => [
“modules” => […]
]
]
]
]
],
‘module-level2’ => [
“modules” => [
‘module-level21’ => [
“modules” => […]
]
]
]
]
]
]
$componentHierarchy = [ 'module-level0' => [ "modules" => [ 'module-level1' => [ "modules" => [ 'module-level11' => [ "modules" => [...] ], 'module-level12' => [ "modules" => [ 'module-level121' => [ "modules" => [...] ] ] ] ] ], 'module-level2' => [ "modules" => [ 'module-level21' => [ "modules" => [...] ] ] ] ] ] ]
模块之间的关系以严格的自上而下的方式定义:一个模块wrap了其他模块并且知道它们是谁,但它不知道也不关心哪些模块wraps了他。
例如,在上面的组件层次结构中,模块'module-level1'
知道它wrap了模块'module-level11'
和'module-level12'
,并且,它也知道它wrap了'module-level121'
;但是模块'module-level11'
不关心谁在wrap他,因此不知道'module-level1'
.
有了基于组件的结构,我们添加了每个模块所需的实际信息,这些信息分为设置(例如配置值和其他属性)和数据(例如查询的数据库对象的ID和其他属性),并且相应地放在条目modulesettings
和moduledata
:
“modulesettings” => [
‘module-level0’ => [
“configuration” => […],
…,
“modules” => [
‘module-level1’ => [
“configuration” => […],
…,
“modules” => [
‘module-level11’ => [
…children…
],
‘module-level12’ => [
“configuration” => […],
…,
“modules” => [
‘module-level121’ => [
…children…
]
]
]
]
],
‘module-level2’ => [
“configuration” => […],
…,
“modules” => [
‘module-level21’ => [
…children…
]
]
]
]
]
],
“moduledata” => [
‘module-level0’ => [
“dbobjectids” => […],
…,
“modules” => [
‘module-level1’ => [
“dbobjectids” => […],
…,
“modules” => [
‘module-level11’ => [
…children…
],
‘module-level12’ => [
“dbobjectids” => […],
…,
“modules” => [
‘module-level121’ => [
…children…
]
]
]
]
],
‘module-level2’ => [
“dbobjectids” => […],
…,
“modules” => [
‘module-level21’ => [
…children…
]
]
]
]
]
]
]
$componentHierarchyData = [ "modulesettings" => [ 'module-level0' => [ "configuration" => [...], ..., "modules" => [ 'module-level1' => [ "configuration" => [...], ..., "modules" => [ 'module-level11' => [ ...children... ], 'module-level12' => [ "configuration" => [...], ..., "modules" => [ 'module-level121' => [ ...children... ] ] ] ] ], 'module-level2' => [ "configuration" => [...], ..., "modules" => [ 'module-level21' => [ ...children... ] ] ] ] ] ], "moduledata" => [ 'module-level0' => [ "dbobjectids" => [...], ..., "modules" => [ 'module-level1' => [ "dbobjectids" => [...], ..., "modules" => [ 'module-level11' => [ ...children... ], 'module-level12' => [ "dbobjectids" => [...], ..., "modules" => [ 'module-level121' => [ ...children... ] ] ] ] ], 'module-level2' => [ "dbobjectids" => [...], ..., "modules" => [ 'module-level21' => [ ...children... ] ] ] ] ] ] ]
接下来,将数据库对象数据添加到组件层次结构中。此信息不是放在每个模块下,而是放在名为databases
的共享部分下,以避免在2个或更多不同模块从数据库中获取相同对象时重复信息。
此外,该库以关系的方式表示数据库对象数据,以避免当两个或多个不同的数据库对象与一个共同的对象相关时(例如两个具有相同作者的文章),信息重复。
换句话说,数据库对象数据是标准化的。该结构是一个字典,首先组织在每个对象类型下,然后是对象ID,我们可以从中获取对象属性:
…
“databases” => [
“dbobject_type” => [
“dbobject_id” => [
“property” => …,
…
],
…
],
…
]
]
$componentHierarchyData = [ ... "databases" => [ "dbobject_type" => [ "dbobject_id" => [ "property" => ..., ... ], ... ], ... ] ]
例如,下面的对象包含一个带有两个模块的组件层次结构"page"
=> "post-feed"
,其中模块"post-feed"
获取博客文章。请注意以下事项:
- 每个模块都知道哪些是其从属性
dbobjectids
(ID4
和9
博客文章)中查询的对象 - 每个模块从属性中知道其查询对象的对象类型
dbkeys
(每个文章的数据都在下面找到"posts"
,文章的作者数据,对应于在文章属性下给出的ID的作者,在下面"author"
找到"users"
): - 因为数据库对象数据是关系型的,所以属性
"author"
包含作者对象的ID,而不是直接打印作者数据