10月有幸参加ThoughtWorks组织的《领域驱动的微服务架构设计实战工作坊》培训课程,尽管课程时间只有短短一天,但老师们的热情讲解为我带来了非常多的灵感和启发,而这种灵感和启发不是用钱可以买到的。结合最近两年多来参与应用系统和应用架构开发实战所积累的经验,本着不迷信专家的立场,借鉴当前比较新的架构设计和开发理念,本文提出作者自身两年来有关所从事领域技术发展的思考和方法论(以下仅代表个人观点,与所在公司无关)。

言归正传,领域驱动设计(DDD)和微服务架构(MSA)近年来非常火热,尤其是在互联网公司的生产实践过程中。微服务架构为互联网公司自身业务的发展壮大提供了技术支撑,日渐显现出解决复杂业务需求的威力。更多的IT公司无论是创业公司,还是传统IT企业或非IT企业的技术支持部门,都将微服务改造视为系统架构发展的蓝图方向,趋之若鹜,意图抢占技术高地,传统的SOA改造思路日渐式微,微服务(MSA)化已成IT公司技术治理和架构转型的公认趋势。包括传统金融部门的开发者们正在非常积极和热情的拥抱新技术、新思想。

微服务模式

微服务东西虽好,盲目运用,缺乏配套,副作用会很大。自上而下,各级领导者一直在追问一个问题,银行间市场如何进行微服务改造,按照怎样的方法论来划分微服务,如何指导设计开发实践?是先开发试错还是先定设计?银行间市场系统众多,非常庞杂,想要完全梳理清楚并非易事,甚至涉及到跨组织问题。本文认为尚需要结合实际,因地制宜的通盘考量,制定自上而下的整体改造方案。开源微服务Spring Cloud技术体系虽然非常成熟,Dubbo也被很多公司采用拿来主义,改造应用,但是不结合金融市场具体业务盲目运用,可能带来巨大的潜在风险。金融设施以安全为先的策略在任何时候都不过时,尤其金融市场一般涉及巨额资金(以百万甚至以亿为单位交易)交易的情况。

领域模型

DDD领域驱动设计尚存在一些争议,(基本判断)个人认为DDD当前的核心问题是针对软件开发导致软件结构越来越混乱,越来越复杂,越来越难以维护的问题,开出的药方超越了当前众多公司的发展阶段和实操水平。当下很多公司仍然是软件修修补补或者推倒重新设计开发(而这正是DDD所判断失准的地方),尚且能够满足业务需求,保证及时交付。廉颇老矣、尚能饭否,故在这个阶段过分强调DDD,反而会使得整个开发设计体系无所适从,类似无头苍蝇到处乱撞。准确说,当前大多数传统金融公司软件开发经验尚且薄浅,缺乏积累和人才积累,盲目运用DDD技术开发,会极大增加开发成本。DDD对开发者素质要求非常高,甚至达到了专家级要求,主要反映在三个方面:

  1. DDD的设计和开发成本非常高。首先需要雇佣领域专家和掌握DDD设计与开发方法的开发者,在开发之前必须前置领域设计流程,在项目建设工期非常紧张的情况下,DDD将导致开发效率降低,开发难度增大,开发工作量剧增的问题,不利于项目建设过程中的贯彻和坚持;
  2. DDD缺乏成熟的IT大厂实践、分享和技术支持。目前社区资源(解道Jdon)不足,开源工程(例如:github/micro-company, github/ddd-leaven-akka-v2)和大型实践项目比较少。了解和掌握DDD的软件工程师凤毛麟角(面试时问:你知道什么是DDD吗?不知道)、不同公司业务场景各不相同、真正理解银行间市场业务的领域专家和全面掌握DDD设计的专家就更少了;
  3. DDD的思想范式相对CRUD、MVC过于复杂,概念繁多,过于抽象,表述不够清晰,难以理解,不易被初级开发者掌握。DDD的范式流派目前主要有EDA、CQRS、Event Sourcing、In-Memory、DCI、CSP等流派,它们之间互有冲突,不分伯仲,各自从某一个角度出发,解决部分问题,但也引出了其它新问题,例如数据一致性危机,事务危机和代码复杂度问题,在当前绝大多数公司和业务场景下,必须混合使用,目前尚未出现大一统的理论架构。当前仍缺乏成熟的配套技术以降低开发者学习和运用其的难度和成本。主要体现在缺乏类似Spring这种现象级别的成熟框架,允许一个在校生学习一个月就基本可以进行生产级的代码开发工作。当前尚且不够成熟的DDD框架AxonFramework、AKKA或国产JdonFramework等学习成本比较高,不易被缺乏开发经验的工程师快速掌握应用。

应用架构与技术突破

微服务架构作为一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调,互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务之间采用轻量级的通信机制相互沟通(通常是基于HTTP的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等。此外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。无论是微服务、领域化还是之前的敏捷开发都强调微服务的核心要义是强调根据业务发展、持续调整现有架构,优化现有代码,以保持架构和代码的生命力,这也是当前传统金融公司最缺乏的经验积累,如果不从组织架构和开发方式上做调整,烟囱式的、系统林立的格局(现状:近百个系统,数据搬来搬去)、推倒重建的生产方式就不会打破。从这个角度来说应用架构、数据架构和技术架构三分是好的开始,正如陆、海、空三军的划分一样(目前中国有五大军种还包括火箭军和战略支援部队)、陆军中有陆航和两栖师,空军中有防空部队和空降兵,海军中有海军陆战队和海航,各个军种又下辖自己的特种兵部队,形成你中有我,我中有你的格局。应用架构不能只关注应用和领域设计、更应该在基础业务、技术层面有所突破和积累、技术架构组不应仅仅提供技术支持,也要为各种应用场景提供标准的、规范的和稳健的技术解决方案,数据架构组不是只审审数据表设计、可视化当前的数据字段信息,吆喝项目组完成数据迁移工作,更重要的是从应用和技术角度厘清数据流,以数据可视化平台提供数据的追踪服务和数据基础设施服务(统一的多租户数据库服务)识别和约束项目建设的非法活动,减少项目组的数据开发和管理成本(每个项目组都必须养一个数据专员)。当然架构三分是远远不够的,也带来了新问题和新挑战,所以需要统一的中央军事委员会联席会议制度,现代战争不是某个军种就可以打赢的,需要多兵种联合作战,软件开发亦如是,必须将技术、设计和数据统一协调推进,以高效率、高质量推进系统建设工作。

传统架构设计流程是先业务架构、再系统架构,后技术架构。其优点是关注点分离,劣势是设计往往滞后开发,无法明确表达系统架构怎样支撑业务架构,关注点分离导致信息割裂和障碍。DDD试图解决业务架构和技术架构一致性问题,关注核心问题域。问题是对于技术实力雄厚,经验积累丰富的团队,业务非常成熟稳定的系统来说,这种优先夺取城市(核心业务),再夺取农村的方案无疑是最佳选择,而对于技术基础薄弱,业务系统缺乏梳理,如同乱麻,开发团队经验总体不足,在建系统尚未成熟的情况来说,优先土地革命,走农村包围城市,武装夺取政权的路线才是上策,也即是优先搭建基础技术框架和平台,逐步明确企业自身意图在领域实践中实现的技术和业务目标,通过一个城池一个城池,由点到面的方式消化边缘业务,最终解决核心业务的领域化问题。

系统建设挑战

对企业来说,业务线随着时间的推移,大多数都会演变得越来越复杂(凡客是反例),以阿里为例,其业务从最初的1688(阿里巴巴)B2B到淘宝C2C,再到天猫、飞猪等B2C、以及阿里云、蚂蚁金服和菜鸟等平台,企业为了生存发展,其业务线越来越复杂;因为未来的不可知性,也会变得越来越不确定,以中金所的股指期权业务为例,其本身是一种应用成熟的风险管理工具,作为金融创新工具,期权的定价包含了对市场所有情况的预期,为市场参与者提供了一种更有效率的投资手段,但该业务因为2015年股灾发生而停滞搁浅。在软件开发过程中,我们不可能找到一个通用的准则,以解决软件开发面临的所有问题,尤其是引入未来这个不确定性变量之后,也就意味着根本上不会存在银弹级解决方案。软件工程的不确定性贯穿其整个生命周期(lifecycle),软件的不确定性包括需求、产品、设计、开发和工程团队自身的不确定性。当架构师已经无法完全掌控系统业务和代码设计、软件建设的危机的种子就埋下了。优秀的开发团队在某种程度上能够弥补架构师业务能力的不足,团队之间的互信和沟通是最有竞争力的制胜武器,在制度和技术控制下将沟通结果显式化的表达和记录是避免失控的有效手段,最好这种表达是通过技术手段自动完成的,人工参与就会引入时滞问题,DDD划分问题域和方案域的方法则是解决软件业务复杂性的根本手段。

当前微服务和DDD非常强调软件的复用,微服务重视服务复用、DDD重视领域模型的复用。过分强调代码复用是一种走火入魔,试图通过一成不变的模型套用不断变化的业务需求是缘木求鱼。试想想,现在很多公司尚做不到在旧有代码基础上修改、不断升级维护支撑新业务需求,那软件的复用意义又从何谈起。盲目追求软件代码级别的复用,只会消耗开发者和设计者的精力,只要业务不断发展,那么软件复用终将是一场黄粱美梦。

用心思考,直达问题本质

面临挑战,技术进步是一个重要的突破口。仅仅只有技术进步,但思想认知不跟进,妄图在旧有体制机制下修修补补或大刀破斧急功近利,就可以建立新中国,则只能是洋务运动和戊戌变法,徒劳无功。解决中国问题的道路千千万万,但只有共产主义一条道路走通了,就是因为它的领导者和践行者抓住了不同阶段中国问题的本质特征,对症下药。接下来,本文将重点介绍一种策略,一种思想,即开发者优先的策略,开发即设计思想;提出一种范式和多种方法,即新范式和新方法。

开发者优先策略

科技是第一生产力,那么在软件这个工程领域中唯有软件工程师是真正的第一生产力,软件的设计可以不准确,软件的目标可以不明确,但是没有软件开发者努力地完成代码编写每一行代码,并在工作中不断修复和纠正错误设计,矫正目标,优化代码质量,解决代码bug,就无法按时并保证高质量交付软件产品。反观架构师和领域专家更应作为支持者和服务者为项目的建设建言献策、为项目未来发展提供思路,针对当前建设过程中暴露的问题进行集中会诊,给出改造方案,并推动落实。架构师和领域专家不可能发现和解决所有问题,软件开发人员是核心生产力,我们需要开发人员自查自纠,提高忧患意思,防微杜渐,并且不断学习充电,保证整个团队整体的知识进步水平保持线性。雇佣领域专家的成本非常高昂,并不符合传统金融公司(瘦死的骆驼比马大,有钱很抠,还要各种审批)目前的实际现状,这些公司的开发主体仍然以开发商为主,开发商以5年经验以内的初级开发人员为主,自有人员大多数是年轻潜力股,尚不足以主导复杂系统的设计与开发工作。在系统林立,项目众多的现实面前,每个项目组拥有的自有人员极其有限,甚至就一个,项目之间的沟通成本极高,各种会议和琐事杂事极度消耗自有人员精力。虽然项目建设投入很高,开发商团队整体招募的质量比较堪忧,开发商中所谓十年工作经验的高级开发工程师经常写出不符合其经验水准的代码(开发商人员10年工作经验需要大打折扣)。所以为了保质保量的完成系统建设,必须坚持以开发者(自有和外包)为中心,全心全力发挥开发人员的学习主动性、调动积极性、通过不定期的技术和业务培训,增加开发人员的获得感和技能,纠正开发过程中的各种问题,将缺陷遏制在开发阶段,解决这一当前开发和交付过程中最主要的矛盾。

传统瀑布模型形成职能部门:需求部门、设计部门、开发部门、测试部门、运维部门。原则上部门之间只需要在交付物传递时做很少的交流沟通工作,就可以完成系统的开发运行。这种方式极为经济,也确保每个职能部门工作的确定性和内聚性,最大程度的减少沟通成本,如果知识传递过程中不存在变异,瀑布模型无疑是最高效的开发交付模式。现实情况往往是,交流沟通过程中经常形成鸡同鸭讲的局面。Scrum敏捷开发受制于部门组织架构难以有效实施(或效果不明显,问题更多,毕竟传统开发方法和工作思维已经固化),而且敏捷开发并不能从根本上解决知识传递过程中的变异问题。Scrum总体只适合于市场竞争剧烈,业务需求并不十分复杂,需要快速将产品推向市场,组织架构调整迅速的互联网公司,金融系统的需求文档可以做到几百页,上千页,业务需求难以迭代,导致开发周期非常长,不适合Scrum的应用场景。领域驱动设计DDD则试图通过通用语言(Ubiquitous Language)来避免信息失真,一个团队保持一种声音,但DDD的没有解决的核心矛盾是由谁主导通用语言的确立?需求人员、设计人员、领域专家还是开发者?Eric Evans在其《领域驱动的设计》一书中强调领域模型是软件项目通用语言的核心,通用语言的词汇包括类和主要的操作名称,开发人员应该使用基于模型的语言来描述系统中的工作、任务和功能。确保团队内部所有交流以及代码中坚持使用通用语言,在画图、写东西,特别是讲话时也使用通用语言,通过不断的重构代码保证代码与通用语言和领域模型的一致(几乎不可能完成的任务)。作者强调对话沟通是优化通用语言的最佳方式,简言之沟通是建模的必要前提,模型最终以鲜活的代码、文字和重点突出的简化图(UML是不必须的)共同表达。而实际工作过程中受制于公司的组织架构和参与人员的知识水平,导致沟通经常不充分,不愉悦,成本高昂,在没有严格遵循DDD设计者初衷的前提下使用DDD最终只会生产出畸变的领域模型和不能真正理解的伪通用语言。现实中开发者的语言模型过于细碎和抽象,领域专家的模型偏重业务领域,开发人员难以理解和贯彻,需求人员往往基于知识惯性,滥用业务术语,无法分离和聚焦重点,没有人主导通用语言的建设过程,简直类似“坏的民主”,是一件非常糟糕的事情。在这种情形下,只有以开发人员主导的通用语言和领域模型才能保持生命力,开发人员必须学习使用业务术语建立代码模型,毕竟开发人员开发的模型最如实的反应了当前的生产实际,其反映的信息失真度最低。

脱离具体业务和技术工作经验的高级工程师不是好的领域专家。开发者优先强调以开发者为中心重新梳理和构建软件开发过程,以及在保持原有组织架构体系下,维护业务需求人员、测试人员、架构设计师的职责和权威,使之紧紧围绕开发人员开展设计和指导工作,不断质询开发者有关设计和开发的问题,确保他们能够对开发模型负最终责任,而非开发人员。测试人员可以通过集成测试提出模型设计和业务表达的诸多问题,倒逼开发者采取TDD的开发方法,不断优化设计、重构代码和功能测试,最终实现理想的通用语言、领域设计模型以及应用系统。

开发者优先的策略,即以开发者为中心,强调开发者的贡献和作用,也突出了开发者应该承担的责任和义务。在项目实战中,该策略能够保证资源优先向前线开发人员提供,开发人员犹如伊拉克战争的美军基层士兵,能够直接呼叫炮火支援,降低沟通成本,提高开发效率。

开发即设计思想

开发和设计在逻辑上是非常和谐的两个过程,但在实际工作中经常产生矛盾。在建筑领域中,也有实施和设计之间的矛盾,但是在软件业,这种矛盾体现的更为激烈。因为,建筑业和软件业有一个关键的区别,设计的结果90%可以映射建筑的结果。建筑业中,设计人员和实施人员的技能领域界限很明确,语言统一,分工明确。一座别墅可以边盖边设计,对于上海中心这样的摩天大楼必须先设计再建设。如果事先不做事无巨细的设计,经过无数次讨论和重绘建筑设计图纸,那项目注定只有一个结局就是失败。但是软件业中则不是如此,设计的结果经常无法正确的指导实践,在实际开发的过程中,为了解决各种问题,完全可能把先前的设计推翻。所以说在高度设计的环境中,开发人员需要很优秀的技能,优秀到足以质疑设计人员的设计,特别是设计人员一天不如一天知道开发平台的具体细节的情况下,开发和设计就会脱节。总体来说规划式(Planed design)和演进式设计(Evolutionary design)是相辅相成,规划式不应太偏重细节,而是将整个架构脉络梳理清楚,使用的技术做好预研工作,针对核心业务优先划分清楚,具体开发过程则更依赖于开发人员的技术和业务能力。

极限编程强调任何一个软件项目都可以从四个方面入手进行改善:加强交流;从简单做起;寻求反馈;勇于实事求是。业务人员应该与开发人员坐在一起开发,使用TDD,结对编程,代码归集体所有,任何人都可以修改它人代码,像我们这样的公司这几乎是不可能完成的事情。当然极限编程并不是一定不可借鉴。不要过多考虑扩展性,以实现需求即可,统一编码标准和规范,不要复杂无用的设计文档的确为开发即设计思想指明了方向。

开发即设计思想(Program For Design),是本人针对近年来的项目开发实际经验提出的一种新思想框架。这种思想强调开发过程与设计过程的协调统一,强调从重视设计、依赖设计回归到关注开发本身,毕竟开发是第一现场,开发的质量直接影响最终软件系统的质量,开发的结果就是设计的目标,只有保证了开发的质量才能降低测试维护的成本。只有通过开发结果才能还原最真实的设计信息,减少设计信息传递过程中的变异失真,有利于形成真正有效的通用语言,减少领域模型本身的歧义(开发者、设计者和需求人员依据自身知识体系各自理解领域模型的含义、最后结果必然是似是而非)。新思想不是否定设计的作用,也不是否定先设计后开发的过程,而是强调通过技术手段实现设计和开发互为一体,同步实施,相互反馈。瀑布模型和敏捷开发更重视设计工作,仿佛好的软件是设计出来的,过分强调有经验的软件工程师的作用,而难以招聘有经验的、合适的软件工程师恰恰是外包开发商面临的突出问题。当初项目团队面试了10位高级架构师,也没有找到一位合适的新系统建设的,就是很好的说明。PFD对设计工作没有硬性要求,在底层基础开发框架的约束和规范下,通过必要的培训和技术约束,确保开发人员使用规范的方式编写程序,通过声明化技术将这些设计与开发信息直接从提交的代码模块中提取出来并存储到数据库中,将开发成果和历史轨迹以实时可视化形式展示出来,通过图形化技术手段、形成通用语言,图形化的展示方式有利于业务需求人员、测试人员和设计人员理解业务和开发现状,这样就可以将所有角色统一在通用语言的石榴裙之下。

叙利亚内战期间俄罗斯国防指挥中心内部通过俄军规模庞大的现代化信息设施实时采集战场动态信息,通过计算分析战场动态情势,直接分配给前线每个作战人员具体的作战任务,指挥作战,并通过大屏幕和各种现实设备监控作战人员的执行状态和当前位置。PFD是这种理念的直接体现,充分发挥了信息技术的价值和魅力,将开发人员的开发信息直接转化输出为设计信息,根据分析和监控结果,近实时的感知问题,就各项问题进行及时的讨论和决定(负反馈),实现更快速的设计和开发响应。统一的设计和开发管理平台也为业务需求人员主动融入开发过程,便于需求人员及时发现需求的疏忽和逻辑缺陷,了解设计和开发全过程(正反馈)。读者可能会问,既然都已经开发完成了,直接使用系统发现问题岂不是更好,为啥还要通过设计发现问题?对于简单需求来说,譬如一个月一两个开发人员就可以搞定的项目来说,PFD完全是多余的,这样的项目最终实现的结果就是设计的结果。但是对于近百人的团队,用时一年以上的大型项目(需要软件生命周期内持续的升级维护)来说,PFD非常必要,外汇市场一个节假日逻辑的微小调整,都可能引发一系列的功能缺陷,因为其它功能模块可能未显示化的声明节假日业务规则。如果没有准确的设计信息来支撑判断节假日功能的影响到的其它业务功能,则对于需求提出者和设计者都是巨大的灾难。

新范式

新范式是一种反范式。北大俞孔坚教授提出“反规划理念”,获得美国艺术与科学院院士殊荣。至今印象深刻的是,它认为传统的城市规划强调人口规模、城市土地性质,功能分区和结构布局,而反规划以土地生命系统的内在联系为依据,优先进行非建设用地规划,包括生态景观、河网湿地、生物保护、历史文化遗产保护等。使规划路径建立在自然过程、生物过程和人文过程分析基础上,以维护这些过程的连续性和完整性为前提。新范式认为数据结构-过程算法在应对解决复杂业务问题时往往力不从心,算法在应对确定性问题时十分有效,但业务不确定性决定不同时期建设的业务功能容易产生冲突,导致应用开发建设过程缺乏连续性、一致性和稳定性。而基于应用场景的对象-事件-消息三位一体分析和设计方法,能够更好的适应业务需求的复杂变化,该方法天然内含了时间维度信息,非常适合事件溯源。新范式强调在设计开发过程中如何实现真正的OOP,对象包含了时间维度上的状态变化信息和基于事件消息的行为定义,数据结构和算法过程被内化于三位一体的分析框架中,为应用场景串联提供服务。

认识世界的正确姿势

事件是一种行为、动作(或命令),对象自身具有所持的状态和行为能力、对象之间通过消息进行信息传递、不能够直接进行行为传递、行为和状态对于对象是私有的、对象只有自身根据相应的消息刺激做出对应的行为动作(应激性),并产生对应的状态结果,这一点很好理解,传统的领域模型在设计和开发时似乎有一只上帝的手,在白头鹰飞翔的场景中,白头鹰作为鸟类继承飞行的动作行为(天赋),一头实例化之后的白头鹰犹如提线木偶一样被它的应用主宰,只要应用调用飞的动作逻辑之后,白头鹰就犹如一道闪电翱翔于空,这一面向对象分析框架显然不够完整,似乎总有哪里不对。新范式针对某一具体的业务场景,从以下分析框架入手:

  1. given for object 假设特定的业务对象(一个或多个)处于某种特定的状态、具有某些特定的行为;
  2. when fired by event 当某一事件发生之时;
  3. then what changed 这些特定的业务对象发生了某些可观察到的状态变化,执行了某些可预见的行为;
  4. spread the event messages 发出消息,通知外界或特定对象,自己状态已发生变化。

领域分析离不开具体的业务场景,我们以聊天室应用为例进行具体分析。对于一个聊天室应用来说,主要场景有:

  1. 用户加入某一聊天室;
  2. 上线的用户可以在该聊天室里发布消息,所有该聊天室在线用户接收该消息;
  3. 用户离开该聊天室,该用户下线,不再接收该聊天室的新消息;

按照领域通用语言,我们来剖析建立领域模型。

业务对象:聊天室、用户和聊天消息(注意:这里的聊天消息是业务对象,而非范式)。业务对象的分析仍然遵循聚合根-实体-值对象的分析方法。聊天室作为聚合根,聊天消息和用户是实体。根据业务分析聊天室必然存在一个用户列表和消息发布列表两个子实体,用户必然存在一个已接收消息列表,并且有上线和离线两种状态,以及记录当前所处聊天室的位置信息,用户名对于用户来说是值对象。对于微信应用的场景,则该模型不能适用,无法满足需求。领域通用语言的建立不可能脱离具体业务需求和业务场景,虽然都是聊天业务,但却可能天差万别。

  1. 在用户加入聊天室场景中,given for object 聊天室中当前没有登录用户when fired by event聊天室收到一个用户加入请求then what changed用户加入该聊天室的用户列表spread the event messages 某个用户已经加入聊天室。

  2. 在用户发布消息场景中,given for object 发送用户已经在线when fired by event聊天室收到用户发布消息请求then what changed聊天室将该消息通知给所有在线用户spread the event messages 用户与某一时间向某一聊天室室成功或失败的发布了一条消息。

  3. 在用户主动离开该聊天室场景中,given for object 某聊天室中该用户在线when fired by event 用户离开聊天室请求 then what changed该聊天室从用户列表中将该用户置为离线spread the event messages 用户与某一时间已成功离开某一聊天室。

经过这一范式简单分析之后,我们通过建立是三个场景模型,帮助我们明确了设计应关注的重点信息,基于场景的分析,让设计和编写的代码具有内聚性,结构更加清晰,也方便TDD测试用例和测试程序的编写。等等我们似乎遗漏了什么。对,这些场景是如何串联在一起的,如果新需求加入,会否破坏原有模型的完整性和内聚性。消息机制为模型扩展提供了可能,也为领域模型之间、问题域之间解耦提供了真正的可能,领域模型之间通过消息实现弱的联系。在第一个场景中,发出用户已经加入某一聊天室的消息之后,设计者可能根据目前的应用需求可能并不需要关注是否有人订阅该消息。但是根据开发者的分析,存在潜在需求需要将用户的状态置为在线状态,如果用户之前没有已接收消息列表,需要为该用户初始化一个空的已接收消息列表。这一个逻辑过程完全可以在另一个业务场景中实现,并在另一问题域中进行解决,我们自此又通过分析识别建立起第四个业务场景。而需求可能没有提及。

  1. 在用户上线的场景中,given for object 某聊天室中该用户离线when fired by event 用户发出上线请求 then what changed 该聊天室从用户列表中将该用户置为上线,用户当前所处聊天室置为该聊天室spread the event messages 用户与某一时间已成功在某聊天室上线。

自此我们没有必要修改第一个场景对应的方案域。接下来如果一个监控需求需要追踪用户活动记录,我们也可以迎刃有余的通过订阅用户已经在线和离线消息来完成扩展支持工作,而不需要改动到原有模型的任何一行代码。在满足性能需求的前提下,开发者和设计者应该努力通过新范式设计领域模型,保持信息的完整性,通过信息的完整性保证模型的稳定性,例如在头寸现金流场景中,通过簿记原理在本方额度下扣掉对应贷记金额,增加借记金额,在对手方则增加借记金额,减少贷记金额,设计者和开发者很可能因为无需求和难以融入新过程的原因,而有意丢弃交易相关的时间信息、位置信息和交易前后的账户状态信息,如果后期新需求需要关联这些信息,必然涉及到代码逻辑的调整,但由于先前逻辑固化在多个关联类的内部,改动起来必然伤筋动骨。传统分析方法难以实现模型的完整性,数据和算法过程过于关注问题本身,新范式的优势在于它能通过针对对象-事件-消息分析,天然解决了状态问题和扩展问题,当然前提是只要你愿意在初始设计时把领域模型考虑的更完整和清晰。

通过新范式的实例分析之后,我们可以慢慢体会到新范式的威力。配合新范式必然需要新方法、新手段支撑这一美妙的大厦,使之勿要成为空中楼阁。最后我们简单分析一下TDD基于行为的测试,实际上只要我们在开发时稍加使点小技巧,在第一个场景的领域模块中针对用户和聊天室分别采用观察者模式,监听用户所在聊天室位置的改变,监听聊天室上线和下线用户的改变,并在单元测试中通过匿名函数实现这些监听器,在监听器中增加断言信息,断言输入行为与断言匹配,最后将监听器注册到用户和聊天室的扩展接口中,执行针对场景一的单元测试用例,我们就会得到是否断言是否匹配的结果,基于结果决定测试成败。这些单元测试用例总是保持在实现该模型的测试代码中,以方便代码演进过程中的回归测试。事实上令人更加兴奋的是,我们为测试实现的状态监听接口也为后期应用模型和业务扩展提供了想象空间。

新方法

领域驱动设计与面向对象设计区别是什么?领域驱动设计是正确的OO,也是实施正确的SOA(ThoughtWorks)。传统面向对象的设计重视UML的作用,讽刺的是UML的类图和流程图几乎就是面向过程开发代码的数据结构和算法的高级版本,新瓶装旧酒。类之间的关联(association)、依赖(dependency)、聚合(Aggregation,也有的称聚集)、组合(Composition)、泛化(generalization,也有的称继承)、实现(Realization)关系在三、四个类时尚能正常工作,但是当若干模型整合成一个更大的模型时,则变得庞杂而难以准确表达,设计者可能认为自己的设计没有问题,但没有足够经验的开发者大抵会碰到艰难取舍的局面。通用语言关注对象是聚合根、实体还是值对象,它们定义都非常清晰,边界明确,几乎不会混淆,聚合之间通过事件-消息进行交互,即保证了整体的有效和完整,又维护了单个聚合的准确和凝聚,实体和值对象依赖于业务上下文。因此新的设计方法原则上我们只需要关注这些信息:

  1. 聚合及其中的聚合根、实体和值对象,包括聚合完成的业务功能和FSM;
  2. 事件和消息,以及聚合之间通过哪些事件和消息进行有向关联;
  3. 领域边界;

从这个角度来讲,理解实体之间一对一、一对多、多对多的关系就变成技术问题,而不再是战略问题。而对于应用开发者,在实现的必经之路上关注到的概念和信息越少越好。

  1. 实现对象包括接口对象(RPC、REST、Handler),数据模型转换和存储对象(绝大多数应用,离不开数据库的支持,尽管数据库可能是NoSQL,甚至区块链),扩展对象(针对接口的扩展配置和实现);
  2. 数据模型,实现数据模型在应用内的路由和流转;
  3. 业务边界,业务边界应该依赖于领域边界;

设计系统(不局限于软件系统)的组织,其产生的设计等同于组织之内、组织之间的沟通结构。这就是康威定律。系统如果按照前台团队、接口团队、后台应用开发团队、领域开发团队、核心技术开发团队、配置部署团队、DBA团队来划分,系统整体大概率就会形成传统MVC的分层开发模式;系统如果按照业务边界划分,开发者按照一个个业务目标把自己负责的业务做成小的功能模块、小的系统的话,系统整体就会逐步形成微服务的架构特征。分层开发模式的弊端是比较明显的。很幸运的是,辅助交易系统从开始进行技术和组织规划就基本贯彻和坚持了按照业务垂直划分的细胞式分工体系模式。每个开发人员负责一个代码库,一个代码库对应一个功能模块,一个功能模块对应一个主要的业务领域同时对应一个需求和开发负责人。横向集成由设计与质量团队总体负责,通过CWAP技术框架实现若干模块的集成部署和启动,确保集成遵循设计规范,部署不出纰缪。在如此分布式开发体系之下,虽然增加了成本,但也带来了巨大的收益,组件模块能够保持独立演化,将代码冲突扼杀在摇篮里,在发生必要的需求调整时,能够借助技术手段快速的评估影响范围,并通过最小的代码改动屏蔽对整体的冲击(前提条件是所有开发人员严格遵守这一开发体系,不开发与自己功能无关的业务代码,严格使用API获取依赖的业务数据)。架构体系中的横切关注点也就是支撑域,例如DEP交互、IMT交互、数据库交互、日志交互等宜交由技术能力强的开发人员单独负责,优先完成通用域的开发工作,例如:用户、机构和权限等。在这个分布式演进的架构体系中,组件模块的融合、重组和分裂是必然的,目标是最终减少组件间的两两依赖关系,保证业务功能模块的独立演进能力。优秀的架构恰恰体现在人员分工与领域驱动的组件划分完全匹配。通过化整为零,分而治之的方法,确保开发人员权责明确,充分调动大家的积极性,保证开发的进度和质量。架构是不断演进的、必须不断适应和满足业务需求,演进的最终目标是几乎消除不必要的组件物理上的两两依赖关系,组件能够完全独立部署,这不就是我们希冀的微服务嘛。从这个角度来说,微服务其实不必刻意强求,只要按照这套模式发展,在技术条件允许的情况下(技术管理委员会授权),任何一个系统最终都会演化成微服务的架构体系。从这个意义上说,贸然直接尝试微服务是危险的,进程数会爆炸,对部署提出了严峻的考验,NTPII就存在典型的问题,这会让初次部署变得非常痛苦。不断演化,小版本快迭代,才会将压力通过时间换空间的方式加以释放。

城市规划的核心内容是对土地使用的规划和管理,建筑和城市设计的核心概念是建筑的尺度和街区的尺度。宜人的尺度,灵巧的空间变化会让办公人员身心愉悦,居住舒适宜人。良好的街区让漫步者能够呼吸新鲜空气,感觉轻松,孩童能够安全快乐的嘻嘻玩耍。土地的合理规划使得职住平衡,商业优良,工作者不会为拥堵而烦恼,不会为生活品质而妥协。尺度对于软件工程来说就是指软件需求和设计的粒度。如果说架构设计师负责土地的规划和管理,那么设计开发者就可以以建筑和城市设计者自居。BA帮助整理和分解需求条目,使之能够以一个较为合理的轮廓展现给相应的开发者,按照新范式的思想,需求最佳粒度就是系统业务(产品)的每个具体应用场景,例如用户消息中心(盒子)接受消息场景、消息中心发布消息场景、清算关系维护场景、成交明细查询场景和成交行情实时展示场景等。每个场景配套展示若干主要业务规则,以方便开发人员更好的理解交互需求。开发团队的规模必须是合理的,一个开发小组适合以3人为宜。3个人就可以相互辅助,一个组件模块上分饰开发者、审核人和测试者角色。3个人所负责的业务领域最好是紧密联系的,以减少与其它小组的沟通成本。合适的粒度不仅仅要求开发,也制约设计,要求领域设计提出的问题子域最好能够匹配单人开发,3人协作的模式,不宜将问题复杂化。

软件的复杂性往往是业务复杂性的直接体现,所以结合企业发展脉络,实际业务情景,组织领域设计与微服务实践是必由之路。领域驱动的微服务设计不可能减少开发者的代码工作量,领域驱动设计的核心目标是允许在系统建设过程中,以更快的速度,以更高的质量和稳定性响应业务需求的复杂变化,让系统演进的曲线变得平缓可控。将业务的复杂性尽量的封装在领域模型之内。这就为具体的实践提出了如下要求:

  1. 落实技术实现和业务实现的分离,将在领域的设计与开发过程中不要考虑物理意义上的数据持久化;
  2. 尽量少的提炼技术和业务概念,作为开发者并不需要向其它开发者秀自己的知识容量;
  3. 正确划分通用域、支撑域和核心域。集中优秀的资源重点解决与业务有关的核心域;
  4. 领域模型核心接口的设计理应基于业务领域来识别,拒绝基于UI交互来设计,在应用层为UI定制特殊的适配层,以提高系统的扩展性,增强系统适应变化的能力;
  5. 应用层通过命令模式与事件行为进行适配,通过消息监听器将行为结果以报表概念持久化,以便于业务查询需求设计与开发变得简洁和高效(CQRS原理)。命令可能来自UI触发、消息事件触发或者定时任务触发,一个命令只触发一个事件,当一个事件能被多个命令触发时,说明事件本身需要拆分或者重新梳理。

如此复杂的分布式系统,如此复杂的分工协作体系,当期的DevOps是远远不能满足项目建设要求的,当前的DevOps重视构建和运维,理念上与交易中心等金融机构的组织架构背道而驰。金融机构强调稳妥管控,DevOps强调自治自助。金融机构强调权责明确,DevOps强调使命协作。开发管控和设计监督是被忽视了的重要环节,所以辅助交易系统建设过程中也在未雨绸缪,稳妥推进,目标在保质保量完成交付辅助交易系统同时最终实现一个强大自主,能够满足项目组实际需求的设计与开发管理平台,搭建起一套集持续集成、设计与开发监控的开发环境,降低开发总体成本,保证系统交付质量。传统的DevOps以CI为核心,设计与开发管理平台以Component为核心。主要实现如下功能:

  1. 版本管理:每日组件迭代信息、组件发布历史记录,以便于追踪历史和控制风险,确保缺陷修复与发布版本一致,自动更新构件依赖,尽早发现依赖问题;监控组件发布对进程的影响,自动审核发布内容是否规范,通过扫描组件,控制代码质量缺陷,不合规版本自动拒发,同时评估开发工作量,为项目管理提供切实依据;
  2. 组件分析:分析组件的依赖关系,识别组件与数据库和数据模型的关系,分析组件与接口的关系,尽早发现不合理的依赖设计,图示展示组件的实时设计模型,关联组件与需求条目的关系,展示接口的使用教程,方便需求影响范围和影响细节评估,为技术管理提供切实依据;
  3. 领域设计:为领域设计和应用提供资源和技术支持,按照新范式展示领域模型和领域组件,以方便领域构件使用者了解构件相关信息,自动连接领域资产库,为领域资产管理提供可行手段;
  4. 应用设计:根据CWAP技术框架原语,自动绘制业务架构图和业务流程图,为概要设计提供实时可视化替代技术方案;
  5. 开发激励:通过一些巧妙的人性化的激励机制设计,以活跃开发者,奖惩并举,提高开发效率、落实审查制度,保证组件开发的质量。

只强调自治不强调管控是软件业自欺欺人的表现,正如政治领域只强调民主不强调集中一样。当今的软件领域过分强调知识分享和软件开源,如果开源分享无利可图,谁还愿意持续开源分享?到现在为止,我们都没有谈到具体的技术细节。实现通用语言的技术手段和实现新范式的技术手段是一致的。这个世界到处都是套路,领域模型本身就是寻找一种套路,将业务发展构建在套路之上。构建特定问题域下的领域模型并不依赖于Actor模式、如果使用普通类能够解决问题,那就应该使用普通类、但是当业务对象中存在状态和并发的问题子域时,Actor实际上是目前最简单、最优秀的解决方案,限界上下文之间、领域模型之间按照新范式设计,那么Actor无疑也是最佳解决方案。这里我们推荐一种架构方案:AKKA+AxonFramework+Disruptor+Guice。AkKA的开发原语过于灵活,如使用编程语言一般灵活简单,Actor建立在更高的抽象层次,开发人员几乎不用关心集群和分布式有关的知识,就可以完成完全分布式并行应用的开发工作。允许应用开发者直接使用AKKA提供API可能是一件十分糟糕的事情,结合设计与开发协调一致,开发与模型实时互相映射的需求,基于AKKA的架构再造是必不可少的。当前商业和开源环境没有先例,AKKA犹如一颗闪耀的新星,埋头许久,刚刚崛起。

最后回到开始的培训课程。在这一天紧张而丰富的课程中,我学习了如何让一个5-6人的团队精诚团结,积极沟通,通过在白板上使用便利贴的方式,共同剖析共享电动车的产品需求,利用事件风暴方法逐步建立基于DDD的领域模型。事件风暴(Event Storming)是快速识别领域模型的有效方法,它是一种结果导向型的设计方法,重点关注将来的结果。传统的建模方法则基于行为和对象的静态数据模型建立模型,在事件风暴面前简直弱爆了。

Event Storming is a useful way to do rapid "outside-in" domain modeling: starting with the events that occur in the domain rather than a static data model. Run as a facilitated workshop, it focuses on discovering key domain events, placing them along a timeline, identifying their triggers and then exploring their relationships.

透过事件风暴的形式看本质,从系统业务角度出发,其主要过程包括:

  1. 梳理出系统的主要业务功能;
  2. 提取系统的用户、主要业务场景和业务流程;
  3. 根据业务流程识别出领域事件并按照时间序列进行排序;
  4. 针对领域事件发掘命令;
  5. 针对领域事件和命令进行聚合和子域(subdomain)的识别;
  6. 识别的实体、值对象及实体关系;
  7. 针对领域模型识别限界上下文(Bounded Context);
  8. 持续探索,不断优化设计结果。

希望本文能够为系统的设计工作提供助益和借鉴。

意义和总结

好的架构和设计都是慢慢迭代演化出来的,领域资产也是逐步积累起来的,好的领域构件重点在于用,而不在于量,好的架构的设计必须用一切手段提升沟通效率,比如WIKI、比如TEST,能2个人讲清楚的事情,就不要拉更多人,每个人每个模块都分工明确,出了问题知道马上找谁,避免互踢皮球。技术工程师追求热门的技术潮流并没有错,但脱离公司业务实际,运用新技术,无疑会火上浇油。设计即编程这条老路已死,我们需要新的思想、新的技术、新的架构和新的血液以追寻伟大的理想。在面向对象的道路上,我们必然会走很多弯路,我们本以为运用设计模式,使用C + + 和Java语言编写出的程序就是面向对象的程序,我们也曾以为使用了Spring MVC框架编写的程序就是面向对象,今天回头来看,只怪当初是多么肤浅和轻狂。

磨刀不误砍柴工,好的士兵需要有好的兵器,PFD的核心宗旨是以开发者为中心。只要坚持开发即设计思想,以建设设计与开发管理平台为依托,DDD、MSA和CI为手段,遵循新范式分析框架,走新方法这条路,我相信这条路我们一定能够走得通,并且建设出伟大的软件系统。

参考文献

ThoughtWorks技术雷达 AKKA入门与实践 领域驱动设计-软件复杂性应对之道 微服务的理论基础-康威定律 微服务设计 响应式架构 设计已死