1. 程式人生 > 其它 >“領域驅動開發”例項之旅(1)--不一樣的開發模式      一、分析業務需求。    二、設計領域物件模型    三、測試領域物件模型    四、設計業務處理類    五、設計Entity和Vi

“領域驅動開發”例項之旅(1)--不一樣的開發模式      一、分析業務需求。    二、設計領域物件模型    三、測試領域物件模型    四、設計業務處理類    五、設計Entity和Vi

    聽說DDD-“領域驅動開發”已經很久了,園子裡面已經有不少大牛寫過博文介紹,但我一直沒有嘗試過,直到今年公司的一個專案出現數據庫移植,原來的業務邏輯都寫在SqlServer的儲存過程中,現在要移植到PostgreSQL中,才真切的體會到,再繼續走“表驅動開發”的模式,沒有好前途了。於是,花了幾個星期,來實踐一下領域驅動開發這種開發模式。

     徵得《領域物件驅動開發:來吧,讓我們從物件開始吧》原文作者的同意,我選擇文中的“超市收銀”業務場景,開發了一個“超市管理系統”--PDF.NET SuperMarket MIS,這裡是下載地址。本系列將會講解這個開發過程的有關技術細節,但作為這個系列的開篇,還是先說說領域驅動開發這個開發模式給我的不一樣感覺的地方。

領域驅動開發模式

     一、分析業務需求。

    超市管理系統包括收銀管理,商品管理,裝置管理,僱員管理,客戶管理等幾部分,其中收銀管理包括收銀員管理,收銀機管理,收銀臺管理;商品管理包括商品基本資訊管理,商品存貨資訊管理;裝置管理、僱員管理和客戶管理都是輔助的,比較簡單,系統的核心還是“收銀過程”,注意是“過程”而不是“管理”,說到管理很容易落入“管理系統”的思路,說“過程”更容易跟業務場景,業務用例,業務流程等結合起來。

    收銀業務場景:

    顧客選購商品之後,來到收銀臺,收銀員檢查掃描商品,收銀機顯示商品的價格清單,收銀員通知客戶貨物總價格,客戶確認,付款,完成收銀。

   收銀業務用例:

   這裡不嚴格區分“場景”和“用例”,我覺得“用例”是更加技術化的詞彙。在該用例中,有一些角色,如顧客,收銀員,收銀機,還有一些物件,如商品,購物車,價格單,有一些活動,如掃描商品(讀條碼),計算價格,付款。

   收銀業務流程:

   大家都去過超市,流程簡單來說很簡單,但專業來說還是很複雜,這裡我不班門弄斧了。

    二、設計領域物件模型

    在《領域物件驅動開發:來吧,讓我們從物件開始吧》一文中,作者已經給出了領域物件模型,這裡也不在重複,不過我設計的模型與原作者有點細微差別,這個以後再說。

    有了DomainModel,在系統進入全面開發之前,就可以測試DomainModel,從而驗證系統的核心邏輯設計是否合理。

    三、測試領域物件模型

    為什麼要這一步?因為我們經過前面的業務分析之後,得到了我們的領域物件模型,但我們的理解是否正確呢?為了驗證我們的理解是否正確,需要對第二步中的模型進行測試,看它是否正確,是否合理。

static void TestModel()
        {
            //http://www.cnblogs.com/assion/archive/2011/05/13/2045253.html
            //我們建立幾樣商品
            GoodsStock RedWine = new GoodsStock() { GoodsName = "紅酒", GoodsPrice = 1800, GoodsNumber = 10 };
            GoodsStock Condoms = new GoodsStock() { GoodsName = "安全套", GoodsPrice = 35, GoodsNumber = 10 };
            //我們建立幾位顧客
            Customer Chunge = new Customer() { CustomerName = "春哥" };
            Customer Beianqi = new Customer() { CustomerName = "貝安琪" };
            Customer Noname = new Customer();
            //有一臺收銀機
            CashierRegisterMachines crManchines = new CashierRegisterMachines() { CashRegisterNo = "CR00011" };
            //當然,我們需要收銀員啊
            Cashier CashierMM = new Cashier(crManchines) { CashierName = "收銀員MM", WorkNumber = "SYY10011" };
           
            //顧客開始排隊結帳了
            Queue<Customer> customerQueue = new Queue<Customer>();
            customerQueue.Enqueue(Chunge);
            customerQueue.Enqueue(Beianqi);
            customerQueue.Enqueue(Noname);
            //隊伍過來,按先後順序挨個收銀嘍
            foreach (var customer in customerQueue)
            {
                //收銀
                CashierMM.CashRegister(customer);
            }
        }

    四、設計業務處理類

    我們在第三步編寫領域物件模型的測試案例的時候,實際上就是針對業務場景的測試,這個處理業務場景的程式碼差不多就是“業務處理類”--BIZ class,我們把它提取出來,在完善下,就得到真正的業務處理類了。這個業務處理類後續還會一直完善的,但這裡已經基本成型了。

    五、設計Entity和ViewModel

    在完善業務處理類的時候,我們需要分析哪些領域物件的屬性需要持久化,注意不要單個的去分析領域物件,而要根據整個領域物件模型去分析,比如可能有兩個領域物件會使用一個持久化屬性的,這個時候我們應該考慮將這個屬性放到一個實體物件中,這樣我們就得到了系統需要的實體類(Entity);分析哪些領域物件的屬性可能是需要給使用者介面(View)使用的,同樣的原因,可能會組合多個領域物件的屬性給一個使用者介面,這樣我們就得到了ViewModel。

    說簡單點,Entity 和ViewModel 都依賴於 BIZ class ,BIZ class排程DomainModel,使用或產生Entity和ViewModel。

    BIZ就像業務用例,它組合MomainModel的呼叫,我這裡這個Biz也許更像Service。系統只有Entity會和資料庫打交到。

Entity

ViewModel

      六、測試業務處理類

    我們已經在第三步中測試了領域物件模型,當時的資料都是模擬的,沒有使用資料庫,現在我們編寫一些測試案例來進行真正的測試了。測試案例可以使用VS自帶的單元測試來做,也可以編寫專門的測試專案,或者直接編寫簡單的測試頁面。由於“領域物件模型”已經測試過,所以這一步的測試我們的業務操作類是否能夠正確的管理領域物件,能夠生成ViewModel等。

static void TestBIZ()
        {
            //我們建立幾樣商品
            GoodsStock RedWine = new GoodsStock() { GoodsName = "紅酒", GoodsPrice = 1800, GoodsNumber = 10, SerialNumber ="J000111" };
            GoodsStock Condoms = new GoodsStock() { GoodsName = "安全套", GoodsPrice = 35, GoodsNumber = 10, SerialNumber ="T213000" };
            //我們建立幾位顧客
            Customer Chunge = new Customer() { CustomerName = "春哥" };
            Customer Beianqi = new Customer() { CustomerName = "貝安琪" };
            Customer Noname = new Customer();
            //有一臺收銀機
            CashierRegisterMachines crManchines = new CashierRegisterMachines() { CashRegisterNo = "CR00011" };
            //當然,我們需要收銀員啊
            Cashier CashierMM = new Cashier(crManchines) { CashierName = "收銀員MM", WorkNumber = "SYY10011" };
            //顧客逛了一圈,選了自己想要的商品
            Chunge.LikeBuy(RedWine.TakeOut(1));
            Beianqi.LikeBuy(RedWine.TakeOut(1));
            Beianqi.LikeBuy(Condoms.TakeOut(1));
            Noname.LikeBuy(Condoms.TakeOut(2));
            //呼叫收銀業務類
            CashierRegisterBIZ biz = new CashierRegisterBIZ(CashierMM ,crManchines);
            biz.AddQueue(Chunge);
            biz.AddQueue(Beianqi);
            biz.AddQueue(Noname);
            biz.CashierRegister();

        }

    七、設計表架構 

有了Entity物件,很自然的就可以得到特定資料庫系統的建立表的指令碼了。超市管理系統使用了PDF.NET框架的實體類,實體類的屬性和表的欄位對映關係非常清楚,因而可以直接從實體類得到建立表的指令碼。執行系統的建表指令碼,這樣我們的資料庫就建好了,系統已經可以運行了。

 八、開發使用者介面

    終於等到這一步了,好多時候我都想直接跨過前面7個步驟,先做這一步的,但為了實踐“領域驅動開發”模式,還是堅持了下來。系統使用ASPX頁面作為使用者介面,在這一步中,根據要展現的功能,設計對應的頁面,呼叫BIZ class得到ViewModel,將它繫結到頁面上。如果說M(BIZ class),V(ASPX頁面),VM(ViewModel),這種模式是不是很像傳說中的MVVM呢?

下面是系統的有關使用者介面:

會員登入頁

顧客購物首頁

表驅動開發模式

    這是我以前以及我們公司現有專案,還有很多公司做專案的開發模式,詳細說說我們公司最近一個專案的開發過程吧:

    一、分析需求,製作靜態頁面作為Demo

    需求人員守著美工製作人員,一個個模組,一個個功能的製作好所有的靜態頁面,作為系統的Demo。這個過程很長,需求人員常常會反覆的修改需求,然後讓美工修改介面,指示這些介面反應的系統功能。以下簡稱這些靜態頁面為Demo頁面。

    二、從Demo頁面熟悉系統功能

    開發人員、需求人員、美工製作人員一起來看這些Demo頁面,開發人員提問,需求人員解答,如果開發人員提出質疑和不合理之處,再由美工去修改。當然,功能上開發人員是無權否定和修改的,只是第功能的佈局和展現方式提出意見,方便程式開發實現。這個過程耗費的時間也很長,通常一個個頁面的過,而且開發人員事先已經有了分工,每個人負責一個模組,聽到自己負責的模組的時候,就打起精神來聽,遇到跟自己不相關的模組,也就是濫竽充數而已,在會議室耗時間。

    三、進行表設計

    這個過程有DBA主導,每個模組的負責人和DBA一道,根據Demo頁面上面展現的功能、表單、表單域,來設計這個模組相關的表和表的欄位。這個過程也要耗費較長的時間,主要糾結於該用什麼英文名稱來對應表字段,和太多的表以及表字段的含義、型別。

    四、開發頁面,設計儲存過程

    開發人員按照前期的Demo頁面,用ASP.NET來實現一遍,主要的工作就是寫好多JS程式碼,來動態呼叫後臺資料。而DBA就將資料工作全包了,為開發人員編寫一個個的SQL函式,輸出每個頁面用到資料,為了對接方便,輸出的資料欄位名稱用的是中文;程式的功能,也就是所謂的業務邏輯,就由DBA全部寫在儲存過程中了。DBA專心寫儲存過程,使用SqlServer 2008上的那些最酷的特性;開發人員專心做ASPX頁面,BLL層只是一個傳聲筒,DAL層已經由PDF.NET的程式碼生成器自動生成了,不用開發人員操心,只是問問DBA這個功能的SQL該怎麼寫而已。DBA樂得專心,開發人員樂得簡單(雖然是體力活),這樣大家都HAPPY 。

    五、測試

    等開發人員開發萬所有的模組,DBA寫完了所有的儲存過程,測試人員終於上陣了。一陣測試下來,Bug不少,主要是資料不對,功能實現跟需求有差異,大家又手忙腳亂的開始改Bug了。我們的那個專案開發用了2個月,而測試改Bug花了4個多月啊,有些同事受不了測試人員和需求人員的“輪番攻擊”只好走人了,大家都盼望著快點結束這個專案,太累了。

    專案開發完成了,有人問我們的系統有沒有業務模型,大部分都不置可否,說到“去看資料庫吧”;問開發技能有沒有提高,答案是對JS更熟悉了,其它的就沒有了;再問你們的開發文件怎麼樣,回道“開發文件早就沒用了”,有問題直接去看程式碼吧。

一個專案一個專案就是這麼開發的,年復一年,日復一日,大家的工作就是寫程式碼,改Bug,沒有什麼改變。

 兩種開發模式的區別

    下面,回過頭來看看“領域驅動開發”模式,有什麼不一樣的地方:

  1.     領域驅動注重“領域物件模型”的設計,可以先設計,再測試,最後才開發;
  2.     領域驅動能夠產生系統的核心價值--“領域物件模型”;
  3.     領域驅動使得整個開發過程更容易關注系統的重點功能,使得“有的放矢”;
  4.     領域驅動無需重點關注資料問題,使得系統跨資料庫移植非常容易;
  5.     領域驅動更關注“業務”,而不是“資料本身”,適合業務非常複雜的場景;
  6. 領域驅動更關注“業務物件”,從而能夠使用各種設計模式,架構模式,使得系統更容易擴充套件和優化。

 關於這點,在我們現有系統中深有體會,由於所有業務邏輯的寫到了儲存過程中,而現在系統執行效率比較低下,在不改變硬體的前提下,想優化的空間都沒有。

    當然,表驅動開發模式併發一無是處,它比較適的情況是:

  1. 開發團隊的整體設計能力欠缺;
  2.     專案的業務不是很複雜,不經常變更業務功能;
  3.     以資料為中心,資料在專案中具有核心價值;
  4.     有很強的DBA團隊

   通常很多專案業務也比較複雜,也不是以資料為中心,也沒有很強的DBA團隊,但仍然選擇“表驅動開發模式”,我想主要原因應該是“ 開發團隊的整體設計能力欠缺”,而專案或產品“設計的好壞”是直接影響專案或產品的“成本”,甚至是“成敗”的。“設計應對變化”,看你是否認同了。

    原來的表驅動開發模式,只會傻傻的根據頁面的DEMO,得知應該有哪些表和欄位,很難分析出中間的複雜業務物件和相關聯的業務流程,做出來的程式每個部分都是嚴重“割裂”的!     領域驅動開發模式,是先分析需求,得到領域模型,然後和業務一起驗證該模型,逐步改善完善模型,第二步是實現業務場景,得到哪些領域物件的屬性是需要持久化的,得到哪些組合的屬性是需要給前端顯示的(ViewModel),第三步才是設計View,使用ViewModel,設計實體類,最後才開始開發使用者介面。

    作為這個系列的開篇,先和大家探討一下領域驅動開發模式與傳統表驅動開發模式的不一樣之處,這裡寫的是我的一點感悟,由於是理論性質的,所以將“超市管理系統”的例項放到下篇講解。