购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.1 代码库间的通用代码及重复代码

我们会讨论微服务架构中共享代码存在的设计问题。假设这样的场景,有两个开发团队,分别是团队A和团队B。团队A为Payment服务工作,团队B为Person服务工作,如图2.1所示。

图2.1 两个独立的服务:Payment和Person

Payment服务在/payment URL端点上提供了一个HTTP API。而Person服务在/person端点上提供了它对应的业务逻辑。我们假设这两个代码库都使用相同的程序设计语言编写。为了能快速交付需求,此时,两个团队都在紧锣密鼓地开展各项工作。

高效软件开发团队能快速交付软件产品的重要原因之一是不需要在多个团队之间同步信息。我们可以借用阿姆达尔定律来估算同步信息对软件交付整体周期的影响。该定律表明同步的需求越少(因此,更多的工作)可以并行,通过增加资源来解决问题能达到的效果越明显。图2.2对这一定律进行了说明。

图2.2 阿姆达尔定律发现系统整体可达的最大处理速度取决于可并行处理的工作所占的百分比

譬如,如果你的任务仅有50%的时间可以进行并行处理(另外50%的时间需要进行同步处理),即便增加资源(即图2.2中的处理器个数)也无法显著提升处理速度。任务的并行度越高,同步的开销越小,增加资源而提升处理速度越明显。

我们可以使用阿姆达尔公式计算并行度,得出增加更多资源可获得的效益,我们也可以将该公式应用到工作于特定任务的团队成员上。减少并行的同步时间,包括花费在会议、合并代码分支以及需要全团队出席的活动所占用的时间。

若允许代码重复,各团队可独立开发,不需要进行团队之间的信息同步。因此,为团队增加新的成员能提升生产率。如果消除重复代码,情况就截然不同,这时两个团队需要在同一段代码上开展工作,随时可能因为代码变更而阻塞对方的工作。

2.1.1 添加新需求导致的代码重复

完成上述两个服务的开发后,紧接着来了一个新需求:为这两个HTTP接口添加授权功能。两个团队的首选方案都是在自己的代码库中实现授权组件。图2.3展示了新的架构设计。

图2.3 新的架构设计

两个团队各自开发、维护一个类似的授权组件。然而,两个团队的工作依旧是相互独立的。

请注意,介绍这个场景时,我们使用了基于令牌的简化版认证,该方案容易遭到流量回放攻击,因此不建议在生产系统中使用。本章中我们使用简化版认证做介绍是为了避免将我们希望介绍的主要内容复杂化。安全很重要,这一点再怎么强调也不为过。如果每个团队都各使用一套方案,保障好安全的概率是非常低的。从避免安全事故角度出发,虽然开发一个共享库的周期更长,但它带来的优势是很明显的。

2.1.2 实现新的业务需求

让我们一起回顾Payment服务提供的功能。它在端点/payment上提供了一个基于HTTP的支付服务。它只提供了一个@GET资源,用于获取某个令牌的所有支付信息,如代码清单2.1所示。

代码清单2.1 /payment端点的实现
@Path("/payment")  ◁--- 暴露Payment服务的接口
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class PaymentResource {
 
  private final PaymentService paymentService = new PaymentService();
  private final AuthService authService = new AuthService();  ◁--- 创建AuthService实例
 
  @GET
  @Path("/{token}")
  public Response getAllPayments(@PathParam("token") String token) {
    if (authService.isTokenValid(token)) {  ◁--- 使用AuthService验证令牌
      return Response.ok(paymentService.getAllPayments()).build();
    } else {
      return Response.status(Status.UNAUTHORIZED).build();
    }
  }
}

如你所见,在代码清单2.1中,AuthService会对令牌做验证,验证通过之后,调用方会继续访问Payment服务,返回所有的支付信息。实际项目中,AuthService的逻辑会更加复杂。我们先看看简化版AuthService的实现,如代码清单2.2所示。

代码清单2.2 创建验证服务
public class AuthService {
 
  public boolean isTokenValid(String token) {
    return token.equals("secret");
  }
}

注意 实际项目中通常不会出现两个团队使用完全一样的接口、方法名、函数签名等情况。这是之前讨论早共享代码能带来的优势之一:方法实现出现差异的概率要降低很多。

其中一个团队负责Person服务的开发,使/person端点提供HTTP服务。该服务也采用基于令牌的认证,如代码清单2.3所示。

代码清单2.3 /Person端点的实现
@Path("/person")  ◁--- 暴露Person服务的HTTP接口
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class PersonResource {
 
  private final PersonService personService = new PersonService();
  private final AuthService authService = new AuthService();  ◁--- 创建AuthService实例
 
  @GET
  @Path("/{token}/{id}")
  public Response getPersonById(@PathParam("token") String token, 
     @PathParam("id") String id) {
    if (authService.isTokenValid(token)) {  ◁--- 使用AuthService验证令牌
      return Response.ok(personService.getById(id)).build();
    } else {
      return Response.status(Status.UNAUTHORIZED).build();
    }
  }
}

Person服务也集成了AuthService服务。它会验证用户提供的令牌,接着使用PersonService获取Person的信息。

2.1.3 结果评估

截至目前,由于两个团队独立并行开发,因此存在代码及工作的冗余。

■ 重复的代码可能导致更多的缺陷与错误。举个例子,团队B在他的授权组件中修复了一个缺陷,这并不意味着团队A不会遇到同样的缺陷。

■ 同样或相似的代码存在于各自独立的代码库中时,由于信息孤岛效应,工程师之间无法及时分享信息。譬如,团队B发现一个令牌计算的缺陷并在自己的代码库中进行了修复。不幸的是,这部分修复的代码不会自动合并到团队A的代码库。团队A仍需要在不久的将来自行修复该缺陷,甚至不基于团队B的代码变更。

■ 不需要协调的工作可能进展更迅速。不过,这也会导致两个团队都做了大量类似或者重复的工作。

在实际生产中,我们推荐使用久经验证的认证策略,譬如OAuth或者JWT,而不是从零开始实现一套逻辑。这些策略在微服务架构中更加重要。当多个服务需要认证从而访问其他服务的资源时,这两种策略都有很大的优势。我们不会在这里专注讨论某个认证或者授权策略。我们更倾向于专注代码层面,譬如代码的灵活性、可维护性以及架构的复杂性。在2.2节中,我们会讨论如何通过抽取通用代码,构造共享库来解决代码冗余的问题。 nxDw2eByYt6ouZXFhOYh3ACMKmtdLy1TyUTj6scDY0L/f64H2ijXWOjt9b+ymtuT

点击中间区域
呼出菜单
上一章
目录
下一章
×