Рубрики
Без рубрики

Весеннее облако: Контракт

Автор оригинала: Dhananjay Singh.

Обзор

В этой статье мы познакомим вас с Контрактом Spring Cloud , который является ответом Spring на Контракты , ориентированные на потребителей .

В настоящее время приложения тщательно тестируются – будь то модульные тесты, интеграционные тесты или сквозные тесты. В архитектуре микросервисов очень часто служба ( потребитель ) взаимодействует с другой службой ( производителем ) для выполнения запроса.

Чтобы проверить их, у нас есть два варианта:

  • Разверните все микросервисы и выполните сквозные тесты, используя библиотеку, такую как Selenium
  • Пишите интеграционные тесты, издеваясь над вызовами других служб

Если мы воспользуемся первым подходом, мы будем имитировать производственную среду. Это потребует дополнительной инфраструктуры, и обратная связь будет запоздалой, так как для ее запуска потребуется много времени.

Если мы воспользуемся последним подходом, у нас будет более быстрая обратная связь, но поскольку мы высмеиваем ответы на внешние вызовы, насмешки не будут отражать изменения в производителе, если таковые имеются.

Например, предположим, что мы имитируем вызов внешней службы, которая возвращает JSON с ключом, скажем, имя . Наши тесты проходят, и все работает нормально. Со временем другая служба изменила ключ на имя .

Наши интеграционные тестовые примеры по-прежнему будут работать просто отлично. Проблема, скорее всего, будет замечена в промежуточной или производственной среде, а не в сложных тестовых случаях.

Spring Cloud Contract предоставляет нам верификатор Spring Cloud Contract/| именно для этих случаев. Он создает заглушку из службы производителя, которую служба потребителей может использовать для подделки вызовов.

Поскольку заглушка имеет версию в соответствии со службой производителя, служба потребителей может выбрать, какую версию выбрать для тестирования. Это обеспечивает более быструю обратную связь и гарантирует, что наши тесты действительно отражают код.

Установка

Чтобы продемонстрировать концепцию контрактов, у нас есть следующие базовые услуги:

  • spring-cloud-контракт-производитель : простая служба REST , имеющая единственную конечную точку /сотрудник/{идентификатор} , которая выдает ответ JSON.
  • spring-cloud-контракт-потребитель : простой клиент-потребитель, который вызывает /сотрудника/{идентификатор} конечную точку spring-cloud-контракт-производителя для завершения ответа.

Чтобы сосредоточиться на этой теме, мы будем использовать только эти службы , а не другие службы , такие как Eureka, Gateway и т.д., Которые обычно включены в архитектуру микросервиса.

Детали настройки производителя

Давайте начнем с простого класса POJO – Сотрудник :

public class Employee {

    public Integer id;

    public String fname;

    public String lname;

    public Double salary;

    public String gender;

    // Getters and setters

Затем у нас есть EmployeeController с одним GET отображением:

@RestController
public class EmployeeController {

    @Autowired
    EmployeeService employeeService;

    @GetMapping(value = "employee/{id}")
    public ResponseEntity getEmployee(@PathVariable("id") int id) {
        Optional employee = employeeService.findById(id);
        if (employee.isPresent()) {
            return ResponseEntity.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(employee.get());
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
        }
    }
}

Это простой контроллер, который возвращает Employee JSON со всеми атрибутами класса в качестве ключей JSON на основе id .

Служба сотрудников может быть чем угодно , что находит сотрудника по идентификатору , в нашем случае это простая реализация JpaRepository :

public interface EmployeeService extends JpaRepository {}

Детали настройки потребителя

Со стороны потребителя давайте определим еще одного POJO – Человека :

class Person {

    private int id;

    public String fname;

    public String lname;

    // Getters and setters

Обратите внимание, что имя класса не имеет значения, если имена атрибутов одинаковы – id , fname и lname .

Теперь предположим, что у нас есть компонент, который вызывает /сотрудника/{id} конечную точку spring-cloud-контрактного производителя :

@Component
class ConsumerClient {

    public Person getPerson(final int id) {
        final RestTemplate restTemplate = new RestTemplate();

        final ResponseEntity result = restTemplate.exchange("http://localhost:8081/employee/" + id,
                HttpMethod.GET, null, Person.class);

        return result.getBody();
    }
}

Поскольку Человек класс из spring-cloud-контракт-потребитель имеет те же имена атрибутов, что и Сотрудник класс из spring-cloud-контракт-производитель – Spring автоматически сопоставит соответствующие поля и предоставит нам результат.

Тестирование потребителя

Теперь, если бы мы хотели протестировать потребительский сервис, мы бы провели пробный тест:

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

@SpringBootTest(classes = SpringCloudContractConsumerApplication.class)
@RunWith(SpringRunner.class)
@AutoConfigureWireMock(port = 8081)
@AutoConfigureJson
public class ConsumerTestUnit {

    @Autowired
    ConsumerClient consumerClient;

    @Autowired
    ObjectMapper objectMapper;

    @Test
    public void clientShouldRetrunPersonForGivenID() throws Exception {
        WireMock.stubFor(WireMock.get(WireMock.urlEqualTo("/employee/1")).willReturn(
                WireMock.aResponse()
                        .withStatus(200)
                        .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
                        .withBody(jsonForPerson(new Person(1, "Jane", "Doe")))));
        BDDAssertions.then(this.consumerClient.getPerson(1).getFname()).isEqualTo("Jane");
    }

    private String jsonForPerson(final Person person) throws Exception {
        return objectMapper.writeValueAsString(person);
    }
}

Здесь мы высмеиваем результат конечной точки /employee/1 , чтобы вернуть жестко закодированный ответ JSON, а затем продолжаем наше утверждение.

Итак, что произойдет, если мы что-то изменим в производителе?

Код, который проверяет потребителя, не будет отражать это изменение.

Реализация Весеннего Облачного Контракта

Чтобы убедиться, что эти услуги находятся “на одной странице”, когда дело доходит до изменений, мы предоставляем им обоим контракт, как и людям.

Когда служба производителя изменяется, для службы потребителей создается заглушка /| квитанция , чтобы сообщить ей, что происходит.

Контракт на обслуживание Производителя

Чтобы реализовать это, сначала давайте добавим зависимость//spring-cloud-starter-contract-verifier в нашего производителя pom.xml :


    org.springframework.cloud
    spring-cloud-starter-contract-verifier
    test

Теперь нам нужно определить контракт , на основе которого контракт Spring Cloud будет выполнять тесты и создавать заглушку . Это делается с помощью spring-cloud-starter-contract-верификатора , который поставляется с Языком определения контракта (DSL), написанным на Groovy или YAML.

Давайте создадим контракт, используя Groovy в новом файле – должен вернуть сотрудника, когда идентификатор сотрудника найден.groovy :

import org.springframework.cloud.contract.spec.Contract

Contract.make {
  description("When a GET request with an Employee id=1 is made, the Employee object is returned")
  request {
    method 'GET'
    url '/employee/1'
  }
 response {
    status 200
body("""
  {
    "id": "1",
    "fname": "Jane",
    "lname": "Doe",
    "salary": "123000.00",
    "gender": "M"
  }
  """)
    headers {
      contentType(applicationJson())
    }
  }
}

Это довольно простой контракт, который определяет пару вещей. Если есть GET запрос на URL /сотрудник/1 , верните ответ со статусом 200 и тело JSON с 5 атрибутами.

Когда приложение будет построено, на этапе тестирования, с помощью контракта Spring Cloud будут созданы автоматические тестовые классы, которые будут считываться с этого заводного файла.

Однако, чтобы сделать возможным автоматическое создание тестовых классов, нам нужно создать базовый класс, который они могут расширить. Чтобы зарегистрировать его в качестве базового класса для тестов, мы добавляем его в ваш pom.xml файл:


    org.springframework.cloud
    spring-cloud-contract-maven-plugin
    true
    
        
            com.mynotes.springcloud.contract.producer.BaseClass
        
    

Наш Базовый класс выглядит примерно так:

@SpringBootTest(classes = SpringCloudContractProducerApplication.class)
@RunWith(SpringRunner.class)
public class BaseClass {

    @Autowired
    EmployeeController employeeController;

    @MockBean
    private EmployeeService employeeService;

    @Before
    public void before() {
        final Employee employee = new Employee(1, "Jane", "Doe", 123000.00, "M");
        Mockito.when(this.employeeService.findById(1)).thenReturn(Optional.of(employee));
        RestAssuredMockMvc.standaloneSetup(this.EmployeeController);
    }
}

А теперь давайте создадим наше приложение:

$ mvn clean install

Наша папка target , помимо обычных сборок, теперь также содержит заглушки банку:

Поскольку мы выполнили установку , он также доступен в нашей локальной папке .m2 . Этот заглушка теперь может использоваться нашим потребителем spring-cloud-контракта//для издевательств над вызовами.

Договор на Бытовое Обслуживание

Как и на стороне производителя, нам также необходимо добавить определенный вид контракта к нашему потребительскому обслуживанию. Здесь нам нужно добавить spring-cloud-starter-contract-stub-runner зависимость от вашего pom.xml :


    org.springframework.cloud
    spring-cloud-starter-contract-stub-runner
    test

Теперь, вместо того, чтобы издеваться над нашими местными издевательствами, мы можем скачать заглушки от производителя:

@SpringBootTest(classes = SpringCloudContractConsumerApplication.class)
@RunWith(SpringRunner.class)
public class ConsumerTestContract {

    @Rule
    public StubRunnerRule stubRunnerRule = new StubRunnerRule()
        .downloadStub("com.mynotes.spring-cloud", "spring-cloud-contract-producer", "0.0.1-SNAPSHOT", "stubs")
        .withPort(8081)
        .stubsMode(StubRunnerProperties.StubsMode.LOCAL);

    @Autowired
    ConsumerClient consumerClient;

    @Test
    public void clientShouldRetrunPersonForGivenID_checkFirsttName() throws Exception {
        BDDAssertions.then(this.consumerClient.getPerson(1).getFname()).isEqualTo("Jane");
    }

    @Test
    public void clientShouldRetrunPersonForGivenID_checkLastName() throws Exception {
        BDDAssertions.then(this.consumerClient.getPerson(1).getLname()).isEqualTo("Doe");
    }
}

Как вы можете видеть, мы использовали заглушку, созданную spring-cloud-контрактным производителем . .stubsMode() должен указать Spring, где должна выглядеть зависимость от заглушки. ЛОКАЛЬНЫЙ означает в локальной папке .m2 . Другими вариантами являются УДАЛЕННЫЙ и ПУТЬ к КЛАССУ .

Класс Потребительский тестовый контракт сначала запустит заглушку, и из-за ее поставщика производителем мы не зависим от насмешек над внешним вызовом. Если предположить, что производитель действительно изменил контракт, можно быстро выяснить, с какой версии было внесено критическое изменение, и можно предпринять соответствующие шаги.

Вывод

Мы рассмотрели, как использовать Spring Cloud Contract, чтобы помочь нам поддерживать контракт между производителем и службой поддержки потребителей. Это достигается путем первого создания заглушки со стороны производителя с использованием заводного DSL. Этот сгенерированный заглушка может быть использован в службе поддержки потребителей для имитирования внешних вызовов.

Как всегда, код для примеров, используемых в этой статье, можно найти на GitHub .