转载

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

【大咖连载】实现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 (可选,方便本地调试)

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

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

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

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 加载(包括配置加载及服务注册)。

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

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

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

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

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

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 所示。

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

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.sh

ENTRYPOINT ["/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

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

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

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

最后,访问 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 接口,然后对返回值进行校验。

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

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

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

从测试用例可以看出,使用 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");

}

...

}

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

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

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

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

10.4  搭建交付流水线

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

10.4.1  构建管理

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

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

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 所示。

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

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

10.4.2 部署管理

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

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

10.4.3 创建流水线

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

  • 提交阶段

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

  • 构建阶段

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

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

10-5   添加单元测试任务

  • 验证阶段

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

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

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

  • 发布阶段

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

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

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

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

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

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

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%

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

End

往期回顾

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

/热点

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

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

/关注

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

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

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

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

京东购买链接:

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

用心做开源,不忘初衷

戳二维码+小助手

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

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

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

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

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

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

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

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

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

点击下方“阅读原文”查看更多精彩内容☺

原文  http://mp.weixin.qq.com/s?__biz=MzUxNTEwNTg5Mg==&mid=2247488247&idx=1&sn=8b299792e2ef554a3955ad768d02bbaa
正文到此结束
Loading...