不管我一生中取得了多大的成功,其主要原因都不是我知道多少事情,而是我知道在无知的情况下自己应该怎么做。我一生中学到的最重要的东西是一种以原则为基础的生活方式,是它帮助我发现真相是什么,并据此如何行动。——瑞·达利欧(RayDalio)
在日常的开发和设计过程中,大家对技术设计上的一些问题往往会面临很多的选择,不同的人会有不同的选择,每每如此,我都会尝试着问自己:我做出选择和判断背后的原则是什么?
经过这么多年的发展,在软件设计过程,目前沉淀下来的原则有很多,但很多情况下,很多原则为了普适性,总结得会比较抽象,一旦太过抽象,对原则的解释和理解就会因人而异,譬如:高内聚低耦合原则,大家都懂,但是如何落地和执行却是很难说完全达成一致。因此,需要针对一些实际的场景中的问题去总结和补充,在大的原则下具化形成大家容易理解一致的相对明确原则。
本文介绍的就是我在工作中遇到的一些问题而总结和使用到的一些常用原则。
一常用原则总结
1分层设计相关原则
单向依赖原则
原则上只允许较高层次依赖较低层次,不允许反向依赖。
我们部门是为B类企业提供金融解决方案的技术部门,针对我们部门,在金融平台层系统不能反向依赖业务产品层系统。同一层的金融平台层系统之间的依赖不进行限制,但会尽量减少同层依赖。
另外,我们在解决底层依赖的高层中沉淀了几种基本方式:
系统依赖转换为数据依赖;接口依赖,通过底层定义SPI,业务层实现,这种做法其实是不得已为之,同时,我们在设计过程中还是尽可能避免走这条路;通过事件机制解耦依赖。
无循环依赖原则
系统设计时,尽量减少系统之间的依赖,同时需要避免系统之间出现循环调用。
这是微服务场景下最容易出现的一个问题,尤其是同层的领域系统之间的调用,导致系统容易出现循环调用,循环依赖带来的一个严重的问题是影响系统的发布和部署问题。
避免跨层调用原则
较高层次不允许之间跨层调用底层。
软件设计中进行分层的一个重要目的是通过分层屏蔽底层的实现细节,如果出现跨层相当于把底层的实现直接暴露了。譬如门面服务层,绕过领域服务层,直接调用DAO层进行数据读写操作,一旦需要重构修改原有的DAO层接口,就发现升级改造成本巨大,我不知道有多少个团队也面临过这种痛苦。
单一职责原则
该原则由罗伯特·C·马丁(RobertC.Martin)于《敏捷软件开发:原则、模式和实践》一书中提出的。这里的职责是指类变化的原因,单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分(Thereshouldneverbemorethanonereasonforaclasstochange)。
这个原则虽然提出时是解决类的职责定义问题,但实际上在对模块的划分上也有指导意义。该原则虽然很简单,但是往往也容易被忽视。
在最近的项目中,我充分体会到这个原则的作用,我们部门的金融网络系统主要解决机构标准化对接问题,我们将系统分为了上下两层,下层通过标准化的接口对接机构,提升机构跨产品的复用能力;上层是产品扩展层,通过提供标准接口给到上游的业务产品层,支持同一个产品接入多家机构,屏蔽机构差异。我们判断一个功能到底属于机构对接层,还是产品扩展层的一个简单的原则是:如果新增一家机构,能否做到只影响机构对接层,而保持产品扩展层代码不改;反过来,如果新增一个产品,是否能做到只修改产品扩展层,机构层能否不改代码。同时,为了避免这个原则被突破,我们甚至在机构对接层的代码中,去除了所有和产品有关的参数,这样,根据产品定制的逻辑天然无法放到这一层。
数据冗余
架构设计应该使得系统中数据的冗余最小。
譬如我们在实践过程中,接口设计时,在Javadoc上强制指定接口的必传参数,尽量做到最小集,减少上游系统使用接口的成本。另外要求在接口实现时,提前进行参数校验,不让不满足要求的数据冗余到系统中。
为了提高系统性能,备份节点和子系统/模块必要时需要对数据进行缓存,当发生变化时,必须有相应的机制保证缓存数据的一致性和有效性。
2质量属性相关原则
数据安全
这块在我们金融业务部门中尤其突出,金融由于其特殊性,往往需要收集大量的客户真实和隐私数据,数据安全是设计中需要重点考虑的问题,通常我们会主要