32 测试方案:如何正确理解针对微服务的测试解决方案?

作为整个课程最后一部分内容,我们将讨论微服务架构中的测试解决方案。对于微服务而言,测试是一个难点,也是经常被忽略的一套技术体系。当系统中存在多个微服务时,除了常见的针对单个服务的单元测试和集成测试之外,面对不同服务之间进行交互和集成的场景,我们还需要引入端到端测试来确保服务定义和协议级别的正确性和稳定性。今天,我们就将基于这些测试需求给出针对微服务的测试方案。

微服务测试的系统方法

测试工作包含很多类型,例如常见的单元测试、集成测试等,本课程无意对这些类型做过多的展开,而是直接抛出在测试微服务架构中需要面对的两个核心问题,即如何验证组件级别的正确性以及如何验证服务级别的正确性。

如何验证组件级别的正确性?

验证组件级别正确性的一大难点在于关于组件与组件之间的依赖关系,这里就需要引出测试领域非常重要的一个概念,即 Mock(模拟)。针对测试组件涉及的外部依赖,我们的关注点在于这些组件之间的调用关系以及返回的结果或发生的异常等,而不在于组件内部的执行过程。因此常见的技巧就是使用 Mock 对象来替代真实的依赖对象,从而模拟真实的调用场景。

例如,在一个通过 Spring Boot 构建的微服务中,Controller 层会访问 Service 层,而 Service 层又会访问 Repository 层,我们对 Controller 层的端点进行验证时,就需要模拟 Service 层组件的功能。同样,对 Service 层组件进行测试时,也需要假定 Repository 层组件的结果是可以获取的,如下所示:

Lark20210107-113745.png

微服务中各层组件与 Mock 对象示意图

对于微服务而言,组件级别的验证工作主要在于需要确保服务内部数据和复杂业务流程的正确性,这里的数据来源一般有关系型数据库、各种 Nosql 或垂直化搜索引擎等;而复杂业务流程则主要面向多个内部服务和数据访问组件之间的整合。

如何验证服务级别的正确性?

服务与服务之间的验证工作一般指的是系统测试,涉及整体应用环境在现实场景中的系统测试也被称为是一种端到端(End-to-End)测试。取决于系统中测试的具体内容,端到端测试也存在很多种不同的类型。在微服务架构中,情况可能变得更加复杂。例如,在 SpringHealth 案例系统中,intervention-service 中健康干预信息的生成需要 user-service 和 device-service 之间完成复杂的接口交互,如下图所示:

Lark20210107-113749.png

SpringHealth 中的端到端测试内容

在基于 Spring Cloud 的技术体系中,端到端测试的内容即为各个服务之间基于 RESTful 风格下的 HTTP 远程调用层。为了完成整个业务流程,端到端测试不得不考虑的问题是如何管理服务与服务之间的数据和状态传递。

以上两个问题构成了我们后续两个课时的主体内容,在下一课时中,我们将介绍如何使用 Mock 和注解实施组件级别的测试。而在下一课时中,我们还会给出端到端测试的具体实施方案。其中,组件级别的测试相对比较成熟,而端到端测试则相对复杂,目前有一种测试策略为我们提供了解决方案,这就是面向契约的消费者驱动测试。而在 Spring Cloud 中同样提供了专门用于实现消费者驱动测试的 Spring Cloud Contract 组件。在本课时的剩余内容中,我们有必要对消费者驱动的契约测试的设计理念和系统方法先做相关的介绍。

消费者驱动的契约测试

对于任何一个服务所暴露的对外接口,我们都可以把它们归为是一种契约(Contract),即接口的调用者希望通过接口获取某种约定的价值。消费者驱动的契约测试就是基于契约思想而诞生的一种端到端测试方法,该测试方法一经提出已经在微服务架构中得到很好的应用和推广。

什么是消费者驱动契约测试?

在微服务中,当服务没有满足约定,就会对服务之间的交互产生影响。传统的接口测试虽然能够发现并解决部分因为违反接口约定所带来的错误,但这种测试方法本身也会存在一些问题。最典型的场景就在于随着服务的不断迭代,接口也会相应地产生变化,这种变化会导致集成测试结果的不稳定。如下图所示:

Lark20210107-113752.png

接口版本变化导致集成测试失败的场景

上图还是以 SpringHealth 案例系统为例,intervention-service 依赖 user-service 和 device-service。在现有情况下,这三个服务都开发了第一个版本用于支持这种集成关系。根据业务需要,user-service 做了一次服务升级,从版本 1 升到了版本 2,我们就会发现集成测试在这个时候可能发生错误,这种错误就来自于接口的既有约定已被打破。

讲到这里,大家可能已经明白了契约的基本概念,那么一个合理的契约应该包括哪些组成部分呢?显然,契约一方面应该定义其他微服务所期望的数据格式、支持的操作方法以及访问的协议。另一方面,也可以约定调用时延或吞吐量等非功能性约束和条件。

对于服务的提供者和消费者而言,存在不同的契约表现形式。服务提供者契约包含了服务提供者所能提供的所有内容,所以一个服务提供者仅包含一种契约,而且这种契约一般会随着版本的演进而不断变化,正如上图中的 user-service 所示的效果一样。

而消费者契约则不同,一个服务可以存在一个或多个消费者契约。这种契约只包含某个或某些消费者真正在使用的一部分服务定义,并且根据服务消费者的变更而做相应的调整。服务提供者契约和消费者契约之间的这层关系如下图所示:

Lark20210107-113755.png

服务提供者契约和消费者契约的区别

基于上图,这里再引申出一个新的概念,即消费者驱动契约(Consumer Driven Contract,CDC),也就是说从消费者的角度出发驱动交互协议的制定和调整。

现在,关于契约相关的内容我们都介绍完毕了,下面回到对测试工作的讨论,来看一下所谓的消费者驱动的契约测试。消费者驱动的契约测试是针对微服务接口开展的测试,它能验证服务提供者所提供的契约是否满足消费者的期望。对于一个服务提供者而言,每个消费者会根据与其交互场景和上下文的不同产生不同的契约。当这个服务提供者频繁变更时,就应该保证每个消费者依然能够具备正确的消费契约。

消费者驱动的契约测试能够提供一定机制验证提供者所提供的服务能否始终满足契约。因为每个消费者拥有自身的消费者契约,所以我们只需要根据消费者契约编写独立的测试用例,并验证这些契约下服务提供者所暴露出来的那一部分接口即可。这些测试用例仅仅关注契约是否满足期望,而不需要深入测试服务内部的行为,所以测试方式相较集成测试而言具有轻量级的优点,可以在很大程度上降低测试成本。

在服务交互过程中,消费者驱动的契约测试能够帮助服务消费者和提供者验证接口是否已经发生变化。每当服务提供者所暴露的接口发生变更,契约测试就能检测该接口是否仍然和契约所要求的保持一致,如下所示:

Lark20210107-113757.png

服务升级导致契约测试失败示意图

在上图中,对于同一份消费者契约而言,一旦 user-service 所提供的接口从版本 1 上升到版本 2 时,如果新版本升级导致了契约不再满足,那么契约测试就能立即做出验证,从而在开展功能测试之前就能尽早发现错误。

如何开展消费者驱动契约测试?

通过前面内容的介绍,我们实际上明确了消费者驱动契约测试是一个比较复杂的过程,所以不推荐对所有的服务交互过程都实施这种测试方法。在本课程中,我们梳理了如下所示的消费者驱动契约测试实施过程:

Lark20210107-113800.png

消费者驱动契约测试实施步骤

正如前面提到的,并不是所有的业务场景都需要使用消费者驱动契约测试,往往越容易发生变更的业务场景就越需要进行测试,所以上图中的第一个步骤是根据业务需求选择合适的测试场景。一旦明确了场景之后,基于消费者驱动的设计思想,就可以将消费者请求契约化。消费者发送的请求、提供者返回的响应都需要明确记录,并整理成该场景下的契约。然后,测试用例将模型消费者,向真实的服务提供者发送请求。最后,通过获取请求结果,验证提供者的契约是否已经发生变化。

为了实施上述步骤,我们需要选择消费者驱动契约测试工具。作为一个完整的微服务套件,Spring Cloud 也提供了 Spring Cloud Contract 作为消费者驱动契约测试的开发框架。在本课程中,我们将以 Spring Cloud Contract 为例对消费者驱动契约测试工具的使用方式做详细介绍。

小结与预告

测试是一套独立的技术体系,需要开发人员充分重视并付诸实践,这点对于微服务架构而言更是如此。在本课程中,我们无意对测试工作面面俱到,而是重点关注于如何确保单个服务的正确性以及如何确保多个服务之间交互的正确性,因此分别提出了组件级别和服务级别的测试方法。在微服务架构中,对于后者而言,还需要引入专门的消费者驱动契约测试体系。

这里给你留一道思考题:消费者驱动的契约测试解决的核心问题是什么?

在介绍完设计理念之后,下一课时我们将先来讨论第一种测试体系,即验证组件级别正确性的测试方法和工程实践。


33 组件测试:如何使用 Mock 和注解实施组件级别测试?

在上一课时中,我们全面介绍了针对微服务架构的测试方案。我们提出在测试微服务架构中需要直接面对的两个核心问题,即如何验证组件级别的正确性以及如何验证服务级别的正确性。本课时和下一课时的内容将分别围绕这两个核心问题进行展开,今天让我们先来看一下组件级别的测试方法和工程实践。

组件级别的测试方案

在上一课时中,我们已经讨论到使用 Mock 来对组件进行测试。Mock 是一种策略而不是技术,今天我们就需要给出如何实现 Mock 的技术体系。假设在 intervention-service 中存在这样一个 InterventionService 类,其中包含一个 getInterventionById 方法,如下所示:

@Service
public class InterventionService{

public Intervention getInterventionById(Long id) {
        …
}
}

那么,如何对这个方法进行 Mock 呢?通常,我们可以使用 easymock、jmockMock 等工具包来隐式实现这个方法。对于某一个或一些被测试对象所依赖的方法而言,编写 Mock 相对简单,只需要模拟被使用的方法即可。在这个例子中,如果依赖于 InterventionService,我们只需要给出 getInterventionById 方法的实现。

让我们回到单个微服务的内部,涉及组件级别测试的维度有很多,包括数据访问 Repository 层、服务构建 Service 层和提供外部端点的 Controller 层。同时,基于常见的代码组织结构,组件测试也体现为一种层次关系,即我们需要测试从 Repository 层到 Service 层再到 Controller 层的完整业务链路。

另一方面,Spring Boot 也内置了一个测试模块可以用于组件级别的测试场景。在该模块中,提供了一批非常有用的注解来简化测试过程,要想使用这些注解,我们需要引入 spring-boot-starter-test 依赖,如下所示:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>

首先,因为 Spring Boot 程序的入口是 Bootstrap 类,Spring Boot 专门提供了一个 @SpringBootTest 注解来测试你的 Bootstrap 类,使用方法如下所示:

@SpringBootTest(classes = UserApplication.class, webEnvironment = SpringBootTest.WebEnvironment.MOCK)

在 Spring Boot 中,@SpringBootTest 注解主要用于测试基于自动配置的 ApplicationContext,它允许你来设置测试上下文中的 Servlet 环境。在多数场景下,一个真实的 Servlet 环境对于测试而言过于重量级,所以我们一般通过 WebEnvironment.MOCK 环境来模拟测试环境。

我们知道对于一个 Spring Boot 应用程序而言,Bootstrap 类中的 main() 入口通过 SpringApplication.run() 方法将启动 Spring 容器。如下所示的 intervention-service 中的启动类 InterventionApplication:

@SpringBootApplication
public class InterventionApplication{public static void main(String[] args){SpringApplication.run(InterventionApplication.class, args);}
}

针对这个 BootStrap 类,我们可以通过编写测试用例的方式来验证 Spring 容器是否能够正常启动,该测试用例如下所示:

package com.tianyalan.testing.orders;import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;@SpringBootTest
@RunWith(SpringRunner.class)
public class ApplicationContextTests{@Autowiredprivate ApplicationContext applicationContext;@Testpublic void testContextLoaded() throws Throwable{Assert.assertNotNull(this.applicationContext);}
}

我们看到这里用到了 @SpringBootTest 注解和 @RunWith 注解。前面已经介绍了 @SpringBootTest 注解,而 @RunWith 注解由 JUnit 框架提供,用于设置测试运行器,例如我们可以通过 @RunWith(SpringRunner.class) 让测试运行于 Spring 测试环境中。

同时,我们在 testContextLoads 方法上添加了一个 @Test 注解,该注解来自 JUnit 框架,代表该方法为一个有效的测试用例。这里测试的场景是指对 Spring 中的 ApplicationContext 作了非空验证。执行该测试用例,我们从输出的控制台信息中看到 Spring Boot 应用程序被正常启动,同时测试用例本身也会给出执行成功的提示。

在验证完容器可以正常启动之后,我们继续来看一个 Spring Boot 应用程序的其他组件层。对于 Repository 层而言,主要的交互媒介是数据库,所以 Spring Boot 专门提供了一个 @DataJpaTest 注解来模拟基于 JPA 规范的数据访问过程。同样,对于 Controller 层而言,Spring Boot 也提供了一个 @WebMvcTest 注解来模拟 Web 交互的测试场景。

讲到这里,你可能会奇怪,为什么 Service 层没有专门的测试注解呢?实际上原因也很简单,因为对于 Repository 层和 Controller 层组件而言,它们都涉及与某一种特定技术体系的交互,Repository 层的交互对象是数据库,而 Controller 层的交互对象是 Web 请求,所以需要专门的测试注解。而 Service 层因为主要是业务代码,并没有跟具体某一项技术体系有直接的关联,所以我们在测试过程中只需要充分使用 Mock 机制就可以了。下图展示了一个业务微服务中各层的测试方法:

Lark20210112-140800.png

组件测试的层次和实现方式

接下来,我们就将对上图中的三个层次和对应的实现方法分别展开讨论。

Repository 层:@DataJpaTest 注解

对于业务微服务而言,一般都涉及数据持久化,我们将首先从数据持久化的角度出发讨论如何对 Repository 层进行测试,并引入 @DataJpaTest 注解。@DataJpaTest 注解会自动注入各种 Repository 类,并会初始化一个内存数据库及访问该数据库的数据源。为了演示方便,我们使用 h2 作为内存数据库,并通过 Mysql 实现数据持久化,因此需要引入以下 Maven 依赖。

<dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId>
</dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency>

让我们回顾 SpringHealth 案例系统中的 intervention-service 中的 InterventionRepository 接口,如下所示:

public interface InterventionRepository extends JpaRepository<Intervention, Long>{List<Intervention> findInterventionsByUserId(@Param("userId") String userId);

List<Intervention> findInterventionsByDeviceId(@Param(“deviceId”) String deviceId);
}

注意到这里 InterventionRepository 扩展了 Spring Data 中的 JpaRepository 接口。针对该 InterventionRepository 接口的测试用例如下所示:

@RunWith(SpringRunner.class)
@DataJpaTest
public class InterventionRepositoryTest{

@Autowired
    private TestEntityManager entityManager;
 
    @Autowired
    private InterventionRepository interventionRepository;
 
    @Test
    public void testFindInterventionByUserId() throws Exception {
        this.entityManager.persist(new Intervention(1L, 1L, 100F, “Intervention1”, new Date()));
        this.entityManager.persist(new Intervention(1L, 2L, 200F, “Intervention2”, new Date()));

Long userId = 1L;
        List<Intervention> interventions = this.interventionRepository.findInterventionsByUserId(userId);
        assertThat(interventions).size().isEqualTo(2);
        Intervention actual = interventions.get(0);
        assertThat(actual.getUserId()).isEqualTo(userId);
    }
 
    @Test
    public void testFindInterventionByNonExistedUserId() throws Exception {
        this.entityManager.persist(new Intervention(1L, 1L, 100F, “Intervention1”, new Date()));
        this.entityManager.persist(new Intervention(1L, 2L, 200F, “Intervention2”, new Date()));

Long userId = 3L;
        List<Intervention> interventions = this.interventionRepository.findInterventionsByUserId(userId);
        assertThat(interventions).size().isEqualTo(0);
    }
}

可以看到这里使用了 @DataJpaTest 以完成 InterventionRepository 的注入。同时,我们还注意到另一个核心测试组件 TestEntityManager,该类内部定义了一个 EntityManagerFactory 变量,而 EntityManagerFactory 能够构建数据持久化操作所需要的 EntityManager 对象。所以,TestEntityManager 的效果相当于不使用真正的 InterventionRepository 来完成数据的持久化,从而提供了一种数据与环境之间的隔离机制。TestEntityManager 中所包含的方法如下所示:

Drawing 1.png

TestEntityManager 中的方法定义列表

基于 InterventionRepository 中的方法定义以及我们初始化的数据,以上测试用例的结果显而易见。你可以尝试执行这些单元测试,并观察控制台的日志输出,从这些日志中可以看出各种 SQL 语句的效果。

Service 层:Mock

前面我们已经介绍了 @SpringBootTest 注解中的 SpringBootTest.WebEnvironment.MOCK选项,该选项用于加载 WebApplicationContext 并提供一个 Mock 的 Servlet 环境,内置的 Servlet 容器并没有真实的启动。现在,我们就针对 Service 层来演示这种测试方式。

InterventionService 类的 generateIntervention 方法是其最核心的方法,涉及对 user-service 和 device-service 的远程调用,让我们做一些回顾:

public Intervention generateIntervention(String userName, String deviceCode){

logger.debug(“Generate intervention record with user: {} from device: {}”, userName, deviceCode);

Intervention intervention = new Intervention();
 
        //获取远程 User 信息
        UserMapper user = getUser(userName);
        if (user == null) {
            return intervention;
        }
        logger.debug(“Get remote user: {} is successful”, userName);

//获取远程 Device 信息
        DeviceMapper device = getDevice(deviceCode);
        if (device == null) {
            return intervention;
        }
        logger.debug(“Get remote device: {} is successful”, deviceCode);
 
        //创建并保存 Intervention 信息
        intervention.setUserId(user.getId());
        intervention.setDeviceId(device.getId());
        intervention.setHealthData(device.getHealthData());
        intervention.setIntervention(“InterventionForDemo”);
        intervention.setCreateTime(new Date());
 
        interventionRepository.save(intervention);
 
        return intervention;
}

请注意以上代码中的 getUser 方法和 getDevice 方法中涉及了远程访问。以 getUser 方法为例,就会基于 UserServiceClient 发送HTTP请求,我们在前面的课程中都已经介绍过这个类,这里也做一下回顾:

@Component
public class UserServiceClient{@AutowiredRestTemplate restTemplate;

public UserMapper getUserByUserName(String userName){

ResponseEntity<UserMapper> restExchange =
                restTemplate.exchange(
                        “http://userservice/users/{userName}”,
                        HttpMethod.GET,
                        null, UserMapper.class, userName);

UserMapper user = restExchange.getBody();

return user;
    }
}

对于测试而言,InterventionService 类实际上不需要关注这个 UserServiceClient 中如何实现远程访问的具体过程,因为对于测试过程而言只需要关注方法调用返回的结果。所以,我们对于 UserServiceClient 以及 DeviceServiceClient 同样将采用 Mock 机制完成隔离。针对 InterventionService 的测试用例代码如下所示,可以看到我们采用的是同样的测试方式:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
public class InterventionServiceTests{@MockBeanprivate UserServiceClient userClient;@MockBeanprivate DeviceServiceClient deviceClient;@MockBeanprivate InterventionRepository interventionRepository;@Autowiredprivate InterventionService interventionService;

@Test
    public void testGenerateIntervention() throws Exception {
        String userName = “springhealth_user1”;
        String deviceCode = “device1”;

given(this.userClient.getUserByUserName(userName))
            .willReturn(new UserMapper(1L, “user1”, userName));
        given(this.deviceClient.getDevice(deviceCode))
            .willReturn(new DeviceMapper(1L, “便携式血压计”, “device1”, “Sphygmomanometer”, 100F));
 
        Intervention actual = interventionService.generateIntervention(userName, deviceCode);
 
        assertThat(actual.getHealthData()).isEqualTo(100L);
    }
}

这里同样基于 mockito 对 UserServiceClient 和 DeviceServiceClient 这两个远程访问类的返回结果做了模拟。上述测试用例演示了在 Service 层中进行集成测试的各种手段,这些手段已经能够满足一般场景的需要。

Controller 层:@WebMvcTest 注解

我们再回到 intervention-service 来看看如何对 InterventionController 进行测试。InterventionController 类的功能非常简单,基本都是对 InterventionService 的直接封装,代码如下所示:

@RestController
@RequestMapping(value="interventions")
public class InterventionController{

@Autowired
    private InterventionService interventionService;

@RequestMapping(value = “/{userName}/{deviceCode}”, method = RequestMethod.POST)
    public Intervention generateIntervention( @PathVariable(“userName”) String userName,
            @PathVariable(“deviceCode”) String deviceCode)
{
        Intervention intervention = interventionService.generateIntervention(userName, deviceCode);

return intervention;
    }

@RequestMapping(value = “/{id}”, method = RequestMethod.GET)
    public Intervention getIntervention(@PathVariable Long id) {
        Intervention intervention = interventionService.getInterventionById(id);

return intervention;
    }
}

在测试 Controller 类之前,我们先介绍一个新的注解 @WebMvcTest,该注解将初始化测试 Controller 所必需的 Spring MVC 基础设施。InterventionController 类的测试用例如下所示:

import org.junit.Test;
import org.junit.runner.RunWith;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;@RunWith(SpringRunner.class)
@WebMvcTest(InterventionController.class)
public class InterventionControllerTests{@Autowiredprivate MockMvc mvc;@MockBeanprivate InterventionService interventionService;@Testpublic void testGenerateIntervention() throws Exception{String userName = "springhealth_user1";String deviceCode = "device1";Intervention intervention = new Intervention(100L, 1L, 1L, 100F, "Intervention1", new Date());given(this.interventionService.generateIntervention(userName, deviceCode)).willReturn(intervention);this.mvc.perform(post("/interventions/" + userName+ "/" + deviceCode).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk());}
}

以上代码的关键是 MockMvc 工具类,对这个工具类我们有必要展开一下。MockMvc 类提供的一系列基础方法来满足对 Controller 层组件的测试需求。首先,我们首先需要声明发送 HTTP 请求的方式,MockMvc 类中的一组 get/post/put/delete 方法用来初始化一个 HTTP 请求。然后我们可以使用 param 方法来为该请求添加参数。一旦请求构建完成,perform 方法负责执行请求,并自动将请求映射到相应的 Controller 进行处理。执行完请求之后就是验证结果,这时候可以使用 andExpect、andDo 和 andReturn 等方法对返回的数据进行判断来验证 HTTP 请求执行结果是否正确。

执行该测试用例,我们从输出的控制台日志中不难发现整个流程相当于启动了 InterventionController 并执行远程访问,而 InterventionController 中所用到的 InterventionService 则做了 Mock。显然测试 InterventionController 的目的在于验证请求是否成功发送和返回,所以我们通过 perform、accept 和 andExpect 方法最终模拟 HTTP 请求的整个过程并验证结果的正确性。

小结与预告

今天的课程讨论了如何对单个微服务中的各个组件进行测试,我们大量使用到了 Spring 框架中的测试注解。作为小结,这里通过一张表格来对这些注解做一个梳理,如下所示:

Lark20210112-140808.png

这里给你留一道思考题:如果我们想要对所依赖的组件的行为进行模拟,可以使用什么方法?

讲完组件级别的测试方法之后,下一课时,我们将关注于基于服务级别测试用例的设计,并将引入 Spring Cloud Contract 框架来实施这一过程。


34 契约测试:如何基于 Spring Cloud Contract 实现面向契约测试?

在上一课时中,我们介绍了组件级别的测试方案和实现方法。组件级别的测试关注于单个微服务的内部,而今天要介绍的面向契约测试则是一种服务级别的测试方法,关注于整个微服务系统中的数据和状态传递过程。Spring Cloud Contract 是 Spring Cloud 中专门用于实现面向契约测试的开发框架,对面向契约的端到端测试过程进行了抽象和提炼,并梳理出一套完整的解决方案。让我们一起来看一下。

什么是 Spring Cloud Contract?

在引入 Spring Cloud Contract 之前,我们需要先明确在测试领域中另一个非常重要的概念,即 Stub,也就是打桩。Stub 与 Mock 经常被混淆,因为他们都可以用来替代真实的依赖对象,从而实现对测试对象的隔离效果。然而,Stub 和 Mock 的区别也非常明显,从类的实现方式上看,Stub 必须有一个显式的类实现,这个实现类需要提供被替代对象的所有逻辑,即使是不需要关注的方法也至少要给出空实现。而 Mock 则不同,它只需要实现自己感兴趣的方法即可,这点在上一课时中已经得到了体现。

回到 SpringHealth 案例系统,我们来看基于 Stub 的测试场景,如下图所示:
图片1.png

基于 Stub 的 SpringHealth 案例系统测试场景

在上图中,对于 intervention-service 而言,我们希望不需要真正启动所依赖的 user-service 和 device-service 就能完成服务契约的正确性验证。要想实现这一目标,这里的 user-service 和 device-service 就需要提供对应的 Stub 供 intervention-service 进行使用。

当使用 Spring Cloud 开发微服务系统时,集成 Spring Cloud Contract 来作为面向契约的测试工具是最佳选择。Spring Cloud Contract 中提供了契约验证器 Contract Verifier 和 Stub 执行器 Stub Runner 等核心组件,这些组件可以确保能够正确模拟服务端的接口,并在契约发生变化时,让服务端和消费端立即能够发现这种变化。

在接下来的内容中,我们将基于 intervention-service 与 user-service 之间的调用关系来讨论如何基于 Spring Cloud Contract 实现面向契约的端对端测试。我们知道,从业务场景上讲,user-service 相当于服务的提供者,而 intervention-service 是 user-service 的消费者。无论是服务的提供者还是消费者,都需要导入关于 Spring Cloud Contract 的 Maven 依赖,如下所示:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-contract-stub-runner</artifactId><scope>test</scope>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-contract-verifier</artifactId><scope>test</scope>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-contract-wiremock</artifactId><scope>test</scope>
</dependency>

基于 Spring Cloud Contract 实现面向契约测试的开发流程比较特殊,也有一定的复杂性,在具体编写案例代码之前,我们有必要先对这个流程做一个梳理,如下所示:

图片2.png

基于 Spring Cloud Contract 实现面向契约测试的开发流程

针对上图,我们先站在服务提供者的角度来看这个流程。显然,服务提供者需要编写契约文件。请注意,Spring Cloud Contract 中的契约文件并不是一个普通的 Java 文件,而是一个支持动态语言的 groovy 文件。有了契约文件之后,基于 Spring Cloud Contract 内置的 Stub 处理机制,我们自动生成一个 Stub 文件,而这个 Stub 文件实际上就是一个 jar 包。然后,我们需要把这个 Stub 文件上传到 Maven 仓库,供服务的消费者进行使用。显然,这里的 Maven 仓库一般指的是我们自己搭建的 nexus 私服。

我们接着讨论服务消费者。对于消费者而言,我们会编写并执行针对契约的测试用例。在执行过程中,Spring Cloud Contract 中的 Stub Runner 组件就会从 Maven 仓库中下载 Stub 文件并使用一个内嵌的 Tomcat 服务器来启动 Stub 服务,这样服务消费者就可以基于既定的测试用例来开展端到端测试。

如何使用 Spring Cloud Contracts 实现面向契约测试?

在接下来的内容中,我们即将围绕 SpringHealth 案例系统给出实现这些步骤的详细过程以及示例代码。

服务提供者制定服务契约

对于 user-service 而言,我们首先要提供了一个 HTTP 端点,所以我们实现了如下所示的 UserController 类:

@RestController
@RequestMapping(value = "users")
public class UserController{@Autowiredprivate UserRepository repository;

@RequestMapping(path = “/userlist”)
    public UserList getUserList() {
        UserList userList = new UserList();
        userList.setData(repository.findAll());
        return userList;
    }
}

为了演示的简单性,这里省略了 Service 层实现类,而是直接在 Controller 层中调用 Repository 层组件并返回一些数据。在面向契约的测试过程中,这个 UserController 的具体细节其实并不重要,因为我们关注的是服务的对外契约而不是内部实现。

然后,我们在引入 Spring Cloud Contract Verifier 组件之后我们就可以使用该组件来定义契约。前面提到 Spring Cloud 中的契约文件的表现形式是 groovy 文件,这里我们就定义一个 UserContract.groovy 契约文件,如下所示:

import org.springframework.cloud.contract.spec.Contract
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaTypeContract.make {description "return all users"request {url "/users/userlist"method GET()}response{status 200headers {header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)}body("data": [[id: 1L, userCode: "user1", userName: "springhealth_user1"], [id: 2L, userCode: "user2", userName: "springhealth_user2"],
[id: 3L, userCode: "user3", userName: "springhealth_user3"]])}
}

我们看到以上契约文件中包含三个部分,即 description、request 和 response。其中 description 是对该契约提供的描述信息;request 则定义了请求时的 url 和 method,而 response 显然对返回的 headers 和 body 信息进行了约定。该契约描述的语义也一目了然,就是通过 /users/userlist 这个 URL 来获取一个 JSON 格式的 User 对象列表,该列表将返回三个用户信息。

服务提供者生成 Stub 文件

定义完契约文件之后,接下来我们就可以生成 Stub 文件。Stub 文件在表现形式上也是一个 jar 包,这个 jar 包的目的就是可以被消费者拿来当作一个模拟服务进行启动并在本地运行测试用例,而不需要服务提供者真正启动服务。

我们首先需要在 user-service 中引入 spring-cloud-contract-maven-plugin 插件,spring-cloud-contract-maven-plugin 插件的使用方式如下,该插件在 Maven 打包过程中会自动创建 Stub 文件。

<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-contract-maven-plugin</artifactId><extensions>true</extensions><configuration><packageForbaseClasses>com.springhealth.user</packageForbaseClasses></configuration></plugin></plugins>
</build>

现在我们通过 mvn install –DskipTests=true 命令打包 user-service,除了普通的日志输出之外,控制台还会生成如下信息(为了显示简单做了裁剪):

    [INFO] Copying file UserContract.groovyy[INFO] Converting from Spring Cloud Contract Verifier contracts to WireMock stubs mappings[INFO]      Spring Cloud Contract Verifier contracts directory: …\user-testing-service\src\test\resources\contracts[INFO] WireMock stubs mappings directory: …\user-testing-service \target\stubs\META-INF\com.springhealth\user-testing-service\0.0.1-SNAPSHOT\mappings[INFO] Creating new stub […\user-testing-service\target\stubs\META-INF\ com.springhealth\user-testing-service\0.0.1-SNAPSHOT\mappings\UserContract.json]Installing …\user-testing-service\target\ user-testing-service-0.0.1-SNAPSHOT-stubs.jar to C:\Users\user\.m2\repository\com\springhealth\user-testing-service \0.0.1-SNAPSHOT\user-testing-service-0.0.1-SNAPSHOT-stubs.jar

根据这些日志信息,我们看到打包过程对 UserContract.groovy 契约文件做了处理。打包完成之后,在 target 目录下会生成两个 jar 包,一个是正常的 user-testing-service-0.0.1-SNAPSHOT.jar 文件,另一个就是新的 Stub 文件。Stub 文件的名称为 user-testing-service-0.0.1-SNAPSHOT-stubs.jar,打开该文件会发现两个文件夹,一个是 contracts 文件夹,内部存放着 UserContract.groovy 契约文件,另一个是 mappings 文件夹,内部存放着 UserContract.json 文件,UserContract.json 文件是用 JSON 格式对 UserContract.groovy 契约文件的一种数据转换。

生成 Stub 文件之后,我们还需要做的事情是通过 install 命令将该 Stub 文件上传到 Maven 仓库,以便消费者通过 pom 中定义的 group-id 和 artifact-id 加载该 jar 包。至此,服务提供者的开发工作告一段落。

服务消费者编写测试用例

现在回到服务消费者端编写测试用例 InterventionApplicationTests 类,该类展示了典型的 Spring Cloud Contract 端到端测试用例代码风格。其中的 testGetUsers() 测试用例使用 RestTemplate 访问远程 HTTP 端点并验证返回数据的正确性,如下所示:

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;@RunWith(SpringRunner.class)
@SpringBootTest(classes = InterventionApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureStubRunner(ids = { "com.springhealth:user-testing-service:+:8080" }, workOffline = true)
public class InterventionApplicationTests{@Autowiredprivate RestTemplate restTemplate;@Testpublic void testGetUsers(){ParameterizedTypeReference<UserList> ptf = new ParameterizedTypeReference<UserList>() {};

ResponseEntity<UserList> responseEntity = restTemplate.exchange(“http://localhost:8080/user/userlist”,
                HttpMethod.GET, null, ptf);

Assert.assertEquals(3, responseEntity.getBody().getData().size());
    }
}

以上代码中引入了一个新的注解 @AutoConfigureStubRunner,通过该注解来自动注入一个 StubRunner。@AutoConfigureStubRunner 注解包含两个参数,即 ids 和 workOffline。其中最重要的就是需要指定 ids 参数。

ids 参数用于定位存放在 Maven 仓库中的 Stub 包,然后在指定端口启动该 Stub 包中的服务。ids 的格式为 groupId:artifactId:version:classifier:port。这里"com.springhealth: user-testing-service:+:8080"表示去 Maven 仓库定位上一个步骤中上传的 user-testing-service-0.0.1-SNAPSHOT-stubs.jar 包并在 8080 端口中启动服务。

服务消费者执行测试用例

执行 InterventionApplicationTests,我们会在控制台中看到很多日志输出,其中重要的信息如下所示。可以看到在执行过程中 StubRunner 会下载 Stub 文件,将该 jar 文件解压到临时目录并启动了内嵌的 Tomcat 监听 8080 端口,然后注册相应的 servlet 并最终运行所有的 Stubs,部分核心日志信息如下所示:

    o.s.c.c.s.StubDownloaderBuilderProvider  : Will download stubs using Aethero.s.c.c.stubrunner.AetherStubDownloader  : Remote repos not passed but the switch to work offline was set. Stubs will be used from your local Maven repository.o.s.c.c.stubrunner.AetherStubDownloader  : Desired version is [+] - will try to resolve the latest versiono.s.c.c.stubrunner.AetherStubDownloader  : Resolved version is [0.0.1-SNAPSHOT]o.s.c.c.stubrunner.AetherStubDownloader  : Resolving artifact [com.springhealth: user-testing-service:jar:stubs:0.0.1-SNAPSHOT] using remote repositories []o.s.c.c.stubrunner.AetherStubDownloader  : Resolved artifact [com.springhealth: user-testing-service:jar:stubs:0.0.1-SNAPSHOT] to C:\Users\user\.m2\repository\com\ springhealth\user-testing-service\0.0.1-SNAPSHOT\user-testing-service-0.0.1-SNAPSHOT-stubs.jaro.s.c.c.stubrunner.AetherStubDownloader  : Unpacking stub from JAR [URI: file:/C:/Users/user/.m2/repository/com/springhealth/user-testing-service/0.0.1-SNAPSHOT/user-testing-service-0.0.1-SNAPSHOT-stubs.jar]…s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)o.apache.catalina.core.StandardService   : Starting service [Tomcat]org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.23o.a.c.c.C.[Tomcat-1].[localhost].[/]     : Initializing Spring embedded WebApplicationContexto.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 512 mso.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'stub' to [/]o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'admin' to [/__admin/*]s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)o.s.c.contract.stubrunner.StubServer     : Started stub server for project [com.springhealth: user-testing-service:0.0.1-SNAPSHOT:stubs] on port 8080o.a.c.c.C.[Tomcat-1].[localhost].[/]     : RequestHandlerClass from context returned com.github.tomakehurst.wiremock.http.AdminRequestHandler. Normalized mapped under returned 'null'o.s.c.c.stubrunner.StubRunnerExecutor    : All stubs are now running RunningStubs [namesAndPorts={com.springhealth: user-testing-service:0.0.1-SNAPSHOT:stubs=8080}]

在上面的日志中,我们看到 servlet 生成了 /__admin/* 映射。该端点在测试用例执行完成之后会自动消失,所以可以在 testGetUsers() 方法中打一个断点,然后执行测试用例就可以访问这些自动生成的 HTTP 端点信息。当访问 http://localhost:8080/__admin/ 端点,我们可以获取根据 UserContract 契约生成的用于 Stub 的 JSON 数据,如下所示:

{"mappings":[{"id":"b134a01f-d983-4a05-8889-b1d5aa2e8781","request":{"url":"/userlist/","method":"GET"},"response":{"status":200, "body":"{"data":[{"id":1," userCode":"user1",
"userName":"springhealth_user1"},{"id":2," userCode":"user2","userName":"springhealth_user2"},{"id":3," userCode":"user3","userName":"springhealth_user3"}]}","headers":{"Content-Type":"application/json;charset=UTF-8"},"transformers":["response-template"]},"uuid":"b134a01f-d983-4a05-8889-b1d5aa2e8781"},{"id":"02f3f379-ce66-4136-8b35-7b2fd1aafec9","request":{"url":"/user","method":"GET"},"response":{"status":200,"body":"OK"},"uuid":"02f3f379-ce66-4136-8b35-7b2fd1aafec9"},{"id":"d5ae77de-dddd-43b3-b1a3-19145ee5582d","request":{"url":"/ping","method":"GET"},"response":{"status":200,"body":"OK"},"uuid":"d5ae77de-dddd-43b3-b1a3-19145ee5582d"}],"meta":{"total":3}
}

Spring Cloud Contract 在内部集成了 WireMock 工具,该工具通过这些 JSON 数据来模拟定义的接口。同时,在执行测试用例的过程中,我们可以访http://localhost:8080/users/userlist 端点来获取所生成的接口数据,正如我们所预想的一样,返回的数据如下所示:

{"data":[{"id":1,"userCode":"user1","userName":"springhealth_user1"},{"id":2,"userCode":"user2","userName":"springhealth_user2"},{"id":3,"userCode":"user3","userName":"springhealth_user3"}]
}

通过整个流程,我们注意到服务提供者是基于消费者的契约来开发接口,而测试用例则是由 Spring Cloud Contract Verifier 根据契约所生成,因此就形成了对契约的一种约束,也就是消费者对服务提供者的约束。如果服务提供者不能满足测试用例则意味着契约已经发生了变化,这正是面向契约的端对端测试的本质所在。

小结与预告

今天的课程讨论了面向契约的端到端测试,我们基于 Spring Cloud 家族中的 Spring Cloud Contract 框架全面介绍了如何制定服务契约、如何生成 Stub 文件、如何编写和执行测试用例等一系列核心的开发步骤。

这里给你留一道思考题:如果使用 Spring Cloud Contract 来实现面向契约测试,开发流程上需要实施哪些步骤?

讲完 Spring Cloud Contract 之后,下一课时是整个课程的最后一讲,我们将对微服务架构和 Spring Cloud 进行总结,并对它的后续发展进行展望。


总结 以终为始:微服务架构总结和展望

终于到了课程的最后一讲。今天,我们将对整个微服务架构和 Spring Cloud 进行总结和展望。Spring Cloud 是业界领先的一款微服务开发框架,提供了多项核心功能,帮忙我们构建完整的分布式服务开发解决方案。作为针对 Spring Cloud 的一门系统化课程,在课程的最后,我们还是先来总结整个课程中所介绍的 Spring Cloud 核心功能,然后梳理我在写作过程中的一些思考和心得。最后,我们还将引出 Spring Cloud 未来发展的演进方向。

Spring Cloud 核心功能

Spring Cloud 是 Spring 家族中的核心开发框架。如果你访问 Spring 的官方网站(https://spring.io/),就会对 Spring 家族中的技术体系有一个宏观了解。在 Spring 的主页中,展示了下面这张图:

Drawing 0.png

Spring 家族技术体系(来自 Spring 官网)

在上图中,这里罗列了 Spring 家族的七大核心技术体系,排在第一位的就是微服务架构,而在Spring 家族中提供微服务开发解决方案的就是 Spring Cloud。Spring Cloud 构建在 Spring Boot 基础之上,它的整体架构图如下所示:

Drawing 1.png

Spring Cloud 与微服务整体架构图(来自 Spring 官网)

技术组件的完备性是 Spring Cloud 框架的主要优势,它集成了业界一大批知名的微服务开发组件。Spring Cloud 的核心组件如下图所示:

Lark20210119-164634.png

Spring Cloud 核心功能组件

可以看到,基于 Spring Boot 的开发便利性,Spring Cloud 巧妙地简化了微服务系统基础设施的开发过程,包含上图中所展示的服务发现注册、API网关、配置中心、消息总线、负载均衡、熔断器、数据监控等。

Spring Cloud 课程总结

总结完所介绍的 Spring Cloud 各项核心功能,我们再来总结整个课程所讲解内容的特色和与其他课程之间的差异化。这里,我也整理了本课程的三大亮点。

本课程的一大亮点在于介绍了完整的微服务系统开发技术体系。通过学习本课程,你可以全面掌握基于 Spring Cloud 的微服务系统开发技术组件,这其中包括注册中心、配置中心、链路跟踪等基础组件,也包括服务安全性管理和微服务测试等专项主题。

本课程的第二大亮点在于提供了深入的微服务组件实现原理分析。通过学习本课程,帮助你在掌握 Spring Cloud 框架应用的基础上,深入理解核心技术组件的实现原理,做到知其然而知其所以然。本课程中对注册中心、客户端负载均衡、API网关、熔断器、配置自动更新等主题开展源码级的原理分析和讲解。

本课程的另一个亮点在于提供了完整的案例代码来介绍 Spring Cloud 中的各大核心组件。这个案例系统足够简单,可以让你从零开始就能理解和掌握其中的各项知识点。但这个案例系统又足够完整,涉及的各个核心功能我们都提供了相关的配置项和示例代码,供你在日常开发过程中进行参考。

整个课程从平时的积累,到酝酿到启动再到上线也经历了小半年的时间,伴随着这个过程,我把 Spring Cloud 的部分源代码系统地梳理了一遍,并对内部的设计思想和实现原理也做了一些提炼和总结。总体而言,Spring Cloud 是一款代码质量非常高的开源框架,其中关于 Spring 和 Spring Boot 框架的集成、与 Netflix OSS 等外部开源框架的整合,以及 Spring Cloud 自建的配置中心 Spring Cloud Config 和消息通信组件 Spring Cloud Stream 等诸多功能都给我留下了深刻的印象,使我受益良多。相信对于坚持学习到今天的你而言也是一样。

Spring Cloud 的发展和演进

最后,我们来对 Spring Cloud 的发展做一些展望。这里重点讲两点。

首当其冲的,我们来讨论一下 Spring Cloud 的生态系统。目前,在 Spring 官网上已经存在将近 30 个以 Spring Cloud 命名的子项目,包含我们课程中介绍到的 Spring Cloud Commons、Spring Cloud  Netflix、Spring Cloud Config、Spring Cloud Gateway、Spring Cloud Stream、Spring Cloud Security 等,也包括课程中没有提到但同样非常有用的 Spring Cloud Function、Spring Cloud Bus 和 Spring Cloud Kubernetes 等。随着技术体系的不断发展,相信这个列表会越来越多。而我们课程中所提到的一些组件,也可能会单独剥离出来形成独立的子项目,这方面比较典型的就是用于实现熔断器的 Spring Cloud Circuit Breaker。

在 Spring Cloud 生态系统中,我们也不得不提一下 Spring Cloud Alibaba,这是阿里巴巴基于 Spring Cloud 专门开发的一套完整的微服务开发框架。在这套框架中,使用 Sentinel 实现流量控制和服务降级、使用 Nacos 实现服务注册/发现以及分布式配置中心、使用 RocketMQ 实现了消息通信架构以及基于 Seata 实现了分布式事务功能。同时,基于阿里巴巴现有的 Dubbo 框架,Spring Cloud Alibaba 还扩展了服务与服务之间调用的通信协议。整体而言,Spring Cloud Alibaba 值得你进行深入学习和应用。

另一方面,我们来看一下 Spring Cloud 与 Spring 框架的演进过程。目前 Spring 已经演进到 5.X 版本,随着 Spring 5 的正式发布,我们引来了响应式编程(Reactive Programming)的全新发展时期。Spring 5 中内嵌了与数据管理相关的响应式数据访问、与系统集成相关的响应式消息通信以及与 Web 服务相关的响应式 Web 框架等多种响应式组件,从而极大简化了响应式应用程序的开发过程和难度。下图展示了响应式编程的技术栈与传统的 Servlet 技术栈之间的对比:

Lark20210119-164629.png

响应式编程技术栈与 Servlet 技术栈之间的对比图(来自 Spring 官网)

可以看到,上图左侧为基于 Spring Webflux 的技术栈,右侧为基于 Spring MVC 的技术栈。我们知道传统的 Spring MVC 构建在 Java EE 的 Servlet 标准之上,该标准本身就是阻塞式和同步的。而 Spring WebFlux 基于响应式流,因此可以用来构建异步非阻塞的服务。

WebFlux 等响应式编程技术的兴起为微服务架构的发展提供了一个很好的场景。我们知道在一个微服务系统中,存在数十乃至数百个独立的微服务,它们相互通信以完成复杂的业务流程。这个过程势必涉及大量的 I/O 操作。I/O 操作,尤其是阻塞式 I/O 操作就会整体增加系统的延迟并降低吞吐量。如果能够在复杂的流程中集成非阻塞、异步通信机制,我们就可以高效处理跨服务之间的网络请求。针对这种场景,WebFlux 也是一种非常有效的解决方案。让我们期待响应式编程技术与微服务之间更加紧密的结合。

至此,整个《Spring Cloud 原理与实战》课程就全部介绍完毕了。最后,祝我们在各自的岗位上能够更上一层楼!


微服务架构实战第十节 微服务的模拟组件测试和契约服务测试相关推荐

  1. Go微服务架构实战 中篇:6. 微服务治理策略

    Go微服务架构实战-[公粽号:堆栈future] Go微服务架构实战目录 1. 微服务架构上篇 1. grpc技术介绍 2. grpc+protobuf+网关实战 3. etcd技术介绍 4. 基于e ...

  2. 中国重汽微服务管理_springcloud微服务架构实战:商家管理微服务设计

    商家管理微服务设计 商家管理微服务是一个独立的RESTAPI应用,这个应用通过接口服务对外提供商家信息管理.商家权限管理和菜单资源管理等方面的功能. 商家管理微服务开发在merchant-restap ...

  3. SpringCloud与Docker微服务架构实战pdf

    下载地址:网盘下载 作为一部帮助大家实现微服务架构落地的作品,<Spring Cloud与Docker微服务架构实战>覆盖了微服务理论.微服务开发框架(Spring Cloud)以及运行平 ...

  4. 微服务架构实战第一节 Spring Cloud介绍

    开篇词 为什么你要学习微服务架构? 你好,我是萧然,长期从事分布式系统的构建和优化工作,负责过大型电商以及健康类系统的设计和开发,曾带领团队完成大规模微服务架构建设,在基于 Spring Cloud ...

  5. 云原生微服务架构实战精讲第七节 调度算法与司机乘客行程查询

    第19讲:如何实现行程派发与调度算法 第 18 课时介绍了司机模拟器如何发布位置更新事件,以及行程派发服务如何处理这些事件,并维护所有可用的司机信息,本课时紧接着第 18 课时的内容,主要介绍行程派发 ...

  6. 疯狂Spring Cloud微服务架构实战

    网站 更多书籍点击进入>> CiCi岛 下载 电子版仅供预览及学习交流使用,下载后请24小时内删除,支持正版,喜欢的请购买正版书籍 电子书下载(皮皮云盘-点击"普通下载" ...

  7. SpringCloud微服务架构实战库存管理与分布式文件系统

    库存管理与分布式文件系统 在电商平台的库存管理系统设计中,将涉及商品和本地图库的管理,这里我们将使用另一种数据开发框架 MyBatis进行数据库访问方面的设计,还将实现与分布式文件系统的对接使用. 本 ...

  8. 【福利】赠书:Spring Cloud与Docker微服务架构实战(第2版)

    本次福利送出好友周立的第二版书籍! 正在关注和使用Spring Cloud的朋友们不要错过哦! 内容提要 <Spring Cloud与Docker微服务架构实战(第2版)>基于Spring ...

  9. 《Spring Cloud与Docker微服务架构实战》配套代码

    不才写了本使用Spring Cloud玩转微服务架构的书,书名是<Spring Cloud与Docker微服务架构实战> - 周立,已于2017-01-12交稿.不少朋友想先看看源码,现将 ...

最新文章

  1. Extjs使用备忘录
  2. linux gnome3安装_Windows 10安装与管理WSL体验原生Linux系统
  3. wxWidgets:wxRichTextStyleListCtrl类用法
  4. ConcurrentHashMap和Collections.synchronizedMap(Map)的区别是什么?
  5. 双11特刊|一站式在线数据管理平台DMS技术再升级,高效护航双11
  6. java gc 循环引用_JVM(3)对象A和B循环引用,最后会不会不被GC回收?-------关于Java的GC机制...
  7. Jexus vs IIS8 非绝对客观对比测试
  8. Table Store实时数据通道服务Go SDK快速入门
  9. [渝粤教育] 西南科技大学 人力资源管理 在线考试复习资料
  10. python身份证照片识别信息
  11. 中国女人比美国女人好在哪?
  12. Linu系统——基础知识1
  13. python turtle 海龟绘图详解(官方文档中文版)
  14. java nio oio_NIO,OIO,AIO区别
  15. python怎么算阶乘_Python 计算阶乘的算法
  16. 【h.264】 SPS写入timing相关及openh264实现
  17. 电力电子技术笔记(2)——电力二极管
  18. 小结(由递推公式得到递推矩阵)
  19. 毕业设计 嵌入式 智能手环设计与实现
  20. 值得收藏——一文让你读懂人脸识别技术

热门文章

  1. 共享自己电脑硬盘空间还能赚钱?
  2. 国产WordPress论坛插件-Sliver BBS
  3. RSNA-MICCAI Brain Tumor Radiogenomic Classification
  4. qc成果报告范例_QC小组活动成果报告编制技巧,进步青年都在看!
  5. mac kafka安装
  6. 如何在谷歌地图上绘制矢量道路线并导出为图片
  7. BZOJ4627: [BeiJing2016]回转寿司
  8. python演化博弈仿真_演化博弈应用:例子与思路
  9. frp服务端和客户端的配置和使用(超详细)
  10. java实现qlearning,DQN(Deep Q-learning)入门教程(一)之强化学习介绍