2.3.3 构建微服务的入口:Spring Boot控制器

现在已经有了构建脚本,并实现了一个简单的Spring Boot引导类,接下来就可以开始编写第一个代码来做一些事情。这个代码就是控制器类。在Spring Boot应用程序中,控制器类公开了服务端点,并将数据从传入的HTTP请求映射到将处理该请求的Java方法。
遵循REST
本书中的所有微服务都遵循REST方法来构建。对REST的深入讨论超出了本书的范围[1],但对于本书,我们构建的所有服务都将具有以下特点。
使用HTTP作为服务的调用协议——服务将通过HTTP端点公开,并使用HTTP协议传输进出服务的数据。
将服务的行为映射到标准HTTP动词——REST强调将服务的行为映射到POST、GET、PUT和DELETE这样的HTTP动词上。这些动词映射到大多数服务中的CRUD功能。
使用JSON作为进出服务的所有数据的序列化格式——对基于REST的微服务来说,这不是一个硬性原则,但是JSON已经成为通过微服务提交和返回数据的通用语言。当然也可以使用XML,但是许多基于REST的应用程序大量使用JavaScript和JSON。JSON是基于JavaScript的Web前端和服务对数据进行序列化和反序列化的原生格式。
使用HTTP状态码来传达服务调用的状态——HTTP协议开发了一组丰富的状态码,以指示服务的成功或失败。基于REST的服务利用这些HTTP状态码和其他基于Web的基础设施,如反向代理和缓存,可以相对容易地与微服务集成。
HTTP是Web的语言,使用HTTP作为构建服务的哲学框架是构建云服务的关键。
第一个控制器类LicenseSerriceController位于src/main/java/com/thoughtmechanix/licenses/controllers/LicenseServiceController.java中。这个类将公开4个HTTP端点,这些端点将映射到POST、GET、PUT和DELETE动词。
让我们看一下控制器类,看看Spring Boot如何提供一组注解,以保证花最少的努力公开服务端点,使开发人员能够集中精力构建服务的业务逻辑。我们将从没有任何类方法的基本控制器类定义开始。代码清单2-3展示了为许可证服务构建的控制器类。
代码清单2-3 标记LicenseServiceController为Spring RestController
 
  1. package com.thoughtmechanix.licenses.controllers;  
  2.    
  3. // 为了简洁,省略了import语句  
  4.    
  5. @RestController    ⇽---  @Restcontroller告诉Spring Boot这是一个基于REST的服务,它将自动序列化/反序列化服务请求/响应到JSON  
  6. @RequestMapping(value="/v1/organizations/{organizationId}/licenses")    ⇽---  在这个类中使用/v1/organizations{organizationId}/ licenses的前缀,公开所有HTTP端点  
  7. public class LicenseServiceController {  
  8.   // 为了简洁,省略了该类的内容  
我们通过查看@RestController注解来开始探索。@RestController是一个类级Java注解,它告诉Spring容器这个Java类将用于基于REST的服务。此注解自动处理以JSON或XML方式传递到服务中的数据的序列化(在默认情况下,@RestController类将返回的数据序列化为JSON)。与传统的Spring @Controller注解不同,@RestController注解并不需要开发者从控制器类返回ResponseBody类。这一切都由@RestController注解进行处理,它包含了@ResponseBody注解。
 
为什么是JSON
在基于HTTP的微服务之间发送数据时,其实有多种可选的协议。由于以下几个原因,JSON已经成为事实上的标准。
首先,与其他协议(如基于XML的SOAP(Simple Object Access Protocol,简单对象访问协议))相比,它非常轻量级,可以在没有太多文本开销的情况下传递数据。
其次,JSON易于人们阅读和消费。这在选择序列化协议时往往被低估。当出现问题时,开发人员可以快速查看一大堆JSON,直观地处理其中的内容。JSON协议的简单性让这件事非常容易做到。
最后,JSON是JavaScript使用的默认序列化协议。由于JavaScript作为编程语言的急剧增长以及依赖于JavaScript的单页互联网应用程序(Single Page Internet Application,SPIA)的同样快速增长,JSON已经天然适用于构建基于REST的应用程序,因为前端Web客户端用它来调用服务。
其他机制和协议能够比JSON更有效地在服务之间进行通信。Apache Thrift框架允许构建使用二进制协议相互通信的多语言服务。Apache Avro协议是一种数据序列化协议,可在客户端和服务器调用之间将数据转换为二进制格式。
如果读者需要最小化通过线路发送的数据的大小,我建议查看这些协议。但是根据我的经验,在微服务中使用直接的JSON就可以有效地工作,并且不会在服务消费者和服务客户端间插入另一层通信来进行调试。
代码清单2-3中展示的第二个注解是@RequestMapping。可以使用@RequestMapping作为类级注解和方法级注解。@RequestMapping注解用于告诉Spring容器该服务将要公开的HTTP端点。使用类级的@RequestMapping注解时,将为该控制器公开的所有其他端点建立URL的根。
在代码清单2-3中,@RequestMapping(value="/v1/organizations/{organizationId}/licenses")使用value属性为控制器类中公开的所有端点建立URL的根。在此控制器中公开的所有服务端点将以/v1/organizations/{organizationId}/licenses作为其端点的根。{organizationId}是一个占位符,表明如何使用在每个调用中传递的organizationId来参数化URL。在URL中使用organizationId可以区分使用服务的不同客户。
现在将添加控制器的第一个方法。这一方法将实现REST调用中的GET动词,并返回单个License类实例,如代码清单2-4所示(为了便于讨论,将实例化一个名为License的Java类)。
代码清单2-4 公开一个GET HTTP端点
 
  1. @RequestMapping(value="/{licenseId}",method = RequestMethod.GET)     ⇽---  使用值创建一个GET端点v1/organizations/  {organizationId}/licenses/{licenseId}  
  2. public License getLicenses(  
  3. ➥  @PathVariable("organizationId") String organizationId,  
  4. ➥  @PathVariable("licenseId") String licenseId) {    ⇽---  从URL映射两个参数(organizationId和licenseId)到方法参数  
  5.     return new License()  
  6.         .withId(licenseId)  
  7.         .withProductName("Teleco")  
  8.         .withLicenseType("Seat")  
  9.         .withOrganizationId("TestOrg");  
这一代码清单中完成的第一件事是,使用方法级的@RequestMapping注解来标记getLicenses()方法,将两个参数传递给注解,即value和method。通过方法级的@Request Mapping注解,再结合类顶部指定的根级注解,我们将所有传入该控制器的HTTP请求与端点/v1/organizations/{organizationId}/licences/{licensedId}匹配起来。该注解的第二个参数method指定该方法将匹配的HTTP动词。在前面的例子中,以RequestMethod. GET枚举的形式匹配GET方法。
关于代码清单 2-4,需要注意的第二件事是getLicenses()方法的参数体中使用了@PathVariable注解。@PathVariable注解用于将在传入的URL中传递的参数值(由{parameterName}语法表示)映射为方法的参数。在代码清单2-4所示的示例代码中,将两个参数organizationId和licenseId映射到方法中的两个参数级变量:
 
  1. @PathVariable("organizationId") String organizationId,  
  2. @PathVariable("licenseId") String licenseId) 
 
 
端点命名问题
在编写微服务之前,要确保(以及组织中的其他可能的团队)为服务公开的端点建立标准。应该使用微服务的URL(Uniform Resource Locator,统一资源定位器)来明确传达服务的意图、服务管理的资源以及服务内管理的资源之间存在的关系。以下指导方针有助于命名服务端点。
(1)使用明确的URL名称来确立服务所代表的资源——使用规范的格式定义URL将有助于API更直观,更易于使用。要在命名约定中保持一致。
(2)使用URL来确立资源之间的关系——通常,在微服务中会存在一种父子关系,在这些资源中,子项不会存在于父项的上下文之外(因此可能没有针对该子项的单独的微服务)。使用URL来表达这些关系。但是,如果发现URL嵌套过长,可能意味着微服务尝试做的事情太多了。
(3)尽早建立URL的版本控制方案——URL 及其对应的端点代表了服务的所有者和服务的消费者之间的契约。一种常见的模式是使用版本号作为前缀添加到所有端点上。尽早建立版本控制方案,并坚持下去。在几个消费者使用它们之后,对URL进行版本更新是非常困难的。
现在,可以将我们刚刚创建的东西称为服务。在命令行窗口中,转到下载示例代码的项目目录,然后执行以下Maven命令:
 
  1. mvn spring-boot:run 
一旦按下回车键,应该会看到Spring Boot启动一个嵌入式Tomcat服务器,并开始监听8080端口。
服务启动后就可以直接访问公开的端点了。因为公开的第一个方法是GET调用,可以使用多种方法来调用这一服务。我的首选方法是使用基于Chrome的工具,如POSTMAN或CURL来调用该服务。图2-5展示了在http://localhost:8080/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/licenses/f3831f8c-c338-4ebe-a82a-e2fc1d1ff78a端点上完成的一个GET请求。
现在我们已经有了一个服务的运行骨架。但从开发的角度来看,这服务还不完整。良好的微服务设计不可避免地将服务分成定义明确的业务逻辑和数据访问层。在后面几章中,我们将继续对此服务进行迭代,并进一步深入了解如何构建该服务。
我们来看看最后一个视角——探索DevOps工程师如何实施服务并将其打包以部署到云中。
 
 
 
 
 
 
posted @ 2019-12-02 20:39  mongotea  阅读(487)  评论(0编辑  收藏  举报