我们如何从领域驱动开发当中获益–王德水

领域驱动设计,遇见你之前

我们公司推行和实践敏捷已经很多年了,SCRUM已经成功应用于大部分项目,得益与业界敏捷开发大师以及国内很多优秀工程师的分享和宣传,我们使用了很多优秀的软件开发实践,比如测试驱动开发(TDD),行为驱动开发(BDD), 持续集成(CI)等等为我们带来了很多收益。由于我们公司以做项目为主,虽然这些软件实践确实能很好的提高软件交付质量和效率,但是要想用好这些实践,涉及到的因素很多,常见的如下:

1. Scrum里需要Product Owner, 客户方很少能有一个比较符合Scrum里提到的Product Owner来定义需求为As a role I want to do something so that I can get some benefit.

2. 行为驱动开发BDD对客户方要求更高,客户需要写Specific, Scenaro, Given…When…Then. 我做过一个项目,客户开始写BDD,而且也能写出比较高质量的BDD, 但是客户后来就不写了,觉得写的麻烦。

3. 我们主要使用.NET,虽然大家都熟悉面向对象,熟悉类,接口,继承,封装等等,但是面对一个项目的时候,如何对业务就行合适的抽象,正确使用面向对象依然是非常大的挑战。

4. 由于使用敏捷,不再像之前传统开发过程中有详细的需求说明书(假定那个需求说明书及时更新,且描述准确易于理解), 强调的是可工作的软件,但是很多业务逻辑很难通过界面来体现,虽然有User story, 但是就像第一步PO很难写出符合SMART原则的用户故事,常出现的情况,就是PO说一个需求的大意,程序员就“秒懂”了,最后也确实开发出了经过验收测试的软件,但是因为有测试和Feedback的修复,这一类的需求就“丢失”了。

5. 人员变动,软件行业成员变动是很正常的现象,但是很多小团队一个萝卜一个坑,如果没有好的方法,项目的相关context就丢失了,且不说有的人走的时候没有心思做交接,就算想好好做交接,也只能是最大程度减少项目相关内容不能很好传递。

几年前,当我开始做分公司的时候,刚开始我一直在致力于敏捷的推行,好的软件开发实践的实践,很多同事的软件技能大幅提升,比如熟悉了很多Clean Code的东西,单元测试的重要性和好处,持续集成,Gitflow等的好处。但是有几个问题一直是我在考虑的问题:

1. 如何减少客户反馈的bug, 虽然敏捷强调客户频繁互动和反馈来让错误无限靠近开发的时间,但是能把事情一次做对而不是多次改对依然能大幅提高项目提交速度,也就节省了客户的成本,提高了开发程序员的效率。

2. 人员变更时候的知识传递,虽然清晰的软件架构,整洁的代码,高的单元测试覆盖率能大幅加快新的人员理解项目的速度,但是看用户故事,单元测试以及散落在很多类中的代码(单一职责的时候,我们会有大量类,注意:这里不是说不要单一职责)对中途加入项目的人依然是需要较长时间熟悉项目和代码。

3. 提高开发人员软件技能,提高开发效率从而提高对客户的产出,然后开发人员的薪水自然能有对应的提升是做为一个分公司负责人的第一责任,员工第一嘛!另一个方面,我认为相对简单的项目,比如一些CRUD项目,一些前端项目比如Angular/React的项目越来越没有竞争力。(注:这里并没有任何贬低前端的意思,只是说直接使用已有的这些框架门槛并不是非常高), 我们必须去接一些”不好做”的项目,也就是业务复杂,需求复杂类的,这类项目才能提升程序员的能力而且报价相对较高。

追求软件卓越,原来,我一直强调质量和效率的重要性,但是大家很难理解和应用,突然有一天,我想到(也许从别的地方看到的),软件最重要的是要解决两个问题:

  1. 做正确的事情 (Do Right Thing)
  2. 把事情做正确 (Do Thing Right)

初遇领域驱动设计

做正确的事情 (Do Right Thing)

把事情做正确 (Do Thing Right)

一直在我脑海里,然后简单的CRUD项目越来越没竞争力,程序员的报价难以提高,自然薪水就会有瓶颈,而我也想解决这样的问题,突然有一天我看到了另一句话:我选择做这件事,不是因为他简单,而是因为他难,这句话对我触动很大。我的脑海里就一直萦绕着“简单”,“复杂”,“麻烦”, 于是我要做的事情就是下面三个:

1. 做正确的事情 (Do Right Thing)

2. 把事情做正确 (Do Thing Right)

3. 做难的事情 (Do Hard Thing)

我就在网上搜索Complex, Software 等,一本叫做Domain Driven Design的这本书出现在我面前,Domain Driven Design这个词语早就听说过,但是更吸引我的是副标题 “Tackling complexity in the heart of software”, 我就大概看了一下书,里面的战略设计正好提供了解决复杂业务的方法,统一语言,Bounded Context, 界限上下文,设计就是代码,代码就是设计等等。 我相信这正是解决 做难的事情,做正确的事情 但是对战略有些了解,怎么去实现呢?一直没有一个好的例子来帮助大家如何使用领域驱动设计,直到一本《实现领域驱动设计》这本书的出现,才真的让我们有了打通任督二脉的机会。

实践领域驱动设计

有了《领域驱动设计》和《实现领域驱动设计》两大神器,也只是向美女要了个联系方式和家庭住址而已,中间还隔了一个漫长的日落和日出,没有正式的项目,我们永远是在岸上游泳,虽然我们也在项目里开始或多或少使用了领域的一些概念,但是我们应该知道基于数据驱动(Database Driven) 是很难成功应用领域驱动设计的。正好这个时候公司来了一个项目(客户是Fortune Global 500), 他们的架构师指定要求使用领域驱动设计,这让我非常兴奋,让我们有机会对复杂业务进行领域驱动开发的实践。

就像实现领域驱动力说的一样,领域驱动主要有两大块儿战略设计和战术设计。

战略设计 (Do Right Things)

Ubiquitous language

领域驱动开发让业务专家(Domain Expert)和开发人员一起来梳理业务,而双方有效沟通的方式是使用通用语言,在这个项目里,一开始我们就定义了很多词汇表, 就是我们自己的通用语言。

Bounded Context  Domain

有了通用语言,词汇表 每一个词汇一定是有边界的,不同的边界内是不一样,比如你爱人在你家这个Bounded Context是你的Wife, 但是如果她是一个老师,那么在学校这个边界里就是一个Teacher. 我们经过多次讨论,采取的方法是拆成多个子系统(Bounded Context,是不是很像现在的微服务?),每个子系统进行自治。

随后我们把一个个业务抽象为领域对象(Domain Model), 每一个Domain对领域进行自治。而模型里的属性和行为表达为业务专家都可以理解的代码,用比如Job.Publish(). 虽然这里面最终产生了聚合根、实体、值对象等,但是我们和业务专家沟通的时候尽量不要说这些词汇,比如我们可以说, 在招聘这块儿,职位是不是必须经过公司进行管理,那样我们就知道 Job是属于公司这个聚合根。 对领域进行“通用”(类名,方法名等都用自然语言表达)建模,业务人员可以直接读懂我们的代码,从而可以知道是否表达了业务需求。

战术设计 (Do Things Right)

在战术设计方面,由于业务行为和规则都在领域里,而且系统被拆分成多个子系统,这对技术实现上带来了非常大的挑战,尤其是大部分人都是有牢固的基于数据驱动开发的思想。 技术上有不同实现方式,但是一开始我们选择了“最佳实践”(实现领域驱动设计),也就是使用了Event Source和CQRS, 但是这条路是陡峭的。

Event Sourcing

Event Sourcing 就是我们不记录数据的最终状态,我们记录对数据的每一次改变(Event),而读取的时候我们把这些改变从头再来一遍来取得数据状态,比如你有100块钱,现在剩下10块了,我们记录的不是money.total=10, 而是记录你每一次取钱的记录,然后从100块开始一步步重放你取钱的过程,来得到10.

一开始,我们写的过程中,时常回想起数据驱动的好,(每次开始一个新东西的时候,是不是很熟悉的感觉?),觉得用Event Sourcing各种麻烦,直到后来随着系统的复杂性不断增加,我们才感觉到带来了非常大的好处, 这个随后单独来说。

CQRS

由于使用了EventSourcing, 对数据查询,尤其是跨业务(aggregate)的查询非常麻烦,很难像关系数据那样有查询优势,CQRS是解决这一问题非常好的方法,CQRS让查询和写入分开,把界面需要查询的数据进行原样写入,原样的意思就是界面显示什么样的,就提前保存成什么样的,类似于原来的缓存,没有任何join操作,这样查询是非常高效的。

实践领域驱动过程中面临的技术挑战

最大的挑战当然是战略设计部分 就是正确的划分Bounded Context和领域建模,这个部分这里难以几句话说清楚,只能多实践,多向大师学习,比如试试Event Storming的方式。

然后,如果团队没有任何领域驱动开发的经验,千万不要低估技术部分的挑战,并不是很多人说的技术部分不重要,如果实现不好,领域驱动很难落地。我们遇到一些典型问题,当然后来都很好的解决。

  1. 开发人员认为EventSourcing不重要,比如,原来你要发布一个Job, 你可能只需要改一个属性Job.Status=”Published”, 但是现在你需要定义一个JobPublishedEvent的事件,很多时候一次改变需要定义很多事件,比如CompanyNameChangedEvent, CompanyEmployeeAddeedEvent. 最重要的是事件的粒度如何定义?
  2. 由于根据Bounded Context拆分成一个个子系统,系统之间的交互比较麻烦。原来在一个Controller里直接调用不同的Repository来改变数据的方式就很不适用了。
  3. 由于使用CQRS,查询必须要单独保存QueryModel, 这相对传统的数据库驱动的开发方法,写和读都是同一个数据库更加麻烦。
  4. 事件的版本管理,比如事件改名,删除和增加都需要考虑重放事件重建领域对象的影响。
  5. CQRS如何保证数据的及时性和一致性,比如我在一个公司详细页面修改了一个公司名字,然后点击保存按钮导航到公司列表页面,这个时候QueryModel可能还没有更新过来,这些如何解决一致性的问题。

领域驱动开发如何让我们和客户共同获益的

1. 做正确的事情(Do Right Things): 领域专家高效的和团队沟通,确保建立了正确的反映业务规则的模型,而开发人员有了直接可以使用的代码,而且可以因为Domain有了数据和行为,非常方便的进行单元测试,因为Domain不依赖第三方的数据存储等,可以确保实现了业务。

2. 大大提高了沟通的效率,我们知道一图胜千言,而对开发人员来说,少废话,Show me the code! 不但代码对程序员更容易读,而且Code(领域对象)就是最新的需求. 可以跑起来的需求。

3. 大大提高新成员进入项目的速度,最主要的是看领域模型以及对领域模型的测试,几乎就知道了系统的所有的业务规则。

4. 领域驱动的技术部分给系统增加功能或扩展带来了极大的遍历,举几个例子:

a)由于使用了事件溯源,我们很容易查询历史数据。我们只需要指定一个时间点,我们重放事件的时候重放到这个时间点就可以了。

b)操作日志,原来如果我们想记录操作日志我们代码里遍布都是Log, 而现在我们只需要重发事件,想看什么日志就看什么日志,而这些就只需要我们回放数据库存储的事件流就可以了。

c)系统之间的通信,我们只需要发布事件就可以了,其它系统订阅我们的事件就可以,我们和其它系统之间没有直接依赖。

d)大大提高了系统增加新功能的方便性,很多时候增加新功能就是订阅事件就可以了。

e)CQRS, Query model极大的提高了系统的查询性能,而且当我需要新的界面的时候,我不需要对写入端代码进行任何修改,包括类文件都不用修改,是不是符合对修改关闭,对扩展开放(OCP)? 我们只需要建一个类似新的EventHandler,然后重放对应的事件就可以了。

f)因为使用了事件溯源,系统之间通过事件集成,比如通过消息队列发布和订阅事件,这可以大大增加系统的抗压能力,我们可以把事件放入队列,后续处理系统即使不能及时处理也不会让前端系统崩溃。

g)系统性能大福提高,在写入端只有插入操作,没有修改操作,在读取端只有Read操作,那么何须锁表,何须开启事务?由此一来,输出存储和读取的瓶颈可以大大缓解。

5. 开发人员可以更集中的处理业务,由于一切都是事件,实现玩基础库后,开发人员可以忽略数据存储,大部分时间都是在写业务代码,不关心数据怎么存储,数据存储部分就俩操作
AggregateRoot.Get(id),
AggregateRoot.Save(),
EventPublish.Pubish(CompanyNameChangedEvent),
而事件订阅端只需要增加一个EventHandler就可以了。

6. 系统很好进行了解耦,业务逻辑集中在领域中,不会像之前的开发里面业务逻辑充斥在很多地方,修改一些功能的时候,不得不如履薄冰,生怕哪里给破坏了,或者哪里没考虑全。

7. 不用过于担心开发人员,尤其初级开发人员不正确的代码遍布系统多个部分,对其它功能的影响可以大大减低,Review代码其实主要Review业务实现的单独的类,不用担心很多技术实现部分不正确,因为基础库写好了,这样可以适当均衡团队成员组成来降低项目开发成本。

最后,系统更加容易修改和增加新功能,不正好支持了敏捷开发的拥抱变化吗?

总结

领域驱动开发好处多多,概念比较多,门槛相对较高,对人员要求较高,团队里至少需要有领路人,不然代价会比较大。 尤其慎用Event Sourcing, 而领域驱动尤其适合业务相对复杂的项目。 对那些很小的项目,CRUD仍然是好的选择。

相关内容:

2018分公司经理会暨太湖大学堂研修活动举行

2018年6月8日-10日,北京盛安德科技运营团队、分公司经理、合伙人及部分同事来到位于苏州吴江的太湖大学堂,参加研修和“南怀瑾讲座”活动。 大学堂位于太湖之滨,是南怀瑾先生多年的理想与筹划,融合东西方教育方法,以生命科学为基础,实践人文融汇的精神理念,结合田园教学、文化课程、心灵教育,希望培育出艺……

Shinetech 故事 洞见与思考 87 阅读

如何选择靠谱的软件外包公司

在信息化建设中,随着IT与业务进一步融合, IT成为推动业务转型、管理变革的重要力量。很多企业在10几年前购买的软件产品,已经无法适应日益变化的业务需求,需要根据企业自身业务模式进行定制化开发,以助力企业发展及业务转型。 传统企业通常没有专业的软件开发团队,组建IT团队的成本比较高,后续IT人才维护……

观察与技术趋势 软件开发 81 阅读

Mind Matter项目分享——设计不仅仅是设计

Mind Matter软件旨在促进企业的战略发展,并帮助推动战略的实践。其核心业务是开发下一代战略软件和服务。与各种类型和规模的企业组织合作,共同定义、设计和执行战略。 目前开发的软件作为一款简单精巧的协助工具,帮助用户定义、设计、讨论、决策和交付发展战略。 Mind Matter项目自2017年9……

技术趋势 观察与技术趋势 76 阅读

远程办公:谈谈我遇到的挑战与机遇

每每与身边朋友说起我在家上班,他们都会投来羡慕的目光,外加两个字:“真爽”。而我,只能无奈地回应:“其实也就那样了,并没有多爽。”这是心里话,但是他们只会觉得我矫情,得了便宜还卖乖,我也只能呵呵苦笑了。
我承认他们部分正确,是有点身在福中不知福,这也是人的天性吧,永远不满足。但是,我之所以如此笃定地说,在家办公没有那么舒坦,是因为这两年的远程办公经验让我明白,这种看似“爽”的工作方式,其实暗含着许多挑战,对远程工作者也提出了更高的要求。

敏捷实践 洞见与思考 软件开发 远程办公 95 阅读

引导客户不是靠话术 而是全然的负责

近期我们接了一个在线教育的客户,他们业务发展很快,旧有的系统虽然比较稳定但已经不能适应业务发展的需求,因此找到我们。充分了解需求之后,我们判断客户提出的任务不现实,在规定时间内完不成,既定目标不可行。于是我们将需求拆分,将功能实现的顺序重新安排:哪些在3个月内可以完成,哪些不行,同时接手客户的运维。

敏捷实践 观察与技术趋势 软件开发 远程办公 79 阅读

跟客户面对面确认需求是一种什么样的体验?

Matthew是个澳洲客户,前期有过很长时间的沟通和推进,我们对业务和项目需求目标大概了解了。但是针对第一个要发布的版本,要做成具体什么样的产品还是两眼一抹黑。故此,客户来我们办公室两周,专门讨论具体细节。期望经过两周的密集讨论,我们能有若干产出: 想想都挺多事情的。当然,理想都是很丰满的……过程不……

观察与技术趋势 软件开发 73 阅读

善用工具——成就高效沟通协作的团队

《敏捷软件开发宣言》  我们一直在实践中探寻更好的软件开发方法,身体力行的同时也帮助他人。由此我们建立了如下价值观: 个体和互动 高于 流程和工具  工作的软件 高于 详尽的文档  客户合作 高于 合同谈判  响应变化 高于 遵循计划 也就是说,尽管右项有其价……

敏捷实践 观察与技术趋势 软件开发 81 阅读

我的ODC项目经验分享

项目介绍:客户公司旨在为病人提供更加优质低价的治疗方案。其主系统联合病人、医师和医保公司,根据病人的病症、体检数据、过敏情况、生活习惯和过往服药方案等信息,结合其内部一套引擎工具,检查用药过程中的问题(Drug Therapy Problem)并提出给药建议。 在三年的合作过程中,我们不断丰富其主系……

观察与技术趋势 软件开发 90 阅读

敏捷实践系列(三):代码管理流程

我们已经从SVN切换到Git很多年了,现在几乎所有的项目都在使用Github管理。对于那些还在坚持使用SVN的,我实在想不出原因,权且称作守旧派吧。 Git的优点 Git的优点很多,但是这里只列出我认为非常突出的几点。 感兴趣的,可以去看一下Git本身的设计,内在的架构体现了很多的优势,不愧是出资天……

敏捷实践 观察与技术趋势 软件开发 75 阅读

敏捷实践系列(二)

大话西游里有一段因为没有沟通的经典, 结局如何大家都知道。 唐僧:你想要啊?悟空,你要是想要的话你就说话嘛,你不说我怎么知道你想要呢,虽然你很有诚意地看着我,可是你还是要跟我说你想要的。你真的想要吗?那你就拿去吧!你不是真的想要吧?难道你真的想要吗?…… 悟空:… 一. 敏捷项目沟通尤其重要 敏捷开……

敏捷实践 观察与技术趋势 软件开发 63 阅读