App架构设计经验谈:数据层的设计

原创文章,转载请注明:转载自Keegan小钢
并标明原文链接:http://keeganlee.me/post/architecture/20160120
微信订阅号:keeganlee_me
写于2016-01-20


App架构设计经验谈:接口的设计
App架构设计经验谈:技术选型
App架构设计经验谈:数据层的设计
App架构设计经验谈:业务层的设计
App架构设计经验谈:展示层的设计


一个App,从根本上来说,就是对数据的处理,包括数据从哪里来、数据如何组织、数据怎么展示,从职责上划分就是:数据管理、数据加工、数据展示。相对应的也就有了三层架构:数据层、业务层、展示层。本文就先讲讲数据层的设计。

数据层,是三层架构中的最底层,负责数据的管理。它主要的任务就是:

  1. 调用网络API,获取数据;
  2. 将数据缓存到本地;
  3. 将数据交付给上一层。

根据这三个任务,数据层可以再拆分为三层:网络层、本地数据层、交付层。

网络层

网络层主要就是对网络API的封装。关于API的设计,该系列的第一篇文章接口的设计已经讲过一些。关于如何封装,可以参考Android项目重构之路系列的架构篇实现篇,其中接口层和本文的网络层是一样的。

还有一些在前面的文章中没有提及到的,在此做一些补充。

首先是不同网络状态的处理。当网络不可用时,则不应该再去调用API;当网络可用,但不是WIFI时,有些比较耗流量的操作也应该禁止,比如上传和下载大文件;当网络状态不同时,还可以采用不同的网络策略,比如,当网络为WIFI时,当前API可以返回更多更全面的数据,还可以预先加载相关联的其他API。

其次,为了节省流量,接口的设计上可以对数据进行简化。例如,对于一些列表类的接口,可以这么设计:只返回更新的部分,比如,上一次请求返回了10条按时间排序的数据,第一条数据为最新的,id为101,当发起下一次请求时,将101的id作为参数调用API,API查到该id,发现该id之后又新增了两条数据,API则只返回新增的这两条数据。

另外,为了保证程序的健壮性,调用API时,对入参的合法性检查也是很有必要的。而且,也应该定义好本地的错误码和错误信息,保证每个错误都能正常解析。

本地数据层

本地数据层主要就是做缓存处理,这需要设计好一套缓存策略。设计缓存策略时,有几个问题需要考虑清楚:

  1. 哪些需要缓存?哪些不需要缓存?
  2. 缓存在哪里?数据库?文件?还是内存?
  3. 缓存时间多长?

哪些需要缓存?

将所有数据都缓存是不明智的,不同的数据应该有不同的缓存策略,比如一个电商App,首页的商品列表数据应该缓存,而且缓存时间应该比较长,而每个商品的详情数据就没必要缓存或缓存时间很短。对于一份数据需不需要缓存,判断标准可以是:用户查看该数据的频率高不高?首页商品列表是用户每次启动都会看到的,而每个商品的详情用户最多只看几次。

缓存在哪里?

从内存读取数据是最快的,但内存非常有限。因此,内存一般只用来缓存使用频率非常高的数据。

文件缓存主要就是图片、音频、视频了。

数据库可以保存大量数据,主要就是用于保存商品列表、聊天记录之类的关系型数据。

然而,不管缓存在哪里,都需要限定好缓存的容量,要定期清理,不然会越积越多。

缓存时间多长?

首先,每份缓存数据都应该设置一个缓存的有效时间,有效期的起始时间以最后一次被调用的时间为准,当该数据长时间没有再被调用到时,就应该从缓存中清理掉。

缓存的有效时间应该设多长呢?可以短至一分钟,长至一星期甚至一个月,具体因数据而异。一般内存的缓存时间不宜太长,程序退出基本就要全部清理了。文件缓存可以设置保留一天或一个星期,可以每隔一天清理一次。数据库缓存再久一些也无所谓,但最好还是不要超过一个月。

交付层

交付层其实就是一个向上层开放的交互接口层,是上层向数据层获取数据的入口。上层向数据层请求数据,它是不关心数据层的数据是从缓存获取还是从网络获取的,它只关心结果,数据层能给到它想要的数据结果就OK了。因此,交付层主要就是定义一堆开放的接口或协议。

如果接口或协议非常多,那么,将接口或协议按照模块划分也是有必要的。比如微信,按模块划分有:IM、公众号、朋友圈、钱包、购物、游戏等等。模块之间应该尽量相对独立、松耦合。

写在最后

数据层如果再扩展,还可以再加入日志管理,这里就不再展开讲了。上面内容讲得也比较乱,有哪里讲得不好的地方欢迎吐槽。


扫描以下二维码即可关注订阅号。

  • 有态度网友06MY5V2016-01-20 18:02

    看一遍顿时觉得以前写的有很多问题。

  • 有态度网友06MY5V2016-03-25 11:32

    上面讲到缓存数据的清理,你感觉啥时间清楚会比较好点?

  • 有态度网友06MY5Z2016-03-25 14:25

    主要是要设置好条件,比如缓存时间到期了就清理,或者缓存满了也要清理,没有具体说哪个时间点的,要看条件是否满足

  • 有态度网友06MY5V2016-03-25 15:06

    像你说的设置到期时间的话,对应app来说岂不要加个计时器,这样会一定程度的影响性能吧?能举个实例吗?

  • 有态度网友06MY5Z2016-03-25 15:47

    也不是说要加个计时器,程序在运行时是经常要访问到缓存的吧,访问时顺便处理一下而已。当然,也可以在用户没有做界面操作时做清理。

  • 有态度网友06MY7w2016-03-26 20:07

    请教两个问题: 1, 怎么更新缓存, 全部替换还是只替换更新的数据, 用什么实现方式比较好, 数据库还是文件(sharepreference)或其他, 实现复杂度和性能等? 2, 后台数据更新了, 想及时看到最新的数据, 而后台更新数据的时间点又是不固定的, 这种缓存策略应该怎么设计, 从用户体验及实现和维护成本说说, 目前我们用的方式是进入应用先获取缓存(数据库)里面的数据, 再同步请求网络, 只是请求的时候会加个时间戳, 这个时间戳是上次服务端返回最新的时间戳, 用来跟服务端这次最新的时间戳对比, 如果相等, 则服务端接口里面返回数据data为null, 如果不等则返回最新数据, 再更新界面,总感觉这种实现方式有问题, 但是又不知道在哪里?
    请指教!

  • 有态度网友06MY5Z2016-03-27 11:42

    首先,缓存更新也是针对每个接口的缓存来说的,一般是全部替换,部分只替换更新的部分,这跟接口怎么设计有关。其次,一般缓存在数据库是必须的,常用数据的还会留一份内存缓存,实现复杂度和性能需要看具体的缓存策略。最后,对于第2个问题,你们目前的处理方式是可行的,当然也有优化的空间,比如,如果是分页数据,而且数据的排序是固定的,可以用上一次数据请求的最新一项做新一次请求的查询条件,只返回这一项之后更新的数据

  • 有态度网友06MY7w2016-03-28 09:19

    谢谢! 我觉得,部分更新的话, 是不是用数据存会好点(好操作), 全部替换的感觉用文件处理要简单一些, 直接保存一个json字符串, 之后的解析方式可以复用网络数据解析部分, 总感觉用数据库要特别小心, 很容易出bug. 第二个的分页问题我们也是这样处理的, 排序是最新的一项排在最上面, 下拉时会将最后一项的id传给服务端, 和你说的原理应该是一样的

  • 有态度网友06MY7J2016-04-03 23:44

    请教一个问题,关于网络状态,是每次请求API 之前都做一次判断吗?还是通过一个自定义数据,然后监听系统网络变化来更改改数据?

  • 有态度网友06MY5Z2016-04-04 08:26

    监听网络变化是比较好的

  • 反革命攻城狮CasaTa2016-04-11 20:45

    大家可以去参照casatwy.com的架构谈文章,比这里出的早,而且这里的思想其实都在casatwy.com找得到,最重要的是,写得比这里好。

  • 有态度网友06MY5V2016-04-23 16:21

    赤裸裸的挑衅作者,作者竟然没反应。[哈哈]

  • 有态度网友06MY5Z2016-04-23 17:20

    回应可以看我最新的那篇文章:《这几天我在跟一个iOS大咖撕逼》

  • 有态度网友06MY8k2016-04-29 11:26

    已举报

  • 有态度网友06MY8M2016-06-07 15:56

    傻逼一个

  • 有态度网友06MY6N2016-06-30 12:25

    请教一个问题:
    之前写的实现篇中,数据层都是在api包中封装好了。
    那现在所的,把数据层再细分为网络层、本地数据层、交付层,在工程中应该如何分包呢,网络层相当于原来的api包,本地数据层和交付层要再建两个不同的包,单独封装吗?

  • 有态度网友06MY6N2016-06-30 12:26

    楼主可否把一个完整的工程结构发张图出来呢?

  • 有态度网友06MY5Z2016-06-30 21:05

    本地数据层和交付层可以分两个不同的包,方便管理。

  • 有态度网友06MY5Z2016-06-30 21:12

    工程结构的话,可以根据层次级别进行嵌套啊,比如外层的三层为:datalayer、businesslayer、uilayer,而数据层里面的三层就是:datalayer.network、datalayer.local、datalayer. deliver

  • 有态度网友06MY6N2016-07-01 09:26

    恩,这个是可以。
    不过还有些问题
    1. 有些操作不需要缓存,直接进行api操作,比如提交之类的,那这个时候,业务层调用数据层时,是先调用数据层中的交互层,再交互层去向网络层请求数据,还是直接调用数据层中的网络层
    2. 有些操作需要在同一次操作中,先获取缓存数据,再获取网络数据,比如获取列表数据。那么是在ui层调用两次业务层(获取缓存和网络数据),还是只调用一次业务层,在业务层中分别调用数据层请求的本地数据和网络数据,那这个时候,业务层如何分两次通知ui层更新数据呢?

  • 有态度网友06MY5Z2016-07-01 14:41

    1.业务层统一通过交互层调用,而交互层的实现具体是调用网络还是缓存是内部的事;
    2.如果简单点可以调用两次,这次处理起来就比较简单粗暴。

  • 有态度网友06MY6N2016-08-02 14:40

    谢谢
    还有一点不太理解,如果在业务层调用两次数据层,分别获取缓存和网络数据,是在调用的方法中加一个参数来区别数据类型吗,如果是这样,那业务层不是进行判断了,和你之前说过的有点违背,“业务层不关心数据层的数据是从缓存获取还是从网络获取的,它只关心结果”

  • 有态度网友06MY5Z2016-08-03 11:06

    我说的调用两次不是这样。重点在于交互层的处理,交互层获取缓存之后再调用网络数据更新缓存,这样,业务层第一次调用该接口时获取到的是旧的缓存,第二次调用后则获取到新缓存