写技术文档的难度太大了!数次删改,都没能满意,所以我还决定,先写出来,以后再逐步整理完善——否则可能这个系列都没办法写下去了。这也算是借鉴了敏捷的思路,先写再改,不断迭代重构吧!
前面的几篇博客反响还不错,但还有一个硬伤,“说了这么多理论,能不能实践?”讲类似概念的文章不算多,但也不少了,但我一直没能从中收获太多的东西,反而更是云里雾里的糊涂了。估计这主要是两方面的原因造成的:我智商低,却爱较真!
你说得得天花乱坠,我只信一点,眼见为实,“是骡子是马,牵出来溜溜?”
按照你说的架构,把系统搭起来,跑起来,需求改上个几百上千遍,高并发大流量冲一冲……咦,这样一番折腾下来,没被砸跨,系统千锤百炼之后,还百炼成钢绕指柔。那我才竖起大拇指,真是不错!
我相信,按照DDD、TTD、敏捷开发之类的理念,一定有成功的案例,不然他们不会被站在巅峰的技术大牛们交相称赞。但很遗憾,我这个野生程序员,没机会融入那个圈子。
所以我就用了一个最蛮最笨的方法:我自己做一个系统,严格按照我自己对于这些概念的理解进行开发,看最后这条路能不能走出来?历经五年甚至更多时间的摸索和实践,我觉得我基本上是走出来了。
所以,如果你愿意,就静下心来,听我细细道来吧。
尴尬
在确定了忘记数据库的大原则之后,我们理应从业务层入手开始系统的搭建。
/* 为什么不是从UI层开始?不要笑,我还真记得,有看到过对这种做法的总结和推荐,还有一个什么专有名词,大概就是“页面驱动”之类的。 而且你静下心想一想,我们很多的开发实际上就是这样做的!确定方案之后,美工出效果图,前台切图出静态页面,程序员改成动态的,一页一页的做。 任务考核就大概是这样的,“我们今天把某个页面做完”。 这种做法的好坏利弊我们就不展开了。但如果你一定要一个不从UI层开始的理由,我觉得最有力的就是:我们系统要做三个版本,电脑桌面页面、手机页面和手机APP。 */
业务层里,通常我们就把需求里的一些名词拎出来,做成一个一个的类,以创业家园为例,就应该有一个博客类(Blog),博客里还有方法,比如GetBlog(int Id),或者GetBlogs(int pageIndex, int pageSize),如下所示:
class Blog { string Title { get; set; } string Body { get; set; } Blog Get(int Id) { return new Blog(); } IList<Blog> GetBlogs(int pageIndex, int pageSize) { return new List<Blog>() { }; } }
这是我最开始接触三层架构时业务层类的样子,写在书上的。
但我就感觉这种做法特别别扭!一个博客对象取出10篇博客,一辆汽车具有提供十辆汽车的能力。这都是些什么乱七八糟的东西?不通啊……
我曾经想过将所有的Get()方法设置成静态的,这样从逻辑上说稍微通畅一点:通过博客类可以获取一些博客实例。但还是不爽,类的静态方法就丧失了对象的继承多态等特性。比如,取10篇文章,和取10篇博客就无法重用。
后来我才慢慢明白了,这种做法其实还是来自于“数据库驱动”的思想。Blog类其实代表的是数据库中Blog表,一个Blog实例就代表着一行数据,然后通过该表取到一些行,这些行又被封装成Blog类(细究起来还是很乱,是吧?)。估计当初微软DataSet的流行加剧了这一现象,当然DataSet本身没有问题,它的逻辑是自洽的;然而有很多开发人员不认可DataSet,说它性能低,要用DataReader,自己“封装”,结果不知怎么的,就搞成了上面那种样式的“四不像”。
Entity
上述传统的业务层架构,除了逻辑上的混乱以外,还有一个很大的问题:难以测试!和数据库搅在一起,怎么测试?我是头都大了。我得去做一个小型数据库啊?而且这个数据库还得insert/update之类 的,测试的基准数据就会变,所以每一次单元测试都得tear down(回到基准测试环境),这个又怎么搞?
//当然,后来我还是找到了混合数据库的测试方法,但我很高兴当时我对数据库的测试完全绝望的状态。因为这促成了我的“忘记数据库”的构想和实践
所以我就在想,能不能把数据库的操作隔离出来?这个时候,我应该是已经开始接触ORM了,他们的操作方式给了我启迪:关系数据库的“增删改查”中“改”没了。改(update)被“异化”成:取出(Load) -> 修改 -> 再存储(Savae)的过程(可参考《忘记数据库》中的例子)。所以,我们是不是就可以首先把“改”独立出来?通过不断的演化,我最后形成了一个Entity的project,负责且仅负责对象状态的改变,而完全不涉及对象的加载存储等功能。
这样做最大的好处,就是解决了Entity的单元测试的问题。由于(至少是暂时)不再需要考虑这些对象和存储问题,那么在测试的时候,我需要一个对象,只需要直接new一个就行了,而不是从数据库里取,这多方便啊!
Query(Repository)
那么,对象的增删查怎么办?从技术层面来讲,我们只能依靠ORM工具了,我用的是NHibernate。简单的说,通过NHibernate,我们可以在对象和数据库结构中建立关系(映射)。然后,可以通过NHibernate的session,调用session.Save(), session.Delete(), session.Load()和session.Query()等方法将对象存储、删除或者加载/检索到内存(C#项目)中使用。