1 模型和目標(biāo)
建模是為了讓不規(guī)則的領(lǐng)域的一些具體方面變成結(jié)構(gòu)化的、可操縱的空間。對于事物實際存在的方式,并沒有一種天然的表達(dá)方式,我們只能有目的地選擇、抽象和簡化,一些方法能更好的滿足某個特定目標(biāo)。
圖建模與其他建模技術(shù)的不同之處在于其邏輯模型和物理模型之間有更加密切的關(guān)系。關(guān)系型數(shù)據(jù)管理技術(shù)背離了用自然的語言來描述領(lǐng)域:
- 先將其表述成邏輯模型,然后生硬的將其轉(zhuǎn)換成物理模型
- 轉(zhuǎn)換使得概念化的世界和模型的數(shù)據(jù)庫實例之間產(chǎn)生了語義失調(diào)
- 圖數(shù)據(jù)庫建模這個分歧會明顯的縮小
2 帶標(biāo)簽的屬性圖模型
帶標(biāo)簽的屬性圖模型主要有以下幾個特征:
- 由節(jié)點、聯(lián)系、屬性和標(biāo)簽組成
- 節(jié)點上包含屬性
- 節(jié)點上可以被打上一個或者多個標(biāo)簽
- 聯(lián)系連接節(jié)點
- 一個方向
- 一個名字
- 一個開始節(jié)點
- 一個結(jié)束節(jié)點
- 聯(lián)系也可以有屬性
- 可以給圖算法提供元數(shù)據(jù)
- 給聯(lián)系增加額外的語義(包括特權(quán)和權(quán)重)
- 可以用于運行時的約束查詢
3 查詢圖:Cypher簡介
3.1 Cypher的理念
這個模式描述了3個有交集的朋友,用ASCII字符畫表達(dá)出來就是:
(emil)<-[:KNOWS]-(jim)-[:KNOWS]->(ian)-[:KNOWS]->(emil)
這個模式描述了一條路徑,它將一個叫jim的節(jié)點和兩外兩個叫ian和emil的節(jié)點連接起來,同時也將ian節(jié)點和emil節(jié)點連接起來。
要注意:ian、jim和emil是標(biāo)識符。標(biāo)識符可以讓我們在描述一個模式時,多次指向同一個節(jié)點(可以幫我們繞過查詢語句其實只有一個方向的事實【只能從左到右處理文本】),而示意圖可以從兩個防線展開。除了偶爾需要使用這種方式重復(fù)使用標(biāo)識符,整個語句的意圖仍然是清晰的。
示意圖一般傾向于使用特定節(jié)點或聯(lián)系的實例,而不是類或原型。即使是非常大的圖,也要用真實的節(jié)點和聯(lián)系來表示,只不過通常會選取較小的子圖來做示例。即我們更喜歡使用實例化需求來表示圖
一個Cypher查詢使用斷言將模式的一個或多個部分錨定到圖的具體位置上,然后通過縮小沒有被錨定的范圍來尋找附近的匹配
- 實際的圖中的錨點和模式中的哪一部分綁定,是Cypher根據(jù)查詢中的標(biāo)簽和屬性斷言決定的
- Cypher使用已有的索引、限制和斷言中的元信息來自動計算出結(jié)果
- 還有少數(shù)情況,用它去幫助確定一些附加的線索
Cypher也是由子句組成的,最簡單的查詢包括:
- 一個MATCH子句
- 一個RETURN子句
- 如下所示:
MATCH (a:Person {name:[Jim]})-[:KNOWS]->(b)-{:KNOWS}->{c}
RETURN b, c
3.2 MATCH
理論上來講,如下模式會在圖數(shù)據(jù)中出現(xiàn)多次:
(a)-[:KNOWS]->(b)-[:KNOWS]->(c)
如果用戶數(shù)據(jù)集比較大的話,可能會有很多相互的關(guān)系能夠匹配這個模式。
要想錨定這個查詢,我們需將它的一些部分先在圖上固定。
同樣,還可以用WHERE子句里的斷言來表示錨定:
MATCH (a:Person)-[:KNOWS]->(b)-[:KNOWS]->(c), (a)-[:KNOWS]->(c)
WHERE a.name='Jim'
RETURN b, c
3.3 RETURN
RETURN子句用來指明已經(jīng)匹配查詢的數(shù)據(jù)中,哪些節(jié)點、聯(lián)系和屬性是需要返回給客戶端的。
3.4 其他Cypher子句
- WHERE : 提供過濾模式匹配結(jié)果的條件
- CREATE和CREATE UNIQUE:用來創(chuàng)建節(jié)點和聯(lián)系
- MERGE:保證給出的模式在圖中一定存在
- 要么復(fù)用已經(jīng)存在的與斷言匹配的節(jié)點和聯(lián)系
- 要么創(chuàng)建新的節(jié)點和聯(lián)系
- DELETE:刪除節(jié)點、聯(lián)系以及屬性
- SET:設(shè)置屬性值
- FOREACH:對一個列表中的每個元素執(zhí)行更新操作
- UNION:合并兩個或更多查詢的結(jié)果
- WITH:鏈?zhǔn)讲樵儯耙粋€查詢的結(jié)果作為后一個查詢的條件。和Unix的管道命令很相似
- START:在圖中制定一個或多個起始點,可以是節(jié)點、也可以是聯(lián)系。(已經(jīng)不推薦使用START了,更推薦在MATCH子句中指定錨點)
4 關(guān)系建模和圖建模對比
一個簡化的數(shù)據(jù)中心應(yīng)用部署如下所示:
作為這類系統(tǒng)的運維方,我們主要關(guān)心兩件事情:
- 為了達(dá)到(或超越)服務(wù)水平協(xié)議,目前提供的功能用來確定單點故障的前瞻性分析,以及與可用性服務(wù)相關(guān)的,用于快速定位客戶投訴原因的回顧性分析。
- 為消耗的資源計費,其中包括硬件成本、虛擬化、網(wǎng)絡(luò)供應(yīng)以及軟件開發(fā)和運營成本(因為這些都是我們可見的簡單的系統(tǒng)邏輯擴(kuò)展)。
當(dāng)構(gòu)建一個數(shù)據(jù)中心管理方案,我們希望用來存儲和查詢數(shù)據(jù)底層數(shù)據(jù)模型,能夠保證有效地解決以上兩個關(guān)心的問題。隨著應(yīng)用組合的變化、數(shù)據(jù)中心物理布局的發(fā)展、或是虛擬機(jī)實例的遷移,我們希望底層的模型也能隨之更新。有了這些需求和限制,在看看關(guān)系建模和圖建模有什么區(qū)別。
4.1 系統(tǒng)管理領(lǐng)域中的關(guān)系建模
關(guān)系世界建模尋求對這一領(lǐng)域?qū)嶓w的理解,它們是如何相關(guān)聯(lián)的,以及它們狀態(tài)變化的規(guī)律。大多是非正式的(可能是草稿、討論),其示意圖如下:
E-R圖也是一種圖,盡管它可以像圖數(shù)據(jù)庫一樣給聯(lián)系命名,但在兩個實體之間,E-R圖只允許建立一條無向的命名的聯(lián)系。而真實世界的實體之間的聯(lián)系豐富多彩,種類繁多,關(guān)系模型無法滿足。
找到合適的邏輯模型之后,將它映射成表和關(guān)系,這種規(guī)范化過程可以消除數(shù)據(jù)的冗余。很多情況下,就類似于將E-R圖用表格形式撰寫一遍,然后用SQL命令把這些表格加載到數(shù)據(jù)庫中。這時一大批出乎預(yù)料的復(fù)雜度悄悄靠近了設(shè)計的模型,如:
- 一對多聯(lián)系的外鍵約束(標(biāo)記為[FK])
- 支持多對多聯(lián)系的連接表(AppDatabase)
- 當(dāng)然模型中沒有重復(fù)的數(shù)據(jù)
關(guān)系范型帶來的挑戰(zhàn)之一是這些規(guī)范化的模型通常都應(yīng)付不了真實世界中需求變化的速度。
理論上規(guī)范化的模式應(yīng)該能夠回答各種突發(fā)奇想的與領(lǐng)域有關(guān)的問題,規(guī)范化的模式還必須為具體的訪問模式做專門的特殊化處理;
-
為了讓關(guān)系型數(shù)據(jù)庫在處理常規(guī)應(yīng)用請求時表現(xiàn)良好,需要接受修改用戶數(shù)據(jù)模型是為了適應(yīng)數(shù)據(jù)庫引擎而不是用戶這個現(xiàn)實,即反規(guī)范化(denormalization)
- 為了獲得查詢性能,反規(guī)范化在某些情況下,會人為制造重復(fù)數(shù)據(jù)
- 為了獲得最佳效果,會把一個標(biāo)準(zhǔn)模型轉(zhuǎn)換成一個反規(guī)范化模型,使其迎合底層RDBMS以及物理存儲層的特性,這個過程會有大量的數(shù)據(jù)冗余產(chǎn)生
- “設(shè)計--> 規(guī)范化 --> 反規(guī)范化”看似可以接受,因為這是一次性的工作。但建造一個高性能的關(guān)系模型所花費的精力相對于開發(fā)整個項目的工作來說只是較小的一部分,很多時候系統(tǒng)變更不只發(fā)生在開發(fā)過程中,有時候產(chǎn)品上線后,需求還會變更
- 很多人假設(shè)系統(tǒng)上線后會使用很長時間,且這段時間內(nèi),生產(chǎn)環(huán)境都是穩(wěn)定的。這個假設(shè)前半段是對的,但問題在于他們很少有穩(wěn)定的時候。隨著業(yè)務(wù)需求的改變和政策法規(guī)的逐步發(fā)展,系統(tǒng)和底層的數(shù)據(jù)結(jié)構(gòu)必然也要隨之改變
- 項目的設(shè)計和開發(fā)階段,數(shù)據(jù)模型總會經(jīng)歷翻天覆地的變化,幾乎所有的變化都是為了使模型適應(yīng)應(yīng)用程序的需求以便上線后可以使用,一旦上線,應(yīng)用程序或是數(shù)據(jù)模型再想增加一些違背最初設(shè)計
將結(jié)構(gòu)變化引入到數(shù)據(jù)庫的技術(shù)機(jī)制叫做遷移(migration)。遷移提供了一種結(jié)構(gòu)化的、步進(jìn)式的方式對數(shù)據(jù)庫進(jìn)行重構(gòu),這樣一來數(shù)據(jù)庫可以有效的響應(yīng)應(yīng)用程序的變化。
- 代碼重構(gòu)只需幾秒鐘或幾分鐘就可以完成,但數(shù)據(jù)庫重構(gòu)不但耗時,并且風(fēng)險高、成本高
- 反規(guī)范化模型的問題是它阻礙了系統(tǒng)業(yè)務(wù)需求的快速發(fā)展,為了適應(yīng)關(guān)系模型,對關(guān)系模型強(qiáng)加了很多變化,使得概念模型和數(shù)據(jù)真實的物理布局之間產(chǎn)生了鴻溝
- 從業(yè)務(wù)相關(guān)方的角度來看,因為這些強(qiáng)加的變化,使得業(yè)務(wù)相關(guān)方無法再參與到系統(tǒng)進(jìn)一步的發(fā)展中
- 從開發(fā)的角度,困難來源于業(yè)務(wù)需求的變化轉(zhuǎn)換為底層的穩(wěn)固的關(guān)系結(jié)構(gòu),使得系統(tǒng)的演化遠(yuǎn)遠(yuǎn)落后于業(yè)務(wù)的發(fā)展
- 沒有專家的幫助和謹(jǐn)慎的規(guī)劃,遷移一個反規(guī)范化的數(shù)據(jù)庫存在很多風(fēng)險
- 就是遷移完成后,如果沒能和存儲保持良好的關(guān)系,性能就會出問題
- 一些曾經(jīng)故意制造的冗余數(shù)據(jù)成了沒人要的“孤兒”,這又帶來數(shù)據(jù)完整性的風(fēng)險
4.2 系統(tǒng)管理領(lǐng)域中的圖建模
圖建模的目的:
- 可以和領(lǐng)域保持高度一致,且不用犧牲性能
- 支撐業(yè)務(wù)發(fā)展的同時,可以在快速的變化和增長之中保持?jǐn)?shù)據(jù)的完整性的模型
圖建模的工作方法:
- 第一步:與關(guān)系建模一樣,用低保真的方法來描述領(lǐng)域并統(tǒng)一意見,比如用白板上的草圖;
- 第二步:關(guān)系建模是將圖轉(zhuǎn)換成表格,圖建模目的是生成一個和應(yīng)用目標(biāo)相關(guān)的領(lǐng)域部分的精確呈現(xiàn)。及領(lǐng)域中的每個實體,
- 確保將其相關(guān)角色轉(zhuǎn)換成標(biāo)簽
- 特性轉(zhuǎn)換成屬性
- 與鄰近實體之間的關(guān)系轉(zhuǎn)換為聯(lián)系
數(shù)據(jù)中心的例子,最后得出的圖模型如下:
從邏輯上來說,不需要表,也不需要規(guī)范化和反規(guī)范化。一旦得到了領(lǐng)域模型的精確表示,把他放到數(shù)據(jù)庫里簡直小菜一碟。
4.3 測試模型
測試模型是為了驗證領(lǐng)域模型是否能適應(yīng)真實的查詢。
- 一些設(shè)計決策像烙印一樣烙進(jìn)我們的程序里,阻礙我們進(jìn)一步的發(fā)展
- 需要事先檢查領(lǐng)域模型和建立的圖模型,這樣圖結(jié)構(gòu)的變化就谷安考業(yè)務(wù)變化驅(qū)動,而不用為了糟糕的設(shè)計做數(shù)據(jù)遷移
驗證的方式:
- 根據(jù)圖的節(jié)點沿著路徑看讀的句子是否邏輯正確,如“應(yīng)用實例1使用了數(shù)據(jù)庫實例123,分布在服務(wù)器a,b上”
- 可查詢的設(shè)計:驗證圖是可以支持哪些我們想要的查詢,必須要描述出這些查詢。舉例:某個用戶使用某個云服務(wù)的時候出現(xiàn)問題,就需要搜索出用戶和這個程序之間的路徑,以及該程序運行所依賴的資源。
5 跨域模型
商業(yè)洞察力往往依賴于我們對復(fù)雜的價值鏈背后的網(wǎng)絡(luò)效應(yīng)的理解。為了達(dá)到一定程度的理解,需要聯(lián)合多個領(lǐng)域,又不能讓每個領(lǐng)域的細(xì)節(jié)失真或者犧牲掉。
- 屬性圖可以給一個價值鏈建模,使其成為一個圖的集合,
- 每張圖都有具體的聯(lián)系關(guān)聯(lián)其子域,又能將它們區(qū)別開來
5.1 創(chuàng)建莎士比亞圖
- 短線表示:文學(xué)領(lǐng)域
- 實線表示:喜劇領(lǐng)域
- 長虛線表示:地理信息領(lǐng)域
創(chuàng)建腳本如下所示:
CREATE (shakespare:Author {firstname:'William', lastname:'Shakespeare'}),
(juliusCaesar:Play {title:'Julius Caesar'}),
(shakespare)-[:WROTE_PLAY {year:'1599'}]->(juliusCaesar),
(theTempest:Play {title: 'The Tempest'}),
(shakespare)-[:WROTE_PLAY {year:'1610'}]->(theTempest),
(rsc:Company {name:'RSc'}),
(production1:Production {name:'Julius Caesar'}),
(rsc)-[:PRODUCED]->(production1),
(production1)-[:PRODUCTION_OF]->(juliusCaesar),
(performance1:Performance {date:'20120729'}),
(performance1)-[:PERFORMANCE_OF]->(production1),
(production2:Production {name:'The Tempest'}),
(rsc)-[:PRODUCED]->(production2),
(production2)-[:PRODUCTION_OF]->(theTempest),
(performance2:Performance {date:'20061121'}),
(performance2)-[:PERFORMANCE_OF]->(production2),
(performance3:Performance {date:'20120730'}),
(performance3)-[:PERFORMANCE_OF]->(production1),
(billy:User {name:'Billy'}),
(review:Review {rating:5, review:'This was awsome!'}),
(billy)-[:WROTE_REVIEW]->(review),
(review)-[:RATED]->(performance1),
(theatreRoyal:Venue {name:'Theatre Royal'}),
(performance1)-[:VENUE]->(theatreRoyal),
(performance2)-[:VENUE]->(theatreRoyal),
(performance3)-[:VENUE]->(theatreRoyal),
(greyStreet:Street {name:'Grey Street'}),
(theatreRoyal)-[:Street]->(greyStreet),
(newcastle:City {name:'Newcastle'}),
(greyStreet)-[:CITY]->(newcastle),
(tyneAndWear:County {name:'Tyne and Wear'}),
(newcastle)-[:COUNTY]->(tyneAndWear),
(england:Country {name:'England'}),
(tyeAndWear)-[:COUNTRY]->(england),
(stratford:City {name:'Stratford upon Avon'}),
(stratford)-[:COUNTRY]->(england),
(rsc)-[:BASED_IN]->(stratford),
(shakespeare)-[:BORN_IN]->(stratford)
上面的語句做了兩件事:
- 創(chuàng)建帶有標(biāo)簽的節(jié)點(以及它們的屬性)
- 將節(jié)點用聯(lián)系連接起來(在需要的地方使用聯(lián)系屬性)
? 標(biāo)識符(shakespeare)幫助我們將聯(lián)系和這個基礎(chǔ)節(jié)點相連。標(biāo)識符在當(dāng)前的查詢范圍內(nèi)是可用的,但是出了這個范圍就不行了。如果想節(jié)點或是聯(lián)系一個可以長久使用的名字,需要為特定的標(biāo)簽或者屬性組合創(chuàng)建索引。
5.2 開始查詢
通常從一個或多個熟悉的起始點開始查詢,也就是所謂的“綁定”節(jié)點。
- Cypher使用MATCH和WHERE子句里任意的標(biāo)簽和屬性謂詞
- 結(jié)合索引和約束提供的元數(shù)據(jù)
一起來尋找錨定這個圖模式 的開始點
舉例:
-
假設(shè)要找所有紐卡斯?fàn)柕幕始覄≡貉莩鲞^莎士比亞戲劇:
作者(Author):莎士比亞
地點(Venue):皇家劇院
城市(City):紐卡斯?fàn)?/p>
-
如下:
MATCH (theater:Venue {name:'Theatre Royal'}), (newcastle:City {name:'Newcastle'}), (bard:Author {lastname:'Shakespeare'})
?
5.3 聲明查找的信息模式
例子:找到所有在紐卡斯?fàn)柕幕始覄≡貉莩龅纳勘葋啈騽。?/p>
MATCH (theater:Venue {name:'Theatre Royal'}),
(newcastle:City {name:'Newcastle'}),
(bard:Author {lastname:'Shakespeare'}),
(newcastle)<-[:Street|CITY*1..2]-(theater)
<-[:VENUE]-()-[:PERFORMANCE_OF]->()
-[:PRODUCTION_OF]->(play)<-[:WROTE_PLAY]-(bard)
RETURN DISTINCT play.title AS play
上述MATCH模式用了幾個特殊的語法元素:
- 通過指定的節(jié)點和屬性值,標(biāo)識符newcastle、theater和bard都被錨定到圖中真實的節(jié)點
- 數(shù)據(jù)庫中有多個皇家劇院,比如:普利茅斯、巴斯、溫徹斯特和諾里奇這些城市都有一個皇家劇院,那么theater會綁定到所有的這些節(jié)點上。為了進(jìn)一步找到在紐卡斯?fàn)柕幕始覄≡海黾恿?lt;-[:Street|CITY*1..2]-,這句的意思是劇院的節(jié)點和紐卡斯?fàn)柕墓?jié)點之間不應(yīng)該超過兩條以上的Street或CITY的聯(lián)系。通過提供一個可變的路徑長度,允許細(xì)粒度的地址分出層次(例如由街、區(qū)或自治區(qū)和市構(gòu)成的地址)
- (theater)<-[:VENUE]-()使用了匿名節(jié)點,即空括號。因為了解數(shù)據(jù),所以知道匿名節(jié)點將會匹配到一些演出,但是對每場演出的具體細(xì)節(jié)并不關(guān)心。在查詢的其他地方也不會用到,所以就沒有把他綁定到任何標(biāo)識符上。
- 在將演出連接到作品時,又一次用到了匿名節(jié)點()-[:PERFORMANCE_OF]->()。如果對這些演出或是原著有興趣,也可以使用標(biāo)識符來替代匿名節(jié)點
- MATCH的剩余部分就是一個簡單的“節(jié)點到聯(lián)系到節(jié)點”的模式:(play)<-[:WROTE_PLAY]-[bard]。保證查詢只返回莎士比亞所寫的戲劇。因為(play)連接到匿名的作品節(jié)點,然后通過連接到的演出節(jié)點,可以有把握的推斷這部喜劇應(yīng)該在紐卡斯?fàn)柕幕始覄≡貉莩鲞^。把這個戲劇節(jié)點命名后,就可以在后面的查詢里用到這個標(biāo)識符
5.4 約束匹配
WHERE子句可以限制圖查詢,基于以下規(guī)則:
- 匹配的子圖中必須有(或者沒有)限制的路徑
- 節(jié)點必須有指定的標(biāo)簽或者指定名字的聯(lián)系
- 在匹配的節(jié)點或聯(lián)系上的某個屬性必須有(或者沒有)特定的屬性,無論它們的值是什么
- 在匹配的節(jié)點或聯(lián)系上的某個屬性必須有特定的屬性值
- 必須能滿足其他任意復(fù)雜的表達(dá)式斷言(如:在某個特定的日期或者之前必須有演出上演)
例子:如果需要將結(jié)果的范圍縮小到莎士比亞晚期的戲劇,通常是指1608年前后,這個可以通過過濾WROTE_PLAY聯(lián)系上的year屬性來達(dá)到目的。調(diào)整語句如下所示:
MATCH (theater:Venue {name:'Theatre Royal'}),
(newcastle:City {name:'Newcastle'}),
(bard:Author {lastname:'Shakespeare'}),
(newcastle)<-[:Street|CITY*1..2]-(theater)
<-[:VENUE]-()-[:PERFORMANCE_OF]->()
-[:PRODUCTION_OF]->(play)<-[w:WROTE_PLAY]-(bard)
WHERE toInt(w.year) > 1608
RETURN DISTINCT play.title AS play
上面的語句還用到了如下語法:
- toInt(w.year) > 1608 將字符串轉(zhuǎn)換車數(shù)值,再進(jìn)行比較
- 當(dāng)然也可以 w.year > '1608' 進(jìn)行比較,但是數(shù)字的字符串最好轉(zhuǎn)換為數(shù)值進(jìn)行比較,所以如果字符串本身就是數(shù)值,最好以數(shù)值的形式存儲在數(shù)據(jù)庫中
舉例:前面將演出的year字段設(shè)置成了字符串,現(xiàn)在需要將其改成年份
MATCH (bard:Author {lastname:'Shakespeare'}),
p = (play)<-[w:WROTE_PLAY]-(bard)
SET w.year = toInt(w.year)
RETURN p
5.5 處理結(jié)果
RETURN DISTINCT play.title AS play:其中DISTINCT 保證返回的結(jié)果是唯一的
RETURN count(play):統(tǒng)計演出的次數(shù)
-
如果根據(jù)演出次數(shù)對結(jié)果排序,可以對PERFORMANCE_OF綁定一個標(biāo)識p,然后對這個p進(jìn)行計數(shù)(count)和排序,如下:
MATCH (theater:Venue {name:'Theatre Royal'}), (newcastle:City {name:'Newcastle'}), (bard:Author {lastname:'Shakespeare'}), (newcastle)<-[:Street|CITY*1..2]-(theater) <-[:VENUE]-()-[p:PERFORMANCE_OF]->() -[:PRODUCTION_OF]->(play)<-[:WROTE_PLAY]-(bard) RETURN play.title AS play, count(p) AS performance_count ORDER BY performance_count DESC
5.6 查詢鏈
有時候一個MATCH得到一切是不可能的。WITH子句允許將幾個匹配連接到一起,將前一個查詢的結(jié)果當(dāng)做條件輸送到下一個查詢中。
舉個例子:查詢一些莎士比亞的戲劇,然后按照寫作的年份將它們排序,年代最近的排在最前面。使用WITH子句,將結(jié)果輸送到RETURN子句里,它使用collect函數(shù)輸出一個用逗號分隔的戲劇名稱列表
MATCH (bard:Author {lastname:'Shakespeare'}) -[w:WROTE_PLAY]->(play)
WITH play
ORDER BY w.year ASC
RETURN collect(play.title) AS plays
上面的語句中需要先對play進(jìn)行排序,然后再對play進(jìn)行collect處理。
WITH可以用來將只讀子句從以寫入為中心的SET操作中分離出來。
WITH通過可以把復(fù)雜的查詢分解成多個簡單模式,將復(fù)雜的查詢分而治之。
6 建模時常見的陷阱
雖然圖建模是掌握復(fù)雜的問題域的一種極具表現(xiàn)力的方式,但只有表現(xiàn)力并不能保證每一個圖都適合其用途。下面說一些常見的有問題的模型
6.1 電子郵件起源問題域
信息交流模式分析是一個經(jīng)典的圖問題,涉及用途去發(fā)現(xiàn)領(lǐng)域?qū)<摇㈥P(guān)鍵影響力以及信息傳播的通信通道。但在這個場景下,我們尋找的是一個壞蛋,而不是正面的榜樣或是專家:就是可疑的電子郵箱通信模式,很可能是違反公司規(guī)定,甚至是違法。
6.2 敏感的第一個迭代
早期的模型,如下所示:
CREATE (alice:User {username:'Alice'}),
(bob:User {username:'Bob'}),
(charlie:User {username:'Charlie'}),
(davina:User {username:'Davina'}),
(edward:User {username:'Edward'}),
(alice)-[:ALIAS_OF]->(bob)
該語句中很容易看出“Alice是Bob的一個別名”。如下圖所示:
!
接下來用他們曾經(jīng)相互發(fā)送過的電子郵件記錄來把用戶連接起來
MATCH (bob:User {username:'Bob'}),
(charlie:User {username:'Charlie'}),
(davina:User {username:'Davina'}),
(edward:User {username:'Edward'})
CREATE (bob)-[:EMAILED]->(charlie),
(bob)-[:CC]->(charlie),
(bob)-[:BCC]->(charlie),
這種描述乍一看合理且忠實于領(lǐng)域的表示方式。從上面的圖可以看到“Bob給Charlie發(fā)了電子郵件”,但Bob到底在電子郵件中寫了什么,雖然我們可以看到的是Bob抄送或是密件抄送了一些人,但看不到最重要的東西:電子郵件本身。如下圖所示:
運行下面的查詢時,這個圖結(jié)構(gòu)帶來的信息缺失顯得尤為明顯:
MATCH (bob:User {username:'Bob'})-[e:EMAILED]->
(charlie:User {username:'Charlie'})
RETURN e
這個查詢返回了Bob和Charlie之間的EMAILED聯(lián)系。只讓我們知道有電子郵件交流,而不能告訴我們電子郵件是什么,如下所示:
也許在EMAILED聯(lián)系上加一些代表電子郵件特性的屬性就可以挽救局面,但那其實只是在拖延時間,我們還是無法知道EMAILED、CC和BCC這些關(guān)系之間是如何相互作用的,也就是說不清楚哪些電子郵件是抄送的,哪些是密件抄送的,以及它們都是發(fā)給誰的。
6.3 第二次魅力
要修復(fù)這個有缺失的模型,需要加入電子郵件節(jié)點來代表在業(yè)務(wù)中來往的電子郵件,并擴(kuò)展聯(lián)集包含所有電子郵件支持的地址信息。替換掉這個有結(jié)構(gòu)缺失的語句:
CREATE (bob)-[:EMAILED]->(charlie)
用如下語句創(chuàng)建包含更多細(xì)節(jié)的結(jié)構(gòu):
CREATE (email_1:Email{id:'1', content:'Hi Charlie,.... Kind regards, Bob'}),
(bob)-[:SENT]->(email_1),
(email_1)-[:TO]->(charlie),
(email_1)-[:TO]->(davina),
(email_1)-[:CC]->(alice),
(email_1)-[:BCC]->(Edward)
6.4 發(fā)展中的領(lǐng)域
在圖庫中,更傾向于添加新的節(jié)點和聯(lián)系來增加信息或者成分,而不是修改已有的模型。這樣做不會影響已有的查詢,并且是完全安全的
使用已有的聯(lián)系類型對圖做修改,或者修改現(xiàn)有節(jié)點的屬性(不僅僅是屬性值)有可能是安全的,但是我們需要運行一些有代表性的查詢來增強(qiáng)我們的信心,來告訴我們即時圖的結(jié)構(gòu)改變了,仍然可用
7 辨別節(jié)點和聯(lián)系
建模的過程可以非常合適地總結(jié)為用圖結(jié)構(gòu)來表達(dá)想問我們的領(lǐng)域的問題。也就是說,我們所說的面向可查詢的設(shè)計:
- 描述驅(qū)動模型的客戶端或者最終用戶的目標(biāo)
- 把這些目標(biāo)轉(zhuǎn)述成要問的領(lǐng)域問題
- 明確這些問題中出現(xiàn)實體和聯(lián)系
- 把這些聯(lián)系和實體翻譯成Cypher的路徑表達(dá)方式
- 用圖的模式來表達(dá)我們想問的領(lǐng)域問題,使用路徑表達(dá)式,就如同我們建模這個領(lǐng)域時一樣
通過拷問用來描述領(lǐng)域的語言,可以快速明確圖中的核心元素:
- 常用的名字可以作為標(biāo)簽,如:user、email,變成標(biāo)簽User和Email
- 帶有賓語的動詞可以作為聯(lián)系名稱,如:sent、wrote,變成SENT和WROTE
- 一個合適的名詞(比如人們或者公司名)指代一樣?xùn)|西的實體,就把他建模為節(jié)點,用一個或是多個屬性來記錄他的特點
8 避免反模式
- 不要把實體建模成聯(lián)系,應(yīng)用使用聯(lián)系來傳達(dá)實體之間如何相連的,以及這些聯(lián)系的性質(zhì);
- 動詞名詞話(一個名詞可以借此轉(zhuǎn)型為動詞的語言習(xí)慣)經(jīng)常影藏了名詞的出現(xiàn)以及相應(yīng)的領(lǐng)域?qū)嶓w;
- 理解圖的天然可擴(kuò)展性,增加一些領(lǐng)域?qū)嶓w以及增加在它們之間使用新的節(jié)點和新的聯(lián)系進(jìn)行連接的方式,這些看起來像是往數(shù)據(jù)里猛灌大量的數(shù)據(jù),但其實是很自然的事情
- 在寫入數(shù)據(jù)時,為了保證查詢效率而混入數(shù)據(jù)元素是很糟糕的做法。
- 按照向領(lǐng)域提出的問題來建模,就能得到一個精確表示領(lǐng)域的模型。確立這樣的數(shù)據(jù)模型,圖數(shù)據(jù)庫在讀的時候就能表現(xiàn)的很好。
- 即使存儲了非常大量的數(shù)據(jù),圖數(shù)據(jù)庫的查詢速度依然良好。
當(dāng)我們學(xué)著去組織我們的圖并且不用去反規(guī)范化它們的時候,我們要學(xué)會去相信圖數(shù)據(jù)庫,這是很重要的。
9 小結(jié)
- 圖數(shù)據(jù)庫賦予了軟件開發(fā)人員用圖表達(dá)問題領(lǐng)域的能力,并可以在運行時查詢圖
- 圖可以清晰的描述問題域;
- 圖數(shù)據(jù)庫可以讓我們在存儲這種描述方式時得以保留領(lǐng)域和數(shù)據(jù)之間的親緣關(guān)系
- 圖建模免去了使用復(fù)雜的數(shù)據(jù)管理代碼來規(guī)范化和反規(guī)范化數(shù)據(jù)這一步驟
- 我們創(chuàng)建的圖應(yīng)該讓那些查詢語句讀起來很順暢,同時要避免混雜實體和動作,在多個迭代中滿足系統(tǒng)的需求,同時也可以跟上代碼的演變;