跨微服务授权的行动

时间:2017-07-18 13:01:29

标签: web-services rest authorization microservices event-sourcing

现代多用户Web应用程序对用户可以执行的操作施加了很多限制。换句话说,行动需要权威。例如,用户只能更改自己的个人数据,并且只有组的成员才能将内容发布到该组。在经典的整体应用程序中,通过连接多个数据库表并根据查询结果执行操作,可以轻松实施此类限制。但是,对于微服务,应该更清楚地处理这些限制的位置和方式。

为了争论,请考虑使用Facebook克隆。整个申请包括几个部分:

  • 以JS和其他网络技术编写的前端
  • 由许多微服务组成的后端
  • 用于检索数据并将数据提交到后端的API,即网关

至于业务逻辑,有两个众所周知的实体(

  • 活动(如音乐会,生日派对等)
  • 帖子(墙壁,页面,活动等上的文字条目)

假设这两个实体由单独的服务EventService和PostService管理。然后考虑以下约束:

  

事件的帖子可以被两种用户删除:帖子的作者和事件的主持人。

在整体中,这种约束在概念上很容易处理。在收到删除帖子的请求后,提供帖子ID和用户ID,

  1. 获取帖子所属的事件。
  2. 检查用户是否是帖子的作者。
  3. 如果是,请删除帖子。如果没有,请获取事件的主机。
  4. 检查用户是否在主机中。
  5. 如果是,请删除帖子。
  6. 然而,通过微服务策略,我很难弄清楚如何在服务中划分这样的操作的职责。

    备选方案1

    一个简单的方法是将这样的逻辑放在网关中。这样,可以基本上执行与上述相同的过程,但是调用服务而不是直接调用数据库。粗略草图:

    // Given postId and userId
    // Synchronous solution for presentational purposes
    
    const post = postClient('GET', `/posts/${postId}`);
    const hosts = eventClient('GET', `/events/${post.parentId}/hosts`);
    const isHost = hosts.find(host => host.id == userId);
    
    if (isHost) {
        postClient('DELETE', `/posts/${postId}`);
    }
    

    但是,我对此解决方案不满意。一旦我开始在网关中放置这样的逻辑,它就会非常诱人始终这样做,因为它是一种快速而简单的方法来完成任务。所有的业务逻辑最终会聚集在网关中,而服务将成为“愚蠢的”#34; CRUD端点。这将破坏具有明确责任区域的单独服务的目的。此外,它可能会很慢,因为当操作变得更加复杂时,它可能会导致大量的服务调用。

    我基本上会重新发明整体,用缓慢而有限的网络调用替换数据库查询。

    备选方案2

    另一种选择是允许在服务之间进行无限制的通信,允许PostService在执行删除之前简单地询问EventService用户是否是相关事件的主机。但是,我担心可能会有大量的微服务相互通信,从长远来看会引入大量的耦合。专家似乎普遍建议不要直接进行跨服务沟通。

    备选方案3

    凭借用于发布和订阅活动的可靠系统,服务可以随时了解其他服务中发生的情况。例如,每次将用户提升为在EventService中托管时,都会发布一个事件(例如events.participant-status-changed, {userId: 14323, eventId: 12321, status: 'host'})。当收到删除帖子的请求时,PostService可以订阅该事件并记住这一事实。

    然而,我对这个也不太满意。它会创建一个非常复杂且容易出错的系统,其中未处理(但可能很少)的事件可能会使服务失去同步。此外,存在逻辑最终会出错的风险。例如,此问题中的约束将由PostService处理,即使在概念上它也是事件实体的属性。

    我应该强调的是,在使用微服务实现应用程序时,我对事件的有用性非常乐观。我不确定他们是这类问题的答案。

    你会如何解决这个假设但非常现实的困难?

2 个答案:

答案 0 :(得分:1)

  

事件的帖子可以被两种用户删除:帖子的作者和事件的主持人。

所以我要开始的第一件事就是确定用于删除帖子的权限的位置 - 使用作为指导主体的想法,即应该有一个编写者负责维护任何给定的不变量

在这种情况下,它似乎是Post服务,足够合理。

我假设管道包含一些机制来检测用户是否是他们所说的人 - 经过身份验证的身份是服务的输入

对于作者删除帖子的情况,验证规则是否满足应该是微不足道的,因为我们有权在同一个地方创建和删除帖子。这里不需要合作。

因此,棘手的部分是确定经过身份验证的身份是否属于事件主机。据推测,确定事件主机的权限存在于事件服务中。

现在,进行实际检查:如果您查询事件服务以找出该事件的主机是谁,而不对该信息进行锁定,则所有者可以与删除命令的处理同时更改。换句话说,ChangeOwner命令和DeletePost命令之间可能存在数据竞争。

Udi Dahan观察到:

  

时间上的微秒差异不应对核心业务行为产生影响。

特别是,行为的正确性不应取决于邮件传输系统发送命令的顺序。

您的事件来源方法是最接近这个想法的方法。

  

然而,我对这个也不太满意。它会创建一个非常复杂且容易出错的系统,其中未处理(但可能很少)的事件可能会使服务失去同步。此外,存在逻辑最终会出错的风险。例如,此问题中的约束将由PostService处理,即使在概念上它也是事件实体的属性。

几乎是这样 - 关键的想法是:如果Post服务是发布时间的权限,那么必须允许它宣布已经改变了主意。您在设计中构建了权威机构根据其可用信息做出最佳决策的概念,并在新信息使先前选择无效时应用更正(有时称为补偿事件)。

因此,当delete命令到达时,您检查它是否来自主机。如果是这样,您可以立即mark the post。如果它不是来自主机,你记得有人想要删除这个帖子,如果它后来发现事件的更新通知你同一个人是主机,那么你可以应用标记。

同样的方法适用于相反的情况 - 删除来自主机,因此帖子被标记。哎呦!我们刚刚发现冒名顶替不是主人。好的,所以再次显示帖子。

答案 1 :(得分:0)

<强>跟进:

上周我一直在仔细考虑这个问题,也许我发现了一个关于这个问题的推理方式的缺陷。我认为邮政实体完全专注于邮政服务,但这可能是一个令人不安的过度简化。

如果每个服务(即有界上下文)有自己的帖子概念,并因此安排存储,该怎么办?活动服务会保留一个在活动中发布的帖子表,墙上的服务记录在墙上的帖子等。

这些实体非常薄,主要包括GUID,海报的身份,也许是其内容。它们还可以包含仅在该上下文中使用的特殊属性。例如,事件,但没有其他服务,可能允许固定帖子。

(Nota bene:低于术语&#34;事件&#34;同时用于完全不同的转换,即使用例如Apache Kafka在进程之间发送的消息,描述发生的事情。)

每次将帖子提交给服务时,都会将事件发布到事件总线。例如,当用户在事件中发帖时,事件服务会创建一个帖子实体并发出events.post-posted {id: ..., authorId: ..., contents: ...}。同样,隔离墙会发布wall.post-posted {id: ..., authorId: ..., receiverId: ..., contents: ...}

反过来,邮政服务会监听所有此类事件。每次将帖子发布到其他服务时,都会在帖子服务中创建对应的帖子实体,共享原始帖子的ID。这是&#34; smart&#34; post实体,具有应用程序中的帖子共有的所有功能。它可以处理发送通知,安排线程,发现滥用行为,录制喜欢等等。

这意味着每个服务在处理其帖子实体时都有更多的自由,因为它们不再引用驻留在单个服务中的单个信息源。它允许网关根据情况选择多种检索后期数据的方法。例如,为了告诉UI帖子被固定,它需要与事件服务对话,但是为了获得文本内容,它可能必须与帖子服务交谈。也许墙上服务中的邮政实体有特殊的选择来处理发布在人们面前的生日祝福。壁。

回到最初的问题:在处理删除事件时,这消除了对服务进行通信的需要。而不是通过邮政服务删除,事件服务的工作是接收删除帖子的请求。由于它有关于帖子的作者和事件的主持人的信息,它可以自己做决定。

批评这个想法

虽然我觉得我在这里走上正轨,但我有两个主要问题。

第一个问题是,在使用微服务实现Web应用程序时,这显然无法回答有关如何处理权限的原始问题。也许这只是一个假设情景的答案,而这个问题的其他微小变化根本没有得到缓解。

我的第二个担忧是我落入了passive-aggressive event trap。我正在描述作为一系列事件发生的事情,但也许我真的在发布命令?毕竟,发布event.post-posted事件的原因是触发在邮政服务中创建事件。另一方面,如果没有听取这些事件,应用程序就不会中断;事件只会真的很干。

您对此方法有何看法?