【大咖连载】实现SockShop的第一个服务

点小蓝字加关注!

前言

本章将介绍SockWorks团队,如何实现SockShop系统的第一个服务,并完成端到端的自动化测试、打包、部署及发布过程。

实际上,从“0到1”的过程往往是具有很大挑战性的,所以团队在实现SockShop系统的第一个微服务时,也希望能将基础机制做扎实,形成可复制的DNA,以便于后续实现更多的微服务时,能作为有效的参考。

10.1  使用Java Chassis实现商品服务

商品的展示是SockShop系统中基本的功能之一,对于用户而言,商品展示的相关功能包括但不限于商品列表的显示、商品详情的显示等。本节将介绍SockWorks团队如何使用Java Chassis,实现商品服务(Catalogue Service),其步骤包括:

  • 环境准备

  • 框架搭建

  • 接口设计

  • 模型设计

  • 功能实现

  • 构建运行

10.1.1 环境准备

在开始使用JavaChassis开发商品列表服务前,团队需要统一开发环境,具体内容包括:

  • 安装JDK 1.8

  • 安装Git

  • 安装Maven 3.X

  • 安装IntelliJ Idea IDE(或Eclipse)

  • 安装Docker(可选,方便本地调试)

为了方便团队成员快速搭建好环境,可以使用自动化的脚本完成(如使用Homebrew/HomebrewCask完成Mac下的环境准备,利用Chocolatey完成在Windows下的环境准备。)

10.1.2 框架搭建

使用JavaChassis,开发人员只需要按照如下步骤,即可快速搭建商品列表服务的代码框架。

步骤一:创建工程。

使用IntellijIDEA创建pom工程,引入相关的Jar包依赖,并通过dependencyManagement对Java Chassis进行统一的版本管理,如下所示:

  <dependencyManagement>    <dependencies>      <dependency>       <groupId>org.apache.servicecomb</groupId>       <artifactId>java-chassis-dependencies</artifactId>       <version>1.0.0-m1</version>       <type>pom</type>       <scope>import</scope>    ...

步骤二:定义服务。

在项目的resource目录下,创建microservice.yaml配置文件,并设置服务相关信息:

  • 配置服务基本信息。

APPLICATION_ID: sockshop //应用名service_description:  name: catalogue        //服务名               version: 0.0.1         //服务版本号
  • 配置访问地址和端口:

cse:  rest:    address: 0.0.0.0:7071  //REST地址和端口

步骤三:运行并配置注册中心。

  • 运行注册中心:

    这里使用Docker,启动注册中心。

docker run -d -p 30100:30100 servicecomb/service-center:latest
  • 配置注册中心:

cse:  service:    registry:      address: https://${SC_HOST}:30100

步骤四:定义服务接口。

定义服务的接口实现(当前只是返回“HelloWorld!”),如下所示:@RestController@RestSchema(schemaId = "catalogue")@RequestMapping(value = "/catalogue")public class CatalogueController {    @Autowired    private CatalogueRepositorycatalogueRepository;

@ResponseStatus(HttpStatus.OK) @RequestMapping(value ="/", method = RequestMethod.GET) public @ResponseBodyString getCatalogues() { return "HelloWorld!" }}

步骤五:启动服务。

public class CatalogueApplication {    public static voidmain(String[] args) throws Exception {        Log4jUtils.init();        BeanUtils.init();    }}

服务启动的过程分为初始化Log4j、Bean加载(包括配置加载及服务注册)。

初始化Log4j:Log4jUtils会从classpath\*:config/log4j.properties文件中读取Log4j配置,初始化Log4j。
Bean加载:BeanUtils会从classpath\*:META-INF/spring/\*.bean.xml路径加载配置文件,完成应用上下文加载。CseApplicationListener会加载Handler配置,将服务注册到注册中心。

最后,运行java-jar -DSC_HOST=127.0.0.1 catalogue.jar。当服务启动后,访问注册中心的portal,可以看到Catalogue的服务信息,如图10-1所示。

图10-1  注册中心信息

在浏览器中访问http://localhost:7071/catalogue,将得到返回结果“Hello World!”。

至此代码框架已搭建完成,接下来可以进行业务功能的实现了。

10.1.2.1  接口设计

商品列表服务的基本功能是提供商品列表以及商品详情。通过Swagger定义其接口:

paths:  /catalogue:    ...    get:      description: "展示商品列表"      operationId:"showCatalogue"      produces:      -"application/json"      responses:        200:          description:"Successfully get catalogue."          schema:            type:"array"            items:              $ref:"#/definitions/Listresponse"    ...    definitions:       Listresponse:         title: "List response"         type: "object"         properties:          id:            type:"string"    ...

请求此API的例子响应:

[  {    "id":"f4c6b5fd99e3f1d1ab7fb78f712af93b",    "name":"P20",    "description":"The latest fashion socks",    "images": [     "product/6901443223459/428_428_1522652355294mp.jpg"    ],    "price": 37.88,    "stock": 996,    "tags": [      "fashion"    ]  },  ...]

10.1.2.1  模型设计

通过前期的设计,团队确定商品列表服务的核心实体是商品(Catalogue),并包含多个标签(Tag)和多个图片(Image),因此商品服务的领域模型及代码如图10-2所示。

图10-2  商品服务的领域模型

public class Catalogue { //商品模型    private String id;    private String name;    private Stringdescription;    private List<Image>images;    private float price;    private int stock;    private List<Tag>tags;}public class Tag { //标签模型    private String id;    private String name;    private String display;    private StringcatalogueId;}public class Image { //图片模型    private String id;    private int size;    private String url;    private StringcatalogueId;}

10.1.3 功能实现

通过之前的分析,团队对商品列表服务的功能实现如下所示:

   

@ResponseStatus(HttpStatus.OK)    @RequestMapping("/")    public @ResponseBodyList<Catalogue> getCatalogues() {        LOG.info("Receiveddata: fetching all product catalogue");    return  catalogueRepository.all(); //返回所有商品列表    }

如上述代码所示,getCatalogues获取商品列表,并返回给消费者。其中catalogueRepository主要负责从数据库中获取相关数据,这里不再展开。关于Catalogue更多的功能实现,请参考本书相关源码。

构建运行
  • 通过JAR运行

在目录sockshop-demo/catalogue下,执行mvn clean package命令完成打包后,即可执行命令 java -jar  -DMYSQL_HOST=127.0.0.1 -DSC_HOST=127.0.0.1catalogue.jar启动Catalogue服务。

  • 通过Docker运行

同时,团队也创建了Dockerfile,将catalogue服务打包成Docker镜像,通过容器运行。其Dockerfile的内容如下所示:

FROM openjdk:8-jre-alpine      # 使用JRE-1.8的alpine版本作为基础镜像,减少镜像大小ENV APP_ROOT=/root/servicestage/catalogue/         #应用安装在容器的目录ENV LOG_ROOT=/var/log/catalogue/                          #应用日志目录RUN mkdir -p $APP_ROOT $LOG_ROOT $APP_ROOT/lib     # 创建安装、类库、日志目录# 拷贝软件包、启动脚本、类库到镜像中COPY ./target/catalogue.jar $APP_ROOT  COPY ./catalogue.sh $APP_ROOT        # 内容为cd到应用目录,执行 "jar -jar" 命令拉起服务COPY ./lib/* $APP_ROOT/lib/# 修改目录权限RUN cd $APP_ROOT && chmod -R 770 . && chmod +x/root/servicestage/ catalogue/catalogue.shENTRYPOINT ["/root/servicestage/catalogue/catalogue.sh"]

然后,执行dockerbuild -t catalogue .构建镜像,并通过如下命令来启动:

docker run -e SC_HOST=127.0.0.1 -e MYSQL_HOST=127.0.0.1 -p 7071:7071catalogue

实际上,这个打包的任务应该交由持续集成流水线自动实现,并将Docker镜像存储到相应的镜像仓库。

最后,访问http://localhost:7071/catalogue,便能看到Catalogue服务返回的商品列表。

10.2  使用Docker-Compose本地运行服务


在Catalogue服务开发一段时间后,团队发现本地运行catalogue服务比较烦琐,除了在运行前启动服务中心、MySQL服务,启动时也需要传入相关环境变量,于是参考本书“5.3.3本地运行服务”提到的实践,使用Docker-Compose进行本地编排,简化这一过程。

首先,在本地安装好Docker-Compose,具体过程请参考Docker Compose文档(https:// docs.docker.com/compose/install/),然后为catalogue工程创建docker-compose.yml文件,并在文件中定义注册中心、MySQL以及catalogue服务,如下所示:

version: '2'services:  catalogue:    build: .    ports:     - "7071:7071"    environment:     - SC_HOST: servicecenter     - MYSQL_HOST: mysql    links:      - mysql:mysql      - servicecenter:servicecenter   mysql:  image: "mysql:latest"  ports:   - "3306:3306"  servicecenter:    image:servicecomb/servicecenter:latest    ports:      - 30100:30100

这样当每次修改完代码后,开发人员只需运行命令mvnclean package && docker- compose up -d --build,就可以完成Docker镜像的构建,同时启动商品列表服务。

10.3  商品服务自动化测试


本节将介绍如何对catalogue服务进行自动化测试。对于单个服务而言,测试主要包括接口测试、组件测试和单元测试。

10.3.1 接口测试

接口测试是从接口使用的角度测试功能实现的完整度。它处于测试金字塔的中上层,接口测试覆盖的范围较广,性价比较高。SockWorks团队使用rest-assured实现接口测试。rest-assured是一个能够简化测试REST服务的Java DSL实现,同时包括了Groovy动态语言的特性。

使用前需要先在catalogue工程的pom文件引入rest-assured的依赖:

<dependency>   <groupId>io.rest-assured</groupId>   <artifactId>rest-assured</artifactId>   <version>3.0.7</version>   <scope>test</scope></dependency>

对于接口GET/catalogue,它将得到的响应如下所示:

{    "catalogues": [      {        "id": 1,        "name":"Fasion Sock",       "description": "For young people",        "images":null,        "price": 16,        "stock": 30,        "tags": [          {            "id": 1,            "name":"winter",            "display": "冬款"          }        ]      }    ]}

基于如上的响应,团队的测试用例如下所示:

public class CatalogueApiTest {    @Test    public voidtestCreateOrder() {       given().contentType("application/json")       .when().get("http://localhost:7071/catalogue")        .then().assertThat()              .body("size()", greaterThan(0))              .body("catalogues[0].id", IsNull.notNullValue())              .body("catalogues[0].name", IsNull.notNullValue())              .body("catalogues[0].descrption", IsNull.notNullValue())              .body("catalogues[0].price", IsNull.notNullValue())              .body("catalogues[0].stock", IsNull.notNullValue())              .body("items[0].tags.size", greaterThan(0))          }}

此用例先发送请求至/catalogue接口,然后对返回值进行校验。

这里只对返回值的格式进行了校验,如需对返回的数据进行验证,请参考rest-assured的更多用法。

从测试用例可以看出,使用rest-assured实现接口测试十分容易。同时,测试代码的格式遵循Given/When/Then这种BDD(Behavior Driven Design)的格式,可读性较强。

10.3.2  单元测试

接口测试是基于服务对外提供的接口进行验证,它是一种黑盒测试。接口测试能覆盖一些场景,但是当测试出现问题时,不易定位。另外对于边界条件的处理,接口测试的实现和执行成本较高。

单元测试覆盖粒度细,实现成本低,测试失败后更容易定位问题。以商品查询功能为例,对于服务的使用者来说,可以通过Tag查询商品,其功能的实现大致分两步:

  • 根据标签字符串查询出标签ID。

  • 将标签ID传给CatalogueRepository,由CatalogueRepository根据标签ID来查询商品。

在CatalogueController中的实现如下所示:

   

 publicList<Catalogue> findByTag(String tagLabel) {        Tag tag =tagRepository.findByName(tagLabel);        returncatalogueRepository.findAllByTagId(tag.id);    }}

对于此功能,可以使用单元测试分别对tagRepository.findByName和catalogueRepository. findAllByTagId进行验证。其中对于catalogueRepository.findAllByTagId测试的具体实现如下所示:

1.在test/java目录下新建单元测试类CatalogueRepositoryTest。

2.准备测试数据。这里使用了H2这种内存数据库辅助数据的存储,以降低环境准备成本。

public class CatalogueRepositoryTest {    ...    @Before    public void setUp() throwsException {        catalogueRepository.create("88957aa4-1af0-11e8-accf-0ed5f89f718b",1);        catalogueRepository.create("88957d2e-1af0-11e8-accf-0ed5f89f718b",1);    }    ...}

3.对CatalogueRepository的逻辑进行验证。

public class CatalogueRepositoryTest {    ...    @Test    public voidshoudReturnTwoCataloguesByTagId() throws Exception {        List<Catalogue>catalogues = catalogueRepository.findAllByTagId(1);        assertEquals(2,catalogues.size());        assertEquals("88957aa4-1af0-11e8-accf-0ed5f89f718b",catalogues.get(0).getUUID());    }    ...}

4.在测试运行完成之后,清理测试数据。

public class CatalogueRepositoryTest {    ...    @After    public void tearDown() throwsException {        catalogueRepository.delete("88957aa4-1af0-11e8-accf-0ed5f89f718b");        catalogueRepository.delete("88957d2e-1af0-11e8-accf-0ed5f89f718b");    }    ...}

本测试用例使用了 H2 这种内存数据库,也可连接一个独立的数据库用来执行自动化测试,或使用powermock之类的工具mock数据库的连接,来提高测试执行效率。

也应该对 findByTag 这个完整的功能做测试,有时候也将这种跨模块完整功能的测试称作组件测试,其实现方式与上述单元测试一致,逻辑上区别开来即可。

10.4  搭建交付流水线


在之前的几节中,SockWorks开发团队已经完成了商品服务的设计、实现、测试以及打包。接下来,他们将使用ServiceStage提供的流水线功能,实现对商品服务的交付流水线搭建。ServiceStage的流水线分为构建管理和部署管理,本节将介绍如何基于构建管理与部署管理,搭建服务的持续交付流水线。

10.4.1  构建管理

通过构建管理,团队可以与创建构建相关(除部署之外)的任务,包括代码检查、单元测试、创建镜像、发布镜像等。这里以定义镜像任务为例,介绍如何在ServiceStage流水线上进行构建管理的任务定义,具体设置步骤如图10-3所示。

图10-3  创建商品服务以及其数据库的镜像

1.首先选择ServiceStage控制台的“应用开发š构建管理š创建job”选项,填写基本信息。

2.在构建设置部分,选择“Docker混合构建”选项,并设置Dockerfile的目录,镜像名称和镜像版本。

${index}是每次构建运行后的序号,类似jenkins的构建号(BUILDNUMBER),可以通过${index}定义版本的自增编号。

3.在构建集群部分,选择对应的集群为msa-ci。

在之前的章节中,团队定义了msa-ci作为持续集成流水线的集群名称。

4.在设置归档部分,设置镜像归档的仓库msa。

在ServiceStage的SWR软件仓库中,可以创建SockShop的镜像仓库空间msa。

依次创建流水线相关的其他任务,创建后的列表如图10-4所示。

图10-4  商品服务的所有构建任务

10.4.2 部署管理

SockWorks的开发团队缺乏自动化部署和持续交付流水线搭建的能力。所以在商品服务流水线搭建前,其运维团队在利用TOSCA模板,创建了SockShop系统堆栈,帮助开发团队利用流水线去独立部署商品服务应用。这样在后续的交付过程中,商品服务的开发团队就可以独立地通过流水线部署商品服务了。

部署的任务需要在创建流水线设置,支持单服务的自动化部署通过流水线自动化测试检测的版本,具体的设置可以参考下面的内容。

10.4.3 创建流水线

在“应用开发→微服务开发→流水线”中为商品服务添加新的流水线。商品服务的流水线主要分为以下四个阶段:

  • 提交阶段

在流水线基本信息页面填写名称和描述,并创建Source、Build、Verify、Publish四个阶段。在Source中添加代码源任务,由于在构建管理时已经定义了Github账号的绑定。因此这里只需要选择命名空间、仓库、分支即可。

  • 构建阶段

接下来在构建阶段中添加代码检查、单元测试、创建镜像等任务,并选择Assembling方式构建系统。添加单元测试任务如图10-5所示。

图10-5  添加单元测试任务

  • 验证阶段

验证阶段包含预生产环境的自动化部署以及冒烟测试。首先添加部署任务,选择预生产环境的集群msa-2048,再添加构建阶段中创建的商品服务及数据库镜像,如图10-6所示。

图10-6  预生产环境的自动化部署任务的配置

  • 发布阶段

发布阶段主要包含了生产环境的部署。由于SockWorks的开发团队的自动化测试还在完善中,运维团队关闭了生产环境的自动部署开关,开发团队可以在预生产环境测试后再手动触发生产环境部署。部署任务和预生产的部署任务类似,但需要选择不同的集群msa-4096和命名空间sockshop。

流水线搭建完成后,即可触发进行构建及部署。单击流水线可以看到每个阶段任务执行的情况,如图10-7所示。

在本书中,对于商品列表服务,笔者只定义了预生产环境和生产环境。在真实的场景中,可能会有更多的环境用于辅助系统验证,读者可以基于该思路,举一反三。

图10-7  Catalogue服务流水线

10.5  小结


在本章中,SockWorks团队使用JavaChassis开发了第一个Catalogue服务,并基于微服务参考模型的相关实践,完成了服务的测试、打包以及发布。同时,利用ServiceStage的流水线功能,搭建了提交、构建、验证和发布的流水线,建立了顺畅的端到端的交付机制。经过如上的过程后,SockWorks团队的IT部门通过对结果类指标和过程类指标的收集,总结了第一阶段的收益。从结果类指标来看,周期时间大幅缩短,部署频率有所提高,每个迭代都可以完成新特性的部署。如下表所示。

结果类指标

改进前

改进后

周期时间(天)

34.5

14

部署频率(次/月)

0.64

1.57

从过程类指标来看,团队的研发效率、持续集成得到了较大的提升,如下表所示。

过程类指标

改进前

改进后

提交频率(次/天)

0.28

1.07

平均提交行数

500

87.58

CI构建时间(分钟)

10

3

单元测试覆盖率

8.5%

75%

End

往期回顾

/热点

新特性解读 | Apache ServiceComb Toolkit 0.1.0发布新特性解读 | Apache ServiceComb Toolkit 0.1.0发布

/关注

Apache ServiceComb 服务网格与微服务开发框架融合实践

《微服务架构与实践(第2版)》是在第1版的基础之上,基于作者近年来对服务化改造的实战经验和思考,并结合业界的技术趋势进行的一次体系化的精进。全书共分为基础篇、策略篇和实践篇,剖析了微服务架构理论、微服务实施的参考模型、最佳实践以及基于真实案例的实战。

本书不仅适合架构师、开发人员以及技术管理者阅读,也适合正在尝试向微服务架构迁移的团队或者个人。

京东购买链接:

https://item.jd.com/12511883.html

用心做开源,不忘初衷

戳二维码+小助手

微服务云应用平台(ServiceStage):提供微服务的开发、构建、发布、监控及运维等一站式解决方案。

https://www.huaweicloud.com/product/servicestage.html

Apache ServiceComb:业界首个Apache 微服务解决方案,致力于帮助企业、用户和开发者将应用轻松微服务化上云,实现对微服务应用的高效运维管理。

http://servicecomb.apache.org/cn/docs/join_the_community/

本文为作者原创文章,未经作者允许不得转载。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值