揭秘三大軟件設計開發方法(DDD,TDD,BDD),到底哪個好?
【引言】
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?{
__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小時內刪除侵權內容。