成为工程师 - 搭建系统先搭建框架

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 成为工程师 - 搭建系统先搭建框架


前言


从上一篇文章开始,我们进入到【成为工程师】篇章。


上一篇文章我们提纲挈领地描述了对优秀工程师的要求。一个优秀的工程师应该具备【扎实的基础能力】【高质量研发】以及【良好的风险控制能力】


我们从这篇文章开始,细细展开其中的内容。


今日内容


作为工程师,日常的工作基本上都是围绕着【系统】展开的。【搭建一个系统】是工程师必须具备的最基础能力。


从业至今,我自己负责过很多系统,也看到过很多系统。有的系统搭建得非常优雅,无论是可读性还是扩展性都非常好。说白了就是代码看起来清晰干净,研发起来快捷且安全,排查问题也容易定位。但还有一些系统你就是看上好几遍代码都捋不清逻辑,改造的时候更是无从下手。


一个系统存在复杂的业务逻辑是正常的,而一个优雅的系统是能够通过良好的结构去管理这些复杂性。我把这个结构称之为【系统框架】


搭建系统框架是系统建设的第一步,也是最重要一步。我们这篇文章就来聊聊如何搭建一个好的系统框架。


我们先看一个反例:

反例时间



假设我们有一个http接口,需要返回用户的信息。用户信息包括:用户昵称、用户vip等级、用户标签、用户余额、余额历史以来充值总额、用户最贵一次消费。


下面的代码是一种典型的实现方式(可以看注释来了解步骤):


很多同学看到这样的代码觉得已经挺好了。有日志、有注释、有异常处理,代码也没有挤在一堆。但真的是这样吗?


我认为这样的代码确实反映了工程师一定的技术素养,但起码存在以下这些问题:

【Q1】如果新增接口,所有的日志打印要冗余写一遍,包括入口日志、出口日志、异常日志。

【Q2】如果新增接口,try-catch的异常处理逻辑也需要冗余重写。

【Q3】如果新增一个只获取用户金额信息的接口,需要冗余复制上述代码中和金额相关的部分。

【Q4】如果接口需要修改,返回新的信息,那就需要往这个代码里添加新的业务逻辑。而这个类一旦有变化,就涉及对这个类的回归验证。

【Q5】如果我要同时支持可以根据用户昵称来搜索用户信息,那么我要新增一个基本完全一样的接口(除了入参不同)。

(记住以上这些问题,我们下面会逐一来解决)


所以,如果用发展的眼光(需求新增)去看这段代码,你可以基本判断以后会存在大量的逻辑冗余大量的冗余会带来研发的低效、升级的遗漏、逻辑不一致的风险等等。此外,不同工程师可能都有自己的编码习惯,同样是处理日志,异常,写法可以迥然不同。


结合在大厂多年的经验,优雅的系统会结合两种设计方式来解决这类冗余的问题。我们一起来看看。


01模板模式


【模板模式】就是设计模式中的“模板方法模式”。模板方法模式的核心思想就是:统一算法框架,暴露算法要素给子类来实现


看定义还是抽象了一些,我们直接看例子。


下图就是一个定义了算法框架的抽象模板类:



可以看到,process方法里仅做了两件事:【1】实现了所有接口的共用逻辑。比如打日志、计算耗时、捕获异常并处理。【2】确定了步骤(步骤也称之为算法框架)。比如先校验参数,后执行业务逻辑。基于上面的模板,我们的服务只需要做如下实现就可以了:



下面我们根据【模板模式】的思想修改我们的反面案例,我们的代码就变成了:



可以看到,通过模板模式,你起码会有这样几个好处:


【1】每个接口都不用担心忘了执行必要的公共逻辑,例如打印日志、异常处理。

【2】不用担心接口有遗漏步骤及搞错步骤顺序,例如入参校验在执行业务流程之前。

【3】接口只需要关心自己业务逻辑的实现即可。

【4】所有接口打印的日志及异常处理方式确保是一致的,方便监控和定位问题。

【5】如果需要增加一些公用的能力,例如埋点上报某个统计平台,只需要在框架中添加逻辑,所有接口都直接生效。


我们使用【模板模式】解决了业务无关逻辑的冗余问题,也就是上述针对反面例子提出的问题中的Q1、Q2,下一步我们要动手解决业务逻辑冗余的问题,也就是针对问题Q3、Q4、Q5。


02流程引擎


流程引擎的核心思想是:将要执行的逻辑看成是一个个步骤的串接,由统一的角色来管理步骤的执行顺序,这个角色就是流程引擎


我们用两张图来对比下使用流程引擎和常规瀑布式编码的不同。


01流程式编码 vs 瀑布式编码



上图分别展示了两种编码法方式【瀑布式编码】和【流程式代码】。


【瀑布式编码】就是从上往下按照步骤把业务逻辑写完。


【流程式编码】是先把可以独立的功能抽成一个个执行器。不同的服务根据自己功能的需求来串接这些执行器。

两者对比,流程式编码有这样一些好处:


【避免冗余】:同样的业务逻辑只有一份代码。


【最小修改】:如果需要加一个环节,只需要新增一个处理器,并且编排到流程中即可,对已有代码没有任何侵入。


【方便追踪】:我们可以在每一个节点执行完以后,在流程引擎中添加一些日志,以此来追踪执行过程。例如在哪里中断了?哪个执行器耗时最长?


【利于分工】:每个处理器约定好职责就可以独立开发,并且可以独立测试。


【可读性好】:流程式代码往往在一处编辑所有的步骤,代码可读性佳。看到一个流程由哪些节点组成,基本上就了解大概的逻辑了。


【灵活多变】:流程式编程还可以支持各个处理器以分支和循环的方式组合。


下面我们就来实现一个简单的流程引擎,并用它来继续改造上面的反例,以此来说明【流程式编程】的思想和好处。


02处理器设计


我们先看下处理器的实现,处理器是被我们抽取出来处理一块业务逻辑的单元。如下图标识



在反例里,我们可以抽取三个处理器。【用户信息处理器】【金额处理器】【消费记录处理器】。处理器接口如下:


细心的同学可能会问,ProcessRequest和ProcessContext是什么?


【ProcessRequest】:对请求信息的封装。例如用户的userId、用户的客户端信息(IOS、安卓、以及对应版本号)、要求转账的金额、转账对象等。每个处理器都能够获得这些信息,根据自己的需要去使用。ProcessRequest中所有的值,原则上不允许被修改,以免原始请求信息被污染。


【ProcessContext】:流程执行的上下文,用于存放整个流程执行过程中的数据。在所有执行器处理完以后,结果组装器可以从ProcessContext中获取到各种结果数据,构造返回结果。接着我们基于Processor接口,实现三个具体的处理器:


我们处理器就搞定了,等着流程引擎来唤起他们吧!


03流程引擎设计


下面我们来看流程引擎的设计,如下图标识,可以把这些箭头的控制理解为流程引擎。流程引擎的核心作用就是控制处理器按照指定顺序执行:



下面是流程引擎接口:


流程引擎只有一个start接口用来启动流程。


以下是流程引擎抽象类。抽象类除了实现对处理器执行的控制外,还可以包括日志打印、异常处理等操作。


那一个流程引擎需要执行哪些处理器呢?这由子类决定,子类通过实现getProcessors()抽象方法来指定使用的处理器。你看,这里又有模板模式了是不



下面看下我们具体的引擎子类是怎样的:



可以看到,引擎子类实现getProcessors()方法即可。此方法就是告诉流程引擎具体要执行的执行器列表及执行顺序。


如果你走读代码到这里,看到list里放的三个处理器名称,你基本上就知道“用户查询接口”提供了怎样的功能。这就是良好的可读性。


试想,如果有一天,一个流程中需要新增一个逻辑,我们可以包装一个新的处理器,然后添加到上图中的processorList中即可。


每个接口都可以实现一个如上截图的引擎子类,用以编排需要执行的处理器。


04主入口的改造


当引入流程引擎后,我们的主入口(controller)就可以改造成如下这样(我们附上和之前两个版本的对比图):



你可以很明显的看到在改造之后,由于业务逻辑被内聚到一个个处理器中,入口处的代码变得简单清晰。同时你再也不用害怕每次业务需求都要改这个类,从而变得的膨胀不堪。


05流程引擎设计一览


我们已经看到了整个流程引擎的实现过程。最后我们再用一张类图来一览整个设计,相信会帮助你更好地了解这种设计方法:


今日总结


今天,我们正式进入到【成为工程师】的细节内容。我们提到,一个工程师最基础的能力就是搭建系统。而一个系统要搭建得好,首先就要有一个好的系统框架。


我们先是通过一个反例来说明了典型的瀑布式编码存在的问题。继而讲了通过模板模式和流程引擎两种设计方式来优化

瀑布式设计。以此让系统的扩展变得”容易、安全且规范“。


对于流程引擎,我们只是给出了一种最最基础的实现方式而已,但是对于很多系统来说,这么设计已经足够了。事实上,真正强大的流程引擎还包括【分支循环】【异步化】【可视化界面管理】等各种高阶功能,你可以自己做一些了解。


不过,流程引擎的选择需要结合实际情况,不然也会引入额外的复杂度。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
C++
BaGet服务之基础搭建(上)
BaGet服务之基础搭建
308 0
|
2月前
|
监控 安全 应用服务中间件
如何搭建高效的Web服务器:技术指南与实践
【7月更文挑战第24天】搭建一个高效的Web服务器需要综合考虑多个方面,包括选择合适的操作系统、安装合适的Web服务器软件、进行配置优化、加强安全防护以及实施性能监控。通过不断地优化和调整,可以确保Web服务器在高负载下仍能保持稳定和高效的运行,为用户提供优质的访问体验。
|
2月前
|
Java 关系型数据库 开发工具
Java开发者必备技能与工具:构建高效开发环境的指南
【7月更文挑战第23天】作为Java开发者,掌握核心技能、熟练使用开发工具与框架、不断提升自己的软技能是构建高效开发环境的关键。希望本文能够为广大Java开发者提供一些有价值的参考和启示,助力大家在Java开发的道路上越走越远。
|
存储 Web App开发 网络安全
一步步实现SDDC--学习平台环境的搭建(2)
在上一篇文章中,我们一起完成了ESXI服务器的安装和管理网络初始化设置。 但是,就像之前所说的,由于台式机很难在ESXI的兼容性列表中,因此使用标准的ESXI安装镜像可能会出现异常失败的情况。 本篇开头,我将向大家演示,如何通过VMware PowerCli创建一个自定义的安装镜像。
一步步实现SDDC--学习平台环境的搭建(2)
|
存储 网络协议 虚拟化
一步步实现SDDC--学习平台环境的搭建(1)
新年伊始,晓冬将分享如何一步步搭建一个超迷你但又完整的VMware软件定义的数据中心。
一步步实现SDDC--学习平台环境的搭建(1)
|
JavaScript Java 数据库
【平台开发】如何快速搭建一个网站
【平台开发】如何快速搭建一个网站
133 1
|
11月前
|
前端开发 测试技术 API
“构建可靠的前端测试环境与主页搭建实践“
“构建可靠的前端测试环境与主页搭建实践“
45 0
|
存储 数据库 C++
BaGet服务之基础搭建(下)
BaGet服务之基础搭建(下)
454 0
|
Java Maven
后端开发1.项目的搭建
后端开发1.项目的搭建
93 0
|
Java 应用服务中间件 持续交付
《小团队web技术搭建》(二)环境和工具的准备-第二部分
《小团队web技术搭建》(二)环境和工具的准备-第二部分
114 0