揭秘三大软件设计开发方法(DDD,TDD,BDD),到底哪个好?

网友投稿 1478 2022-05-30

【引言】

GraphQL是一种开源的数据查询和操作语言,是一种用于API的数据查询和操作的语言,也是一种利用现有数据完成查询的动态理念。 GraphQL于2012年由Facebook内部开发,2015年公开发布。2018年11月7日,GraphQL项目从Facebook转到了新成立的GraphQL基金会,由非营利性的Linux基金会主持管理。自2012年以来,GraphQL的崛起一直遵循着GraphQL的创建者Lee Byron所制定的推广时间表,并准确无误。Byron的目标是让GraphQL在Web平台上无所不在。

它提供了一种开发Web API的方法,并与REST和其他Web服务架构进行了比较和对比。它允许客户端定义所需的数据结构,而服务器返回的数据结构也是一样的,因此可以防止过量的数据被返回,但这对查询结果的web缓存的有效性有影响。查询语言的灵活性和丰富性也增加了程序设计的复杂度,对于简单的API来说,可能并不值得。它由类型系统、查询语言和执行语义、静态验证和类型自省组成。

GraphQL 支持读取、写入(包括突变)和订阅数据的变化(实时更新--最常见的是使用 WebHooks )。

GraphQL服务器适用于多种语言,包括Haskell、JavaScript、Perl、Python、Ruby、Java、C#、Scala、Go、Elixir、Erlang、PHP、R和Clojure。

2018年2月9日,GraphQL Schema Definition Language (SDL)成为规范的一部分。

GraphQL 规范最新的版本发布在 https://graphql.github.io/graphql-spec/。

最新的规范草案可以在 https://graphql.github.io/graphql-spec/draft/

找到。

之前发布的GraphQL规范可以在与其发布标签匹配的permalinks中找到, 例如,https://graphql.github.io/graphql-spec/October2016/。

【概览】

GraphQL是Facebook创建的API查询语言。

目标受众不是客户端开发者,而是那些已经对构建自己的GraphQL服务程序和工具感兴趣的人。

为了被广泛采用,GraphQL必须提供针对各种后端、框架和语言的支持,这将需要跨项目和组织的协作努力

GraphQL 由类型系统、查询语言和执行语义、静态验证和类型自省组成,下面会分别介绍这些部分:

下面的例子中,我们会用GraphQL来查询《星球大战》三部曲中的人物和位置信息。

【类型系统】

任何GraphQL实现的核心是描述它可以返回哪些类型的对象,在GraphQL类型系统中描述并在GraphQL Schema中返回。

对于我们的Star Wars例子,GraphQL.js中的starWarsSchema.js文件定义了这个类型系统。

系统中最基本的类型将是Human,代表着像Luke、Leia和Han这样的角色。在我们的类型系统中,所有的人类都会有一个名字,所以我们定义Human类型有一个名为 "name "的字段。我们定义 "name "字段为不可空的字符串。使用我们将在整个规范和文档中使用的速记符号,我们将把人类模型定义为:

type Human {

name: String

}

这个速记词方便描述类型系统的基本状况;JavaScript实现的功能比较齐全,可以对类型和字段进行记录。它还设置了类型系统和底层数据之间的映射;对于GraphQL.js中的一个测试用例来说,底层数据是一组JavaScript对象,但在大多数情况下,支持的数据将通过一些服务来访问,而这个类型系统层将负责从类型和字段到那个服务的映射。

在许多API中,甚至在GraphQL中,一个常见的模式是给对象一个ID,可以用来访问这个对象。所以,让我们把这个添加到我们的Human类型中,此外我们还要添加另一个字符串: homePlanet。

type Human {

id: String

name: String

homePlanet: String

}

既然我们讨论的是《星球大战三部曲》,那么描述一下每个角色出现的剧集是很有用的。为此,我们先定义一个枚举,列出三部曲:

enum Episode { NEWHOPE, EMPIRE, JEDI }

现在我们要在Human中添加一个字段,描述他们参加过的剧集。是一个剧集列表:

type Human {

id: String

name: String

appearsIn: [Episode]

homePlanet: String

}

现在,我们来介绍另一种类型,Droid:

type Droid {

id: String

name: String

appearsIn: [Episode]

primaryFunction: String

}

现在我们有两种类型了! 让我们在这两种类型之间增加一种关联方式:人类和机器人都有朋友,人类和机器人也都可以做朋友。

人类和机器人之间有共同的属性;它们都有ID、名字,以及出现的剧集。

我们添加一个接口Character,让Human和Droid从这里派生。有了这些之后,我们就可以添加好友字段,也即返回一个Character列表。

我们的类型系统就成了这样:

enum Episode { NEWHOPE, EMPIRE, JEDI }

interface Character {

id: String

name: String

friends: [Character]

appearsIn: [Episode]

}

type Human implements Character {

id: String

name: String

friends: [Character]

appearsIn: [Episode]

homePlanet: String

}

type Droid implements Character {

id: String

name: String

friends: [Character]

appearsIn: [Episode]

primaryFunction: String

}

这里我们需要问一个问题,就是这些字段中的任何一个字段是否可以返回null? 默认情况下,null是GraphQL中任何类型的允许值,因为获取数据以完成GraphQL查询经常需要与不同的服务对话,而这些服务有可能是可用的,也可能是不可用的。如果我们想把某个类型标记为不能为空,我们可以通过在类型后添加一个"!"来表示。

注意,在实现中,我们可能把一些字段保留为nullable,这样我们就有了灵活性,最终可以判断返回null来表示后端错误,同时也告诉客户端发生了错误。

我们尝试把id设定为不为空字段:

enum Episode { NEWHOPE, EMPIRE, JEDI }

interface Character {

id: String!

name: String

friends: [Character]

appearsIn: [Episode]

}

type Human implements Character {

id: String!

name: String

friends: [Character]

appearsIn: [Episode]

homePlanet: String

}

type Droid implements Character {

id: String!

name: String

friends: [Character]

appearsIn: [Episode]

primaryFunction: String

}

接下来我们需要定义一个入口来访问类型系统,实际上就是一个查询数据类型。

这个类型的名称按照惯例是 Query,它描述了我们公共的、顶层API。这个例子中的查询类型是这样的:

type Query {

hero(episode: Episode): Character

human(id: String!): Human

droid(id: String!): Droid

}

在这个例子中,有三个操作字段:

l  hero 返回《星球大战》三部曲中的英雄人物;它有一个可选的参数,允许我们获取特定剧集的英雄人物。

l  human有一个非空字符串ID作为查询参数,返回人类。

l  droid有一个非空字符串ID作为查询参数,返回机器人人。

这些字段展示了类型系统的另一个特点,即字段可以指定参数来配置它们的结果。

当我们将整个类型系统打包在一起,将上面的查询类型定义为查询的入口点,这就创建了一个GraphQL的设计定义。

查询语法

GraphQL查询准确的描述了调用者需要获取什么数据。

简单查询

我们来看例子:

query HeroNameQuery {

hero {

name

}

}

上面这个查询的名字描述了很清晰的目的,就是要查询一个名字。其返回结果大概是这样的:

{

"hero": {

"name": "R2-D2"

}

}

更多数据查询

再来看另一个查询:

query HeroNameAndFriendsQuery {

hero {

id

name

friends {

id

name

}

}

}

这个查询希望查询到id, name以及其朋友的id和name。返回结果大概这样:

{

"hero": {

"id": "2001",

"name": "R2-D2",

"friends": [

{

"id": "1000",

"name": "Luke Skywalker"

},

{

"id": "1002",

"name": "Han Solo"

},

{

"id": "1003",

"name": "Leia Organa"

}

]

}

}

嵌套查询

下面看一个嵌套查询的例子:

query NestedQuery {

hero {

name

friends {

name

appearsIn

friends {

name

}

}

}

}

返回结果:

{

"hero": {

"name": "R2-D2",

"friends": [

{

"name": "Luke Skywalker",

"appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],

"friends": [

{ "name": "Han Solo" },

{ "name": "Leia Organa" },

{ "name": "C-3PO" },

{ "name": "R2-D2" }

]

},

{

"name": "Han Solo",

"appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],

"friends": [

{ "name": "Luke Skywalker" },

{ "name": "Leia Organa" },

{ "name": "R2-D2" }

]

},

{

"name": "Leia Organa",

"appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],

"friends": [

{ "name": "Luke Skywalker" },

{ "name": "Han Solo" },

{ "name": "C-3PO" },

{ "name": "R2-D2" }

]

}

]

}

}

指定ID查询

query FetchLukeQuery {

human(id: "1000") {

name

}

}

或者:

query FetchSomeIDQuery($someId: String!) {

human(id: $someId) {

name

}

}

返回结果:

{

"human": {

"name": "Luke Skywalker"

}

}

指定ID的多个查询

query FetchLukeAndLeiaAliased {

luke: human(id: "1000") {

name

}

leia: human(id: "1003") {

name

}

}

结果:

{

"luke": {

"name": "Luke Skywalker"

},

"leia": {

"name": "Leia Organa"

}

}

特殊字段:__typename

下面来看特殊字段的例子:

query CheckTypeOfR2 {

hero {

__typename

name

}

}

结果:

{

"hero": {

"__typename": "Droid",

"name": "R2-D2"

}

}

验证

通过使用类型系统,可以预先确定GraphQL查询是否有效。服务器和客户端之间如果创建了一个无效查询,类型系统就可以有效地通知开发人员,而不需要运行时才报错。

上面我们列出了一些有效的查询例子,接下来我们看看无效的查询例子:

字段未定义

# INVALID: favoriteSpaceship不存在于Character上。

query HeroSpaceshipQuery {

hero {

favoriteSpaceship

}

}

在最基础字段(标量)下再查询

# INVALID: name 已经是最基础的字段了,不能再进行子字段查询。

query HeroFieldsOnScalarQuery {

hero {

name {

firstCharacterOfName

}

}

}

片段定义

query DroidFieldInFragment {

hero {

name

...DroidFields

}

}

fragment DroidFields on Droid {

primaryFunction

}

上面这种查询是有效的,但是如果这个片段没有被多次调用的话,我们可以匿名定义:

query DroidFieldInInlineFragment {

hero {

name

... on Droid {

primaryFunction

}

}

}

上面的查询中,hero没有primaryFunction字段定义,但是Droid有这个字段,通过片段声明可以使这个查询有效。

自省

这项功能允许我们询问GraphQL支持哪些查询。

缺省询问

比如对上面的系统,我们可以这样询问:

query IntrospectionTypeQuery {

揭秘三大软件设计开发方法(DDD,TDD,BDD),到底哪个好?

__schema {

types {

name

}

}

}

得到的结果类似于这样:

{

"__schema": {

"types": [

{

"name": "Query"

},

{

"name": "Character"

},

{

"name": "Human"

},

{

"name": "String"

},

{

"name": "Episode"

},

{

"name": "Droid"

},

{

"name": "__Schema"

},

{

"name": "__Type"

},

{

"name": "__TypeKind"

},

{

"name": "Boolean"

},

{

"name": "__Field"

},

{

"name": "__InputValue"

},

{

"name": "__EnumValue"

},

{

"name": "__Directive"

}

]

}

}

上面类型很多,我们分一下类:

l  Query, Character, Human, Episode, Droid - 这些是我们在类型系统中定义的。

l  String, Boolean - 这些是类型系统提供的内置标量。

l  __Schema, __Type, __Type, __TypeKind, __Field, __InputValue, __EnumValue, __Directive - 这些都在前面加了双下划线,表示它们是自省系统的一部分。

现在我们只询问查询支持:

query IntrospectionQueryTypeQuery {

__schema {

queryType {

name

}

}

}

结果如下:

{

"__schema": {

"queryType": {

"name": "Query"

}

}

}

询问特定类型

query IntrospectionDroidTypeQuery {

__type(name: "Droid") {

name

}

}

结果:

{

"__type": {

"name": "Droid"

}

}

类别查询

query IntrospectionDroidKindQuery {

__type(name: "Droid") {

name

kind

}

}

结果:

{

"__type": {

"name": "Droid",

"kind": "OBJECT"

}

}

上面是对象类型,我们来看接口类型:

query IntrospectionCharacterKindQuery {

__type(name: "Character") {

name

kind

}

}

结果:

{

"__type": {

"name": "Character",

"kind": "INTERFACE"

}

}

字段支持查询

query IntrospectionDroidFieldsQuery {

__type(name: "Droid") {

name

fields {

name

type {

name

kind

}

}

}

}

结果:

{

"__type": {

"name": "Droid",

"fields": [

{

"name": "id",

"type": {

"name": null,

"kind": "NON_NULL"

}

},

{

"name": "name",

"type": {

"name": "String",

"kind": "SCALAR"

}

},

{

"name": "friends",

"type": {

"name": null,

"kind": "LIST"

}

},

{

"name": "appearsIn",

"type": {

"name": null,

"kind": "LIST"

}

},

{

"name": "primaryFunction",

"type": {

"name": "String",

"kind": "SCALAR"

}

}

]

}

}

无名字段,查类型

像列表类型字段没有名字,我们可以查看类型ofType。

query IntrospectionDroidWrappedFieldsQuery {

__type(name: "Droid") {

name

fields {

name

type {

name

kind

ofType {

name

kind

}

}

}

}

}

结果:

{

"__type": {

"name": "Droid",

"fields": [

{

"name": "id",

"type": {

"name": null,

"kind": "NON_NULL",

"ofType": {

"name": "String",

"kind": "SCALAR"

}

}

},

{

"name": "name",

"type": {

"name": "String",

"kind": "SCALAR",

"ofType": null

}

},

{

"name": "friends",

"type": {

"name": null,

"kind": "LIST",

"ofType": {

"name": "Character",

"kind": "INTERFACE"

}

}

},

{

"name": "appearsIn",

"type": {

"name": null,

"kind": "LIST",

"ofType": {

"name": "Episode",

"kind": "ENUM"

}

}

},

{

"name": "primaryFunction",

"type": {

"name": "String",

"kind": "SCALAR",

"ofType": null

}

}

]

}

}

询问描述文档

query IntrospectionDroidDescriptionQuery {

__type(name: "Droid") {

name

description

}

}

结果:

{

"__type": {

"name": "Droid",

"description": "A mechanical creature in the Star Wars universe."

}

}

进一步的,我们可以使用自省系统创建更强大的文档系统,从而丰富编码人员的开发体验。

【小结】

上面我们对GraphQL从类型系统,查询语法,验证和自省功能进行了学习。

接下来我们看看GraphQL有什么缺点:

缺陷

GraphQL的主要缺点是它使用单一的端点,而不是遵循HTTP规范进行缓存。而网络层面的缓存很重要,它可以减少服务器的流量。

此外,对于简单的应用程序来说,这并不是好的解决方案,因为它增加了复杂度(类型、查询、验证、自省), 而使用REST可以更简单地完成的任务。

欢迎讨论。

架构设计

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:让煤炭生产更安全更智能,精英数智联手华为云打造煤矿大脑
下一篇:调用链系列(4):服务信息上下文传递
相关文章