查看原文
其他

OrientDB实战进阶 - RBAC案例

曲健Nicholas 曲水流觞TechRill 2020-02-06

本文以最最常见的 RBAC(Role-Based Access Control,基于角色的访问控制)作为入口来对比关系型数据库与OrientDB在建模、查询效率方面的区别,通过实战来进阶最直观,最直接。

关键词:OrientDB, GraphDB

开始之前,我们对RBAC模型做两个小改动,如此更符合自然世界关系的复杂语义,对比后也更能直观体现图数据库的强大:

1.角色Roles之间有继承关系,即角色A是角色B的Parent, 则角色A拥有角色B的所有权限;

2.除开角色Roles可以拥有权限资源,某个具体的User也可以拥有具体的权限;



OK, 有了这两个变动后,在RDBMS中我们怎么设计表结构呢? 如下图示简化的PDM设计。简单说明下:

1. 共五张表:三张基本信息表,两张映射关系表(紫色)。其中Group的继承关系,我们暂时简化每个分组只能有一个Parent Group,所以继承关系直接体现在Group表了。

2. ResourceOwner关系表中有用户姓名和分组名字段,这两个字段互斥;当用户与资源映射时,分组列为空, 当分组与资源映射时,用户列为空。


本文后续的建模名称略有变化,但是横向类比比较容易,应该不会影响大家的阅读和理解。 

Users=Person, Roles=Group, Rights=Resource




为了让大家理解下这个模型的运作机制,我已经在OrientDB中同步建模并生成了部分示例数据。

上图左侧的图示,圆圈代表顶点Vertex, 横线代表边Edge。示例数据直接通过图形化展示如上,无需过多解释,相比于RDBMS,这里更加一目了然。


解释一下该图的示例数据的权限情况:

  1. p1属于分组g1

  2. p1拥有r1-r5所有的资源,因为g1是其他所有分组的Parent

  3. p3只拥有r1, 而g2没有拥有任何资源,也不是任何分组的Parent

  4. p5只拥有r4, 因为p5所属的g5拥有r4

  5. p4对r5无权限, 因为r5是p4所属分组的Parent组拥有的


模型和示例数据描述完毕,现在开始我们的需求实现!


1 查询用户名为John拥有的所有资源


  SQL 写法   


--查询John直接拥有的资源

select rname from ResourceOwner where pname='John'

union all

--查询John所属分组拥有的资源

select rname from ResourceOwner ro, PersonGroup pg

where ro.gname=pg.gname

and pg.pname='John'

union all

--查询John所属分组的所有子分组拥有的资源

select rname 

from ResourceOwner ro, Group g, PersonGroup pg

where ro.gname=g.gname

and g.parent = pg.gname

and pg.pname='John'


  Graph 写法   


select from (

   traverse out('Owns'), out('BelongsTo'), in('Parent') 

   from (select from Person where name='John')

where @class='Resource'

注解:

  1. 从SQL写法中我们可以看出关系型面向这种多重关系的处理只能穷举、笛卡尔积来scan出所有的记录,然后再做一个聚合;

    当然该示例只是为了更清晰的让读者可以理解SQL的实现思路,没有做任何优化;

    另外我们的用户、分组、资源表都是一个字段的最简单的表,现实情况肯定是多个字段,关系表存的ID, 那么就不是上面三个表关联这么简单的事了,可能要关联所有的5张表。

  2. Graph语句首先从精炼程度上就比SQL优秀,读者没有OrientDB语法基础也不妨碍阅读这句话。


    图数据库的首要也是基本原则就是能找到某个/多个具体的顶点然后开始遍历图,找到你需要的数据。


    此语句充分验证了这个原则,先找到名字叫John的Person顶点(很可能不止一个), 以此为中心进行遍历(Traverse),罗列出所有需要遍历的边(Owns, Belongs, Parent), 最后过滤所有遍历过的顶点,只保留Resource作为结果。图的遍历是线性计算,相对于SQL中的笛卡尔积指数性计算,性能要快到飞起来。

2 查询出所有拥有资源123的用户


  SQL 写法   


--查询直接拥有资源123的用户

select pname from ResourceOwner where rname='123'

union all

--查询直接拥有资源123的分组内的用户

select pg.pname from ResourceOwner ro, PersonGroup pg

where ro.gname=pg.gname

and ro.rname='123'

union all

--查询直接拥有资源123的分组的父分组/祖父/曾祖父...的用户

???


  Graph 写法   


select from (

    traverse out('Owns'), out('BelongsTo'), in('Parent') 

    from (select from Resource where id='123')

where @class='Person'

注解:

  1. SQL你会发现在向上回溯获取N级父节点的时候做不到!当然你可以重新设计表结构来解决,但是第一种场景你会发现又解决不掉了...

  2. Graph语法与第一个问题的语句基本一致,遍历+过滤,搞定!

3 查询John的所有分组,且这些分组不能有子组


  SQL 写法   


select gname

from Group g, 

(select gname from PersonGroup pg where pname='John') tmp

where g.gname=tmp.gname

and g.children is null --重新设计表结构



  Graph 写法   


select person.name as name, group.name as groupName

from (

    Match 

        {class:Person, as:person, where:(name='John')}

        .out('BelongsTo')

        {class:Group, as:group, where:(in('Parent').size()==0)}

        return person, group)

)

注解:

  1. SQL语法中的如何判断“子分组”不存在的实现比较有难度:

    若group表里面加上children字段,逗号分隔,对该场景比较容易实现;

    若新建一张group + children的映射关系表,那这里SQL要再多加一层JOIN;

    不管哪种方案都是与Parent表设计的数据冗余。

  2. OrientDB的Match语句,通过John遍历到所有的Group, 然后过滤条件 in('Parent').size==0 表示这个Group有0个Parent入边,表达的就是Group没有子组,搞定!

本文太干,请静心阅读

如有帮助,务多多支持

Nicholas Qu

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存