User
(包含基本用户数据,如姓名)Passport
(包含认证凭据,即密码)
UserService
(负责创建和管理用户及其基本数据)AuthService
(负责用户身份验证和密码处理)
User
实体属于 UserService
,而 Passport
实体属于 AuthService
。这两个服务之间必须分开,因为它们解决的任务非常不同:一个是处理“个人资料”(profile data),另一个是处理“身份验证”(authentication)。此外,假设有一个注册表单,包括三个字段:
- 电子邮件
- 姓名
- 密码
GatewayService
,后者会截取所有针对应用程序的请求并将其路由到内部微服务(或组成/聚合它们)。现在,当网关服务接收到带有所有表单数据的请求时,需要执行以下操作:
- 调用
UserService
创建新用户(它将响应生成的userId
)。 - 调用
AuthService
为新创建的用户创建护照。它需要在第一步中收到的userId
和原始请求中的password
字段。
AuthService
呢?我们需要以某种方式分离这些请求!经典方法是使用最终一致性,并通过异步调用创建
Passport
实体(我们可以将此请求放置在队列中并在单独的服务中处理它)。为此,我们将向 AuthService
发送异步请求,向其中传递 userId
和 password
,而不是在第二步中执行操作,因此第一步将立即向客户端返回响应。但是,如果
password
字段格式不正确(违反验证规则),该怎么办?验证逻辑仅存在于 AuthService
中,因此在进行调用之前无法确定密码是否正确。现在,请求正在异步处理,因此我们无法回到用户并告诉他更正密码。
那么,如何在面向微服务的应用程序中正确处理分布式组合请求的验证呢?
天真的解决方案是将验证逻辑移动到
GatewayService
本身,但这是一个可怕的想法,因为它会使其变得臃肿,并且会泄漏来自AuthService
的业务逻辑。另一个想法是提供一个额外的密码验证方法,并在步骤#1和#2之前调用它。看起来像是一种可行的解决方案,但它将强制我们在微服务中为每个业务方法拥有两个方法,一个用于前置验证,一个用于实际操作。此外,在验证和操作之间存在时间空间,因此当实际执行操作时,早期的正确值可能变为不正确。
我们可以将表单分成两个部分,以避免复合请求,并在询问个人数据并为用户创建帐户后要求他输入密码。然而,这可能会导致安全问题,例如用户帐户可能会被其他人截取,其他人可能会猜测下一个
userId
。我们可以使用一些额外的安全令牌,但这将向服务引入奇怪的功能,并使整个设置更加复杂。此外,这种方法看起来像是试图逃避问题,你不能总是避免复合请求。
我们可以使用完整的分布式事务,例如2PC,但这将使系统显着复杂,并且在首次使用MSA时会减弱其使用。
最后一个想法是将这两个服务合并在一起,但在微服务架构中这样做没有任何意义。