第9期:報表應用的三層結構
在傳統的報表應用結構中,報表工具一般都是與數據源直接連接,并沒有一個中間的數據計算層。確實,大部分情況下的報表開發并不需要這一層,相關的數據計算在數據源和呈現環節分別處理就夠了。不過,在開發過程中,我們發現,有一部分報表的計算即不適合在數據源也不適合在呈現環節實現,這類報表在數量上并不占多數,但耗用的開發工作量占比卻很大。
有過程的計算
報表工具都可以完成計算列、分組排序等運算,有些報表工具還提供了跨行組運算和相對格與集合的引用方案,可以完成頗為復雜的運算。
不過,報表工具中的運算是一種狀態式的計算,也就是把所有計算表達式寫在報表布局中,根據依賴關系自動處理計算次序。這種方法很直觀,在依賴關系不太復雜時能一目了然地了解各單元格的運算目標。但是,在依賴關系較為復雜,數據準備計算需要分成多步時,狀態式計算就困難了。如果一定要在報表中實施過程式計算,常常需要借用隱藏格,而隱藏格不僅將破壞狀態式運算的直觀性,還會占用更多不必要的內存。
比如要列出銷售額占前一半的大客戶,如果不借助數據準備環節,就要在報表中使用隱藏行列手段將不該列出來的條目隱藏,而不能直接過濾掉。再比如帶明細的分組報表要按匯總值排序,需要先分組后排序,許多報表工具無法控制這個次序。
還有個典型例子是舍位平衡,明細值四舍五入后再合計,可能會與合計值的四舍五入值不相等,會造成了報表上明細與合計數值不一致,需要根據合計的舍入值倒推明細的舍入值,這種計算的邏輯并不復雜,但即便用了隱藏格也難以由報表工具完成。
多樣性數據源
與多年前的單一數據源不同,現在有許多報表的數據源并不只來源于關系數據庫,還可能是NoSQL數據庫、本地文件、從WEB上傳來的數據等。這些非關系數據庫的數據源缺乏標準的數據獲取接口和語法,有些甚至沒有最基本的過濾能力。而計算報表時總還要進行一些過濾甚至關聯運算,雖然報表工具一般都能提供這些計算能力,但由于都是內存計算,只適合于數據量較小的情況,數據量較大時就會導致容量負擔過重。而且,大多數報表工具也不能很好地處理像json或XML這種多層數據,也沒有靈活編碼能力以登錄遠程WEB服務獲取數據。
動態數據源也是常見的需求,報表工具使用的數據源一般事先配置好的,不能根據參數動態選擇,直接使用報表工具無法實現。報表被用于通用查詢時,取數用的SQL不能簡單地用參數控制條件,而經常可能要替換某個子句,有些報表工具支持宏替換,能夠一定程度地解決這個問題,但根據參數計算宏值也是個有條件和過程的運算,直接在報表工具中很難完成。
性能優化問題
我們在往期的文章中曾談到過,大多數情況的報表性能問題都需要在數據準備階段來解決,其中有許多場景都不能在數據源內部處理。比如并行取數本來就是解決數據源IO性能問題,只能在數據源外部實現;可控緩存需要在外存寫入緩存信息,也不能在數據源內部處理;清單列表中的異步數據緩存和按頁取數的功能,都不是數據源本身提供的能力;即使可以在數據源環節處理的多數據集關聯問題,在多數據庫或非數據的場景、以及希望減輕數據庫負擔時,仍然需要在數據源外部解決。這些無法在數據源內部處理的場景,顯然也無法在報表環節處理。
數據計算層
如果把傳統報表應用結構的兩層改成三層,增加一個中間的數據計算層,這些問題就容易解決了。
上述的各種運算都可以在數據計算層實現,報表工具只解決呈現問題以及少量適合狀態式的直觀計算即可。
其實,傳統報表應用結構雖然沒有刻意強調數據計算層,但仍然有這一層,只是比較隱蔽。典型的實現手段就是使用數據源中的存儲過程或者在應用中使用報表工具的自定義數據源接口。存儲過程能夠解決一些過程式計算和性能優化問題,但它只能應用于單個數據庫中,相當于在數據源內部的處理,對于必須在數據源外處理的場景無能為力,有較大的局限性。自定義數據源則在理論上可以解決上述所有問題,而且幾乎所有報表工具都提供有這個接口,所以這種方式的應用更為廣泛。
那么,使用報表工具的自定義數據源是否就可以方便地實現數據計算層呢?我們將在下一期討論。