模型是对业务问题的抽象和解决方案。模块是对业务问题的分解,是模型的边界。本文基于多模块系统讨论,不适用于单模块系统。

特定的模型只需要关注特定的业务问题,因此应该呆在模块内。但是现实中,有时模型会穿越边界进入另一个模块,并带来问题。

假设我们在开发一个网上商店,首先,为了实现身份认证,我们创建了User类,用于认证身份,其API形如:

public class UserController {    public User authenticate(String username, String password);}

此时,直接返回User已经埋下了问题,我们继续看。

为了让用户能够买东西,我们需要向User(调用身份认证API获得)添加了充值和结算功能。

为了提高购买体验,我们再次向User添加一些统计功能。

接下来,我们发现模型都是通用的依赖,将其放入一个公共模块,称为模型模块给业务模块直接使用。

随着开发,更多的功能被加到User,每次都需要修改认证服务(User是认证服务提供的),这很不方便,于是改为各个模块直接访问数据库,并继续添加功能直到出现下面的问题。

问题

问题1 修改Schema困难

当我们需要删除或者修改User的功能时,我们发现必须先更新所有依赖模块后才能更新,否则未更新的模块无法存取相应的表。然而,由于模型是一个公共模块,我们需要查看每个业务模块后,才能确定修改计划。

问题2 安全隐患

每个依赖User的模块都能看到User全部数据。这可能出现安全漏洞,比如,某个模块把User的密码打印到日志里。为此,向模型添加敏感信息时,需要检查所有依赖模块的代码,防止泄漏。

问题3 事故放大

模型一般都需要持久化,这些数据存储在一起的,可能造成事故的放大。比如,一个模块异常删除了User数据,那么所有使用User的功能受到到影响。

问题4 意外修改

修改模型时,如果不注意本模块是否拥有相关部分,很可能会破坏其他模块。并且,有时候这种修改是基于条件触发的,使得这种问题较难排查。然后,即使本模块拥有修改的部分,也不能保证没有其他模块依赖。因此,修改模型时,需要先分析修改范围是否属于本模块,之后再查看所有模块是否依赖现有实现。

由于以上问题,我们对模型开始只加不删,轻易不动已有功能...

分析

究其原因,我们发现根源在于认证API返回了User。这使得模型穿越边界变成可能(依赖认证模块并直接使用User)。之后,公共模块的做法降低了穿越的难度(依赖该公共模块)。最后,共享数据库彻底卸下了边界,使得模型能够被任意模块存取。

三步操作后,模型成了各个模块的共享内核,任何对内核的改动都会变得困难。并且这种共享内核,如果由多个团队维护,很可能变成没人维护任其发展。

解决

微信图片_20200730112023.png

理想的解决方案是恢复模型的边界。每个模块应当有专门的关注点,其模型亦如此。

首先,可以逐步将User拆分到各自的模块中。过程中如果发现拆不开的,有两种办法,如果是模块划分问题,可以调整模块划分。如果不是,则需要选择一个模块拥有该模型,在另一个模块的模型中通过引用该模型ID或内联(复制)所需的部分。

拆分完成后,检查提供API的地方,确保模型没有出现在请求响应中,如果有则需要更新API。

最后,消费外部API时,确保外部模型没有直接进入,如果有则先将其翻译成本地模型。这一步是一种防御机制,用来防御外部API变化造成破坏。

环信新产品预告:进入5G时代,各种先进的技术为企业办公赋予了更丰富的想象力。以全平台、全覆盖为特点的企业统一通信平台正在迅速崛起,环信基于环信即时通讯云建设了一款企业统一通信平台,包括办公场景全平台化、防止内部信息泄露、沟通形式多样化、强大的多人视频会议、办公应用全集成等特色,高效解决现在企业一体化办公的需求。简言之就是一款专为全球企业组织打造的智能移动办公平台。环信企业统⼀通信平台支持公有云SaaS、专有云、私有云和海外云四种部署方式,详情请点击阅读原文跳转商务咨询,或者拨打:400-622-1776。