初创期应用架构实践 (6):架构实践

叁叁肆2018-11-14 09:30

欢迎访问网易云社区,了解更多网易技术产品运营经验。


3.2 架构实践


在确定好技术选型后,就进入具体的实践阶段。本节主要介绍在实践阶段如何快速迭 代开发、快速交付部署、实现初步的高可用。同时我们的实践从一开始就要注意安全风险, 避免写出有安全漏洞的代码。


3.2.1 快速迭代 

在初创期,一般业务不是很稳定,往往会不停地增加新功能。因此,这也要求我们的 应用能够满足快速迭代的需求。


为了快速地进行功能的迭代,除了在需求管理和团队沟通优化上做足文章外,对于业 务功能的具体实践者,也就是我们的开发人员来说,无非就是在接到需求,理解需求的情 况下,能够快速完成功能的开发并且上线。


快速搭建开发环境 

当有新人加入团队及团队有人更换开发环境的时候,传统的搭建开发环境的方式,一 般要安装不同的软件,如数据库、语言虚拟机(如 JVM 之于 Java)和一些依赖的三方库等。 结果经常出现开发环境搭建出错,无法正常运行,浪费了我们的时间。
究其原因主要是我们的项目,除了我们在依赖配置文件中指定的一些三方依赖及我们 应用程序的本身配置以外,实际上还隐式地依赖了运行环境及运行环境的相关配置,不同 的运行环境,由于版本的不同,底层系统的不同,从而导致我们无法快速地完成一个开发 环境的搭建。


云原生应用的 12 要素里面就有一条,要求显式地声明依赖关系,这里的依赖不仅仅是 应用的依赖,还包括对系统级的依赖项。使用基于 Docker 镜像的方式,可以快速开发环境 搭建。在 Docker 镜像中,我们可以将一个应用运行时的所有依赖,都打包到镜像中,这些 依赖不仅包括程序使用的三方库,还包括系统级别的依赖,比如依赖的 libc 库版本,使用 的一些系统级别的工具等。这样,当搭建开发环境的时候,我们只需要将镜像拉下来,就 可以获得一个包含了所有依赖项的开发执行环境,在这个镜像基础上开发,保持了开发环 境的一致性,同时也节省了搭建环境的时间开销。



代码模块化 

很多时候,为了保持新功能快速迭代,我们可能会做如下的事情。


当我们需要加新功能的时候,往往是直接在原来的代码库中,找到一个可以加代 码的地点,然后将代码加上去,而不去考虑代码加的位置是否合适。


如果新增加的代码与原来已有的某个代码片段功能类似,直接将原有的代码片段 复制过来,然后进行少量修改,甚至不用修改仅仅是复制代码,造成内部代码的 大量重复。


上述行为,短期内看是合理的,它可以带来新功能的快速迭代,然而从长期来看,伤 害了一个项目快速迭代的可持续性。


在代码中不合适的地方加入新的代码,可能导致业务代码耦合严重,不同的业务逻辑 的代码全部糅合到一块,当后续再有新的功能需求的时候,导致另外一处与新功能本来不 相干的功能出现问题,就好比给汽车换了个发动机,结果轮胎出问题了。


大量的代码直接进行复制,同样也会带来问题,假如被复制的代码出现了 bug。在进 行修复的时候,我们需要在所有复制的地方都进行修复,一旦有某些地方出现遗漏,会导 致 bug 依然存在,代码的可维护性变得很差,严重拖慢我们的迭代速度。


模块化做好的代码,模块之间低耦合,只要保持模块对外的接口稳定,其内部实现可 以独立进行更新迁移。阅读代码的时候,也更易理解,更容易维护,不会导致修改了一个 模块,在另一个不相干的地方出现问题的情况。同时相对独立的模块,往往可以在多个地 方被用到,提高了代码的可重用性,也更易于测试。当发现某一个模块的性能出现问题后, 我们还可以直接将这一个模块抽出并进行独立部署,为后续的服务化转变打下基础。


为了做好模块化,我们可以先对业务进行分层,最典型的是使用 MVC 模式的时候, 模型和控制器之间有明显的分界线,它们可以仅仅通过接口进行耦合,面向接口编程。 保持接口的稳定,即使我们的底层数据库选型改变了,只要模型层的接口不变,控制器 的代码就不用改变,需要修改的仅仅是模型层代码具体实现的配置。


在模型层的代码中,两个完全不同的业务之间也可以划分到不同的模块中,分别进行 开发及测试。同样,在控制器的代码中,毫不相干的业务逻辑之间也可以划分到不同模 块中。 使用前面所说的控制反转及依赖注入框架,在具体编程的时候,仅仅面向接口编程, 不在代码中硬编码具体的实现类。通过配置文件的方式,将具体对象的实例化及依赖注入 延迟到运行时,提高代码的可扩展性。


对于一些业务中可能出现相似的代码,不要进行代码的复制,要进行抽象,可以考虑 通过增加一个参数,或者增加配置项的方式来把这些相似的代码做统一,避免代码中的硬 编码。始终要牢记的一点就是我们的代码中,尽量多提供机制,而具体的策略选择,可以 考虑作为配置项的形式来提供。最常见的例子,就是很多时候程序中需要某一个参数,这 个参数往往是一个经验值,此时将参数作为配置项比直接硬编码到代码中修改简单。


有时候,由于业务的需要,或者由于业务上线时间紧迫,可能还会临时做出一些不符 合模块化的行为,比如需要临时加一个功能,这个功能又与已有的某一个功能相似,直接 复制已有的代码,然后再做简单修改会比较快,我们往往也会这样做来达到快速的功能上 线。这样做短期内是没问题的,但是在上线以后,后续的版本中一定要将这段代码进行重 构,消除重复代码。


根据破窗效应,一旦项目中出现了不良代码,又没有及时重构,后续可能就有更多的 不良代码出现,就好比一个有少许破窗的建筑,如果破窗没有及时被修复,将会有更多的 破坏者破坏更多的窗户。


完善的测试 

一个比较好的模块化系统,其模块内部高内聚,模块间松耦合。我们要在模块内部做 好单元测试,模块之间对接口做好测试。测试用例尽可能完善,能覆盖到尽可能多的情况。 每当我们完成新功能开发的时候,都要针对新功能加测试代码,时时保证测试用例集比较 高的代码覆盖率。只有测试完善了,无论是新功能增加,还是 bug 修复,我们都可以快速 回归测试,以确定是否真正达到需求预期,从而提高我们开发迭代的速度。


上面讲到了如何在搭建环境阶段和开发测试阶段保持快速迭代,除了这些,还必须有 比较快速的交付及部署方式作为保证。在 3.2.3 节,我们会着重介绍如何在云原生架构下, 快速完成交付及部署。 


文章节选自《云原生应用架构实践》 网易云基础服务架构团队 著


网易云计算基础服务深度整合了 IaaSPaaS 及容器技术,提供弹性计算、DevOps 工具链及微服务基础设施等服务,帮助企业解决 IT、架构及运维等问题,使企业更聚焦于业务,是新一代的云计算平台。点击可免费试用