领域驱动开发模式s

70 篇文章 0 订阅

一、来个实例,立马呈现思路: 

今天我们通过一个“超市收银”业务来作为我们的示例(虽然这个示例看上去不太正常,但是它确表述我们所需要的)。我们将从业务分析到业务建模然后最后的编码来用“面向领域对象”的方式来做我们的项目。

好,我们开始吧!

 

一、业务分析

 

大家都去超市买过东西,对超市收银业务都比较熟悉。什么?你不熟?好吧,那我们找个收银员给大家讲解下(领域专家)。

 

收银员小慧:哦,是这样呢。顾客排队银帐我就收银呢,我要使用收银机呢。收银机就能计算出要收的钱呢,我就扫一下呢,就OK了呢。然后就收银了呢。

 

听了小慧的讲解,我们心中有了业务的概念了。我们这里采用《业务关键字分析法》来找出此业务流程里面的一些关系字:

 

商品

顾客

收银员

收银机

*收银

*选商品

*收银员使用收银机

*收银机扫商品计算金额

 

好了,列出这些“业务关键字”了,我们就可以建我们的对象模型了。

 

二、系统建模

 

上面我们分析出了一些“业务关键字”接下来我们分析这些业务关键字并深入他们的业务。

 

商品对象(Goods)

属性:商品名称(GoodsName)、商品价格(GoodsPrice)

行为:在这里商品对象是没有行为的,我们也可以叫它“值对象”。

 

顾客对象(Customer)

属性:顾客姓名(CustomerName)、顾客选购的商品(Goodss)

行为:选购想买的商品(LikeBuy)、听收银员说要收多少RMB(ListenAmount)

 

收银员对象(Cashier)

属性:收银员姓名(CashierName)

行为:收银(CashierRegister)

 

收银机对象(CashierRegister)

属性:收银机编号(CashRegisterNo)

字段:总金额(_totalAmount)

行为:收银(CashRegisters)、显示收银总额(ShowAmount)

 

 

有木有!有木有?有木有很直观,这也就是面向对象分析的好处,因为对象就是对现实的抽象,我们现实中的事务可以很方便的用对象抽象出来。我们很容易发现,这和用表来描述这些业务模型显然要不方便的多。表还只能描述属性,造成了属性与行为的分离。

 

三、代码示例

 

商品对象

 

复制代码
///   <summary>
    
///  商品
    
///   </summary>
     public   class  Goods
    {
        
///   <summary>
        
///  对象标识
        
///   </summary>
         public  Guid OKey {  get set ; }
        
///   <summary>
        
///  商品名称
        
///   </summary>
         public   string  GoodsName {  get set ; }
        
///   <summary>
        
///  商品价格
        
///   </summary>
         public   decimal  GoodsPrice {  get set ; }
    }
复制代码

 

顾客对象

 

复制代码
///   <summary>
    
///  顾客
    
///   </summary>
     public   class  Customer
    {
        
///   <summary>
        
///  对象标识
        
///   </summary>
         public  Guid OKey {  get set ; }
        
///   <summary>
        
///  顾客姓名
        
///   </summary>
         public   string  CustomerName {  get set ; }

        
private  List < Goods >  _goodss  =   new  List < Goods > ();
        
///   <summary>
        
///  顾客购买的商品
        
///   </summary>
         public  List < Goods >  Goodss
        {
            
get  {  return  _goodss; }
            
set  { _goodss  =  value; }
        }

        
///   <summary>
        
///  顾客选购商品
        
///   </summary>
        
///   <param name="goods"> 商品 </param>
         public   void  LikeBuy(Goods goods)
        {
            
this ._goodss.Add(goods);
        }

        
///   <summary>
        
///  听收银员应收多少钱
        
///   </summary>
        
///   <param name="amount"></param>
         public   void  ListenAmount( decimal  amount)
        {
            Console.WriteLine(
" 我是[{0}],我买了{1}件商品。我共花了{2}元RMB。 " this .CustomerName,  this .Goodss.Count, amount.ToString( " f2 " ));
        }
复制代码

 

收银员对象

 

复制代码
///   <summary>
    
///  收银员
    
///   </summary>
     public   class  Cashier
    {
        
///   <summary>
        
///  对象标识
        
///   </summary>
         public  Guid OKey {  get set ; }
        
///   <summary>
        
///  收银员姓名
        
///   </summary>
         public   string  CashierName {  get set ; }

        
///   <summary>
        
///  收银
        
///   </summary>
        
///   <param name="customer"> 顾客 </param>
         public   void  CashRegister(Customer customer)
        {
            
// 打开使用收银机
            CashRegister cashRegister  =   new  CashRegister();

            
// 对顾客的商品进行收银机扫码,收银
             foreach  (var goods  in  customer.Goodss)
            {
                
// 使用收银机扫商品进行收银
                cashRegister.CashRegisters(goods);
            }

            
// 通知顾客一共收多少钱
            customer.ListenAmount(cashRegister.ShowAmount());
        }
    }
复制代码

 

收银机对象

 

复制代码
///   <summary>
        
///  对象标识
        
///   </summary>
         public  Guid OKey {  get set ; }
        
///   <summary>
        
///  收银机编号
        
///   </summary>
         public   string  CashRegisterNo {  get set ; }

        
///   <summary>
        
///  总价格
        
///   </summary>
         private   decimal  _totalAmount {  get set ; }

        
public  CashRegister()
        {
            
// 收银总额置0
             this ._totalAmount  =   0 ;
        }

        
///   <summary>
        
///  收银
        
///   </summary>
        
///   <param name="goods"> 商品 </param>
         public   void  CashRegisters(Goods goods)
        {
            
this ._totalAmount  +=  goods.GoodsPrice;
        }

        
///   <summary>
        
///  显示收银总额
        
///   </summary>
        
///   <returns></returns>
         public   decimal  ShowAmount()
        {
            
return   this ._totalAmount;
        }
复制代码

 

模拟业务流程

 

复制代码
// 我们创建几样商品
            Goods RedWine  =   new  Goods() { GoodsName  =   " 红酒 " , GoodsPrice  =   1800 ,OKey = Guid.NewGuid() };
            Goods Condoms 
=   new  Goods() { GoodsName  =   " 安全套 " , GoodsPrice  =   35 ,OKey = Guid.NewGuid() };

            
// 我们创建几位顾客
            Customer Chunge  =   new  Customer() { CustomerName  =   " 春哥 " , OKey  =  Guid.NewGuid() };
            Customer Beianqi 
=   new  Customer() { CustomerName  =   " 贝安琪 " , OKey  =  Guid.NewGuid() };

            
// 当然,我们需要收银员啊
            Cashier CashierMM  =   new  Cashier() { CashierName  =   " 收银员MM " , OKey  =  Guid.NewGuid() };

            
// 顾客逛了一圈,选了自己想要的商品
            Chunge.LikeBuy(RedWine);
            Beianqi.LikeBuy(RedWine);
            Beianqi.LikeBuy(Condoms);

            
// 顾客开始排队结帐了
            Queue < Customer >  customerQueue  =   new  Queue < Customer > ();
            customerQueue.Enqueue(Chunge);
            customerQueue.Enqueue(Beianqi);

            
// 队伍过来,按先后顺序挨个收银喽
             foreach  (var customer  in  customerQueue)
            {
                
// 收银
                CashierMM.CashRegister(customer);
            }
复制代码

 

显示结果

 

 

上面的例子虽然不是很恰当,但是它也很好的像我们表达出了领域驱动分析问题、面向对象驱动开发的好处了。

 

最后大家回想一下,用数据库表驱动的方式。分析这个业务会是什么样子的。。。。



二、开发过程分析


 一、分析业务需求。

    超市管理系统包括收银管理,商品管理,设备管理,雇员管理,客户管理等几部分,其中收银管理包括收银员管理,收银机管理,收银台管理;商品管理包括商品基本信息管理,商品存货信息管理;设备管理、雇员管理和客户管理都是辅助的,比较简单,系统的核心还是“收银过程”,注意是“过程”而不是“管理”,说到管理很容易落入“管理系统”的思路,说“过程”更容易跟业务场景,业务用例,业务流程等结合起来。

    收银业务场景:

    顾客选购商品之后,来到收银台,收银员检查扫描商品,收银机显示商品的价格清单,收银员通知客户货物总价格,客户确认,付款,完成收银。

   收银业务用例:

   这里不严格区分“场景”和“用例”,我觉得“用例”是更加技术化的词汇。在该用例中,有一些角色,如顾客,收银员,收银机,还有一些对象,如商品,购物车,价格单,有一些活动,如扫描商品(读条码),计算价格,付款。

   收银业务流程:

   大家都去过超市,流程简单来说很简单,但专业来说还是很复杂,这里我不班门弄斧了。

 

    二、设计领域对象模型

    在《领域对象驱动开发:来吧,让我们从对象开始吧》一文中,作者已经给出了领域对象模型,这里也不在重复,不过我设计的模型与原作者有点细微差别,这个以后再说。

 

    有了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,设计实体类,最后才开始开发用户界面。

    作为这个系列的开篇,先和大家探讨一下领域驱动开发模式与传统表驱动开发模式的不一样之处,这里写的是我的一点感悟,由于是理论性质的,所以将“超市管理系统”的实例放到下篇讲解。




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值