点小蓝字加关注!
本章将介绍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.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
实际上,这个打包的任务应该交由持续集成流水线自动实现,并将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 服务网格与微服务开发框架融合实践
《微服务架构与实践(第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/
本文为作者原创文章,未经作者允许不得转载。
点击下方“阅读原文”查看更多精彩内容☺