首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

《解剖 PetShop》系列之五:PetShop 業務邏輯層設計深度解析

     編輯:關於.NET

業務邏輯層(BLL)無疑是系統架構中體現核心價值的部分。關注點集中於業務規則制定、業務流程實現等與業務需求相關的系統設計,即與系統所對應的領域邏輯有關,常稱領域層。Martin Fowler《Patterns of Enterprise Application Architecture》將架構分為表示層、領域層、數據源層三主要層。領域驅動設計先驅 Eric Evans 進一步劃分為應用層與領域層,透過分層將領域邏輯與解決方案分離。

BLL 在架構中位置關鍵,處於數據訪問層與表示層中間,起數據交換承上啟下作用。層為弱耦合結構,層間依賴向下,底層對上層「無知」,改變上層設計不影響其調用的底層。若分層設計遵循面向介面思想,向下依賴應為弱依賴。理想分層架構應支援可抽取、可替換的「抽屜式」架構。BLL 設計對可擴展架構至關鍵,因其扮演雙重角色:對數據訪問層是調用者,對表示層是被調用者。依賴與被依賴關係糾結於 BLL,如何解耦依賴關係,是實現業務邏輯外留給設計師的任務。


5.1 與領域專家合作

設計 BLL 最大障礙不在技術,而在對領域業務的分析與理解。難想像不熟悉領域業務規則與流程的架構設計師能設計出符合客戶需求的架構。幾乎可下結論:BLL 設計過程必須有領域專家參與。筆者參與專案涵蓋電力、半導體、汽車等行業,缺乏領域專家,軟體架構設計尤其是 BLL 設計無從談起。唯一例外是架構設計師同時也是領域專家,但「千軍易得,一將難求」,難尋覓此類人才。

領域專家在團隊中稱 Business Consultor(業務諮詢師),負責提供領域業務諮詢,與架構師共同參與架構與資料庫設計,撰寫需求文檔與設計用例(或用戶故事)。測試階段應包括撰寫測試用例。理想狀態:領域專家參與整個專案開發過程,而非僅需求階段。

領域專家可為專門聘請的顧問,也可為需求提供方客戶。極限編程(XP)將客戶作為領域專家引入團隊,強調現場客戶原則:參與計劃遊戲、開發迭代、編碼測試等各階段。領域專家與設計師、開發人員組成團隊貫穿始終,可避免需求理解錯誤。即使專案開發與實際需求不符,亦可早期及時修正,避免不必要延期,加強過程與成本控制。Steve McConnell 指出:發現錯誤時間應盡可能接近引入錯誤時間。需求缺陷潛伏越久,代價越昂貴。充分與領域專家合作可最大效果規避惡性鏈式反應。

傳統軟體開發模型同樣重視領域專家合作,但主要集中在需求分析階段。如瀑布模型強調早期計劃與需求調研,但早期計劃方式對架構師與調研人員技能要求極高,強調需求文檔精確性,一旦分析偏差或需求變更,進入設計階段後因缺乏溝通機制,開發人員估量不到錯誤,難以及時修正。問題像毒瘤蔓延暴露時,已成難以逾越高山,需消耗更多人力物力修正,導致成本成數量級增加甚至延期。項目「滑鐵盧」多因 BLL 分析出現問題。

迭代式模型較瀑布模型大幅改進,允許變更優化需求,整個迭代過程即與領域專家合作過程,透過向客戶演示迭代產生功能獲取反饋,逐一解決問題,保證系統朝符合客戶需求方向演化。因而迭代模型常能解決早期計劃不足問題,允許發現缺陷時重新設計、編碼、測試。

無論採用何種模型,與領域專家合作都是項目成敗關鍵。基於軟體開發普遍真理:世上沒有不變的需求。經典名言:「沒有不變的需求,世上軟體都改動過 3 次以上,唯一只改動 2 次的軟體擁有者已死,死在去修改需求的路上。」一語道盡軟體開發殘酷與艱辛!

如何加強與領域專家合作?James Carey 與 Brent Carlson 基於 IBM SanFrancisco 專案經驗提出 Innocent Questions 模式,意義為「改進領域專家與技術專家溝通質量」。若團隊無兼具首席架構師與領域專家人選,加強雙方合作顯得尤為重要。領域專家可能不熟軟體設計方法學、不具 OO 開發與架構設計能力;技術專家對業務領域可能僅一知半解。若無法有效溝通,專案前途岌岌可危。

Innocent Questions 模式解決方案:

  1. 選用能與人和諧相處的人員組建團隊
  2. 清楚定義角色與職權
  3. 明確定義需要的交互點
  4. 保持團隊緊密
  5. 雇傭優秀的人

實際上已從技術角度上升到團隊管理層次。如籃球運動,即使集合五名世界頂尖球員,各自為戰也難取勝。團隊精神與權責分明才是勝利保障,軟體開發同樣如此。

合作基礎:保證團隊永遠保留至少一名領域專家(可為客戶、第三方顧問、自家僱傭專家)。缺乏則建議去僱傭,否則將遭遇「西伯利亞寒流」。

確定領域專家角色任務與職責:讓每個人明確其在團隊中角色、職責。合格專家必須對業務領域有深入理解,能俯瞰整個系統需求、總攬全局。開發過程中負責業務規則與流程制定、客戶溝通、需求調研討論、與設計師共同參與系統架構設計。編檔為必參與工作(需求文檔、設計文檔、用例編寫),至少為評審委員會重要成員。

規範業務領域術語與技術術語:雙方必須在不產生二義性語義環境下溝通。出現理解分歧必須及時解決,討論確立術語標準。若語言不通難合作,解決辦法加入翻譯人員,在專家間搭建語義橋梁。另可在團隊內開展培訓,尤其開發人員了解業務領域知識對專案有大幫助。筆者參與半導體專案,團隊專門邀請專家介紹生產過程業務邏輯,「磨刀不誤砍柴工」,雖消費培訓時間,但掌握業務規則的開發人員能提升進度,總體節約成本。

加強與客戶溝通:客戶可作為團隊領域專家,XP 現場客戶原則為最佳示例。現實不完美時,聘請或安排專門領域專家加強與客戶溝通顯得重要。專案透過領域專家獲取客戶及時反饋,透過專家了解變更需求,最大程度減少需求誤差。


5.2 業務邏輯層的模式應用

Martin Fowler《企業應用架構模式》對領域層架構模式作整體概括,將業務邏輯設計分三種主要模式:

  • Transaction Script:將業務邏輯視為一個個過程,典型面向過程開發模式。可不需數據訪問層,直接用 SQL 訪問資料庫。為有效管理 SQL,可將資料庫訪問行為放入專門 Gateway 類。無需太多 OO 知識,簡單直接為該模式全部價值。業務邏輯相對簡單專案多採用。
  • Domain Model:典型面向對象設計思想體現。充分考慮業務邏輯複雜多變,引入 Strategy 等設計模式,透過建立領域對象與抽象介面實現可擴展性,利用繼承、封裝、多態處理複雜業務邏輯。唯一制約為對象與關係資料庫映射,可引入 ORM 工具或用 Data Mapper 模式完成映射。
  • Table Module:與 Domain Model 相似,同具 OO 設計思想,唯一不同在於獲得對象非單純領域對象而是 DataSet 對象。Domain Model 為資料表每條記錄建立領域對象,Table Module 將整個資料表視為完整對象。雖用 DataSet 會失去 OO 基本特性,但在為表示層提供數據源支援方面有得天獨厚優勢,特別在 .NET 平台下,ADO.NET 與 Web 控件為 Table Module 提供肥沃土壤。

5.3 PetShop 的業務邏輯層設計

PetShop BLL 設計引入 Domain Model 模式,與數據訪問層對數據對象支援不可分。因 PetShop 未深入寵物網店業務邏輯,省略許多複雜商務細節,故 Domain Model 應用不明顯。最典型為對 Order 領域對象處理,引入 Strategy 模式完成插入訂單行為封裝(第 27 章詳述,此不贅述)。

本應是架構設計最核心的 BLL,因簡化業務流程緣故,設計略顯乏善可陳。雖定義了針對 B2C 業務的領域對象,但僅完成對數據訪問層數據對象的簡單封裝,目的僅在於分離層次支援多資料庫擴展,同時將 SQL 排除在 BLL 外,避免 SQL 四處蔓延。

除訂單管理外,最能體現業務邏輯的為購物車與 Wish List 管理。PetShop.BLL 定義 Cart 類負責相關業務邏輯:

[Serializable]
public class Cart
{
    private Dictionary<string, CartItemInfo> cartItems = new Dictionary<string, CartItemInfo>();
    public decimal Total
    {
        get
        {
            decimal total = 0;
            foreach (CartItemInfo item in cartItems.Values)
                total += item.Price * item.Quantity;
            return total;
        }
    }
    public void SetQuantity(string itemId, int qty) { cartItems[itemId].Quantity = qty; }
    public int Count { get { return cartItems.Count; } }
    public void Add(string itemId)
    {
        CartItemInfo cartItem;
        if (!cartItems.TryGetValue(itemId, out cartItem))
        {
            Item item = new Item();
            ItemInfo data = item.GetItem(itemId);
            if (data != null)
            {
                CartItemInfo newItem = new CartItemInfo(itemId, data.ProductName, 1, (decimal)data.Price, data.Name, data.CategoryId, data.ProductId);
                cartItems.Add(itemId, newItem);
            }
        }
        else cartItem.Quantity++;
    }
    // 其他方法略
}

Cart 透過 Dictionary 儲存購物車內容,定義 Add、Remove、Clear 等方法管理內容。

領域對象封裝數據訪問層數據對象,在架構設計中扮演舉足輕重角色。以 Cart.Add() 為例,內部引入 PetShop.BLL.Item 領域對象並調用 GetItem()。若無 BLL 封裝 Item 而直接調用 DAL Item 數據對象,為保證層間弱依賴,需調用工廠對象工廠方法創建 PetShop.IDAL.IItem 介面類型對象。一旦 DAL Item 被多次調用,將造成重複代碼,不利修改擴展,導致結構臃腫。此外,領域對象封裝 DAL 數據對象,也利於表示層調用 BLL。三層架構中表示層應對 DAL「無知」,減少層間依賴,避免「循環依賴」。

Cart.Total 屬性值得商榷。其值獲取方式為遍歷購物車集合累加價格與數量乘積,顯然簡化業務邏輯,未充分考慮需求擴展。實際上此算法僅為眾多策略之一,應考慮折扣情況(如總價超過 100 元給予折扣,與促銷計劃相關),或贈品策略。故需引入 Strategy 模式,定義 IOnSaleStrategy 介面:

public interface IOnSaleStrategy
{
    decimal CalculateTotalPrice(Dictionary<string, CartItemInfo> cartItems);
}

private IOnSaleStrategy m_onSale;
public Cart(IOnSaleStrategy onSale) { m_onSale = onSale; }

public decimal Total { get { return m_onSale.CalculateTotalPrice(cartItems); } }

如此 Cart 可有效支援網站促銷計劃,符合開閉原則,亦為 Domain Model 體現。修改後設計:

引入 Strategy 模式圖

作為 B2C 電子商務架構,涉及業務領域為多數設計師熟悉,故本例中與領域專家合作顯得不那麼重要。但開發成功電子商務網站,領域專家參與仍不可或缺。以訂單管理為例,考慮複雜商業應用需管理訂單追蹤、網上銀行合作、賬戶安全、庫存管理、物流管理、客戶關係管理(CRM),涵蓋電子商務、銀行、物流、客戶關係學等多領域,無領域專家參與,BLL 設計或許會「敗走麥城」。


5.4 與數據訪問層的通信

BLL 需與 DAL 通信利用其訪問資料庫,故存在依賴關係。在 DAL 引入介面組件與數據工廠設計前提下,能做到弱依賴。從 BLL 引用組件可見,BLL 模組未引用 SQLServerDAL 與 OracleDAL 組件。BLL 中調用 DAL 數據對象,均利用多態原理定義抽象介面類型對象,再用工廠對象工廠方法創建具體數據對象。如 PetShop.BLL.Product:

namespace PetShop.BLL
{
    public class Product
    {
        private static readonly IProduct dal = PetShop.DALFactory.DataAccess.CreateProduct();
        public IList<ProductInfo> GetProductsByCategory(string category)
        {
            if (string.IsNullOrEmpty(category)) return new List<ProductInfo>();
            return dal.GetProductsByCategory(category);
        }
        // 其他方法略
    }
}

Product 類利用 DAL 工廠類創建 PetShop.IDAL.IProduct 類型實例,解除對具體組件依賴。只要 PetShop.IDAL 介面方法不變,即使修改介面具體實現,不影響 BLL 實現。此鬆散弱耦合關係最大程度支援架構可擴展。

Product 領域對象實際完成對 DAL Product 數據對象的封裝,暴露介面方法一致。透過封裝使表示層完全脫離資料庫與 DAL,調用者僅需關注 BLL 實現邏輯與領域對象暴露介面調用方式。設計合理、規範各層介面方法,三層架構可由不同開發人員同時開發,有效利用資源、縮短週期。


5.5 面向介面設計

或許因業務邏輯簡單,BLL 設計未秉承 DAL 中面向介面設計思想。除完成插入訂單策略抽象外,整個 BLL 僅以 BLL 模組實現,未為領域對象定義抽象介面。故 PetShop 表示層與 BLL 存在強依賴,BLL 需求變更必然影響表示層實現。唯一欣慰的是,因採用分層架構將 UI 與業務領域邏輯完全分離,若 UI 發生更改(如 B/S 改 C/S),BLL 實現模組可完全重用。

然而最理想方式仍為面向介面設計。根據第 28 章 ASP.NET 快取分析,可將表示層 App_Code 下 Proxy 與 Utility 類劃分到 BLL,修改為實例類,將與業務領域相關方法抽象為介面,建立如 DAL 一樣的抽象工廠,透過「依賴注入」解除與具體領域對象類依賴,使表示層僅依賴 BLL 介面組件與工廠模組。

這樣設計是否有「過度設計」嫌疑?需視業務邏輯需求情況而定。若需引入快取機制為領域對象創建代理類,則為領域對象建立介面顯得必要。可建立專門介面模組 IBLL 定義領域對象介面。以 Product 為例:

public interface IProduct
{
    IList<ProductInfo> GetProductByCategory(string category);
    IList<ProductInfo> GetProductByCategory(string[] keywords);
    ProductInfo GetProduct(string productId);
}

public class Product : IProduct
{
    public IList<ProductInfo> GetProductByCategory(string category) { /* 實現略 */ }
    public IList<ProductInfo> GetProductByCategory(string[] keywords) { /* 實現略 */ }
    public ProductInfo GetProduct(string productId) { /* 實現略 */ }
}

為代理對象建立專門組件 BLLProxy,引用 IBLL 並依賴 BLL。代理對象 ProductDataProxy:

using PetShop.IBLL;
using PetShop.BLL;
namespace PetShop.BLLProxy
{
    public class ProductDataProxy : IProduct
    {
        public IList<ProductInfo> GetProductByCategory(string category)
        {
            Product product = new Product();
            // 其他實現略
        }
        public IList<ProductInfo> GetProductByCategory(string[] keywords) { /* 實現略 */ }
        public ProductInfo GetProduct(string productId) { /* 實現略 */ }
    }
}

此設計為典型 Proxy 模式,類結構:

Proxy 模式圖

參照 DAL 設計方法,為領域對象與代理對象建立抽象工廠,在 web.config 配置相關區段,利用反射技術創建具體對象實例。如此表示層僅依賴 PetShop.IBLL 組件與工廠模組,解除與具體領域對象依賴。修改後表示層與 BLL 關係:

修改後業務邏輯層與表示層關係圖

原有 PetShop 4.0 設計層次關係:

PetShop 4.0 原有表示層與業務邏輯層關係圖

比較兩圖,雖後者模組個數與關係相對簡單,但 Web Component 與 BLL 為強耦合,不利應對業務擴展與需求變更。引入 IBLL 與 BLLFactory 解除與具體 BLL 依賴。此設計對業務邏輯相對複雜系統更符合 OO 設計思想,有利建立可抽取、可替換的「抽屜式」三層架構。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved