现代的多用户Web应用对用户操作施加了很多限制,也就是说,这些操作需要权限。例如,用户只能更改自己的个人数据,只有组成员才能在该组中发布内容。在经典的单体应用程序中,可以通过连接多个数据库表来轻松执行此类限制,并根据查询结果采取相应行动。然而,在微服务中,这些限制应该在哪里和如何处理变得不太清楚。
为了举例说明,请考虑一个Facebook克隆版。整个应用程序由以下几部分组成:
- 一个前端,使用JS和其他Web技术编写 - 由多个微服务组成的后端 - 用于从后端检索和提交数据的API,即网关
至于业务逻辑,有两个众所周知的实体:
- 事件(如音乐会、生日聚会等) - 帖子(存在于墙壁、页面、事件等上的文本条目)
假设这两个实体由单独的服务EventService和PostService管理。然后考虑以下约束条件:
“活动帖子可以由两种类型的用户删除:帖子的作者和活动的主办方。”
在单体应用程序中,这个约束概念上非常容易处理。收到删除帖子的请求,提供帖子id和用户id,
1. 获取帖子所属的事件。 2. 检查用户是否是帖子的作者。 3. 如果是,则删除该帖子。如果不是,则获取活动的主办方。 4. 检查用户是否在主办方之中。 5. 如果是,则删除该帖子。
然而,在微服务策略下,我很难想象如何跨服务划分此类操作的职责。
备选方案1
一个简单的方法是将此类逻辑放入网关中。这样,可以基本上执行与上述相同的过程,但调用服务而不是直接调用数据库。大致草图:
然而,我对此解决方案并不满意。一旦我开始在网关中放置这样的逻辑,就会变得非常诱人,因为这是一种快速简单的完成任务的方式。所有业务逻辑最终都将聚集在网关中,服务将变成“愚蠢”的CRUD端点。这将违背拥有具有明确定义的责任范围的独立服务的目的。此外,随着操作变得更加复杂,它可能会变得缓慢,因为它可能会导致对服务的高数量调用。
本质上,我将重新发明单块架构,用缓慢和有限的网络调用替换数据库查询。
备选方案2
另一个选择是允许服务之间的无限通信,在执行删除操作之前,PostService可以仅仅询问EventService用户是否是所讨论事件的主持人。然而,我担心大量的微服务相互通信可能会在更长时间内引入许多耦合。专家似乎普遍建议反对直接的服务间通信。
备选方案3
通过一个可靠的事件发布和订阅系统,服务可以保持更新关于其他服务中发生的事情的最新状态。例如,每当在EventService中将用户提升为主持人时,都将发布一个事件(例如,“events.participant-status-changed,{userId:14323,eventId:12321,status:'host'}”)。PostService可以订阅事件并在收到删除帖子请求时记住这个事实。
然而,我对这一点也不太满意。它将创建一个非常复杂且容易出错的系统,其中一个未处理的(但可能罕见的)事件可能会导致服务不同步。此外,有风险导致逻辑放错地方。例如,尽管概念上这是事件实体的属性,但此问题中的约束将由PostService处理。
但我应该强调,当使用微服务实现应用程序时,我非常乐观地认为事件的有用性。我只是不确定它们是否是解决这类问题的答案。
你会如何解决这种假想但相当现实的困难?
为了举例说明,请考虑一个Facebook克隆版。整个应用程序由以下几部分组成:
- 一个前端,使用JS和其他Web技术编写 - 由多个微服务组成的后端 - 用于从后端检索和提交数据的API,即网关
至于业务逻辑,有两个众所周知的实体:
- 事件(如音乐会、生日聚会等) - 帖子(存在于墙壁、页面、事件等上的文本条目)
假设这两个实体由单独的服务EventService和PostService管理。然后考虑以下约束条件:
“活动帖子可以由两种类型的用户删除:帖子的作者和活动的主办方。”
在单体应用程序中,这个约束概念上非常容易处理。收到删除帖子的请求,提供帖子id和用户id,
1. 获取帖子所属的事件。 2. 检查用户是否是帖子的作者。 3. 如果是,则删除该帖子。如果不是,则获取活动的主办方。 4. 检查用户是否在主办方之中。 5. 如果是,则删除该帖子。
然而,在微服务策略下,我很难想象如何跨服务划分此类操作的职责。
备选方案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}`);
}
然而,我对此解决方案并不满意。一旦我开始在网关中放置这样的逻辑,就会变得非常诱人,因为这是一种快速简单的完成任务的方式。所有业务逻辑最终都将聚集在网关中,服务将变成“愚蠢”的CRUD端点。这将违背拥有具有明确定义的责任范围的独立服务的目的。此外,随着操作变得更加复杂,它可能会变得缓慢,因为它可能会导致对服务的高数量调用。
本质上,我将重新发明单块架构,用缓慢和有限的网络调用替换数据库查询。
备选方案2
另一个选择是允许服务之间的无限通信,在执行删除操作之前,PostService可以仅仅询问EventService用户是否是所讨论事件的主持人。然而,我担心大量的微服务相互通信可能会在更长时间内引入许多耦合。专家似乎普遍建议反对直接的服务间通信。
备选方案3
通过一个可靠的事件发布和订阅系统,服务可以保持更新关于其他服务中发生的事情的最新状态。例如,每当在EventService中将用户提升为主持人时,都将发布一个事件(例如,“events.participant-status-changed,{userId:14323,eventId:12321,status:'host'}”)。PostService可以订阅事件并在收到删除帖子请求时记住这个事实。
然而,我对这一点也不太满意。它将创建一个非常复杂且容易出错的系统,其中一个未处理的(但可能罕见的)事件可能会导致服务不同步。此外,有风险导致逻辑放错地方。例如,尽管概念上这是事件实体的属性,但此问题中的约束将由PostService处理。
但我应该强调,当使用微服务实现应用程序时,我非常乐观地认为事件的有用性。我只是不确定它们是否是解决这类问题的答案。
你会如何解决这种假想但相当现实的困难?