元數據驅動設計 —— 設計一套用于API數據檢索的靈活引擎
如果你曾在企業開發方面具有一些經驗,那么基本上可以斷言,你必定承擔過一些類似于搬運工的職責,將數據從你的數據庫中不斷地搬進搬出。此外,如果你在這方面有過過往的經驗,那么你肯定也曾經做過將大量對共享文件進行解析,并且加載到某個schema的表中的事。從純文本文件到結構化的XML文件,再到更為艱澀的文件格式(例如ISO 2709),開發者與管理員在這些文件之間不斷轉換,并從中獲取數據,這種狀態已經維持了數十年。
對于抽取數據文件這種歷史悠久的做法,有人提倡也有人批評。批評者們認為,數據文件不是來自于實時的信息源,并且根據所選擇的信息格式的不同,要正確地處理這些文件可能需要經過大量的協調與策略。另一方面,提倡者分辯道,數據文件的使用已經有幾十年的歷史了,而它的結果是產生了豐富的類庫與命令,即使是未經過訓練的初學者也能夠掌握它們的使用方法。
這些已經證實了它們實用性的工具讓數據文件的解析與加載工作看起來只是舉手之勞,對于加載海量數據來說,這些工具通常也是最快的方式。那些數據文件的支持者也會指出,雖然文件并非實時的,但它們提供了數據快照的一個記錄集,可以將這些結果進行存檔,可用于今后審記員在檢查公司的行為是否合法時的有力證明。比方說,在美國法律中,塞班斯法案(Sarbannes-Oxley Act)規定公司必須保留最近至少五年以上1的相關數據。如果你當前的系統是根據這些需求規格處理文件的,那么你的數據流大概會像以下方式一樣:
但是,雖然關于使用數據文件的爭議還在繼續,但當今世界對于實時信息的渴望卻一天比一天強烈,而通過API的方式進行數據檢索的這種實踐越來越多地開始滿足人們的這種渴望。在今后的許多年中,遺留程序與系統仍將從文件中獲取數據,但這些系統最終很可能會因為干系人對實時數據的渴望而被取代。當然,為了讓這種轉變成功地實現,我們還需要新的系統與程序。此外,由于這種方式不會再將數據進行壓縮并歸檔至某個目標文件夾,因此我們必須創建一種自己的數據保留方案。
但一般來說,我們希望能夠使用一種與從文件中獲取數據的解決方案類似的工作流:
解決這一問題的方法有許多種,可以選擇一種臨時方案并快速地實現它。不過,如果你曾經閱讀過我之前所寫的文章2,你就知道我很喜歡使用一種更為系統化的方式處理這些問題。實際上,我們在這種情況下可以使用元數據驅動設計以創建一種健壯的架構,它能夠承擔并執行這些我們所期望的職責。那么,到底元數據驅動設計是什么呢?為了簡潔起見,可以將它簡單地歸結為一種軟件設計與實現的途徑,讓元數據組成并集成這兩個開發階段。換句話說,在這種方式下,開發者能夠在整個軟件開發的生命周期3中采取敏捷式的迭代。通過使用由領域驅動設計所派生的元數據,你可以進而進行下一步的元數據驅動設計,并且創建出令人印象深刻的靈活架構。
正如Mike Amundsen所建議的一樣,API的創建應該貫穿整個設計4之中,在我們創建會調用這些API的系統時,也應該保持相同的心態。因此,為了讓具有高度復雜性的問題更易于解決,將這種難題分解為各個組成部分是一種良好的實踐(由于這種方式有助于使用簡潔的方案分別解決整體性問題中的每個部分,因此元數據驅動設計天然的模塊化特性對這種情況來說尤其合適)。舉例來說,由于API數據檢索方式通常會對某個請求所返回的數據的最大數量加以限制,因此我們需要設計一種自己的引擎,并且假設它會通過對該API的循環調用,在大量的數據集中進行枚舉。但是,在我們開始處理這些小問題之前,我們需要提前預料到更高級別的問題,也就是在API數據檢索中存在著不同的風格。某些提供商在獲取本身數據時所采用的機制不夠強大,能夠參與查詢的參數數量有限,甚至完全沒有。用戶不得不在每次查詢中獲取完整的內容,并自行處理。某些用戶或許希望能夠用重復調用的方法獲取某個提供商的完整記錄,但有很多用戶(比如我自己)希望能夠獲取一部分數據子集,尤其是自最后一次數據獲取以來發生過變更的記錄(即增量記錄)。在更加直接的實現中,提供商或許只需要在URL查詢字符串中加入一個參數,就可以獲得增量數據。但在更抽象的用例中,審計信息與實際數據是通過兩種相互分享的API中獲取的。因此,審計API會被首先調用,在結果中提供了一份清單,其中詳細地說明了哪些記錄與字段在某個日期之后產生了變更。隨后可以使用這份清單通過數據API獲取增量數據。在我們設計這套架構時,必須考慮到這種方案。
盡管我們可以對同時使用某個數據API與對應的審計API的方式進行一些調整,但我們要關注的是在通用的情況下如何系統地對API進行調用。在這兩種情況下,我們都需要考慮到某些因素:生成適當的URL、對格式(JSON、XML等等)進行解析,獲取特定屬性的值、過濾掉不需要的記錄等等。為了為這套架構創建一組合適的元數據,最終的結果需要包括一套大范圍的數值,這些值能夠滿足每一個必要步驟的需求,無論是調用審計API或是數據API。為了展現這一點,以下分別顯示了一個增量清單以及對應的增量數據:
將記錄包裝在一個集合體(即<products>)內,并將每條記錄作為一個獨立的內容體(即<product>)進行保存是一種常見的做法,盡管這一點并不總是成立。API也能夠幫助我們處理對大數據集進行重復式調用的問題(即分頁),其做法是提供一個對下一批數據(即<hasMore>與<next>)的鏈接,這種做法也十分普遍。這些模式能夠幫助我們在自己的引擎中打造泛用性的功能。為了在這些記錄中進行枚舉,我們可以利用運行時環境中提供的底層機制來實現這一點。在本文的余下部分中,我們將專注于.NET作為我們所選定的平臺。在這個案例中,.NET XPath庫與W3C XPath符號5為我們提供了在這些記錄間進行移動的必需功能。
在我們提供一種用以實現這套數據獲取引擎的元數據之前,由于我們目前在討論的是原始的數據,因此我們應當趁此機會解決在法律方面的關注點。為了滿足對系統進行審計的人員的需求,我們應當將這些原始的數據持久化,以證實我們現有數據的真實性。我們接下來要創建一張表,它描述了我們的特定目標,此外還有一張表記錄了對我們的查詢所返回的API響應:
這些數據不僅滿足了我們對于數據保留政策的需求,同時我們也有了一個潛在的健全性檢查工具,可以作為我們的數據處理管道中的第一個步驟。如果在之后的步驟中對于這個引擎的性能有任何疑問,我們都可以隨時檢查這個響應的“快照”,以確保我們正確地解析并加載了包含在響應中的值。
現在我們就具有了一個適當的基準線,我們終于可以設計這個元數據了,它將用于對如何從指定的API中獲取數據進行配置。為了正確地執行以上描述的這個假想的流程,流程的第一步就是通過審計API獲取變更清單。有了這份變更清單之后,我們就可以獲取之前所描述的增量記錄了,可以選擇單獨獲取或批量獲取:
這個schema的原型與示例的行數據將成為我們為引擎進行元數據驅動設計的第一步。它將試圖捕捉能夠驅動我們的軟件的抽象概念(正如領域驅動設計之父所說:“只有找到一組適用于所有細節的抽象概念后,工作才算成功6”)在這個示例中,類型’A’這一行表示獲取變更清單所必須的API配置信息,而其它所有行中的值組成了我們用于進行實際數據檢索所需的配置。之前,我曾提到需要某種能力,通過重復式的調用獲取大數據集的內容。(盡管在XML中的內容所包含的數據集僅包含了一個增量數據,但在大多數情況下所指向的記錄集會大上許多)。我們已經有了對該URL進行第一次調用所需的元數據了,并且’Anchor’列能夠幫助我們在一個大集合中進行枚舉,以實現這些重復式的調用:
最初的Auditing API調用
http[s]://api.example.com/products/changes?since=1432746033000&products=VD
之后的Auditing API調用
http[s]://api.example.com/products/changes?limit=1000&anchor=MTQyNDcyN
這些元數據不僅提供了用于生成URL的構建塊,我們現在還擁有了對API調用的響應內容進行解析與過濾的功能。作為一個小提示,我會建議你實現某個接口(例如IEnumerable),它會無縫地對響應內容中的數據進行枚舉,并且將之后的API調用也作為整個枚舉過程中的一部分。由于.NET平臺在這個示例中是我們的底層運行環境,因此這里.NET XPath類庫就派上了用場。通過使用XPath符號查詢某個元數據列(例如“Target_Child_Tag”和 “Target_Child_Key_Tag”)中的數據,我們就能夠在響應體中的記錄之間方便地跳轉,并獲取我們將進行處理的數據體。只需幾行代碼,就能夠使用.NET LINQ功能獲取數據,并忽略響應體中我們并不感興趣的那部分數據:
List<Hashtable> CurrRecordList = from tmp in CurrXmlResponse.Root.Elements(TargetChildTag) where tmp.XPathSelectElement(XPathRespFilter) != null && tmp.XPathSelectElement(XPathRespFilter).Value != null select new Hashtable() { {targetChildKeyTag, tmp.Element(targetChildTag).Value}, { “body”, tmp.ToString()} }.ToList();
這個泛型功能允許我們將某個響應體中的記錄加載到某個容器之中。在審計API的情況下,這段LINQ代碼的執行結果會生成一個列表,其中包括了我們的增量清單中的各個部分。而在數據API的情況下,這個結果列表將包含實際產品記錄的內容。這個變更清單容器的目的在于:它能夠作為一份指南,告訴我們應當通過數據API獲取哪些部分的產品記錄。不過,在數據記錄列表的情況下,我們還需要更多的說明。我們將如何處理這些數據?我們如何打造一套將這些數據持久化的機制?當然,我們需要進一步地實現元數據驅動設計!
在這個架構游戲中的倒數第二條功能就是將這些增量記錄指向它們最終的目的地。雖然我們可以選擇將這些增量記錄持久化到文件系統(與變更清單一起)中,大多數企業系統都將數據庫選為他們的主要存儲機制,這一點應當不會令你感到吃驚。如果你只是需要顯示這些數據,那么你可以選擇使用一種NoSQL數據庫,例如MongoDB以保存這些增量記錄(甚至選擇保存完整的原始響應數據),但由于查詢以及處理這些常見的需求,多數企業數據系統都會選擇將這些屬性分散保存在關系型數據庫中的多張表中。在這種情況下,我們需要創建一系列元數據,它們將幫助我們將增量記錄轉移到某個適當的預發布區域:
如果這套引擎的軟件實現足夠良好,我們還可以創建一個智能的審計子系統,它能夠了解當數據進行這個預發布區域時,將如何檢測變更并進行記錄。最重要的是,如果需要在記錄中加入額外的數據并進行持久化,只需方便地添加更多的行就可以了,這些改動對代碼產生的修改要求減至了最低,甚至完全為零。
最后,我們還需要實現對這些響應記錄的歸檔操作。根據你的數據庫配置以及所預期的變更數量,可以合理地選擇暫時將這些記錄保留在響應體相應的表中,考慮到法律與所有權政策的迫切需求。不過,這些數據很有可能超出你的數據庫的空間限制,并且在相關人員進行查詢時會表現得很慢。在這種情況下,可以考慮其它幾種選擇。一種選擇是與一位DBA進行合作,共同創建一個對你的響應體表中的某些部分進行歸檔的策略。不過,在這種情況下,這個解決方案將不再屬于你的引擎的領域之內,同時也將脫離你的掌握范圍之外。對于不喜歡這種方式的其他人來說,還有一種替代方案。在過去幾年中,許多提供商都以合理的價格推出了數據歸檔服務(例如Microsoft Azure Backup)。雖然其中有些服務要求你安裝客戶端的軟件,但這種選擇能夠為你提供除了對數據進行簡單地歸檔之外的服務(例如數據恢復等等)。當然,這套API數據檢查引擎需要配合你的歸檔解決方案。在使用某些服務的情況下,該引擎或許要將原始的響應體搬到某個文件系統中,通過它自動分發到遠程的地點。不過,只需要幾行元數據與幾行代碼,你就能夠充分利用這一服務,在你的引擎工作流中創建一種靈活的歸檔步驟。
不過,在我們開始感覺飄飄然之前,你或許該考慮到這種設計中缺乏了任何形式的保護機制。比方說,我們目前假設這套系統能夠以某種方式運行,也就是說這個API服務器能夠提供指向一些增量記錄的變更清單。但是,如果今后這些變更的數量產生了變化,使我們目前的期望突然間變得與事實不符了呢?如果突然間產生了潮水般的變更數量,壓垮了我們的系統呢?又如果這個API服務器突然間無法正常運行,開始了無限循環,不斷地發送給我們相同的變更清單呢?在最后一種情況中,我們在潛在中也可能會無意間對API服務器造成了一種DoS攻擊,而如果最終發現這個無意中產生的攻擊是來自于你的引擎本身,情況會變得更加嚴峻。這會令人感覺尷尬(至少可以這么說),我們應該致力于避免這種情形,無論是出于技術原因還是政治上的原因。那么為了避免這種情形出現,我們需要做些什么呢?我們將再一次創建一個健壯的元數據驅動解決方案,以幫助我們對應這些潛在的問題:
當然,像“Email”與“打開閥門”這樣的操作需要在你的引擎中實現,不過實現這些功能應當十分簡單。由于我們已經了解了領域模型,我們已有了足夠的業務知識,因而可以建立一些對問題場景進行量化的參數值,由此觸發應對這些問題的適當的回應。更重要的是,我們還可以簡單地對這些數值進行調整,以適應不斷變化的需求,因為隨著時間的推移,你的環境以及期望很可能會產生改變。
有了這套模型之后,我們就能夠實現一套架構,它對于已建立的IT部門來說是一種能夠令人心安的轉變。同時由于我們為通過API檢索到的數據保留了記錄快照,我們就能夠滿足管理者在法律問題以及心理上的需求。此外,一如既往,我們能夠在過程中采用敏捷,以進一步改善這個數據檢索引擎。如果你傾向于使用數據API(而不會產生一個相應的調用去獲取變更清單),那么你完全可以選擇移除整個設計中關于變更清單的這一部分的功能,以及對應的代碼。如果你還希望加入某種策略,使舊記錄能夠被自動移除,你也可以對設計進行迭代,加入另一個元數據集(以及對應的代碼)。這個新功能能夠基于某些數值,例如源標識以及已保存記錄所允許存在的最長時間,將無效的記錄移除。無論在哪一種情況下,這種配置都能夠為數據檢查未來的需求提供一種靈活的設計。
關于作者
Aaron Kendall是一位居住在紐約的軟件工程師,在企業數據系統的設計與實現方面具有近20年的經驗。他剛開始是一位設備驅動程序的開發者,隨后轉為專業軟件的開發者,在此過程中他表現出了對軟件設計及架構方面的熱情。他曾經在多個平臺上通過多種語言創建了具有創新性的商業解決方案,以及許多作為自由職業者創建的軟件項目,包括開源的軟件包,以及游戲設計和移動應用。如果你想進一步了解他的工作,歡迎閱讀他在LinkedIn上的帳號以及他的博客。