借书场景领域建模浅析

关于借书场景的领域建模,我从以下几个方面进行分析:

分析模型静态结构

我分析一个领域模型的静态结构的思路一般是:先找出我们需要关心的对象,对于借书这个场景,我们关心的有:
1. Account(账号):Id(账号唯一标识,自动生成), Number(卡号), Owner(账号当前拥有者用户信息), BorrowedBooks(账号当前借到的书)
2. Book(书本):Id(唯一标识,自动生成),BookInfo(值对象,包含书本基本信息),Count(表示当前库存数量)
3. BorrowHistory(借书历史、借书日志):AccountId(借书账号),BookId(书本Id),Count(数量,表示借了几本),BorrowTime(借书时间)
4. BorrowedBook(借到的书):BookId(书本Id),Count(书本数量)
通过上面的分析,那么模型的静态结构就很容易画出来了。

按面向过程的思维实现逻辑

这种分析思路是最容易的,因为我们不用考虑对象之间如何交互,我们只需要考虑场景结束后,每个对象会发生什么变化即可。所以,按照这个思路,我们得出借书这个场景发生后有以下几个对象会发生变化:

  1. Book的Count属性会变化(减少,因为书本被借出);
  2. Account的BorrowedBooks属性会增多(因为借到书);
  3. BorrowHistory会被创建,因为发生了一次借书的操作;

上面这3步在经典DDD中,我们通常会设计一个领域服务来完成,比如叫BorrowBookService

可以发现,其实面向过程的分析思路是一种面向结果的分析方法;我们只需要考虑一个交互过程的结果改变了哪些对象的什么状态即可,而对象之间到底如何交互的我们不用显式的建模出来;所以这种建模方法相对简单,因为我们考虑的东西比下面第这种方式要少一样东西,那就是对象之间的交互。

按面向对象的思维实现逻辑

也许有人会说,上面的面向过程的建模思路不是真正的OO,因为对象之间没有交互,对象只是一个data,只是一个对数据的封装而已。
那么,如果要让对象之间体现出交互,那我们该如何分析呢?我觉得最关键的是要把握一点:我们分析的时候要时刻按照“谁通知谁做什么事情,或谁被通知做什么事情”这个思路来分析;

好,那按照这个思路,那么对上面的对象:Account,Book,BorrowedHistory,我们如何来分析呢?

首先假设我们已经设计好了这个软件,然后有一个界面显示在屏幕上,然后用户用它的卡号(account.Number)登陆了系统,然后用户通过查询选择了几本书,然后点击“借书”按钮。整个借书场景就是从这个“借书”按钮开始启动。另外,整个借书场景的参与者信息有:accountId,bookId,count,表示哪个账号对哪本书借了几本。
好,那么,“借书”按钮被点击后,应该有一个对象被激活,先不管该对象是什么,我们只要知道该对象会做一件事情,就是:

var borrower = repository.load<Account>(accountId); //将借书人账号load到内存
var book = repository.load<Book>(bookId); //将书本对象load到内存
borrower.BorrowBook(book, count); //启动账号的借书行为

接下来borrower.BorrowBook方法内部会发生什么呢?想想现实世界怎么发生的就知道了,你去图书馆借书,你肯定告诉管理员说“我要借这几本书”,这句话潜在的意思是,请你把这几本书借给我,谢谢,呵呵。

那就很明白了,应该有一个对象,如图书馆管理员(administrator),他有借出书(LendBook)的职责行为,那么上面的BorrowBook方法内看起来就是如下这样:

borrower.BorrowBook(book, count)
{
    //通知图书馆管理员把指定的书借给我count本,this就是我,呵呵
    administrator.LendBook(this, book, count);

    //管理员把书借出来后,更新账号自己的当前借到的书的信息
    var borrowedBook = _borrowedBooks.SingleOrDefault(x => x.BookId == book.Id);
    if (borrowedBook == null)
    {
        borrowedBooks.Add(new BorrowedBook(book, count));
    }
    else
    {
        borrowedBook.AddBookCount(count);
    }
}

接下来我们可以考虑administrator.LendBook这个行为做了什么?

administrator.LendBook(account, book, count)
{
    //通知书本减少其库存数量
    book.DecreaseCount(count); //方法内部需要检查库存数量是否足够,如果不够需要抛异常;

    //这里应该要记录借书记录了,因为译本书是否被借出的衡量标准是图书馆管理员说了算的,当他
    //用扫描仪对该本书进行了扫描并确认后,就表示该本书确定被借出去了,所以我们可以在这里做
    //创建借书记录的逻辑。
    var borrowHistory = new BorrowHistory(account, book, count, DateTime.Now);
    //下面理想情况下我们不希望在这里保存borrowHistory,但是如果光是new一个BorrowHistory对象出来,
    //是没办法被持久化出来的,必须通过某种方式通知框架保存new出来的这个对象,
    //如果用经典的ddd,那如何保存borrowHistory呢?后面我会谈到一些关于这个的思考。
}

好,上面的分析我想应该很清晰地表达了对象之间如何交互,从而完成整个借书场景。但是,上面提到“借书”按钮被点击后,应该有一个对象被激活。那么这个对象会是什么呢?

我觉得你可以设计一个BorrowBookService领域服务,也可以设计一个BorrowBookContext场景类,它有一个Interaction(交互的意思)方法:

borrowBookContext.Interaction(Guid accountId, Guid bookId, int count)
{
    var borrower = repository.load<Account>(accountId); //将借书人账号load到内存
    var book = repository.load<Book>(bookId); //将书本对象load到内存
    borrower.BorrowBook(book, count); //启动账号的借书行为
}

所以,整个交互的过程就是:

  1. 软件使用者(user)通知系统(system)我要借书;
  2. 系统于是创建一个BorrowBookContext场景对象,并通知该场景对象启动交互过程(Interaction);
  3. borrowBookContext通知仓储(repository)将account,book这两个对象从内存激活,通过load方法实现;
  4. 然后通知借书账号执行其借书行为,其实此时借书账号是扮演了IBorrower角色(即借书人的角色),所以严格来讲,BorrowBook这个行为是属于IBorrower这个角色的;
  5. 然后borrower通知administrator把书借出来;
  6. 然后administrator通知book减少其余额;
  7. 然后administrator创建借书记录,即产生借书日志;

从上可以明显的看出,每一个交互都是对象a通知对象b做什么,这是关键;上面的分析和代码充分体现了“对象交互”,也许这样的代码才是更OO吧,呵呵。

为了大家更好的理解这种OO的方式,我特地写了一个完整的例子。源代码下载地址:http://files.cnblogs.com/netfocus/BookLibraryExample.rar 

最后一些补充

  1. 因为,现实生活中,你去图书馆借书,那执行借出书的那个管理员(administrator)代表的就是图书馆。甚至我们可以这样想,假设现在有一个自动借书机或借书网站,你插入你的借书卡(网站用户登录),然后输入要借的书,然后点击确定,然后书本就自动从借书机里吐出来了,呵呵。如果是网站,那就是会自动邮寄过来(当然还要输入寄送地址,呵呵)。所以,从这个分析可以知道,其实图书馆管理员不重要,它其实代表的是图书馆,而图书馆本质上就是提供借书服务。当然,因为我们上面只考虑的借书的场景,我们有没有想过books这个集合放在哪个对象上比较合适呢?我觉得很显而易见把,那就是图书馆,即library.Books,图书馆维护了所有的书本信息;所以,从整体来看,图书馆也有状态。
  2. 有时我们认为产生借书日志不是核心领域逻辑,因为并不是所有的图书借阅系统都需要记录借书记录,那这样的话,我们可以在应用层(也就是我上面的BorrowBookContext中)生成借书记录;
  3. 虽然按照OO的思路去领域建模出来的结果看起来很舒服,但实际上不是很实用,我个人认为属于中看不中用的设计,呵呵。因为这样的设计虽然做到了对象与对象之间的交互,但实际上当我们在面对并发和数据一致性时,都会引入事务。像上面的分析,我们知道一次借书,至少会影响3个聚合根的修改或新增,那意味着一次事务会跨3个聚合根。一旦引入事务,那在当用户访问量大,并发高的情况下,系统可用性是很差的;所以,国外DDD专家才推荐,一次事务只更新一个聚合根,那如果要遵守这样的规定,那如何实现上面的一次要修改3个聚合根的需求呢?呵呵,为了解决这个问题,我们需要通过saga了,就是类似于一个流程管理器的东西。引入saga,相当于实现了聚合根与聚合根之间的异步通信,而不是直接调用聚合根的方法通知其做事情;上面的设计的最大问题就是都是某个聚合根直接调用另一个聚合根的方法通知其做事情。实际上每个聚合根都自己内部维护了其一致性,聚合根之间完全可以通过异步的方式实现交互。saga就是用来实现聚合根之间异步交互的一种技术。saga就是将方法调用修改为:publish-subscribe,以及command的模式,呵呵。学习Saga的一个例子可以看看这篇文章:http://msdn.microsoft.com/en-us/library/jj591569.aspx
时间: 2024-11-16 06:07:20

借书场景领域建模浅析的相关文章

jsp-JSP借书系统借完书后用什么方法让剩余图书数量减掉借阅的数量

问题描述 JSP借书系统借完书后用什么方法让剩余图书数量减掉借阅的数量 import java.sql.Connection; import java.sql.Statement; import java.text.SimpleDateFormat; import java.util.Date; import com.core.DBUtile.DBConnection; public class jieshuadd { private String username ; private Stri

c#-C#图书管理系统的课程设计怎么做?借书和还书的功能怎么编代码

问题描述 C#图书管理系统的课程设计怎么做?借书和还书的功能怎么编代码 C#图书管理系统的课程设计怎么做?借书和还书的功能怎么编代码 解决方案 课程设计怎么做,听我和你细细说. 这种玩意纯扯淡,千万不要当了真. 老师当年就不会,所以才来把书教. 胡乱抄来个课题,答案google有很多. 直接根据关键字,打开google来搜索. 下载几个来"参考",如果不行换一个. 知识要点要记牢,如何编译得掌握. 抄袭不忘改名字,修改时间别忘了. 老师学生互相骗,混个及格就行咯. 解决方案二: htt

c语言-关于图书管理系统借书和还书的问题

问题描述 关于图书管理系统借书和还书的问题 想问问各位,用c写的图书馆管理系统里,借书和还书这两个功能怎么实现啊? 解决方案 你用的是链表还是把数据存储在文件里呢? 解决方案二: 可以把建立一个book结构体,在里面有书的编号,书的名字,书的状态,下一本书的指针等等,可以改变书的状态来实现借出和归还. 也可以不用书的状态,如果借出就将他在链表里删除,如果归还就根据编号插入到链表中.

借书类服务网站遭遇发展瓶颈苦寻赢利模式寻出路

[硅谷网讯]相对于低价的电商图书和电子书,借书网站从出生那天起,就注定了只有靠更低价的http://www.aliyun.com/zixun/aggregation/36284.html">营销模式甚至是免费借阅来谋取市场空间. 不过,这样做却给借书网站带来生存的压力,一直没有找到 合适的盈利模式,是它们面临的最大难题. 夹缝中顽强生长 青番茄创始人张丽娟告诉<IT时报>记者,"青番茄的借阅和上门取书送书都是免费的,我们抱着浪漫的初衷,希望大家都能来享受阅读的快乐.但

整理关于牛人们对图书管理系统领域建模的精彩讨论,以此希望大家学习下别人是如何思考的

关于图书管理系统的业务大家都应该比较了解了,主要的核心业务是:用户持图书卡去图书馆借书或还书.下面是他们几个人讨论的最经典内容,我特地整理出来供大家可以集中的观看他们的讨论.其实在我看来更是一种世界观与世界观的碰撞,我想借此表达的思想是:代码不一定要写很多,但是思维方式或者说世界观一定要正确,否则方向错了,就什么都错了. 以下是讨论的详细内容: Jdon007: 1.借书人(Reader)与借书卡(Card)不是镜像,借书人(Reader)是借书卡(Card)的使用者. 2.认证是认证,跟借书卡

从领域、对象、角色、职责、对象交互、场景等方面去分析和设计领域模型(附源码)

好久没有写文章了,最近比较忙,另一方面也是感觉自己在这方面没什么实质性的突破.但是今天终于感觉自己小有所成,有些可以值得和大家分享的东西,并且完成了两个可以表达自己想法的Demo.因此,趁现在有点时间,是写文章和大家分享的时候了. 首先给出这两个Demo的源代码的压缩包的下载地址,因为之前有博友说他没有装VS2010而没办法运行Demo,所以这次我分别用VS2008和VS2010实现了两个版本. http://files.cnblogs.com/netfocus/DCIBasedDDD.rar

分享我的面向对象分析方法

先分享一下我的面向对象分析方法 找出最关键的一些业务场景:一般通过动词来寻找,比如招聘系统中,一个应聘人投递一个职位就是一次应聘,应聘就是一个业务场景:一个学生参加某门课的考试,那么考试就是一个业务场景:一个学生去图书馆借书,那么借书就是一个业务场景: 针对每个业务场景分析出有哪些场景参与者,哪些参与者以对象的形式参与,哪些参与者以服务的形式参与:为什么要区分对象还是服务是因为有时候我们不关心参与者是哪个,而只关心参与者是什么.一般服务在系统中我们只关心它是什么服务,并且在系统中服务一般也只有一

OO系统分析员之路--用例分析系列(6)--用例实现、用例场景和领域模型

上一篇说到我们经过初步的业务分析,得到了用户.业务用例以及业务场景模型.这三项工作成果形成了基本的需求框架,并圈定了业务范围.这时应当做一份基线. 当然,第一份基线所包括的内容是非常粗的,要达到完整的需求说明还有更多工作要做.这一篇就来说说详细的需求过程和产出物,以及这些成果对需求的贡献.在开始之前,还是提醒读者下载实例,本文下面只会从实例中挑选很少一部分来说明,对照实例读者将能更好的理解. 上一篇确定了业务用例,以及业务场景.该场景只描述了业务框架,接下来要对业务用例进行场景分析.用例场景分析

书山有径——走进清华大学图书馆

在你的印象中,图书馆应该是什么样子的?很多人的脑海中还闪现着书架.借书.还书等这样的传统场景.实际上,随着信息技术的发展,互联网成为知识传播的一个重要载体,图书馆的服务远不止传统的借还书业务,而是充分利用网络信息环境开启了全新的知识服务模式. 在一个春光明媚的下午,我们来到了拥有百年历史的清华大学图书馆,深入了解当前图书馆发展的最新步伐. 图书馆迈入"数字化"时代 清华大学图书馆的历史可谓悠久.1911年清华学堂成立,并于1912年改为清华学校,随之建立了清华学校图书室,逐步发展为现今