App架构总结
架构因人而异,不同的架构师大多会有不同的看法;架构也因项目而异,不同的项目需求不同,相应的架构也会不同。然而,有些东西还是通用的,是所有架构师都需要考虑的,也是所有项目都会有的需求,比如API如何设计?架构如何分层?开发环境和生产环境如何分离?而以下内容就是根据我的这些经历提炼出来的关于以上几个问题方面的经验总结,内容不多,旨在抛砖引玉。
制定安全机制
出现安全问题,主要是以下两个漏洞导致的:
- 缺少对调用者进行安全验证的方式
- 数据传输不够安全
那么就要解决以下两个问题:
- 保证API的调用者是经过自己授权的App
- 保证数据传输的安全
保证API的调用者是经过自己授权的App
最常用的放到就是除了login,其他所有API都需要AccessToken(后端的授权认证),这个AccessToken可以是一组随机数组成的字符串,跟着每个用户的session,并且具有超时时间,每次login就会有一个新的AccessToken。
保证数据传输的安全
1. HTTPS,HTTPS因为添加了SSL安全协议,自动对请求数据进行了压缩加密,在一定程序可以防止监听、防止劫持、防止重发,主要就是防止中间人攻击。
我们的放如下:
App架构总结
(1)call API,拿到后端的公匙
(2)前端自己生成一个16位的随机数(key1)
(3)用公匙加密key1,变成encryptKey1,传给后端
(4)后端用私匙解密encryptKey1,拿到key1
(5)后端自己生成一个16位的随机数(key2)
(6)后端用key1加密key2,变成encryptKey2,传给前端
(7)前端用key1解密encryptKey2,得到key2a
(8)这样前后端就会有一组相同的key1和key2,组成一个sessionKey,数据就用这个sessionKey加密后形成加密字符串,传给后端
接口协议标准化
API返回的数据,一般都是采用JSON格式进行传输。然而,JSON的值只有六种数据类型:
- Number:整数或浮点数
- String:字符串
- Boolean:true 或 false
- Array:数组包含在方括号[]中
- Object:对象包含在大括号{}中
- Null:空类型
1. Date的分序列化,当返回的对象中有Date属性时,前端要反序列化有可能会出错,可以改用时间戳。
2. 正确的数据类型,如果是整型1,最好不要返回“1”,还有如果是null就要返回null,不要返回“null”
3. 参数命名,对于有分页数据的接口,一般都有当前页的参数,A开发人员可能将参数命名为currentPage,第一页是从0开始;B开发人员在另一个接口则命名为currPage,第一页却从1开始;C开发人员在另一个接口又命名为presentPage,第一页又是从0开始。客户端的开发人员看到也是醉了。
4. 规范好API文档,写清楚API、对应的URL、参数、返回值、异常、example
接口版本控制
我们已经不止一次因为接口发生变动而导致旧版本的App出错的问题,而且变动不一定是修改了接口本身,有可能是底层增加了一种新的数据结构,接口把新数据也返回给客户端了,但客户端旧版本是解析不了的,从而就导致出错了。
做法:
- 每个接口有各自的版本,一般为接口添加个version的参数(小版本)
- @RequestMapping,加上produces属性,这样,同一个API,能够通过request的Accept来过滤,调用哪个,例如:@RequestMapping(value = "/query", method = RequestMethod.POST, produces = AppVersion.V1_0_1)(小版本)
- 整个接口系统有统一的版本,一般在URL中添加版本号,比如http://api.domain.com/v2(大版本)
架构分层
一个App的核心就是数据,那么,从App对数据处理的角色划分出发,最简单的划分就是:数据管理、数据加工、数据展示。相应的也就有了三层架构:数据层、业务层、展示层。它们之间的关系如下图,数据层是三层中的最底层,往下,它接入API;往上,它向业务层交付数据。业务层夹在三层中间,属于数据的加工厂,将数据层提供上来的数据加工成展示层需要展示的数据。展示层处于三层中的最上层,主要就是将从业务层取得的数据展示到界面上。
数据层
数据层是数据管理者,主要任务就是封装API,并将数据结果交付给上层,中间会再加个数据缓存。整个主流程如下图:
- 业务层向数据层请求数据;
- 数据层检查缓存中有没有请求需要的数据;
- 如果有缓存数据,则直接返回缓存数据;
- 如果没有缓存数据,则从网络API获取数据,并将数据加入缓存,然后返回数据。
网络考虑
对于大数据量的业务,调用数据层前,最好先判断网络,以免消耗用户太多的流量。
- 网络不可用时,就无需发起请求。
- 移动网络时,一般需要限制调用比较耗流量的请求。
- WIFI时,就无需考虑流量,还可以预先请求一些接口,比如请求当前分页数据时,可以将下一页的数据也预先请求。但是要注意延迟。
缓存策略
- 缓存只适用于获取数据的接口,对于修改数据的接口则不适用。
- 不同接口缓存时间一般也不同,对于很少变动的数据缓存时间可以设置长一些,而频繁变动的数据缓存时间则比较短,甚至不进行缓存。
- 缓存数据因为比较多,我们一般保存在数据库,而对于调用频率高、最新的数据,还会在内存中也拥有一份缓存,不过缓存时间比较短。
- 请求缓存数据时,会先检查内存缓存中有没有,有则直接将缓存的数据返回,没有才从数据库获取。
业务层
业务层是数据加工者,主要就是从数据层获取数据,然后经过业务逻辑处理后转化成展示层需要的数据。业务层因为夹在数据层和展示层中间,起着承上启下的作用。也因此,业务层很容易沦落为只是一个数据的中转站,主要就是因为对业务层具体的作用和职责没有理解清楚。
展示层
展示层作为数据展示者,它只要关心数据如何展示就可以了。不过,数据如何展示却不是那么简单。展示层是三层架构中最复杂的一层了,要考虑的东西远远多于其他两层,涉及的东西包括但不限于界面布局、屏幕适配、图片资源、文本资源、颜色资源等等。在开发一段时间后,展示层出现代码混乱是最常见的。因此,做好展示层,就需要保持高质量的代码。要保持高质量代码,我觉得至少应该遵循几条基本的原则:
- 保持规范性:定义好开发规范,包括书写规范、命名规范、注释规范等,并按照规范严格执行;
- 保持单一性:布局就只做布局,内容就只做内容,各自分离好,每个方法、每个类,也只做一件事情;
- 保持简洁性:保持代码和结构的简洁,每个方法,每个类,每个包,每个文件,都不要塞太多代码或资源,感觉多了就应该拆分。
说到单一性,面向对象设计中,有一个基本原则就是单一职责原则,它规定一个类应该只有一个发生变化的原因。
- 界面的单一,首先是界面的布局和界面的数据应该分离。
- 方法的单一,则表现为一个方法是对一个行为的封装。
- 资源文件的单一,主要是指Android的各类资源文件,包括存放字符串的strings.xml,存放字符串数组的arrays.xml,存放颜色值的colors.xml,存放尺寸值的dimens.xml,等等。
环境分离
每个App项目,至少都会有两个环境:测试环境和生产环境。多的甚至有四个环境:开发环境、测试环境、预生产环境和生产环境。开发人员经常需要在环境之间切换,测试人员也同样。经常出现测试人员今天需要测试环境的最新版本,叫App开发人员打包一个给她,明天需要切换到生产版本,再叫App开发人员打包一个生产环境的给她。我们知道,一个App,在一台手机上要么只能是测试环境的,要么只能是生产环境的。测试人员要测试两个环境,只能不断替换不同环境的同个App,这实在太麻烦了。为了解决此问题,最好的方案就是环境分离,不同环境有不同的App。
参考:http://keeganlee.me/post/architecture/20160303