業務層——把焦點從數據移到任務

把焦點從數據移到任務

在這裏插入圖片描述
這幅圖展示了產出模型的忠實程度與被觀察的流程之間的關係。毫無疑問,領域驅動設計改善了忠實程度。DDD的出現也開啓了從以數據爲中心的設計向基於任務的設計轉變的過程。這種自然而然的轉變本質上受到在某種程度上處理複雜性的需要的驅使。

向基於任務的世界前進是“說,別問”原則的終極啓示.。任務是每個用例以及用戶和系統之間的每個交互背後的工作流。從設計的角度來看,包含介於表現和數據之間的一切的這一層是一個相對臃腫的層。就這點而言,把任務編排隔離到一個單獨的層可以還領域邏輯代碼一片淨土。領域邏輯更適合放入領域對象,如果使用貧血設計的話,就放入單獨的模塊。

ASP .NET MVC裏的任務編排

在ASP .NET MVC應用程序裏,任何用戶界面操作最終都會轉化成控制器的類上調用的方法。應避免把所有編排邏輯放入Button1_Click事件處理器,同樣地,也應該避免把所有編排邏輯放入控制器方法。

把控制器看作協調者

責任驅動設計(Responsibility-Driven Design,RDD)。
RDD的本質是把系統特性分解成系統必須執行的多個操作。接着,每個操作對應正在設計中的一個組件(通常是一個類)。執行這個操作就變成這個組件的專門責任了。這個組件的角色取決於它所承擔的責任。

RDD定義了一套原型(stereotype),用來分類每個組件的角色。其中一個RDD原型(協調者)非常貼切地描述了ASP.NETWC控制器的理想原型。RDD的協調者原型建議你把構成操作實現的所有步驟組合起來放入單個應用程序服務。然後,從控制器方法裏調用應用程序服務,並把它的輸出傳給視圖模型對象。如下:

public ActionResult PlaceOrder(OrderInputModel orderInfo)
{
    // OrderInputModel - 訂單輸入數據已映射到OrderInputModel中
    // 執行任務調用應用程序服務,從應用程序層獲取視圖模型
    // 對Orderservice類的調用正是穿越表現層和應用程序層之間的邊界的例子
    var service = new OrderService();
    var model = service.PlaceOrder();
	// 適配新的視圖模型
	var newFrontendModel = someAdapter.NewViewModeI(response);
    // 調用下一個視圖
    return View(model);
}

連接應用程序層與表現層——MVC自帶

應用程序層和表現層之間的交接點是控制器。使用Microsoft Unity實現控制反轉(IOC)模式。
在global.asax裏,在應用程序啓動時註冊一個自定義的控制器工廠:

var factory = new UnityControllerFactory();
ControllerBui1der.Current.SetControllerFactory(factory);

UnityControllerFactory類

public class UnityControllerFactory : DefaultControllerFactory
{
    public static IUnityContainer Container {get; private set;}
    public UnityControllerFactory ()
    {
        Container = new UnityContainer(); // 初始化loc
        Container.LoadConfiguration();    // 從web.config加載詳細配置信息
    }
    protected override IController GetControllerInstance(RequestContext context,Type type)
    {
        if(type = null)
            return null;
        return Container.Resolve(type) as IController;
    }
}

調用

public class HomeController
{
    private IHomeService _service;
    public HomeController(IHomeService service)
    {
        _service = servIce;
    }
}

連接應用程序層與數據訪問層

通過依賴注入連接應用程序層與包含數據訪問代碼的基礎設施層。

public class HomeService
{
    private ISomeEntityRepository _someEntityRepo;
    public HomeService(ISomeEntityRepository repo)
    {
        someEntityRepo = rePO;
    }
}

在領域裏編排任務

一般而言,應用程序的業務邏輯包含任務編排、領域邏輯以及一些附加的東西。這些附加的東西通常是指領域服務。簡而言之,領域服務包含任何無法放入領域實體的邏輯。當遵循領域模型模式的指導原則時,業務邏輯大多數情況下會分散到不同的領域實體。如果某些概念無法以這種方式處理,那麼它們就屬於領域服務的範疇了。

跨實體的領域邏輯

領域服務通常包含的業務邏輯的操作牽涉多個領域實體。大多數情況下,領域服務是複雜的多步驟操作,它們在領域甚至基礎設施層的邊界裏執行。
領域服務的標準例子是OrderProcessor、BestPriceFinder或GoldCustomerEvaluator等服務。(需遵循統一語言,取名應反映現實操作,並且容易被利益相關者和領域專家理解。)
例如,GoldCustomerEvaluator,在一個電子商務系統裏,在用戶下訂單後,可能需要判斷這個新的訂單有沒有改變這個客戶的狀態,因爲它可能己經超過指定的最低銷量。爲了執行這個任務,需要讀取這個客戶的當前銷量,在上面應用某些獎勵系統的邏輯,然後判斷新的狀態。在一個真實的實現裏,這可能需要你讀取Orders表,一些與獎勵有關的視圖,以及執行某些計算。最終,它混合了持久化、訂單、客戶和業務規則。涉及的實體不止一個,而邏輯也是跨實體的。於是,它的最佳容身之所是領域服務。
最後,定義成領域服務的操作大多數情況下都是無狀態的:傳入某些數據,然後獲得某些結果。沒有狀態維護,可能除了某些緩存。

連接字符串在哪?

領域模型裏的實體應該是普通C#對象(Plain-old C# Objects,POCO),並且與持久化無關。換句話說,Invoice類只包含日期、編號、客戶、項目、稅務信息以及支付條款等數據。此外,它可能包含GetEstimatedDateOfPayment方法,這個方法讀取日期和支付條款,計算一下節假日,然後確定發貨單的支付日期。

從存儲讀取Invoice實體的代碼和把它保存到存儲的代碼並沒有和實體本身放在一起。系統的其他組件會處理這點。這些類就是倉儲。它們大多數情況下涉及CRUD功能以及你可能需要的任何附加的持久化邏輯。倉儲是整個系統裏唯一處理連接字符串的地方。

你應該關心物理層嗎?
多個物理層通常並非一個系統裏該有的好特性。MartinFowler曾經說過,分佈式編程的第一軍規是:“不要使用分佈式對象,至少要到完全必要時才使用。"物理層降低整體性能,提高整體複雜性。這兩點都影響了構建和維護應用程序的整體成本。即便如此,某些物理層也是無法避免的。因此,它們在一定程度上是惡,但也是必要的惡。
舉個例子,在用戶的機器上運行的代碼和服務器端代碼之間就存在不可避免的障礙。服務器端代碼和數據庫服務器之間則存在另一個典型的分隔。Web應用程序多數屬於這類。
此外,每個應用程序都有自己的好理由引入多個物理層。一個理由可能是要支持的外部產品需要自己的進程。多個物理層的另一個好理由是對安全性的追求。一個模塊運行在隔離的進程裏更易受到保護,僅限授權調用方訪問。
少數情況下,多個物理層可以用來提高系統的可伸縮性。更準確地說,把系統的某些可以遠程訪問的組件複製到額外的物理層比起沒有這樣做可以獲得更好的可伸縮性。但可伸縮性只是系統的一個方面。通常,對可伸縮性的追求一一也就是在一定壓力下保持性能穩定的需要一一損害了日常性能。
總之,物理層的數量原則上應該儘可能少。添加新的物理層應該在謹慎分析成本與收益之後才能進行,其中,成本通常牽涉增加複雜性,而收益則牽涉安全、可伸縮性以及容錯等方面。當然,你看到過系統集成人員創建瘋狂的三層或者更多的系統(尤其是2000年初),然後很疑惑爲什麼系統採用了正確的架構方案還是那麼慢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章