|
這個問題來源于Apworks應用開發框架的設計。由于命令與查詢職責的分離,使得基于CQRS體系結構風格的應用系統的外部存儲系統的結構變得簡單起來:在“命令”部分,簡單地說,只需要 Event Store和Snapshot Store來保存Domain Model;而“查詢”部分,則又是基于事件派送與偵聽的系統集成。之前我也提到過,“查詢”部分由于不牽涉到Domain Model,于是,它的設計應該隨意性很大:不需要受到Domain Model的牽制,例如,我們可以根據UI所需的數據源結構進行“查詢”庫的設計。Greg Young在他的“CQRS Documents”一文中也提到了這樣一些相關話題:就“查詢”部分而言,雖然也存在“阻抗失衡”效應,但是事件模型與關系模型之間的這種效應,要遠遠小于對象模型與關系模型之間的“阻抗失衡”效應。這是因為,事件模型本身沒有結構,它僅僅表述“該對關系模型做哪些操作”這樣的概念。在設計上,Greg Young建議,采用一種非正規的方式設計“查詢”數據庫,以便盡量減少讀取數據時所需的JOIN操作,比如可以選用基于第一范式(1NF)的關系模型。這是一種反范式模型,雖然Greg Young并沒有建議根據UI所需的數據源結構進行設計,但思想是相同的:基于事件而不拘泥于對象模型本身。由此引申出來的另一個好處就是外部存儲系統架構的隨意性:你可以選用任何存儲技術和存儲媒介,這又給基于系統架構的性能優化提供了便利。因為并非所有的存儲架構都支持“表”、“字段”、“存儲過程”、“JOIN”這些概念。
根據上面的簡單分析,我們得到一個結論:通常情況下,或許基于CQRS體系結構風格的應用系統更多的是采用的“平整”的外部存儲結構,簡而言之,就是一個數據訪問對象(DAO)對應一張數據表。這也是我所設計的Apworks應用開發框架中默認支持的一種存儲結構。有讀過Apworks源代碼的朋友會發現,在Apworks.Events.Storage命名空間下,有兩個定制的DAO:DomainEventDataObject,用于表述領域事件的數據結構,以及SnapshotDataObject,用于表述快照的數據結構,與之相對應的就是數據庫中的兩張表:DomainEvents和 Snapshots。雖然結構變得這么簡單,但是映射關系總還是需要維護的:最簡單的就是需要在對象類型名稱與數據表名之間,以及對象屬性與數據表字段之間建立起映射關系。在Apworks中,這種映射關系是由Apworks.Storage.IStorageMappingResolver接口完成的。有關這個接口的內容不是本文討論的重點,暫且不深入分析了。
至此,也許你不會接受我上面的討論,認為“基于UI設計數據庫結構”或者“采用1NF、反范式設計數據庫結構”是無法接受的,那么,接下來的討論可能對你來說意義也不大了。因為下面的問題是以上面的描述為基礎的:一個數據訪問對象對應一張數據表。不過即使你不認同我的觀點,我也建議你繼續看完本文。
Query Object模式
雖然只是簡單的映射,但畢竟不能忽略這樣的映射關系。Apworks作為一個應用開發框架,需要提供方便的整合接口,以便今后能夠根據不同的客戶需求進行擴展。例如在存儲部分,數據的增刪改查(CRUD)是基于數據訪問對象(DAO)的,這樣做的一個好處是能夠對外部存儲系統進行抽象,使得訪問存儲系統的部分能夠無需關系存儲系統的細節問題。客戶有可能選擇SQL Server、Oracle、MySQL等關系型數據庫作為存儲系統,也可以選擇其它的非關系型數據庫作為存儲系統,因此,我們的設計不能僅僅局限于關系型數據庫,我們需要同時考慮其它形式的數據存儲產品以便將來能夠方便地集成新的存儲方案。假設我們要設計一個針對 DomainEventDataObject的“查詢”功能,我們需要考慮的問題可能會有(但不一定僅限于):
* 需要查詢對象的哪些屬性(或者說與DomainEventDataObject相對應的數據表的哪些字段)
* 需要根據什么樣的條件進行查詢
* 查詢是否需要排序
* 是否只查結果集中的任意一條記錄,還是要返回所有的記錄
在Apworks框架的Alpha版本中,查詢的方法定義在Apworks.Storage.IStorage接口中。比如,根據給定的查詢條件和排序方式,對指定DAO進行查詢的方法定義如下:
/// <summary>
/// Gets a list of ordered objects from storage by given selection criteria and order.
/// </summary>
/// <typeparam name="T">The type of the object to get.</typeparam>
/// <param name="criteria">The <c>PropertyBag</c> instance which contains the criteria.</param>
/// <param name="orders">The <c>PropertyBag</c> instance which contains the ordering fields.</param>
/// <param name="sortOrder">The sort order.</param>
/// <returns>A list of ordered objects.</returns>
IEnumerable<T> Select<T>(PropertyBag criteria, PropertyBag orders, SortOrder sortOrder)
where T : class, new();
NET技術:使用LINQ Expression構建Query Object,轉載需保留來源!
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。