导读
本次分享的主题主要是关注两个方面,当我们面对一个业务的时候,一方面怎么使用领域驱动去指导去做整体的架构的设计,怎么进行微服务的建设(排除基础设施等的应用架构);另外一方面是结合现有的软件设计生态去实现系统,在软件实现上不在强调聚合根、实体、仓储服务等,而是强调边界、状态和基本的设计原则(开闭原则、单一职责原则、依赖倒置原则、接口分离原则、迪米特法则等)。
从业务到战略设计
哈啰本地生活的业务主要是售卖电子卡券,但是要解决的业务问题基本上涵盖整个电商,本身具有一定的复杂性,本次主要选用本地生活的业务和大家讨论一下。
业务分析阶段
这个过程不同的人可能会有不同的介入方法,总的目的就是摸清我们要做的系统最终要提供什么功能。在这个过程中可以从几个核心的W入手,Who(谁)、Why(为什么要这么做)、What(要做什么)、How(怎么做)去拆解问题。
先看一下下面这张图:
这里通过角色整理了一下现在系统的用例,可以得到一个大致的业务边界,比如说BD这一块可以明显的感觉到它是特定的人处理特定的问题,并且处理的问题的方法自己就可以完成,不需要依赖其它的东西,跟其它的系统耦合比较低,我们可以首先把它拿出去。
通过上面这张图可以看到不同的人处理不同的业务,更大程度上趋于产品视角,更关注的是解决的不同问题。当我们把用例进行一定程度的归纳就得到了问题空间(下图整体对齐商家、消费者、BD、供货商、渠道商、业务运营的需求),这个过程中需要和产品不断的讨论,因为它是一类业务问题的集合,要尽量贴近我们业务发展的需要。
为什么要强调问题空间
一个问题空间往往代表了一类需要解决的业务问题。业务空间的划分可以在一定程度上指导我们团队的划分,让业务关联性最强的成员形成一个小组,能够解决大的协同问题,并且更好得为目标服务。
假设我们的业务团队按照营销域(现在的中台)来划分,那么这个小组的成员更多的是关注怎么去发券,怎么去做营销活动,会更加关注能力本身,而不是更大程度上的用户增长或者交易促成。对于中台的建设如果说这种基础的营销、交易、商户等能力是V1.0,那么提供业务问题整体的如商家入驻、用户增长等解决方案应该就是V2.0,中台不同能力的建设是根据公司不同阶段来决定的,但是对于我们业务来讲,我们再按照业务方的交易、商户、等中台的方式来划分可能就不太合适了。
所以在一定程度上问题空间可以给我们的团队划分提供一定的参考价值,让我们的团队更加关注业务问题,让我们的成员更好的跟产品进行协同,而不是仅仅关注某一领域。
从问题空间到系统架构
当问题空间明确了,我们要做的业务基本上能够达成共识。现在要做的就是进行系统架构的规划,对应我们就是怎么进行微服务的建设。我们看上图交易促成中有下单,在供货商对接中也有下单,虽然分属不同的问题空间,但是有时却需要共享相同的功能;再比如在做用户增长的时候,我们可能会想到通过一些营销手段去拉新,而在交易促成中也需要营销。
所以问题空间离指导系统的落地还是有一定的差距,这里就需要我们分析不同业务实现的业务流程,最终指导我们进行微服务的落地,用DDD的行话讲就是问题空间到解决方案的落地。
下面给一个之前做渠道商的事件图,这里就是结合我们已有的的业务知识做一个边界的抽象,这里需要一定的电商业务积累,当然这个过程我们不一定是第一次划分就会合理,而是可以先假设,然后在分析其它业务流程的时候看能否吻合,然后看是否需要抽象出新的上下文和形成新的边界。
当我们分析完不同业务流程的,可以得到一个对于应用架构的一个大致的认识,那么基于对上下文的梳理,可以形成一个战略设计图,如下:
在通常情况下一个上下文可以映射一个微服务存在,指导我们的系统建设,但是也一定不要固守成规,系统的发展是需要经历一个过程的,我们不妨把它当做我们后期可以知道我们进一步拆分的手段。我们在实际落地的时候,可能还需要考虑稳定性等因素,如考虑到开单作为最核心保障的,可能会以一个独立应用来存在,在落地上与订单管理服务拆分开;另外有些架构方式可能会抽象出事件中心去处理事件,以应对消息量QPS上万等很大的场景;还有可能会建设文件服务、任务等,以应对可能是IO密集型、CPU密集等;甚至在系统没有那么复杂的时候一个上线文作为一个包也是OK的,符合当下的架构才是最好的架构嘛。
软件设计实现
使用领域驱动,更应关注核心
提供了一个很好的实现方式,大家按照这个模板来写能够保证风格一致,而且能够对我们的代码有一定的控制力,所以从整体实施上来讲,在保障我们项目质量上,确实是一个比较不错的方式。对于深谙领域驱动这一套的同学可能在实现时会得心应手,但是对于刚接触的同学在前期上手的时候确实也会有很多疑问,这种理论储备的复杂性,也是软件设计的复杂性。另外更多的时候是希望是自己写的代码,对于后来的那些不熟悉领域驱动的同学,在接手后就能很好的理解我们的应用结构,我们的编码方式和习惯,这样软件设计的稳定性和持续性会更强。
基于此希望能在领域驱动这套大框架之下,关注其中更加核心的设计思想或者原则,让它给我们提供最核心的参考标准,让实践过程来的更加简单。
内聚是一个很好的标准
谈到这个,可能有些同学会想到领域驱动的几层边界,当应用(系统)这个大的边界确定了以后,DDD的思想主要在分层和下面的聚合/实体这两层边界。这里给出的分层架构会和大家书里看到的有差别,另外后文会弱化聚合和实体。
按需分层
下图是我们现在使用的一种分层方式,对于Depenency和Infrastructure这两个分层,比较好理解,一个是所有外部依赖的入口,可以很方便的知道我们依赖外部哪些应用,另外一个是我们访问基础设施(如存储)做一个收口。后期可以很方便的帮我们梳理系统依赖项,同时也可以把Dependency看成一个防腐层。
这个分层实际上大家编码的时候最容易产生疑问的是这个逻辑该是放在应用层还是领域层?这里不给标准答案,我说如果你分析这个是完成这个功能必不可少的功能你就放在领域层,要不然你就放在应用层,即使现阶段分析错了也没有关系,关键是你的逻辑要写的有条理,当后期你发现另外一个应用层入口也需要调用这个领域方法,而且也需要这个功能的时候,那么你自然就会想到这个是不是应该下沉到领域层了。
弱化战术的形式,简化编程
领域驱动中战术一词主要是在指导我们怎么进行落地,这里所说的弱化是说尽量避免引入战术本身给系统实现带来的复杂性,让别人接手的时候能够无差别的理解我们的设计方式和思想。
实践中我们可以弱化聚合根和实体,弱化聚合根和实体并不是说我们不在思考系统中的逻辑关系,而是说我们不一定需要书中标准的去建具体的实现类。聚合根和实体的分析本身是系统逻辑关系的分析一种体现,在工作过程中相反我们不仅要分析,而且最好能够和产品对齐,我们需要通过它的Id来分析上下游的关联关系和状态扭转。这种逻辑分析,能帮助我们分析整个业务上下游是否能够串起来,是业务能否够实现的关键。
当我们弱化聚合根和实体,在我们项目中的一种实践如下图,不在使用传统的方式将一个聚合根和一个实体跟一个Id相对应,而是一个Spring接管的Singleton对象,更好的与当下的spring技术栈相结合,核心思想是一个包代表一个聚合,其中Aggregate主要作为一个状态变化的入口,对多个Manager的行为具有控制力,同时Manager能够直接给上游透出数据查询等能力。
多思考设计原则
当我们将实体等的初始化问题交给Spring来处理,我们也不在用Factory去创建他们了,其实Factory是一种很好的封装实现,我们在代码实现的时候不应该局限于官方对于领域驱动的实践,相反更应该主动去思考设计原则,如用接口去屏蔽细节形成边界,类的功能做到聚焦,形成单一职责等。
关注分治和抽象
分治
分治本身是分而治之,也就是上文说的边界,关注领域边界、系统边界、分层边界、包(聚合)边界,帮助我们把复杂的问题简单化,同时也能更好的遵循单一原则让系统更加稳定,同时也有利于某一领域的持续深耕。因为其复杂度更多的是在前期的分析阶段,也是提升自己架构思维一种很不错的方式,所以比较推荐。
抽象
一个大家都知道的词汇,我们应该更好的将它落实到我们的软件实现中去。在实践过程中比较推荐的两种切实可行的方式,一个是流程抽象,一个是概念抽象。
流程抽象:当我们写业务的时候是否能够提炼出流程中核心的1、2、3、4步,然后用方法替代它,编码过程中推荐一边编码一边重构,以防止为了抽象出流程不知道怎么写了。
概念抽象:比如像营销中有加价购、满减、优惠券等,这个时候上游更希望的看到的是一个营销活动的接口,那这个时候就是对于概念的抽象,底层怎么把这一个个能力积木组合起来上游不在关心,增加扩展性的同时也提高了系统的稳定性。
(本文作者:刘政)
本文系哈啰技术团队出品,未经许可,不得进行商业性转载或者使用。非商业目的转载或使用本文内容,敬请注明“内容转载自哈啰技术团队”。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。