领域建模之道

宏观架构方式:

在做业务架构的时候,往往有两种大类的方式:第一种自顶向下,第二种自底向上。

自顶向下

这种情况往往是架构师明确知道当前问题,直面问题去寻求一种解决方式。这种架构方式往往是在软件生命周期的中期,问题爆发期。对架构师的核心要求是:

  1. 定义清楚问题和目标, 甚至去做一些升维思考, 明确问题背后的问题。
  2. 尤其是明确目标和手段,有时候手段并不是目标。
  3. 分解问题,由轻及重, 由远及近。

自底向上

自底向上核心就是根据用例建立领域模型,一步步演进到逻辑模型,根据逻辑模型指导我们去做应用切分和架构设计。

DDD 指导微服务架构:

我们说好的微服务架构是拆分良好的架构设计,这里我们不做过多阐述,着重于如何做。我们以一个外卖业务为例:

纵向分层:

按照DDD思想去指导架构,通用情况下会把架构层次划分为以下几个层次:

View:

不用过多解释,目前大部分公司都已经实现了前后端分离,view主要还是专注于前端展现层。

BFF(backend for fronted):

支持前端的后端, 因为WEB/ IOT /APP 的展示不尽相同,如果后端为支持此类展示需求必然会过分膨胀,为了维护后端服务的整洁性,BFF承担了很重要的功能。

ApplicationService:

1. 业务服务专注于业务的特异性,提供更灵活轻便的服务能力,依赖更底层的领域服务来提供能力。

2. 针对业务去做一些资源的管理,不同业务做不同的缓存,把业务特殊的需求提到更灵活的application service.

DomainService:

这一层隔离业务, 提供最纯粹的领域能力。这一层承担了很重的复用性要求,必须要纯粹,不因为业务的频繁变化而变化是这一层设计是否合理的重要衡量标准。

Infra:

底层中间件

核心原则:

  1. 只由更上层调用下层,除领域层禁止同层调用。
  2. 领域层尽量不要出现互相调用,尽量由上层application-service去组合领域层提供出去。

为什么这么做?

当别人告诉我们某项最佳实践之后,我们总要想想为什么?其实问题往往集中在ApplicationService 和 DomainService上,这与传统的MVC到底有何本质不同?

这里有几层意义:

第一层: 高复用.

我们可以看到无论是视频领域,还是换到大众点评。系统中业务无关的核心领域,在这种划分方式下能达到最大复用。

第二层: 高内聚低耦合

所有的改动,都只改动个别模块没有对其他领域模块造成修改,影响范围隔离到了最低。

防止教条:

当一个团队只是维护一个领域的服务内容时,没有更上层复用需求的时候或者性能要求的时候,把ApplicationService 和 DomainService合在一起也没啥大问题。因为ApplicationService的主要功能是领域的组合描述,如果这个能力不需要了,开一个ApplicationService可能ROI不会太高.

例如我们的团队只维护评论一个领域,这样的设计也并没有什么问题:

横向分层:

横向分层在这两篇文章很高频的提出了基于业务领域去建模,这样的建模方式有几点好处为大家一一道来。

内聚性强:

往往业务的变化是出现在领域内的,例如一个人他年纪增长,增长的是人这个实体的身高,而不是院子里的大树。应对于我们程序中的情况,我们的用户账号有很多种状态分别为正常, 封禁, 注销。如果我们不把用户的状态封闭在用户账号信息领域服务内。而是在各个业务事务脚本中,每次增加一个状态要对各种事务脚本中进行修改。

微服务场景性能高:

因为内聚性强, 我们的应用的处理流程往往封闭在内部,不会出现很多的RPC调用,性能相对比较高。

易于理解记忆:

人类更善于归纳总结, oo的一个很大好处就是与现实世界联系紧密., 而领域建模也是OO思想的充分体现。

案例分享:

笔者在工作期间就遇到过一些团队因为草率架构,导致团队每一个小迭代都要花10-20人日的时间去完成。在这个过程中,笔者作为架构师帮团队重新梳理了业务,进行架构迁移,最终初步完成了整个改造工作。

这个业务是一个视频网站的账号业务,由于初期业务迭代压力大时间紧,架构年久失修腐烂严重,整体看起来相对比较混乱:

其主要的问题有以下几个方面:

横向分层不合理(几乎没有层次):

user-service(提供网页端的部分功能) 这样业务迭代非常快的应用,被太多业务所依赖,改动频繁影响的内容过多。

纵向分层混乱:

可以观察到原有架构纵向分层毫无章法。

有按提供端服务种类的ott-service(支持ott服务), user-service(支持web服务), user-login (架构图里没列出来,其实是用于mobile服务的)。当然这个有一部分因素是因为横向分层不合理,如何横向分层,在application-service 搞的话其实问题不大,当然这更应该放在BFF层。

有按具体事务行为划分的user-profile-service(安全中心,主要是修改账号密码)。

有按领域行为划分: third-party-service, risk(风控)划分的。

问题1: 没有按领域划分:

不按领域划分应用就会有一个问题,霰弹式修改,这里我举两个个例子。

实名制需求, 未手机实名制认证的用户不得操作自己的账号, 这里如果不把逻辑封闭到账号领域就会出现,所有事务划分的应用都要修改一遍code。

当然这边原有的开发同学想到了去复用,但是因为复用不合理引发了其他的问题。

问题2: 划分颗粒度不合理:

这里可以看到这边为数不多的按照领域划分的两个应用,third-party-service和user-service.

然而问题的关键是当前的这个账号业务,三方账户是与用户的原生账号所绑定,基本一同出现。所以这两个领域放到一起更合理一些。

复用不合理:

之前一直说防止霰弹式修改,所以一些同学把重复逻辑进行抽象打成了jar包,这其实并不能解决人力花费很大的问题。因为依赖应用更换jar包依然需要重启发布,维护jar包本身也非常耗费精力。

这种情况下我们首先需要合理分层和分块【BFF层就暂不展示】:

领域层:

领域层的下沉关注点还是哪些下沉,一些行为是放在应用层进行组合还是放到领域层。放到应用层关注的是灵活,放到领域层关注的是复用。

这边以三方用户绑定为例,在三方用户和主账号没有合并的时候,绑定用户必然是在上层应用服务层做的。

但是合并之后,是放在领域层,还是业务层。主要看一个新的应用层服务用到领域层的数据会不会有非常灵活的要求,例如流程中段有特殊逻辑。这边无论是来疯这个应用还是优酷本身,都是没有此类特殊情况,所以把解绑和换绑的逻辑沉淀到领域层。

应用层:

应用层可以按照一些业务性质去做拆分。切记不要同层互调。

拆分过程中要考虑把逻辑一样的业务放到一起,不要重复代码。

如果业务逻辑不会出现分叉,考虑打jar包复用,但是一定要做好开闭,不然频繁打jar包会很痛苦。另外做适当冗余,有利于业务发展,合永远比拆容易。

例如登陆方式都需要验证用户账号状态,触发风控,生成登陆token,下发toke, 于是我们把逻辑封装。后来假如出现了一类外接账户,验证方式和登录态生成都是外部账号提供,那么就要把逻辑拆开,当然如果程序员做好了开闭当我没说。

迁移方案:

宏观原则:

  1. 从容易或者性价比最高先开始,但是切记要做好灰度和开关。
  2. 切记迭代要快,否则可能导致分支合不上去,颗粒度要细,局部过程需要提高人手。
  3. 与产运商议整体节奏,做好计划。

迁移策略:

  1. 首先沉淀下层的领域逻辑,这边不会影响到外部业务。只需要注意到快,与业务协同。
  2. 上层的应用层做拆分或者合并非常困难:
    1. 只是对客业务,因为有统一接入层通过MTOP或者TOP修改相对比较容易。
    2. 如果是对内,本身就是通过注册接口的,拆相对容易,先通过路由的方式做proxy(绞杀者模式),一般合的话动力会很不足,如果业务方相对较少的应用,会去做合并,多的话推动起来非常麻烦。
  3. 关键的接口(类似于登陆,验票)要做灰度转发(绞杀者), 根据用户去做灰度路由。

发表评论