对代码设计的一些小想法

- 次阅读

上周开始接触公司的一个新业务,让我想聊下对代码设计的一些小想法:

一次是写一个客户端设置项的接口,就是 app 各种个人设置保存在服务器上,客户换其他设备登录也能使个人设置保持一致。比如说用户的默认语言、是否自动登录、股票行情的红涨绿跌显示等。 最开始没什么经验,给每个设置项提供了一个接口,比如设置默认语言用 SetLanguage 接口,设置自动登录用 SetAutoLogin 接口。 正常是没问题的,但是,需求总是会变的。后面跟着陆陆续续有一大堆类似的设置项需求,比如红涨绿跌显示、字段排序、下单设置、皮肤设置等等。 这个时候问题就来了,为每个设置项加一个接口么。加了修改还要加查询接口。如果只有一两个,单独给每个项开发接口倒也合理,几十上百个呢?是不是太繁琐且不优雅?

抽象来看,这些工作的本质就是记录一些设置项,设置项包含什么?名称和内容啊!对了,所以完全可以提供一个通用的设置接口,设置的时候你告诉我你要设置什么(设置项名称-key)和设置成什么(设置项的内容-value),再提供一个通用的查询接口,不管你要设置多少项,两个通用接口搞定!所以后面有新需求时,前端开发同学甚至都不用再调新接口!

保持对坏代码/坏味道的敏感性,相似的东西抽象起来统一处理。

一次是实现一个注册登录流程,有各种登录方式:手机号、邮箱、账号、第三方、手机运营商等等,还有各种登录入口:app端、网页端、微信小程序、第三方信登等等, 还有各种逻辑:未注册的账户要自动注册、密码或者验证码错误次数要限制、注册如果开户了要验证2FA、2FA的方式还要根据第一步的登录方式来判断、没有设置密码还要引导设置密码、多账户怎么支持等等,还有一连串的流程: 频率限制、验证密钥、验证用户信息、记录设置、记录登录历史、处理响应等等。 东西看起来不难,但是内部逻辑繁琐,当时来这个公司做的第一个模块,没有经验,也没有前辈咨询。如果是像和前后端有强交互的流程还能找其他公司的类似产品抓包研究,当时也研究过几个其他网站的登录流程,但这个实现复杂度完全是后台内部的逻辑。所以在需求出来后确实花了挺多时间想这东西要怎么搞。

后面想出了一个还不错的方法,不过怎么想出来的过太久记不清了,这方法我把它叫做流程化。关键点是流程节点和流程编排。 核心的一个前提就是一个用户会有多个登录方式。那我们就可以区分一下大的节点:1.找出登录方式,2.通过登录方式找出用户,3.根据用户状态做响应。每一个大的节点又可分几个小节点,比如第一步的找出登录方式可以分通过短信验证码的方式找到、通过第三方登录的信息找到、通过运营商或其他信登平台的API 方式找到。这样,把每一个节点细分,然后再针对不同的登录方式编排流程。这样就可以像插件一样编排和拓展不同的接口了。

这个笔记是我想实现方法的时候整理出来的,看起来还比较清晰。纵坐标是当时区分的各个小节点,横的是不同的登录方式或者说接口,打勾的就是对应的接口会用到这个节点,然后按先后顺序编排,如果哪一个节点有错误或者已产生了结果,那后面就不用执行,直接返回。 这样的好处是每个节点内部只处理好它自己的事情,一般节点内部是不太会经常改变的。改变多的地方是可能要支持不同的登录方式和不同的登录入口,那这个就变成了只需要增加相应的节点再编排进去。对原来不变的流程没有任何影响,所以后来新增了各种登录方式:第三方信登、语言验证码、增加邮件通知流程,改动都不大,也没出过什么问题。

遇到没处理过的问题第一步当然是找有经验的请教或者查找业界最佳实践了,但是也会有一些问题来不及从外界找到直接答案,那就在动手前是尽量多花时间想,想清楚了再动手做。

还有就是现在刚接触的一个新业务。给别的公司做一个 app,内容基本就是复制一套自家公司的产品。从服务开发,部署,测试,到上线维护迭代都全包。简单讲就是外包,不过包的是自家已经做过的产品。业务方面以后有机会再说,今天先聊下对功能开发方面的小想法。 对这个需求,最简单直接的做法就是把以前做的一整套都复制一遍,重新跑一个,再把需要适配的地方改改,就差不多可以交付了。 但是,如果有第二家呢?第三家第四。。。呢?是不是感觉有点类似上面的第一个场景。但这里不一样的是,每重新复制一个的成本非常大!中间涉及项目管理、产品需求管理、数据库管理、服务器管理、服务管理、运营管理……如果只有一家,新增一套是少不了的。因为是从无到有,从 0 到 1。但如果有后面的第二、第三家,还按这套模式的话,就非常不优雅了。 这个情况,业界的方案是Saas 化。即软件架构支持多家机构接入(多租户)。
在软件开发层面,要实现这个,该怎么做呢?
因为原来也没经验(菜),所以接触到这个项目后有空会想想如何做合适。
第一个想法先查业界最佳实践或者相关资料,在查了一些SAAS架构的技术文章后了解到有个东西叫元数据驱动开发,核心是把一些最基础的信息(元数据)抽离出来,用这些信息来驱动代码的设计和运行。这部分理解起来比较困难,根据元数据连不同租户的数据表都可能动态生成,门槛太高,不太适合当前场景。
第二个想法是最近对接过恒生柜台和接口。虽然提供的资料有限,但通过暴露出来的东西,也可以大概理解一下恒生柜台的部分设计:1.从最开始就是为多租户设计的,所以从数据库和提供的服务接口都需要指定租户标识;2.有一些参数和流程是可以让租户自定义的,这就满足了一部分不同租户的定制化需求。
恒生柜台的设计确实能满足业务需求,我们对柜台来说是使用方(租户),而现在目标是要做一个平台方(支持多租户)。目前来说,我们只是作为一个租户来使用对方平台的能力,而并没有他们的能力,也就是说他们的能力并不是我们的能力,只能部分参考他们的能力然后在我们的能力的基础上来想怎么设计合理。
所以,恒生柜台正好是一个参考对象,我们或许可以从这个方向入手。但怎么结合起来,既要尽量SAAS化,又要尽量用到我们当前的能力(改动少)呢?
首先,参考柜台的设计,我们哪些改动是一定要做,少不了的?我理解正好是上面说的柜台的两点:1.对每个业务数据和提供的服务接口都需要标识出不同的租户(支持多租户);2.对需要定制化的模块或者参数抽象出来,配置化。这两点少不了, 数据和服务接口有改造的方向了,但是服务内部怎么处理呢?

前两天突然一下来了个新想法,灵感来源是软件里面的面向对象编程。 先来简单讲一讲面向对象编程:它就是将程序当做一系列相互作用的对象。对象就像现实世界中的实体,拥有自己的属性和行为。比如电商中的订单,有订单编号、订单商品、价格等属性,还有订单生成、订单结算、订单取消等行为。 对象是一个具体的实体,比如张三、李四是具体两个人。面向对象会把具有同样属性和行为的对象抽象为类,比如张三、李四都属于人类。对象和类是面向对象最核心的概念,对象是具体的实体,类是对对象的抽象描述,对象是类的实例化。 面向对象有三大特性:封装+继承+多态。 封装:一个类有哪些属性和方法是固定的,数据和逻辑的处理隐藏在内部,对外只暴露必要的接口。就像我们对接恒生柜台,开户的逻辑校验和数据处理他们内部封装好了,只需要暴露一个接口给外部。 继承:类和类是可以有继承关系的,叫子类继承父类,这样子类可以有父类所有的属性和行为,子类也可以在这基础上修改。比如订单是一个父类,股票订单和基金订单都可以继承订单这个父类,然后再扩展自己的属性和行为。还可以继续下去,比如公募基金订单和私募基金订单可以继承基金订单这个类,这层关系中公募基金订单类和私募基金订单类就是子类,基金订单类就是父类。 多态:指不同的对象的同一个行为会有不同的表现,比如基金的下单行为,如果是股票下单,会直接下到股票交易所,基金下单,可能是直接下到基金公司。

好,技术前提已经介绍完,现在回到问题本身:数据和服务接口有改造的方向了,但是服务内部怎么处理呢? 借用面向对象编程的思想,把公共的流程抽象成一个父类,每个租户当做子类继承这个公共的父类,重点是区分好对应业务会有什么节点和流程,不会变的节点和流程就在父类中实现,继承的子类就可以不需要改动直接复用,可能会变的节点(定制化)做好设计,要变时可在子类中修改或者拓展,这样就既能支持多租户的业务需求,又把原来已有的能力复用起来。 举一个开户的例子:
原来的流程是:

  1. 用户点击开户的网页进入开户流程,申请开始开户
  2. 用户填写开户资料,中间会有多个步骤123
  3. 用户填写完资料,提交申请
  4. 客服审批用户的开户申请
  5. 审批通过后开户成功,流程结束。
    改造第一步:我们简单定义开户类:有三个属性:开户号、开户状态、开户信息;有三个行为:开户申请、提交资料、开户审批。
    第二步:把原来的流程根据这个设计用面向对象的方式构建起来,当成一个父类处理,因为原来就是正常的一套流程,所以只需要1.在更新和查询的时候加上租户信息,2.在有可能要拓展的地方预留好位置。
    第三步:把原来的流程用一个租户实现一遍。
    这样做完以后,如果新加的租户和原流程一样,就可以直接继承父类不做任何改动,如果新加的租户有定制化的需求,那就只需要在这个租户子类中把定制化的节点改写一下。这样不会影响其他的流程,只在当前的租户子类生效。虽然现在还只是个想法,没有实际应用改造,但可能是解决这个问题比较合适的一个方法。当然还有其他很多问题需要解决,像是配置化参数的抽象处理、多租户的管理等。但不是今天的重点,就不先扩展了。

想到这个方案其实花了一点时间,开始一直没有什么思路,有一天突然想起可以按照面向对象编程的思想来改造,仔细想过觉得确实可行。而且想过之后觉得这个方法很自然就应该想到,为什么还会思考这么久呢?除去自身经验能力限制外可能还会有使用语言的惯性限制,原来的项目使用的是 go 语言,虽然 go 语言支持面向对象编程,但是大部分实践中并没有按照面向对象的思想来编码,主要还是怎么简单怎么来,一般就是围绕数据库的增删改查,如果原来编码用的是 Java 来实现,应该会更自然的想到。不同的语言会有侧重不同的编程模型,旧的语言模式下思考会有很多限制,结合最佳实践研究编程思想,不要被旧有模式限制。

编码实现、架构设计、编程思想、甚至是处事方法,或许有些东西是相通的。

Read More

那家伙又来电话啦

【2024-07-09】“喂,舅舅 ~” “怎么啦?” “我们现在回家,要洗澡睡觉了。” “好啊,我才下班呢!” “你每天都坐地铁上班吗?” “对呀” “远不远啊,你要坐几个站呢?” “不远,10 个吧” “那你要早点睡觉哦,不要熬夜啦!” “好呀,不熬夜” “拜拜~” 前几天我姐给俩外甥配上电话手表后,时不时就收到两个小家伙的来电,手机的通话记录都被小家伙们给霸占了。 和小家伙打 …