戏说领域驱动设计(七)——限界上下文——延伸
上一章是真的不好写,吃奶的劲儿都快使出来了。本章计划是查缺补漏,对BC的内容进行补充。您也看到了,战略设计作为DDD中最重要的一部分,只写一节就完事儿也差点意思。不过您也别期望太多,咱这个文章本来就是自身经验的总结性,我是假设您有一定的DDD基础,所以不会按照书的那种程度去写,否则还不如直接看书呢。再说了,文章的读者什么层级都有,我要是再啰里啰嗦把书的内容都事无巨细的都写下来一是会造成您阅读的负担,二是没新意读起来也没劲。哪没有类似的东西啊非得读我写的。废话不多说,走起。
一、BC中到底包含哪些内容?
前面我们已经提过,BC其实更偏向于分析模型,还尚未到达系统的开发阶段。即便如此,一旦开始了BC的设计,就需要考虑其应该包含哪些内容。咱们本章就这项内容进行一下系统介绍。噢,对了,有一个事情忘了说了。您在进行BC设计的时候必须要以BC为基本单位啊,千万别陷入每个实现类或接口的细节中,那种东西最多组个小会儿就OK了。
1、BC设计阶段
如果您是老读者,应该看到前面我画过一些图,那个可真不是全量的BC内容。BC设计要包含三项任务:1)定义,即有哪些BC;2)识别BC中的领域模型是什么;3)确认BC间的关系是什么。而前面我只提到了BC的定义,实际上2和3两项也是非常重要的两点,请务必重点关注。
领域模型:这里面的领域模型可不是指类或接口,更多的是指如业务实体、实体间的关系、业务流程定义(设计阶段我比较喜欢用时序图建模,您呢?)、业务说明文档等。举一个已经臭了市的案例:论坛。下图为论坛系统中“贴子管理”限界上下文的分析过程中所定义的两个实体和实体的关系。业务流程定义不画了,建议使用泳道图来为业务流程建模,显得专业。
并非领域模型的定义必须细化到业务实体的层次,在实体很多、关系复杂时到达这个粒度还是很有必要的,具体需要看业务的复杂度来决策(又是视情况而定,是不是准备拍桌子了?您不觉得这很正常吗?做一个合格的研发人员必须带着脑子工作,谁再说自己是码农我跟谁急。对自己的工作都不尊重,你还想别让人尊重您?)。针对领域模型的定义,这里面有一条非常重要的原则:您定义的领域模型必须以BC为边界且不能有任何歧义,一旦完成定义,涉及此BC的所有人员包括客户、开发、需求等,都应该明白您讲的领域模型的精确含义。
关于BC间的关联关系,书上说的有点晕,把我都给整不会了。您先看一下下面的图,有一个感性的认识。这东西在书上“Context Map”,翻译成中文叫……,我们还是叫“Context Map(简称:CM)”吧,免得乱。总而言之,CM定义了BC及BC间的交互模式。“OHS/PL”说白了就是指服务提供方以什么协议提供其接口供消费者使用。想传输速度快搞Dubbo、gRPC,想简单易懂搞RESTful,如果是老一点的系统就整Web Service(我是真心不推荐再使用这个协议了,消费者老难受了)。反正不管搞什么必须结合您公司的背景及技术普及度,比如您非得整一个COM+,那我怀疑您这几年可能干得就不是IT的事儿……
ACL一般指“防腐层”,这可是大用啊。在系统的交互场景中,被调用方所提供的数据一般需要转换成强类型数据比如DTO或者对调用结果进行验证,这类工作就是防腐层要干的。所以我认为,但凡需要与其它BC交互,就必须得有一个ACL层,也强烈推荐建立这样的开发规范。
至于说在CM中其它的规则,比如什么“合作(Partner)”啊、“尊奉者(Conformist)”啊,您知道有这么个东西就行了,也不必考虑这么多。毕竟现实中的关系更为复杂,合作仍然需要以友好为前提,万事是可以商量的。针对“共享内核”,特殊情况下也许可以用。现实中当做类库还凑合,也不是特别推荐,改起来忒麻烦。要是一个团队还能说得过去,一旦跨团队就容易拉跨。图中的U表示上游(Upstream),D表示下游(Downstream),说白了谁调用谁。调用方是下游,服务提供方是上游。针对BC间的交互我们下面会专门开一个小节做详细展开说明。
2、BC的物理结构所含内容
1小节说所的内容主要是指在宏观上面BC的逻辑定义和设计。当进入到BC的物理设计阶段后也需要限制其所包含的内容(说白了就是从代码的角度看每个BC中要包含的东西)。事实上,我们后续所讨论的各类对象比如“聚合”、“领域服务”都是以BC为单位的,也就是某个BC内有什么对象。一般来说,BC内会包含三类对象:基础设施、领域开发模型(特指类、接口等,也就是在开发阶段中设计的各种类型)和UI,让我们细聊一下。
- 基础设施:一般是指用于完成如远程调用、数据库访问、消息处理等各类与业务无关的服务或工具。以当前IT界的技术发达度,几乎所有的基础设施都有对应的工具或框架尤其是在Java圈,各类优秀的组件信手捏来便用。不说别的,光数据库访问类框架就快1万个了。
- UI:现在不都是说微前端吗?讲究以微服务的方式落地前端。这种方式还有另外一个好处理:沟通成本低。一个团队能完整的搞定一个垂直功能,不用天天和其它部门或团队扯皮,少死多少亿个脑细胞。不过按这种方式搞的时候您也得注意,需要有个能对UI进行全局管理和拍板儿的带头大哥,别回头8个团队使用8个样式,诈骗网站都比您这个专业。说句题外话 :据说有些类似抢票的系统,其架构比人家官方的架构还牛掰,你能不服?
- 领域设计模型:我特意加上“设计”两个字,就是想强调这里的模型是指类、接口这种的。DDD中强调使用“对象驱动设计(ODD)”的方式进行系统落地,以这种方式开发时领域模型种类数量也比较多,请参看如下图。
重点! 在软件设计阶段也需要有建模过程,此阶段的建模粒度更为细腻,要考虑至每一个类型。常使用的建模方式包含两种:使用时序图描述领域模型间的交互(哪个类调用哪个类的哪个方法),这叫“动态建模”;使用类图表达实体定义的过程叫“静态建模”。一般在开发阶段的建模成果不需要形成很正式的文档,能说明问题即可。 |
三、BC的粒度
上一章中我们已经讲过了BC的粒度,但我琢磨了一下,为了凑点字数还是得再说一次,这个东西过于灵活,很多时候需要依据主观的判断。说到这儿我又想吐槽两句(咱写文章的风格就是喜欢常态化的歪楼),您说为啥有人是高级程序员,有人是初级。我觉得不仅仅因为经验,有些人干了10年仍然也就是初级开发的水准。高级的人才需要有强烈的主观能动性,一方面在行动上,另一方面在思维上。所以灵活不是障碍,这世上也不是好多事情都有规则可言的。再说了,干咱这行的大部分人都上过大学(本人除外),从小到大考了那么多的试哪一次不需要自己思考?怎么一到工作上就不愿意动脑了呢?有这种情况的人,赶紧去找个马桶坐上面,左手一根儿烟右手一瓶酒,回顾一下所走过的工作人生,是否会为自己的碌碌无为而悔恨。
谈到BC的粒度,可以按这么一个层次开展:最小的为聚合根,最大的为所部署的系统。有人搞一个接口一个微服务,按技术区分不同的BC,这种情况多数是因为对性能的妥协。比如说您要搞共享单车,需要每辆车每5分钟报告自己的位置,这里的同步位置信息接口出于性能考虑可以是一个服务一个。而正常的场景下面不建议这样搞,因为在BC的划分中,一个最重要的标准为“自治及完整”。过细的粒度会导致业务概念丢失。同样,如果有可能也不建议使用太大的单体,这样经常会导致在一个BC中出现概念冲突的情况,也就是书上说的“通用语言”混乱。假如您的团队真的不是资源捉襟见肘,也尽量把整个系统分割一下,哪怕粒度粗一点也行,总比没有强。软件设计中经常会见到两种状态:一是几乎没有设计;一是设计过度,请您举起双手在内心中呐喊:“中庸、中庸、中庸”,这个是我们在设计时所追求的终极目标。
还有,还有,您在设计过程中要时刻的提醒自己:从业务角度进行划分。咱是DDD,中文叫“领域驱动设计”,不是“技术驱动”或“页面驱动”,您得始终围绕着业务转。我知道有些人是微服务的极端拥护者,这门技术先进性更强是无可厚非的。单体相对来最大的问题就是各模块无法独立演化、系统内部关系混乱纠缠在一起。您可以在网上找到很多的微服务划分规则,DDD也有自己的那一套这些我们前面也介绍过。这里想重点提醒的是:您在划分微服务的时候要注意,原来是大单体,不要出现划分后变成了小单体的情况,要不然就是开发和运维的噩梦。原来一个大单体维护起来虽然麻烦点,但都在一个系统内。划小后从物理上有了隔离,不仅定位问题更麻烦,开发间的协作难度也更大。解决这个问题的方式包括两个方面:一是保证每一个BC的业务完整和自治;二是BC内部的各对象间的访问要做好严格限制,具体规则后面会有详解。
三、BC间的交互
BC的交互在前面的内容中大概写了一些,尤其是引入了“Context Map(简称:CM)”的概念。因为BC间是物理的隔离,所以需要使用各类框架或协议实现远程交互,生活在21世界的您应该也用过或至少知道一些远程调用框架或工具(听说过Feign不?)。总的来讲,了解BC间的交互关系很重要,它可以帮助您从上到下去俯视整个系统的结构(多说两句,我们日常工作中需要注意一下工作的方式方法,看待问题尤其如此。需要学会从两个维度去看:一是高度,古语有云‘欲穷千里目,更上一层楼’,这里面的深意您琢磨琢磨;二是广度,下棋还知道走一步想三步呢,您工作也是这个理儿)。不过文章写到这分儿上肯定不行,这没法和您交待啊。谁都知道了解BC交互有用,书上也这么说的,但问题是其作用到底有多大?
1、BC设计中定义交互的责任方
我平常办事,就喜欢把责任这事儿事先说清楚了,说明白了,所谓“亲兄弟明算帐”。这种方式挺容易得罪人,但我告诉你,前期不整清楚,后面打的架才厉害呢。职场可不是讲兄弟友情的地方,这里面涉及到各种利益比如工资利益、事故利益,职场即战场,竞争很残酷的。分明白了谁主责谁配合,出了问题大家日后也不会因此脸红,先小人后君子没毛病。说到这儿,您应该知道哪个重要信息也需要写在CM上了吧?“BC所属团队及负责人”。
2、BC定义了交互路径
说到这个事情,估计您可能也有和我一样的刻骨之痛。服务多了后,各种交互乱七八糟,没人能讲得明白。每人都各自只管自己那摊子事儿,谁都不负责。轻一点的也就是管理混乱,重一点的搞不好就是个事故,比如微服务的调用链中出现了业务环。有些时候,明明知道是上游服务出现了问题,可愣是找不到到底是哪个下游服务调用的。如果说都是部门内部的系统还好说。如果涉及到外部的调用呢?如果外部还特别的多呢?虽然说大部分情况可以通过企业网关进行限制,不过如果能在BC设计时有个路径说明是不是会方便很多?后续想看看都和谁交互了,直接一上图岂不美哉?当然了,这种理论上的东西谁都会说,想要到达这一步肯定需要对CM做实时维护,这就是爱美的代价。实践中,如果不能保证所有细节,核心路径至少还是需要的。
3、BC定义了关系
BC间的关系有两种:上游和下游。A调用B:A是下游;B是上游。定义这种关系后,您如果对系统进行了修改,就能据此知道可能会影响哪个系统了。不过还是那句话,好是好,得有人维护CM才行。光靠脑子记也不是那么回事儿,好记性还不如烂笔头儿呢。一般来说,作为架构师还是需要有责任维护这么一套东西的,同时也能约束项目组一旦涉及对外调用时需要提前和技术负责人说明,技术负责人再去修改BC设计文档形成一个良好的循环。这里其实也涉及到一个如何处理与上司的关系的问题。假如您是个9级小领导,下面有10几个人儿。一方面要管好手下,至少得有规矩;一方面您也得想想如何治理与上司的关系。最起码您想干什么得和领导说说吧?这世道酒香也怕巷子深,会哭的孩子才有奶呢。想争取点什么您得让老板知道你干的事儿值那个价儿。再说了,您一个9级小领导,上面那人也没大到哪儿去,上有屁股下有脸的他比你还难受呢,啥事你不让他知道不得被穿小鞋儿?再说了,如何与外部系统交互也是有规则、有管控的,您偷偷的就搞了,万一出了事情你看领导不得把责任甩你身上。扯扯就远了呢,咱这儿讲DDD呢,您辛苦也快点把思绪拉回来。
针对BC间的交互方式,一种是常见的远程服务调用如RESTful,另外一种是消息队列。如果条件允许,强烈推荐引入消息队列(此种方式不适用于需要主动获取上游服务的信息的场景)。DDD有一种称之为事件驱动架构(EDA)的东西,简单来说就是通过事件的方式推动业务流的前进。这种方式当然也适用于BC间的交互,因为事件本就是用于BC或聚合(具体概念后面细说)间的通信的,只是我们不用“通信”这个含义不明的词,而是给这种通信赋予一个业务概念即“事件”或“命令”。在BC间使用消息队列通讯基本上已经成为了一个事实上的标准,这种方式可以大大的减少系统间的耦合。
上面已经讲了BC所应包含的三类信息,就此打住不能再加其它的信息了。现实的项目种类多样,各团队性质也很不同,本节的BC说明只是给出了一个理论指导,具个怎么个用法及用到什么程度要学会取舍。反正我个人地使用的时候,就定义出了BC、关键路径和团队信息。至什么上下游啊、详细的调用关系啊完全没用。主要是文档需要随时维护这个事儿就是个不可能的任务。
四、经验分享
个人针对BC设计的经验大概分为三个方面:1)设计文档如何写;2)领域模型如何提取;3)BC间的交互如何设计。
设计文档方面,建议使用层次化的方式编写文档。现实中的系统要比上面的例子复杂的多,所以不可能使用一张图就画出所有的元素,那么就先从总的架构去写,然后逐渐分解到每一个BC中。以上面的说过的电商购物系统为例,请参看如下图。从左到右一层层的进行细化。
其实还是可以再细化的,比如您可以在图上标识出BC间到底使用哪种协议进行交互、领域模型各自的角色及交互细节(一般使用时序图)等。具体要看业务的复杂度、团队人力资源的配置程度等。记住一点:这东西不要强求。有些领导喜欢把所有的一切都文档化,最后还问开发你都干什么呢?怎么进度这么慢。就种情况你就把文档拍他脸上,让看看谓的文档所带来的资源损耗有多大,带来的价值有多小。当然了,话是这么说,有一个高层次的Context Map还是有必要的,至少让团队知道系统的整体结构是什么样的。
领域模型提取方面,如果您是团队带头大哥同时还负责技术,找客户或产品谈的时候建议只用泳道图展示业务流程足矣。别整什么类图、领域实体,你费了半天劲不说其实没人关心这个,把自己还整得怪累的。书上说你需要和领域专家谈BC的设计,总的方针是对的,但我们也得使用合适的方式。我可以很负责任的跟你说两点:1)所谓的领域专家大部分只知道一个大致方向,有些时候他自己都不知道怎么搞,完全是想当然的。这个从0到1的过程,就需要发挥您的三寸不烂之舌并结合自身的工作经验,引导客户把流程整明白就齐了,后续再优化还能再骗点项目款;2)大部分需求方的人也不懂什么是领域实体,您就踏实的把他们的需求转化成文档然后内部分析吧。至于在研发团队内部,找一两个高手按DDD战术指导负责核心业务的设计;非核心或不是那么重要的按工程师的技能水平和工作态度(在技术与态度间取舍时,我选择后者)进行分配,只要不超出BC规定的圈子和团队的规范、纪律,问题总不会大的。需要记住一点:DDD文化的培养成本非常非常高,不是一般企业能负担的起的,更好的方式是建立规范,不服就直接斩首示众。
BC间的交互,比较简单。以现在的技术发展来看,如果使用Dubbo或Eureka等,框架内部已经规范了交互协议或使用第三方工具如Feigh基本也不再多考虑。遗留系统尽量使用HTTP的方式。另外,消息队列也是一种交互利器,在实现系统解耦的同时还可以大大提高系统的吞吐。个人推荐在业务系统中使用RabbitMQ,其超高的可靠性几乎已经是金融类项目的标配。如果涉及对外的交互,建议您建立一个企业网关,一般也是通过HTTP的方式供外部消费。至于Web Service这类的,我感觉是过时的产物了。是不是有些严肃的系统强制要求使用WS不太清楚,我做的系统以垃圾居多。
写在最后,BC的设计是一个系统“分”的过程。但企业级的系统都是“先分后合”。所谓“天下大势,分久必合,合久必分”,您合计合计是不是这么个意思。所以如果你能在“分”的阶段做好,还用担心“合”吗?另外,即使是单体系统也并不影响我们实践DDD指导,我们可以把BC定义为“包(Java)”或“名称空间(.NET)”,并不是强制放在微服务层次。后续如果涉及到性能需求、扩展性需要、运维需求、安全需求等再做“分”的时候,因为前期遵循了BC设计原则,很快就能搞定。单体系统很优秀,可怕的是由于团队缺少规矩和开发缺少责任感,造成系统内部都纠缠在一起。此种现象不仅是单体,微服务架构中也一样,说白了就是大单体变成了小单体,本质上的差劲没变化。