近日公司的项目开始重构,项目中的一些地方需要相对独立的解决方案。想出解决方案的落脚点倒没有花太多的时间,但是,就像我们定了一个目的地,我们只是看到了它,要达到那个地方,还要走上一段路。这其间,不断在选择,妥协,尝试中摸索,试图在这个情景之下找到一个最合适的方法,就好像你觉得你已经想好了一个方案,但是实际上,你还需要好多配套的方案,这让我思考良多。
以权限系统为例。我们做的是一个 Web 的管理系统,权限控制是很常规的需求,如果要说额外的考虑,那可能要算权限除了与逻辑有关,还与界面是相关的。关于这点,我之前没有接触过完整的方案。当然,在模板中去写 if 在我看来显示不算是一个好办法。从“角色”入手,权限的控制很快定下来,根据实际需求,一个用户一定且只能是一种角色。每种角色有不同的权限。
从关系上来看,角色与权限,是一个多对多的关系。同时,角色和权限都比较多,控制起来比较麻烦,所以,决定使用关系数据库来保存权限相关的数据。这看起来,哪怕和 Django 自己的那套权限系统都差不多,只是一个角色和组的区别。
后来我考虑,“权限”的定性问题。因为按之前的经验,如果一个事情,我们界定了原则,那么通常讨论起解决问题的方法时就要有效得多。大家一致肯定了这样一个事实,对于我们这个项目而言,“权限”是一种配置。那么对于配置,我们有这样的看法:
- 稳定,变化小。
- 项目运行时它不变化。
- 由开发人员维护。
- 应该写到 conf 文件当中去。
第 4 点很重要,因为它和我们之前决定了使用关系数据库来保存发生了矛盾。或者说,使用数据来保存配置也不是不可以,特别是对于复杂点的配置来说,这是很正常的做法,而且,这套权限配置,它也算得上是复杂的程度。
随之而来的,就是把配置,弄到数据库去之后产生的各种问题。
Q1. 如何存储?
前面说过,角色和权限是一个多对多的关系,按关系模型,应该使用三张表来建立模型。除去角色表,还需要有一个权限表,和一个角色和权限的关系表。但是,作为配置,数据存储应该是直观的,就像写到 conf 中一样。使用三张表来保存数据,没法做到一目了然,所以我考虑把这三张表合成一张表,不去遵循范式的要求。
角色是相对稳定的,并且大概只有个位数的数量级,它不需要单独建表保存,枚举的整数就可以了。同时,对于一个权限,它和所有角色的关系,因为角色的数量少,可以在一行中,建立多个字段来枚举所有角色。这样,在一张表当中,字段“权限名,角色1,角色2,角色 3……”就把权限配置完整地保存下来了。并且直接映射成一个二维结构。
如果日后角色有变动,要添加角色,那也只是修改表结构,添加一个字段的事。
Q2. 如何编辑?
配置只是变化少,不是变化。并且,在这里,变化少只是对于运营说的,开发过程中,这个权限是不断变化的,对于新需求要不断添加新的权限。这就涉及到如何去编辑这组配置的问题。如何是文本文件,那么可以直接打开,修改。但是,它写到了数据库,虽然只有一张表直观表现了数据,但是,直接在数据库的客户端中去编辑,一来效率低,二来不直观容易出错。
所以,为此,我们专门写了工具来做权限的编辑。
看来起没什么事了?我之前也这么想。
Q3. 多人协作怎么办?测试怎么办?
把配置存到数据库当中,看起来没什么问题,也容易管理。但是,它造成最大的问题在于,让多人协作泡汤了!让测试尴尬了!
就问一句,在开发过程当中,权限信息写到哪里?
测试用数据库?听起来应该是这样。但是上线时怎么办?开发的一项工作,就是去写这个配置。好不容易写完了,等到上线时,难道需要做一下差异化比较,然后把测试库中的权限信息同步到正式库。或者再去正式库中写一遍。
不管哪种方法,都不现实。
我曾想过,把权限信息单独存到一个 sqlite 中去,这个 sqlite 作为代码的一部分,配置信息本来也应该作为代码的一部分,上线时新的替换旧的就没有问题了。但是,使用 sqlite 没有办法多人协作,它是二进制数据。别人添加的权限,和我修改的权限没办法合并,只能后者覆盖前者。
又要使用数据库,又要区分测试库与正式库,还要能多人协作——最后我们定下来的方案是,存 SQL 语句!
具体地,开发时写数据到测试库,编辑工具中添加这样的功能,因为开发时都是在本机启动应用的,使用本机的 dump 工具,把数据库的数据导出 SQL 语句,这些个 SQL 文件进入版本库。上线时,根据自动化部署的配置,启动多个应用实例时,有一个特别的实例,它会把这些个 SQL 文件重新导到正式库,当然,导入之前会 drop 掉已有的表。配置类信息数量不大,重新建表花不了几秒钟。
把不断变化的 SQL 文件作为配置信息,成为代码的一部分,想想挻有意思的。