向死而生,李开复的7个死亡学分

向死而生,李开复的7个死亡学分

投递人 itwriter发布于 2015-06-27 19:25评论(8)有1556人阅读原文链接[收藏]« »

  今天下午,纪录片《筑梦者者之李开复忏悔录》举行首映。这部高晓松导演的纪录片是以李开复癌症后的心路历程和人生思考为背景进行拍摄的。片中,李开复分享了自己的思考:脱去虚名与成就,你的人生还剩下什么?

  与此同时,李开复还推出了自己的新书《向死而生——我修的死亡学分》。他在现场演讲中说,死亡共有 7 个学分:健康无价;一切事物都是有它的理由;珍惜缘分,学会感恩和爱;学会如何生活,活在当下;经得住诱惑;人人平等,善待每一个人;我们的人生究竟是为什么?

  以下是李开复今天的演讲实录:

  谢谢各位,非常感谢今天各位嘉宾的到来。让我有这个机会跟大家分享一下我生病的心路路程。平时我工作的时候,非常热爱我的工作,包括今天,我从来没有想到,要面临死亡,面临癌症,我心中想过的每一个思念都和我的工作丝毫无关。有一个很著名的护士看护了很多临终病人,大部分的临终病人最大的遗憾就是没有和自己的家人在一起。

  我们每个人都要临死才会想到这样的事情吗?我相信今天的纪录片和我出的书,能阐述我个人向死而生的过程。向死而生本身的意思,就是人在世俗里面很容易陷入今天的现实世界里面。而面对死亡,我们反而容易得到顿悟,了解生命的意义,让死亡成为生命旅程中无形的好友,温和提醒我们,好好珍惜我们的生命,不是只渡过每一天的日子,也不是只是追求一个现实的名利目标。

  学分1:健康无价

  在我平时的生活中,我热爱美食,不爱睡眠,认为睡眠是浪费时间,每天起来回E-mail,给我员工证明我工业多努力。生病以后,才深深体会到,其实健康失去了,就什么都没有了,生命最重要,健康和生命是一样重要的。

  如果我们要维护自己的健康和生命,很多人会认为说,如果你养生,就没有事业了,什么都不要了,过退休的生活,过慢日子吧。我领悟以后,和几位朋友交流以后,其实真的不是这样的。每一个人的健康,其实不是要放弃一切。我们的健康如果简单来说,其实就是我们的睡眠、压力、运动、饮食。如果这 4 点达到即可,对年轻人来说。你是可以努力工作的,一个礼拜拿三四个小时维护你的健康,我非常希望在这里告诉大家要爱惜自己的健康,不要等到有一天,像我这样几乎后悔,几乎来不及才知道学会要爱惜自己的身体。

  学分2:一切的事物都是有它的理由

  我们往往把发生在自己身体的事情,一定做错了才惩罚我身上。其实不见得如此,世界的玄妙我们只了解里面的千分之一,万分之一,也许每一件事情的发生都有它的理由,我们应该多思考当一件事发生以后,是不是有什么正面的启示或者正面的力量。发生一个灾难,是不是不要把它当成一个果,而是把它当成因 ,如果把它当成因,任何的灾难都是学习的机会。如果我们生病了,是让我们学会生活更健康,也许我们无助的时候,让我们接受无法改变的事情。也许我们面临死亡才能教会我们分辨什么才是人生最重要的事情。

  学分3:珍惜缘分,学会感恩和爱

  一直面对死亡的时候,对于家人对我无私的爱,我当年是多么冷漠。虽然我告诉朋友说,我一放假就陪我们的母亲,但是我们只有 4 周的假。陪母亲 5 天以后,我就认为我的任务完成了。一直到我自己面临死亡的时候,我才知道,我是多么冷漠,我是以多么敷衍的方式表达了人们口中的孝顺。

  我觉得真正改变应该有三个层次。最基本的是别人对你好,你感觉到了这是感恩,再稍微好一点的是别人对你好你要回报他。第三个层次就是主动不要求回报付出关怀,这才是最高的境界。这是我发现的,不知道过去做的什么好事,如果有上辈子,上辈子做了好事,有这样的家庭,我的父母、姐姐、妻子、女儿都是不要求回报的。无论我怎么对待他们,无论我是因为事业把家庭从美国搬来了中国又搬回美国,再搬回中国,又迁回台湾,整个过程对他们是多么煎熬,我只想做好我的事业。

  有一句话,我觉得很有意义,每一次的相遇都是久别重逢,能和亲人在一起,他们能这样对我们,这不是猿猴演变出来的人类,就因为被教导,孔子说的亲情,我觉得这样的缘份,真的是久别之后的重逢,我们应该珍惜人生中的缘份和爱护。

  所以我生病以后我就决定说,我要改变我的方式,每一周不但要陪我的妈妈,还要陪我的姐姐。我到了台湾,花更多的时间和我爱人在一起。我女儿要考大学,我帮她做各种准备。后来有一天她弄了一个刺青,刺了一个 try,这就是我没有付出足够教育方式的现象。然后她学业上升了也被大学录取了,然后又把 try 变成了 stay gold,我认为我自己是发光的黄金。也许我对父母的爱可能很难直接给他们回报,但是至少对妻子女儿,过去的 17 个月,我做的一些事情,也学会如何感恩,如何爱,如何直接表达。

  父亲节的时候,我发了一条微博,是我女儿亲我的照片,我鼓励更多的孩子亲他们的父亲。我看他们的留言,很多女孩子说这么大了,怎么好意思,父亲很威严。爱不是藏在心里的,是应该表达出来的,如果没有表达,以后没有机会的话会很后悔。

  学分4:学会如何生活,活在当下

  我的癌症是淋巴癌四期,我认为我的生命并不长了。当时我也想到,如果我的生命真的只有 100 天了,我会怎么样渡过这个时间?我的结论和看护临终病人的护士是非常相似的。我的结论是说,我要让我的亲人知道我如何爱他们,我和他们在一起渡过特别难忘的时光,无论是和妻子去我们蜜月的地方,或者和孩子去一个我们过去特别快乐的地方,怀念回忆过去的美好,去吃我们爱吃的东西,做我们爱做的事情,这才是活。我希望活的时候,能全心全意每一刻活着,不只是脑子不停想我的公司,想我的事情。开始看世界其实是充满了美好的东西的。

  如果稍微偶尔慢一下,能活在当下,才能体验到这些美好,才能感觉自己没有白活。我活了 50 多岁,一直没有分清什么是桂花,什么是茉莉花等等,我就知道他们都有香味。有一次我到朋友家这是什么味?我说桂花,什么时候种的?他说种了很长时间了。慢下来的时候,才会感受世界的美好。这是美食,这是我们最爱的酒,最漂亮的衣服,留到哪一天才会穿。我鼓励你们,不要把所有事情都推到以后,别说将来,找机会,还有一天等特殊的日子,我希望我们都活在当下。我们今天为什么不能成为那个特殊的日子呢,让每一天都成为最特殊的一天。我觉得人生如果这样活下去,不仅仅是最后的一百天,而是每一天都这样活下去,一定会非常圆满,丰富。

  学分5:经得住诱惑

  第五个学分经得住诱惑。我们小的时候,我父亲跟我们说,不要爱钱,对财富来讲,越多越好,但是不要贪婪的想得到更多。中国有一个通病,特别爱美。我们看到这么多古时候的皇帝,各方面的慈善家,做的各种事情都让别人知道自己有好。我父亲留给我 10 个字,有容则乃大,无求则更高。人死留名,我们希望做好的事情是对的,希望留名没有任何的必要,除了孔子以外,有哪个人被大家都记住了。我相信我们每一位 50 年以后都没有被别人记下来。

  当你特别纠结自己“名”的时候,或许刻意,或者不刻意,都会让自己追求名成为一种方式。比如说之前我告诉年轻人追求自己的梦想,最大化自己的影响力,做最好的自己。这个话没有错,如果把最大化影响力这个词发挥到极致,每天机械化衡量影响力有没有提升,有没有人听我的演讲,成为我的粉丝。当我人生过去生病前的几年,5-10 年,慢慢越来越越顺,越来越有更多人喜欢把我当成他们的导师,一方面出于善心的帮助年轻人,但是不可避免的,每天的追随希望有更大的影响力。

  听起来是很灰色的地带,影响力是好的吗?要一点名没有关系,我和清云大师讨论这个事情的时候,他告诉我,其实人是禁不住诱惑的。你要影响力的目的就是让世界更好,不断做好事情,不断衡量,我和别人都做好事就够了,为什么算我卖了多少本书,有多少粉丝呢?这样的过程,让我发现,虽然我认为我一直追求的方向和建议并没有错,但是如果特别机械化的追求效率,衡量每一天的结果,会让我们变得更冷漠无情。所以我发现,虽然我走的道路是正确的,但是过度追求名声,让我走偏了。

  学分6:人人平等,善待每一个人

  当你追求每一件事情影响力最大化的时候,你就想认识更多聪明的人。见创业者只见最顶尖的,一个青年人找你签字,如果是普通人,你就不考虑,你会见聪明人,成功人,把自己的一圈都变成社会的顶尖人士。但是我发现,如果真的再继续这么做的话,其实丧失了非常重要的一点就是人人是平等的。当我得了癌症,发的第一条微博,癌症面前人人平等。但是我慢慢觉醒的时候,我发现任何事上,人人都是平等的。世界的奥妙,不允许我们渺小对人类的评估。我们凭什么说这个人是普通人,这个人不怎么样,这个企业不会成功,这个创业者不行。

  既然我们没有权利,也没有能力做评估的话,既然人人都是平等的,只要时间允许,我会秉承这样的理念,让我花更多的时间在网上和一些包括所谓的普通网友交流,每一周见一些想要见我的人,哪怕我们从来不认识,哪怕他们并没有特别光辉的履历。我建议大家,不要吝啬给别人爱的关怀。因为你对任何人,优秀的人,普通的人,都是一样的。你对任何人的微笑,一个行为,都可能帮助别人,帮助生命。

  学分7:我们的人生究竟是为什么?

  我觉得如果我们太狂妄的说,我们来到人生就是为了改变世界,我们懂得这么小,这么渺小,凭什么狂妄,我们世界改变了,是更好,还是更不好,每天做的每一件事都能评估出来吗?我认为我们不必强求把改变世界作为我们的要求。如果每天拼命改变世界,那是充满压力的。

  我认为来到人间,我们有缘认识周围的人,好好体验人生,结交善缘,做事问心无愧,凭良心,做人真诚平等,让自己的每一天都能有学习,成长,其实那就足够了。如果世界上每一个人都这么做,世界就会变得更好。如果过去我的哲学更多的是因为人生只有一次,所以要分秒必争,征求效率做最好的自己。现在我更觉得说,其实生命里很多东西,并没有办法用科学的方法解释,并没有办法每天衡量,比如说人与人之间的缘份。从现在开始,我不再看世界上很多的缺陷,批评他们,我相信每一个平等的生命都是来到这里不断学习,不断成长。人只有有缺陷才能学习成长,我们没有权利过分的批评别人,我们需要做的是怎么让自己成为一个更好更完善的人。

  既然每个人都在持续成长,对于那些曾经伤害我,打击我,或者未来打击我的人,我不但宽恕他们,而且感谢他们,因为他们可能点醒我很多的不足。我相信人生的生命是与大宇宙连在一起的,我们有责任提升自己。我们的生命随着心跳停止也没关系,我们的人生只有一次,死去离开世界,如果这一生是体验学习提升,我相信也会让世界更美好,整个世界的群体意识也会变得更正向。

  我经过这七个教训,我认为我们珍贵的生命旅程,应该保持着初学者的心态,对世界有儿童一般的好奇心,好好体验人生,让每天的自己都比以前有进步有成长,不要想着改变他人,做事问心无愧,多感恩和爱你周围的人。对人真诚、平等,这样就足够了。如果世界上每个人都能如此,世界就会更美好,谢谢。

筑梦者之李开复《向死而生》

筑梦者之李开复《向死而生真情实录版》
[……]

详细内容

深度剖析Byteart Retail案例:仓储(Repository)及其上下文(Repository Context)

深度剖析Byteart Retail案例:仓储(Repository)及其上下文(Repository Context)

在领域驱动设计(DDD)的案例中,仓储及其上下文都是开发人员学习和讨论的重点。对这两个内容的讨论,大致包含两个方面:第一个方面是有关仓储及其上下文在整个应用程序架构中的位置;第二个方面,则是仓储及其上下文的设计与具体技术实现。我将在本文中,结合Byteart Retail案例,对这两个内容进行讨论。

仓储及其上下文在整个应用程序架构中的位置
仓储是DDD中管理对象生命周期的一个重要组件。在面向对象的世界里,不仅仅是DDD,甚至是整个软件设计和开发过程,都离不开对象生命周期的管理:对象的创建、持久化(Persistence)、反持久化(Materialize)以及销毁,每种管理任务都对对象的状态造成影响。在传统的应用程序开发中,我们会使用类似DAO(Data Access Object)的类型来实现对象的持久化、反持久化操作,或者会使用Finder/Mediator来完成类似的任务。在DDD中,同样存在着两种与对象生命周期管理任务相关的组件,它们就是仓储和工厂。与DAO、Finder/Mediator所不同的是,仓储的实现更为限定在对整个聚合的操作上(事实上工厂也是如此),通过对聚合根的引用来完成整个聚合的持久化、反持久化操作;而DAO、Finder/Mediator则随意性更强:它们的设计可以是面向DTO(Data Transfer Object)的,也可以是直接面向数据库的。

刚刚提到,仓储的实现需要限定在对整个聚合的操作上(工厂也是如此),因此,不管是从持久化机制读取对象,还是将对象保存到持久化机制,都需要通过聚合根,以聚合为单位。根据DDD不难理解,聚合是领域模型的重要内容,而在整个应用程序的架构中,领域模型是属于领域层的,于是,仓储也是领域层的一个组成部分。

前不久,有网友向我询问这样的问题:如果说仓储是领域层的一个组成部分,但是仓储的实现往往需要涉及到很多技术层面上的东西,比如如果采用关系型数据库作为对象持久化机制,那么仓储的实现就需要封装类似ORM的功能,这样做岂不是使得领域层需要依赖这些技术的具体实现,从而使得两者之间紧密耦合?

对于这个问题的回答,我想应该从两个方面考虑。首先,领域层和领域模型是两个概念,前者是应用程序架构中的一种分层,而后者则是应用程序的业务核心组件。领域模型定义在领域层中,领域层中还能包含诸如仓储、工厂、服务等组件,一方面辅助领域模型完成完整的业务处理需求,另一方面为领域模型提供生命周期管理。因此,即使领域层耦合了其它基础结构组件,它也能通过合理的模式应用,将这些实现细节从领域模型中剥离开来,以保证领域模型的纯净度;其次,即使可以解除领域模型与基础结构组件的耦合关联,我们也不应该使领域层也直接依赖这些组件,否则,我们得到的后果是,当基础结构组件发生改变时,整个领域层组件将变得不再可用,我们不得不对领域层也进行重构,以适应新的接口需求。

综上所述,一方面,仓储的操作对象是领域模型中的聚合,无论是从DDD的实践思路上,还是从仓储与领域模型之间的关系上考虑,仓储都应该属于领域层,然而与领域模型不同,仓储需要通过基础结构组件的支持来提供服务,因此仓储又将依赖于这些组件。这就使得开发人员在设计应用程序架构的时候,对于仓储的部分具体应该如何设计产生了疑惑。

合理的做法是,将仓储的接口定义和具体实现分开处理,仓储接口定义在领域层,而仓储的具体实现则划分到领域层之外(注意,这里可以理解为将仓储的具体实现划分到基础结构层,也可以理解为架构的一种外部插件的实现)。具体到.NET应用程序架构,仓储接口定义在领域层的程序集中,仓储的具体实现则同时引用领域层程序集和基础结构组件程序集,以实现仓储接口。这里或许又会引来一个新的问题:既然仓储的具体实现引用了领域层的程序集,那领域层如何调用仓储呢?再去引用仓储的具体实现,岂不是造成了循环引用?我的答案是:领域层不需要,也不应该引用仓储的具体实现,仓储的具体实现应该以依赖注入(Dependency Injection)的方式,在应用层中获得,并由应用层通过仓储来完成领域对象管理和任务协调(比如:通过启用分布式事务来保证仓储和服务总线之间的事务性)。

下图来自于微软的DDD分层架构案例:Microsoft NLayerApp,从图中的彩色高亮部分可以看到,仓储接口和仓储实现分别位于领域层和基础结构层:

image

根据以上总结,我大致描绘了一下.NET解决方案中各层的程序集(Assembly)之间的引用关系,以供参考。

image

在Byteart Retail案例中,仓储接口定义在ByteartRetail.Domain程序集中,而仓储的实现部分则写在了ByteartRetail.Domain.Repositories程序集中,以下是Visual Studio 2012中解决方案资源管理器下的项目结构,我用数字对四个主要部分做了标注:1、领域层的所有内容都定义在ByteartRetail.Domain程序集中;2、在该程序集的Repositories目录(命名空间)下,定义了仓储的接口(事实上还包含了仓储上下文的接口定义);3、仓储的具体实现部分写在了ByteartRetail.Domain.Repositories程序集中,该程序集引用了ByteartRetail.Domain程序集;4、在ByteartRetail.Domain.Repositories程序集中提供了针对Entity Framework的仓储实现。

image

接下来,再让我们一起了解一下,Byteart Retail案例中,基于Entity Framework的仓储实现。

仓储及其上下文的设计与实现
仓储的实现其实网上有很多相关的资料,有基于NHibernate的仓储实现,也有基于Entity Framework Code First的,在我自己开发的面向领域驱动的应用程序开发框架Apworks中,就提供了基于三种技术的仓储实现:NHibernate、Entity Framework Code First以及MongoDB。相对而言,网文中所提供的一些解决方案虽然简单有效,但与实际项目应用之间还是有一定的差距。比如,对于EF Code First的实现,在很多文章中,都是直接在仓储的泛型基类中封装了DbContext对象,这样做可以完成一般性的事务处理需求,但需要注意的是,由于DbContext对象被封装在泛型类中,因此,这种事务性只能应用在对某个特定聚合的仓储操作上,例如:Repository可以保证所有针对Customer聚合的仓储操作都在同一个事务处理范围内,而Repository则可以保证所有针对SalesOrder聚合的仓储操作都在另一个事务处理范围内。从DDD的应用层角度看,由于应用层服务负责任务协调,而多个任务很有可能需要在同一事务下完成,如果某个任务需要同时更新Customer及其相关的SalesOrder,那么,将DbContext限定在仓储的泛型类中,显然无法完成这样的设计需求。

为了解决这个问题,Byteart Retail案例和Apworks框架都引入了仓储上下文(Repository Context)的概念,Repository Context负责事务处理,每一个Repository的实现都会被关联到一个Repository Context上,以便来自不同仓储的操作能够被限定在同一个事务中。具体地说,在这种设计下,应用层服务只需要通过服务定位器来获得一个Repository Context的实例,就能够保证后续的仓储操作都是在该Repository Context所管理的同一个事务之中:由于服务定位器的使用,应用层服务在获得Repository实例的同时,会通过服务定位器来解析获得Repository Context,因此,只要在IoC容器中注册Repository Context类型时,使用了合理的生命周期管理器(Lifetime Manager),就能确保所有Repository类型中所使用的Repository Context是同一个实例,于是,当应用层服务完成任务处理之后,直接使用Repository Context的Commit方法,即可将事务一次提交。

从实现上看,仓储上下文应用了Unit Of Work模式[PoEAA],鉴于主流ORM框架都具有对象状态托管功能,因此,仓储上下文的实现基本上也都是对ORM会话组件(比如NHibernate Session或者EF DbContext)的封装。当然这样的封装会有一定的风险性,以NHibernate为例,由于Session对象并不是线程安全的,因此尽量不要跨线程使用Session;更进一步,由于仓储上下文是对这些会话组件的封装,所以,在使用仓储上下文时也应该遵循一些最佳操作条款,比如尽量不要使用单件(Singleton)模式来创建和使用Repository Context,除非你对你的设计有着十足的把握。下面的UML类图体现了在Byteart Retail中,Repository和Repository Context相关的类型定义以及这些类型之间的关系,到目前为止,我们的讨论还处于抽象层面,并没有引入与NHibernate或者Entity Framework相关的类型定义。(注意:图中仅展示了所涉及的类型及其关系,为了简化图形,类型中方法和属性的定义并不一定与Byteart Retail案例的源代码完全一致,如有出入,以源代码为准)

image

接下来,我将讨论在Byteart Retail中基于Entity Framework Code First的仓储设计和实现细节。在这部分讨论中,我不会过多地涉及EF Code First的用法,需要了解如何在应用程序开发中使用EF Code First的读者,请直接参考Byteart Retail的源程序代码。更多地,我会把重点放在架构设计部分,让读者充分了解到选择这样一种架构的好处。

基于Entity Framework Code First的仓储设计和实现
仓储的实现是多样化的,总体上讲,还是根据项目本身的实际情况而定。比如基于NoSQL的仓储实现所采用的技术,就与基于关系型数据库的仓储实现所采用的技术不同;即使是关系型数据库,使用不同的ORM,也会造成仓储实现上的差异,不难理解,基于NHibernate的仓储实现和基于EF Code First的仓储实现之间就有着一定的区别。不过无论如何,如果采用上文给出的仓储及其上下文的设计能够满足项目需求的话,我们总是可以在这个框架的基础上进行扩展,以实现面向特定技术的仓储及其上下文组件。

在Byteart Retail中,我选用了EF Code First作为ORM,实现了仓储(Repository)和仓储上下文(Repository Context),先来看看Repository Context。从技术实现角度分析,基于EF Code First的Repository Context封装了DbContext,这跟上文中的分析是一致的,从设计和框架应用的角度分析,基于EF Code First的Repository Context需要实现IRepositoryContext的接口,以便当服务定位器在解析并提供IRepositoryContext类型实例的时候,能够返回我们的EF Repository Context。为了提供一定的扩展性,我在Byteart Retail的ByteartRetail.Domain.Repositories程序集中引入了一个新的接口:IEntityFrameworkRepositoryContext,在这个接口中,向外界公开了访问DbContext的属性:

1
2
3
4
5
6
7
8
9
10
11
12
///

/// 表示继承于该接口的类型,是由Microsoft Entity Framework支持的一种仓储上下文的实现。
///

public interface IEntityFrameworkRepositoryContext : IRepositoryContext
{
#region Properties
///

/// 获取当前仓储上下文所使用的Entity Framework的实例。
///

DbContext Context { get; }
#endregion
}
由于Repository类本身引用了Repository Context,因此,对于EF Repository而言,它能够很方便地通过这个DbContext属性来实现基于EF的仓储操作(CRUD相关的操作)。至于IEntityFrameworkRepositoryContext接口的具体实现,我就不多探讨了,读者朋友请直接参考ByteartRetail.Domain.Repositories.EntityFramework命名空间下的EntityFrameworkRepositoryContext类的源代码。

接下来是基于EF Code First的仓储设计。仓储设计相对简单,不需要引入新的接口,只需要继承上文所设计的Repository抽象类即可,当然,为了能够在仓储中使用EF的DbContext,在EF Repository的构造函数中,需要将注入的IRepositoryContext实例转换为IEntityFrameworkRepositoryContext实例,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class EntityFrameworkRepository : Repository
where TAggregateRoot : class, IAggregateRoot
{
private readonly IEntityFrameworkRepositoryContext efContext;

public EntityFrameworkRepository(IRepositoryContext context)
: base(context)
{
if (context is IEntityFrameworkRepositoryContext)
this.efContext = context as IEntityFrameworkRepositoryContext;
}
// 暂时忽略其它方法和属性
}
在引入了基于Entity Framework Code First的仓储实现以后,与仓储相关的类型及其关系可以用下图表示(同样,省略了不少方法和属性的定义):

image

现在再让我们对仓储部分的实践和应用中的几个问题进行更进一步的思考。

设计更为专注的仓储接口
这个标题听起来似乎不太好理解。在上面的设计中,仓储类型都是以泛型的方式定义的,于是,无论在向IoC容器注册的时候,还是在使用的时候,都需要以泛型的方式进行定义和调用,这样虽然没什么不好,但始终会让代码看起来别扭。或许,在我们的设计中再加上一种更为专注的仓储接口会显得更好一些。例如,对于User的仓储,我们可以定义这样的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface IUserRepository : IRepository
{
#region Methods
///

/// 根据指定的用户名,获取用户实体。
///

/// 需要获取的用户的用户名。
/// 用户实体。
User GetUserByName(string userName);
///

/// 根据指定的电子邮件地址,获取用户实体。
///

/// 需要获取的用户的电子邮件地址。
/// 用户实体。
User GetUserByEmail(string email);
#endregion
}
在这个接口中,我们可以看到两个可读性更好的方法:GetUserByName和GetUserByEmail,从方法名就能很快得知其含义,当然,这些方法本身也是使用某些规约(Specification)来调用已有的仓储方法来获取结果,不过增加了代码的可读性,而且在IoC注册仓储实例的时候,也可以直接使用这些接口,这对于仓储部分的纵向扩展是有好处的。这我将在下面介绍这部分内容。

IUserRepository接口的实现比较简单,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class UserRepository : EntityFrameworkRepository, IUserRepository
{
public UserRepository(IRepositoryContext context)
: base(context)
{ }

public User GetUserByName(string userName)
{
return Find(new UserNameEqualsSpecification(userName));
}

public User GetUserByEmail(string email)
{
return Find(new UserEmailEqualsSpecification(email));
}

}
仓储的横向扩展
在Byteart Retail中,我采用的是基于Entity Framework Code First的仓储实现,假如我们希望Byteart Retail能够使用基于NHibernate或者MongoDB等其它技术的仓储,应该怎么办呢?

其实很简单,只要定义两个分别继承于RepositoryContext和Repository抽象类的类型,并在这两个类型中使用这些技术来完成仓储及其上下文的操作,最后在ByteartRetail.Services项目的web.config中配置使用新的仓储实现即可。这并不是一个很难的问题,关键是要能够管理好仓储所使用的技术资源。

仓储的纵向扩展
以上的仓储及其上下文的设计,作为一种框架而言,无法涵盖所有的对象持久化/反持久化操作需求。比如以前有很多朋友问过我,假如我希望仓储能够根据多个字段进行排序,然后以分页的方式给出某页中的对象集合,应该怎么办?不错,目前的这个设计无法满足这样的需求,因为仓储的接口中没有一个方法能够接受多个排序字段的参数,但是,我们可以借用上面“更为专注的接口”对这个设计进行扩展。

首先,另外定义一个接口,比如:ICustomUserRepository,使得这个接口继承于IUserRepository接口,然后在这个接口中定义支持多字段排序、分页获取对象的方法;然后,另外定义一个类,比如:CustomUserRepository类,使其继承于UserRepository类,并实现ICustomUserRepository接口,如此一来,就无需修改任何现有的仓储代码,即可完成新功能的添加。最后,我们会发现:我们新引入了一个接口和一个类(你可以将它们定义在另外一个单独的Assembly中),同时我们还修改了ByteartRetail.Services项目的web.config,将IUserRepository接口的注册替换为了ICustomUserRepository(或者也可以增加了ICustomUserRepository的注册),于是,整个仓储框架无需修改,更无需二次编译。恭喜你,你已经可以将这个仓储框架作为一个通用组件发布了!

你或许还有疑问,这样一来,岂不是我还需要修改仓储的调用部分?这就要看你的整个应用程序的设计是否能够满足这样的修改了。对于类似Byteart Retail这样的面向DDD分层架构的应用程序来说,仓储的调用部分都位于应用(Application)层,而Byteart Retail的应用层也是面向接口定义的,因此,使用面向对象的手段来替换应用层的实现并非难事。

总结
本文详细介绍了仓储及其上下文在整个应用程序架构中的位置,并结合Byteart Retail案例讲解了基于EF Code First的仓储设计和实现方式。在本文的结尾部分,对这样的仓储设计进行了更深层次的分析和讨论,尤其是在仓储扩展的相关问题上。希望本文能够解答读者朋友心中大多数与领域驱动设计“仓储”相关的疑问。也欢迎大家积极参与讨论,提出宝贵意见。[……]

详细内容