31

架构的 “一小步”,业务的一大步

 5 years ago
source link: https://mp.weixin.qq.com/s/z3kJ8_dT1wRa9K3J9iWZ8A?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

0 1

谈到“架构”这两个字,会有好多的名词闪现,比如:分层架构、事件驱动架构、DDD、CQRS等。亦或者一堆的软件设计原则,如:KISS原则(Keep it Simple and Stupid)、SOLID原则(单一责任原则、开放封闭原则、里氏替换原则、接口分离原则、依赖导致原则)等。甚至如状态图、用例图、时序图、活动图等UML建模,GOF设计模式等。

本文不会讨论这些架构概念,而是从闲鱼详情页这个业务场景出发,分析出当前的业务问题和痛点,然后通过一步步的架构推导设计,解决这些痛点。随着业务的发展,相信这些问题大家都会遇到。而解决问题的过程,或多或少的会用到上面的设计原则。

图1 一种架构的定义

02

老业务架构-MVC架构

很多同学开始写业务的时候,基本都会先建表,然后生成CURD,最后再堆业务逻辑,从DAO->Manager->Service->Controller一路写到底。在业务小的时候,这种架构非常的简单实用,可以快速的开发上线。

但随着业务发展,人员不断增加,老的架构难以支撑业务的发展,稳定性和效率受到极大挑战。蛮荒时代已过,精耕细作的时代到来,急需一种更合适的架构来支撑业务的发展。

以闲鱼的详情页举例,在业务初期,详情的样式只有普通的开价宝贝一种,但随着业务的发展,演变出拍卖、免费送、租房、玩家等细分领域的商品详情页(我们将细分领域的业务命名为“垂直业务”)。

7NVfayE.png!web图2 闲鱼详情页业务演变

此时,还不断的在老的业务逻辑里添加新的业务逻辑,导致所有的详情业务逻辑堆在一起。于是乎,会出现下面的场景:

1)今天A详情业务线的同学,加了段逻辑,挂了,影响了所有业务线的同学;

2)B详情业务线的同学想做单独的监控、缓存、降级等,做不到啊,大家的逻辑在一起,改造成本太高;

3)C详情业务线的同学本想只关注C详情业务逻辑,发现所有业务都在一起,不得不将所有业务都理清楚一遍;

4)D详情业务线的同学发现前面的业务逻辑太复杂,为了将影响面减少到最小,找了一个认为最安全的地方,加了一段D详情业务的特殊处理。

22iiiyN.jpg!web图3 详情页堆逻辑代码

有人可能会问,堆逻辑正常的啊,加几行代码,业务就上线了,互联网提倡的敏捷开发,当然是怎么快怎么来。但 敏捷开发 != 提需求 + 编码 + 发布 ,加几行代码交付业务上线,可能会带来眼前的收益,但一直这么下去,代码会越来越臃肿,没有设计和文档沉淀的系统,难以维护,出故障只是时间问题。

03

新业务架构-业务隔离+领域建模

吉德林法则讲:把难题清清楚楚地写出来,便已经解决了一半。老的 架构的问题,归纳起来讲:

1) 业务没有做隔离,所有的垂直业务逻辑都堆在一起,互相影响。

2) 详情页业务足够的复杂,却没有统一的模型,形成统一的认知。

因此,架构的设计方案就着重解决这两个问题。

1 业务隔离架构推导与设计

一个业务,有多种形态的实现,很容易对应到设计模式 里的策略模式。 最粗暴的方式,每个垂直业务都自己实现详情页。

jMfe6rU.jpg!web 图4 使用策略模式,隔离每个业务的实现

这种方式,业务虽然隔离了,但维护成本极高,添加一个通用的功能,所有业务都需要添加一遍。 因此需要 将共性内容(不变的部分)抽象出来,将变化的部分由各个垂直业务去实现。

Ib2A3im.jpg!web 图5 抽象出共性(不变)和特性(变)的内容

这种方式,解决了业务的隔离,共性的内容统一维护,变化的部分由各个垂直业务独立维护。但此时,所有的业务团队还是在一个应用工程里写业务代码,会出现如下场景:

1) 开发阶段,各业务线都在这个应用里拉取一个分支进行开发,集成部署时,代码冲突难以避免。

2) A业务线添加了一段自己业务线的逻辑,部署失败了,导致其它业务线也无法使用。

3)

N业务线不在自己的团队内,属于外部合作团队,如何添加该业务线的逻辑。

这些场景存在的原因在于, 业务代码虽然隔离了, 但人员的开发过程并没有隔离。 有如下3中方法可供选择:

a. 将共性的内容打成二方依赖包,每个垂直业务依赖这个二方包,进行独立应用开发。

这种二方依赖的方法最常用,但在二方服务里添加一个通用功能的时候,要告知所有业务方都升级二方包,还要发版,升级的成本高。

nuY3Ana.png!web图6.a 共性内容打成二方包

b. 垂直业务独立应用开发,然后将代码打成jar包,再集成到共性业务应用里,一起部署

该方法依赖关系跟方法a相反,但部署方式不够灵活。如果要实现垂直业务的独立部署,改造成本太高,需要做类隔离,budle隔离等。

mQbyaeN.png!web图6.b 垂直业务打成二方包

c. 综合a和b的优点,将共性的业务独立部署,垂直业务既可以独立部署,也可以写在共性内容应用里。当调用某个垂直业务实现时,可以自动路由到具体的垂直业务实现(这个垂直业务实现可以是一个本地调用,也可以是一个远程调用)。这样,垂直业务的开发人员就可以在自己的应用中开发、部署、运维,解决开发人员的隔离问题。

J7ZvQjr.jpg!web图6.c  互不依赖,部署方式可选

至此,业务的隔离,开发人员的隔离问题都已解决。但该架构方案显然不只有详情这一个场景可用,其他类似的业务场景也有相似的问题。因此,架构的代码不能与业务的代码耦合在一起,需要将架构的代码独立出来,形成通用的技术工具,以应用于所有类似的业务,业务开发应该只关心业务的事情。我们特地为此开发了一个多实现“业务隔离”路由工具,最终的隔离架构设计图如下:

q6bm2e7.jpg!web图7 总体架构图

2 领域模型在详情页的使用

隔离的问题解决了,再来谈谈详情页的领域建模。

为何需要领域建模?好多java开发的同学,大都会遇到这样的问题:1)一门OO(面向对象)的语言,写出来的代码都没有OO的感觉,到像是过程式的代码,面向对象的思想基本没有使用到。2)虽然代码满足了业务需求,但从代码中,完全看不到业务领域的影子,业务领域和代码是脱节的。3)随着业务的越来越复杂,里面的依赖关系梳理起来非常困难,业务模块没有边界可言。

为了不给后人挖坑,为了解决详情页复杂的逻辑,为了让代码更有范,为了让接手详情的同学都有统一的业务领域认知,因此决定对详情领域进行领域建模。

veaYJv7.jpg!web图8 领域驱动设计-模型关系总图

Eric Evans那本《领域驱动设计——软件核心复杂性应对之道》经典书籍大行其道十几年,网上关于领域建模的文章也是浩如烟海,自顶向下、自底向上、四色原型建模、问题空间领域模型抽象方法论也非常的多。但这些文章和书籍,要么谈论理论概念,要么谈论建模方式,对初学者来说,看完之后,还是写不出相应的代码。

所以本文不在重复的去讲领域建模的概念,直接通过闲鱼详情页这个业务场景,讲述建模的步骤、DDD的代码展示,给读者一个更直观的参考。

3 详情页领域建模

闲鱼详情页是一个纯展示的页面,用一句话可以概括为,“详情页是包括:商品、卖家、买家、鱼塘、认证、互动等内容的信息聚合展示页” 。

这里我们使用四色原型建模法进行建模。上面的这句话最骨干的内容为:详情页是一个“信息聚合展示页”(瞬间事件)。

E3Uz6bz.jpg!web

图9 详情页是一个信息聚合展示页

骨干内容定义好后,为了更好的描述详情页是什么,需要补充一些实体对象,详情页主要包含商品、卖家、买家、鱼塘这些实体(人-物-地点)。

Qf6ba2V.jpg!web

图10 详情页中包含的主要实体

在此基础上,进一步的进行抽象,用户实体中,有卖家、买家这个角色存在;鱼塘实体中,又有塘主、塘民角色存在(塘主也是塘民,所以塘主应该继承自塘民)。加入角色后的模型如图11所示:

7jYjiqA.jpg!web

图11 详情页中的角色

最后,再把一些描述信息放进去,

Y7nQFjF.jpg!web

图12 模型中补充描述信息

有了模型的设计,再转为类图设计。根据模型的抽象,详情页是信息展示的聚合,因此它是个聚合根,包含了商品、卖家、买家、鱼塘这些实体信息。 商品信息描述里,又有视频、图片、文本、互动等信息。视频、图片可以抽象为媒体信息。使用UML设计出最终的类图,如图13所示:

uAJva2j.jpg!web

图13 详情页类图结构

4 DDD代码展示

有了领域到模型,需要将模型再映射为代码。在实现详情页时,依据的是DDD中的定义。DDD中最主要的内容包括:entity、value object、aggregate、repository、factory和service (如图8所示), 以及Infrastructure, Domain, application和User Interface 分层结构,如图14所示:

Aj6FRfR.jpg!web图14 领域驱动设计分层架构

Infrastructure主要用于持久化数据的读取和写入;Domain为领域层,提供领域信息,这是业务的核心所在;Application是很薄的一层,没有业务逻辑,用来协调应用活动;User Interface负责用户信息展示。将这个分层结构映射到工程结构如图15所示。

MJNnEvN.jpg!web图15 DDD领域建模工程结构

这里的Applicaiton层没有业务逻辑,只作为二方服务对外提供。此外,工程结构中没有写User Interface层,因为该应用是以二方服务提供,当然,如果提供REST服务,则可以写在这一层。

多数的用户对MVC架构比较了解,因此,图16对比了DDD的分层架构和MVC的架构,以做参考。

aa2eQfA.jpg!web图16 DDD分层对比MVC分层

根据上文的模型抽象,领域对象主要有 ItemEntity, SellerEnity, BuyerEntity, FishPoolEntity,并通过详情页聚合根DetailAggregate聚合。

UJv2EjE.jpg!web图17 详情页聚合根

在图15应用结构分层中,有3种类型的数据对象,DO对象表示持久化的数据对象, Entity为领域对象,DTO为对外的传输对象。

首先通过领域层的Repository,调用基础设置层的Dao读取DO结构,再使用Convertor转为Entity领域对象。

BJJjay7.jpg!web图18 Repository的定义

NNv26rz.jpg!web图19 Converor转化器的定义

领域entity处理各自的领域业务逻辑,然后通过领域层的DetailService,对聚合根DetailAggregate进行整体详情页业务领域处理。最后转为DTO传输对象提供对外服务。

04

总结和思考

本文从详情页业务出发,当业务越来越复杂时,如何做业务的隔离,做开发人员的隔离,以及如何通过领域建模,形成统一认知。给大家提供一个可行的参考。

没有任何一种架构可以适用于所有的场景,也没有任何一个架构是最优的, 所谓架构,都在解决“边界”的问题。 因此都需要从实际的业务场景出发,明确出问题的边界在哪里,要达到什么样的目标,再遵循一些基本的原则和方法,基本都能够设计出符合自己业务特性的架构。接下来将会给大家分享一篇从不同的视角出发,进行的业务架构设计,敬请持续关注公众号。

qANJN3Z.gif

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK