¡Paren las rotativas!

Paradigma se suma a

Logo Indra

¡Tenemos novedades! Nos sumamos a la familia Indra para formar parte de un nuevo gigante digital. ¿Nos acompañas en esta nueva aventura?

Nota de prensa completa ×

Implementando test de integración entre Microservicios con Spring Cloud Contract

En artículos anteriores hablamos de cómo el patrón Consumer-driven Contracts nos proporciona un marco de trabajo para el desarrollo de acuerdos de interfaz de forma que nos permite un modelo de trabajo ágil, independiente y mantenible.

También analizamos, a alto nivel, cómo Spring Cloud Contract nos ofrece las herramientas necesarias para implementar dicho marco de trabajo.

En este post iremos un paso más allá y recorreremos todo el proceso: desde la generación del contrato hasta su uso en consumidor y productor, detallando los comandos y el código utilizado para implementar un acuerdo de interfaz con Spring Cloud Contract. ¡Empecemos!

Con la finalidad de mantener la sencillez y hacer el artículo lo más comprensible posible, intentaremos reducir el guión a su mínima expresión manteniendo, eso sí, todas las etapas clave.

Así mismo, asumiremos en este caso un único consumidor con una única interfaz a consumir. Como dijimos en el anterior post, algunas partes de este proceso se pueden producir en paralelo. El proceso constará de las siguientes etapas:

  1. Desarrollo de los tests de integración en el consumidor.
  2. Definición del contrato por parte del consumidor.
  3. Publicación del contrato.
  4. Configuración de los stubs en el consumidor.
  5. Validación del contrato en el productor.

El caso de uso que da lugar a esta comunicación es el siguiente: tenemos un Microservicio account-manager que se encarga de la administración de las cuentas de usuario, como parte de este proceso dicho MS puede crear una cuenta nueva.

El proceso de creación de nueva cuenta lleva asociada la creación de una serie de recursos adicionales como son el cliente, sus credenciales, sus permisos, su configuración…

La administración del cliente está gestionada por el Microservicio customer. Así, durante el proceso de creación de cuenta, será necesario crear también el recurso cliente.

Este será el contrato que utilizaremos como ejemplo, el que permite la creación de un cliente. Por tanto, el MS customer será el productor o servidor y el MS account-manager será el consumidor.

Desarrollo de tests de integración en el lado consumidor

El proceso comienza derivado de una necesidad de negocio que es la administración de cuentas de usuario, será por tanto el MS account-manager el responsable de dicha funcionalidad. Será account-manager, el consumidor a quien primero se trasladará la necesidad de negocio.

Como comentamos, CDC está fuertemente ligado a TDD y como parte de esta filosofía primero implementaremos el test de integración que prueba la nueva funcionalidad. En este caso la funcionalidad será invocada en el endpoint /account a través de la operación POST proporcionando la información de la cuenta a crear.

Como resultado del proceso, se debe retornar un código HTTP 200 y la información de la cuenta creada en el cuerpo del mensaje. A continuación podemos ver el test de integración:

@RunWith(SpringRunner.class)
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
	classes = AccountManagerApplicationTest.class)
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class AccountControllerTest {

	@Autowired
	private MockMvc mockMvc;

	@Test
	public void shouldCreateAccount() throws Exception {

		String body = "{\"name\":\"Manuel\", \"username\":\"manuel32\", \"email\":\"[email protected]\", "
				+ "\"password\":\"m45u23\", \"age\":32}";

		mockMvc.perform(
			post("/account")
			.contentType(APPLICATION_JSON_UTF8_VALUE)
			.content(body))
		.andExpect(MockMvcResultMatchers.status().is2xxSuccessful())
		.andExpect(content().contentType(APPLICATION_JSON_UTF8_VALUE))
			.andExpect(jsonPath("name", is("Manuel")))
			.andExpect(jsonPath("age", is(32)))
			.andExpect(jsonPath("email", is("[email protected]")));

	}
}

Si observamos detenidamente no estamos definiendo comportamiento para ningún mock del MS customer, solo estamos definiendo la petición que se realizará y validando la respuesta.

Por supuesto el test fallará, ya que todavía no hemos desarrollado el código correspondiente a esta funcionalidad. Es necesario remarcar la simpleza del test, ya que él mismo solo valida que ante una entrada se produzca una determinada salida. Un test más completo incluiría validación de las interacciones que durante el proceso se realizan, elementos que se fueran a persistir…

El siguiente paso será, por tanto, implementar el código que cumplirá nuestro test.  En este punto no podemos llegar al funcionamiento del test, ya que cuando realicemos la llamada al MS customer (http://localhost:8080) no tendremos ninguna instancia levantada, ni ningún stub listo que nos responda para que la ejecución del test sea verde.

A continuación podemos ver en la traza de error que el fallo en el test se debe a que no es capaz de localizar una instancia del MS customer:

En este punto es donde requerimos de la nueva funcionalidad del MS customer. Para ello redactaremos un contrato/acuerdo de interfaz que defina cómo realizar la comunicación cuando sea necesario crear un nuevo cliente.

Definición del contrato por parte del consumidor

* Inciso: en este caso nosotros somos los que operamos tanto el MS customer como account-manager, por lo que pasaremos directamente a trabajar sobre customer. Pero en la situación más habitual, donde cada MS fuese operado por un equipo, será necesario que nos abramos una rama sobre el repositorio (ya sea un repositorio exclusivamente de contratos o el repositorio de código del MS customer) donde definiremos el contrato.

Tras acabar la definición, crearemos una pull-request al equipo que administra el MS customer para que revisen el contrato definido. En este punto comenzará un proceso iterativo de comentarios, revisiones y cambios hasta alcanzar un acuerdo que satisfaga a ambas partes (sin que esto sea obstáculo para futuros cambios en el contrato). En el artículo obviaremos este proceso para centrarnos únicamente en lo referente a SCC.

A continuación podemos ver el contrato definido:

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

[
	Contract.make {
		name("should create a customer")
		
		// You can give a description of the user case that the contract represents
		description('''
			given:
				A request to create an user
			when:
				The user is stored in the database
			then:
				The created user is returned
		''')
		
		request {
			method 'POST'
			url '/customer'
			
			body(
				//If we provide only the side of the regular expression, the other one will
				//be generated according to the provided expression
				name : $(c(regex(nonBlank())), p('Antonio')),
				email : $(c(regex(email())), p('[email protected]')),
				age : $(c(regex(number())), p(42))
			)
			headers {
				contentType('application/json')
			}
		}
		
		response {
			status 201
			body (
				//We reference values from the request
				name: $(p(fromRequest().body('name'))),
				email: $(p(fromRequest().body('email'))),
				age: $(p(fromRequest().body('age')))
			)
			headers {
				contentType('application/json')
			}
		}
	}
]

Respecto a los elementos del mismo queremos destacar los siguientes detalles:

  • La estructura que hemos utilizado (array) nos permite la definición de varios contratos en el mismo archivo groovy.
  • Inicialmente podemos ver cómo se da un nombre al contrato, nombre que se utilizará también para el nombrado del test de integración. Así mismo también podemos darle una descripción, lo que nos servirá para detallar un caso de uso en formato BDD.
  • Para la petición detallaremos el path donde estará disponible, el verbo HTTP a utilizar y para la respuesta el código HTTP devuelto. En ambos casos detallaremos las cabeceras necesarias.
  • Tanto en la petición como en la respuesta detallaremos los elementos obligatorios del cuerpo. Como se puede observar podemos utilizar la función value(…) o la notación $(…) para establecer el valor de cada atributo. Aquí es necesario entender la necesidad de la diferenciación entre productor y consumidor.Mientras que en la petición, para el productor, necesitaremos detallar un valor concreto para que se utilice en el test de integración, para el consumidor será necesaria una expresión regular o un tipado para utilizar en el stub, ya que un valor concreto nos forzaría a utilizar dicho valor en el test de integración que nosotros escribamos. De la misma forma, en la respuesta necesitaríamos un valor concreto para el consumidor y un tipado para el productor. Por tanto, para la definición del valor se recomienda distinguir entre consumidor y receptor (no es obligatorio hacer esta distinción).
  • Como podemos observar en la definición de los campos de la petición, podemos indicar que el valor debe cumplir una expresión regular. SCC nos proporciona una serie de expresiones regulares ya definidas que podemos ver aquí.
  • SCC dispone de una funcionalidad por la que si definimos una expresión regular tanto para consumidor en la petición como para productor en la respuesta, y no definimos la otra parte, se generará un valor aleatorio acorde a la expresión regular definidaEs decir, si definimos que en el campo email de la petición el consumidor debe cumplir regex(email()), entonces dicha funcionalidad nos generaría un valor aleatorio para el productor.

    Aquí tengo que mencionar que yo
    no he conseguido hacer funcionar dicha generación, ya que xeger, la librería utilizada para ello, siempre da un StackOverflow, concretamente en la generación del email. Por lo tanto, en el contrato podemos ver cómo están definidos valores concretos para el productor.
  • En el caso del cuerpo de la respuesta vemos el uso de la función fromRequest() que nos permite referenciar elementos de la petición. En este caso nos será de especial utilidad ya que un punto clave de este contrato es que los valores del cliente creado sean los mismos proporcionados en la entrada. Aquí podemos ver en detalle el uso de dicha función para usos más avanzados.
  • El uso de p(…) y c(…) como funciones para la definición de los valores de productor y consumidor es solo una de las posibles notaciones existentes que se ha elegido por su mayor simplicidad. Aquí podemos ver en detalle las diferentes notaciones disponibles

Publicación del contrato

Una vez tenemos el contrato definido, utilizaremos el plugin de spring-cloud-contract que, entre otras cosas, nos permitirá publicar contratos como un artefacto separado. La configuración inicial que utilizaremos (para maven) será la siguiente:

<plugin>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-contract-maven-plugin</artifactId>
       <extensions>true</extensions>
</plugin>

También hemos añadido previamente las dependencias necesarias para utilizar algunas de las funcionalidades que incluimos en el contrato:

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-contract-dependencies</artifactId>
      <version>2.0.0.BUILD-SNAPSHOT</version>
      <type>pom</type>
      <scope>import</scope>
</dependency>

Una vez tenemos el contrato redactado, debemos hacer que esté disponible para que cualquier posible consumidor de nuestro servicio lo descargue y pueda ejecutar stubs basados en dicho contrato.

El plugin que hemos incorporado nos generará un artefacto con los contratos durante la fase de construcción de nuestra aplicación. Para lanzar dicha construcción ejecutaremos el comando: mvn clean install -DskipTests.

En el punto en el que estamos será necesario desactivar la ejecución de los tests (-DskipTests), ya que al incorporar el plugin de SCC se nos generará de forma automática, durante la fase de prueba, un test de integración asociado a la definición del contrato.

Como aún no hemos implementado el código que hace que el test funcione, no podremos publicar los contratos.

Al estar la definición del contrato en el repositorio de código del productor, es este quien debe realizar la construcción para hacer públicos los contratos. Como comentamos previamente existe otra posibilidad de crear un repositorio dedicado exclusivamente a los contratos de forma que podría ser administrado por las diversas partes.

A continuación podemos ver la parte de la ejecución correspondiente al plugin, cómo se identifica la existencia de los contratos y se crea la definición del stub:

Aquí podemos ver cómo, posteriormente, se generarán e instalarán dos artefactos correspondientes a la aplicación misma y a los contratos/stubs (customer-0.1.0-SNAPSHOT-stubs.jar):

Si inspeccionamos dicho artefacto podemos ver que, además del contrato que hemos definido, también se incluye un json con el mapeo para el stub correspondiente a cada uno de los contratos definidos (cada mapeo corresponderá a un contrato y llevará el nombre del mismo, en nuestro caso should create a customer.json’) y que luce de la siguiente manera:

{
  "id" : "6da61e7a-3ec8-4302-bbae-d7fabb8105af",
  "request" : {
    "url" : "/customer",
    "method" : "POST",
    "headers" : {
      "Content-Type" : {
        "matches" : "application/json.*"
      }
    },
    "bodyPatterns" : [ {
      "matchesJsonPath" : "$[?(@.['age'] =~ /-?(\\d*\\.\\d+|\\d+)/)]"
    }, {
      "matchesJsonPath" : "$[?(@.['email'] =~ /[a-zA-Z0-9._%+-][email protected][a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}/)]"
    }, {
      "matchesJsonPath" : "$[?(@.['name'] =~ /^\\s*\\S[\\S\\s]*/)]"
    } ]
  },
  "response" : {
    "status" : 201,
    "body" : "{\"name\":\"{{{jsonpath this 'name'}}}\",\"email\":\"{{{jsonpath this 'email'}}}\",\"age\":\"{{{jsonpath this 'age'}}}\"}",
    "headers" : {
      "Content-Type" : "application/json"
    },
    "transformers" : [ "response-template" ]
  },
  "uuid" : "6da61e7a-3ec8-4302-bbae-d7fabb8105af"
}

Una vez se ha publicado el contrato definido de forma colaborativa entre productor y consumidor (en caso de ser equipos diferentes) con sus stubs asociados, ambas partes podrán trabajar de forma independiente por lo que los siguientes puntos se podrían producir en paralelo.

Configuración de los stubs en el consumidor

Una vez se ha generado el artefacto con los contratos y el mapeo de los stubs, ya tenemos todo lo necesario para completar nuestros tests en el lado consumidor.

Para ello primeramente añadiremos la dependencia necesaria para disponer de un ejecutor de stubs. Será necesario también añadir la dependencia previamente definida en el servidor:  spring-cloud-contract-dependencies.

Aquí podemos ver la dependencia de nuestro ejecutor de stubs:

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

Con esto ya podemos volver a los tests que habíamos programado inicialmente en el consumidor. Si recordamos, nuestro test fallaba a la espera de una instancia de customer a la que invocar, ahora, gracias al contrato definido, podremos levantar un stub que reproduzca dicho comportamiento.

Para ello solo debemos añadir la siguiente anotación en nuestra clase de test:

@AutoConfigureStubRunner

Para configurar el comportamiento de nuestros stubs añadiremos también la siguiente configuración en nuestro application-test.yml

stubrunner:
  workOffline: true
  ids:
    - com.example.contract:customer:+:stubs:8080

Con esto indicamos el paquete y artefacto que deseamos descargar y como classifier indicamos que deseamos los stubs. El ‘+’ indica que descargue la última versión existente de dicho artefacto. El parámetro workOffline indica que se utilice el repositorio local para la búsqueda de dicho artefacto (más adelante explicamos la configuración necesaria para poder descargar de un repositorio remoto).

Ahora nuestro test sí pasará. Si observamos las trazas de su ejecución podremos ver algunos detalles interesantes.

En la siguiente captura se puede ver cómo resuelve, descarga y arranca el stub:

Podemos ver también cómo el stub registra el mapeo asociado a nuestro contrato:

Finalmente aquí podemos ver cómo el stub recibe la petición que indicamos en nuestro test y le asigna una respuesta en base a la definición de nuestro contrato:

Como comentamos previamente, si quisiéramos que los stubs se puedan descargar de forma remota, como la mayoría de las veces deberá ser, deberemos ajustar nuestra configuración utilizando la propiedad repositoryRoot, indicando el repositorio maven de donde queremos descargar la definición de los stubs.

Obviamente también deberemos eliminar el workOffline = true. La configuración resultante se muestra a continuación:

stubrunner:
  repositoryRoot: http://nexus.paradigmadigital.com/content/repositories/snapshots
  ids:
    - com.example.contract:customer:+:stubs:8080

En la siguiente captura podemos ver en las trazas cómo el contrato es descargado de un repositorio remoto:

Con esto el consumidor ya dispone de un contrato que le garantiza cómo será la interacción con el productor, así como de un stub que reproduzca su comportamiento, pudiendo así completar los tests de integración necesarios.

Hasta aquí hemos visto cómo sin una sola línea de código en el servidor, solo con la definición de un contrato, podemos avanzar el desarrollo de la aplicación cliente e incluso de forma automática reproducir el futuro comportamiento del servidor sin que este esté siquiera implementado. Con esto terminamos la parte que afecta al consumidor.

Validación del contrato en el productor

Ahora debemos volver a la parte productor, donde el mismo contrato que permite generar el stub en el cliente nos permitirá generar el test en el servidor para comprobar que estamos cumpliendo dicho contrato.

Para ello lo primero que debemos hacer, al igual que añadimos la dependencia del stub runner en el consumidor, es añadir la dependencia del contract verifier en el productor:

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

El test que se nos genere incluirá los datos y las validaciones basadas en el contrato que hemos definido, pero de todas formas sigue siendo necesario inicializar nuestra configuración de tests (perfiles spring, mockMvc, puerto de ejecución…) y esa es una labor que el plugin no puede hacer. Para ello nos permite indicar esta configuración de dos formas:

  • Indicando cuál es el directorio donde se encontrarán las clases base a través de basePackageForTests.
  • Indicar directamente cuál es la clase base con baseClassForTests.

Aquí podéis consultar en detalle las configuraciones que ofrece el plugin. En nuestro caso utilizaremos la segunda opción y añadiremos la siguiente configuración al plugin:

<configuration>					        
<baseClassForTests>com.example.contract.configuration.CustomerBaseIntegrationTest</baseClassForTests>
</configuration>

Estas dos posibilidades dependen de si queremos definir una clase base específica para cada contrato o que tengamos una común para todos los contratos.

En nuestro caso centralizaremos la configuración común a todos los test de todos los contratos en la clase BaseIntegrationTest de la siguiente forma:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, 
	classes = CustomerApplicationTest.class)
@AutoConfigureMockMvc
@ActiveProfiles("test")
public abstract class CustomerBaseIntegrationTest {
	
	@Autowired
	private MockMvc mockMvc;
	
	@Before
	public void setup() {
		RestAssuredMockMvc.mockMvc(mockMvc);
	}
}

Una vez tenemos nuestra clase base lista ejecutaremos: mvn clean package.

Esta vez sin saltar la ejecución de los tests, para que, ante la presencia de nuestro contrato, se generen de forma automática los tests asociados al mismo. En las siguientes trazas podemos ver dónde se generan los tests y cómo se utiliza nuestra clase base:

La presencia de estos nuevos tests lógicamente provocará que fallen (ya que aún no hemos implementado ninguna funcionalidad) y que por tanto falle la construcción.

Ahora vamos a la carpeta donde se generó el código de dicho test (target/generated-test-sources/contracts/paradigma/microservices) para ver cómo luce el mismo:

import com.example.contract.configuration.CustomerBaseIntegrationTest;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import io.restassured.module.mockmvc.specification.MockMvcRequestSpecification;
import io.restassured.response.ResponseOptions;
import org.junit.Test;

import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson;
import static io.restassured.module.mockmvc.RestAssuredMockMvc.*;
import static org.springframework.cloud.contract.verifier.assertion.SpringCloudContractAssertions.assertThat;


public class CustomerTest extends CustomerBaseIntegrationTest {

	@Test
	public void validate_should_create_a_customer() throws Exception {
		// given:
			MockMvcRequestSpecification request = given()
					.header("Content-Type", "application/json")
					.body("{\"name\":\"Antonio\",\"email\":\"[email protected]\",\"age\":42}");

		// when:
			ResponseOptions response = given().spec(request)
					.post("/customer");

		// then:
			assertThat(response.statusCode()).isEqualTo(201);
			assertThat(response.header("Content-Type")).matches("application/json.*");
		// and:
			DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
			assertThatJson(parsedJson).field("['name']").isEqualTo("Antonio");
			assertThatJson(parsedJson).field("['age']").isEqualTo(42);
			assertThatJson(parsedJson).field("['email']").isEqualTo("[email protected]");
	}

}

Algunos puntos importantes a resaltar son los siguientes:

  • Los tests asociados a los contratos se generarán cada vez que ejecutemos un comando maven que lance la fase test, por lo que no es necesario mantenerlos como parte del código de la aplicación.
  • La forma en la que se utiliza la clase base para los tests es extendiéndola.
  • El método del test se llamará de la misma forma que hemos nombrado nuestro contrato.
  • En la sección ‘given’ se creará la petición que hemos definido con los datos de ejemplo incluidos en el contrato para el productor.
  • En las secciones ‘then’ y ‘and’ se validarán los criterios que hemos incluido en la sección ‘response’ del contrato.

Si ahora ejecutamos el test, veremos que aún sigue fallando. En este caso nos indica que la petición espera como resultado un código 201 y sin embargo recibe un 404:

Pero esto entra dentro del comportamiento esperado, ya que todavía no hemos implementado el comportamiento asociado al test (recordemos que estamos siguiendo la filosofía TDD y, por tanto, el test se desarrolla primero).

En este punto, hemos generado de forma automática un test que valida la funcionalidad en base a la definición que hemos hecho en el contrato. Ahora solo faltará desarrollar la funcionalidad que pase de rojo a verde este test.

Podéis encontrar dicha funcionalidad así como todo el código y configuración utilizado para el artículo en este repositorio en Github.

Otras consideraciones

En este caso el proceso llevado a cabo se ha caracterizado por su sencillez ya que ambos Microservicios eran gestionados por el mismo equipo. Hemos tratado de obviar algunas de las complejidades asociadas para centrarnos en el uso de Spring Cloud Contract.

Pero debemos tener en cuenta que este proceso se producirá de forma habitual siendo productor y consumidor equipos diferentes. Algunas de las consideraciones que deberemos tener en cuenta en el mundo real son las siguientes:

  • Existirán diversos consumidores de la misma interfaz y cada uno de ellos puede definir uno o varios contratos enfocados en diversas situaciones que se pueden producir en su interacción con el productor. La implementación del productor deberá cumplir todos y cada uno de ellos. Lo que nos aporta esta aproximación es que al conocer que consumidor nos ha definido que contrato, en caso de necesitar desarrollar una nueva funcionalidad y romper algún contrato, sabremos con quién tendremos que negociar los cambios en el mismo.
  • En muchas situaciones los equipos que gestionen al productor y el consumidor no serán los mismos teniéndose que producir entonces una negociación entre ambas partes para la definición del contrato. Este será un proceso iterativo en el que se irán revisando definiciones y añadiendo otras nuevas durante todo el ciclo de vida de la aplicación. En este caso el mecanismo de pull-request nos proporciona los medios necesarios para gestionar este proceso. Según su definición en GitHub:

“Pull requests let you tell others about changes you’ve pushed to a repository on GitHub. Once a pull request is opened, you can discuss and review the potential changes with collaborators and add follow-up commits before the changes are merged into the repository.”

  • Conforme a lo mencionado en los dos anteriores puntos, otra posible alternativa para la gestión de los contratos es utilizar un repositorio separado destinado a tal fin. De forma que este almacenará todos los contratos correspondientes a un proyecto o incluso a la compañía. El mecanismo de interacción puede seguir siendo las anteriormente mencionadas pull-request.
  • Aunque en nuestro ejemplo hemos utilizado el repositorio local para el almacenamiento de los stubs, en los entornos corporativos será necesario el uso de un repositorio remoto. Ya no solo por la disponibilidad de los stubs, si no por el uso de la notación ‘+’ para indicar la versión a descargar. De forma que si un nuevo stub es desplegado en el repositorio remoto, nuestra aplicación lo descargará y ejecutará sin que haya sido necesario que se nos notificara la existencia de la nueva versión.
  • En nuestro caso hemos definido un contrato que representa una situación concreta que es la creación exitosa de un cliente, pero se pueden definir tantos contratos como situaciones se pueden dar sobre la misma interfaz. Por ejemplo, para la misma interfaz podríamos definir un contrato indicando el comportamiento cuando se tratase de crear un cliente que ya existe en nuestro sistema.
  • En la mayoría de situaciones nuestro microservicio consumidor será también un productor al mismo tiempo. Es el caso de ‘account-manager’ donde además de consumir funcionalidad del MS ‘customer’, expone a su vez la interfaz de creación de cuentas. En este caso, para no complicar más el proceso, hemos definido un test de integración para dicha interfaz al comienzo del artículo, pero nos podríamos haber ahorrado este test definiendo un contrato como el siguiente:
import org.springframework.cloud.contract.spec.Contract

[
	Contract.make {
		name("should create an account")

		description('''
			given:
				A new user that does not have an account
			when:
				The user requests the account creation
			then:
				The created account is returned
		''')

		request {
			method 'POST'
			url '/account'
			body(
					name : $(c(regex(nonBlank())), p('Jesus')),
					email : $(c(regex(email())), p('[email protected]')),
					age : $(c(regex(number())), p(36)),
					username: $(c(regex(nonBlank())), p('jesus36')),
					password: $(c(regex(nonBlank())), p('password'))
			)
			headers {
				contentType(applicationJson())
			}
		}
		response {
			status 201
			body (
					name: $(p(fromRequest().body('name'))),
					email: $(p(fromRequest().body('email'))),
					age: $(p(fromRequest().body('age'))),
					username: $(p(fromRequest().body('username')))
			)
			headers {
				contentType(applicationJson())
			}
		}
	}
]

Conclusión

En un mundo en el que cada vez más la tendencia es que cada servicio que se expone se haga en forma de API y con la extensión de arquitecturas distribuidas como la de microservicios, se hace indispensable que la gestión de las APIs sea sencilla y ágil.

A su vez, con la extensión de filosofías como Continuous Delivery o Continuous Deployment, cada vez es más necesario que el proceso de pruebas sea automático y mucho más riguroso.

En este sentido, la intercomunicación de las piezas que componen nuestra aplicación suele ser un punto que muchas veces se obvia y que puede dar lugar a algunos de los problemas más graves, como es la denegación de servicio.

En este post hemos visto cómo Spring Cloud Contract nos proporciona las herramientas y la metodología para poder gestionar de forma ágil la definición de acuerdos de interfaz, sus cambios, así como que estos sean notificados a todas las partes implicadas permitiendo además que estas puedan llevar a cabo la práctica totalidad del proceso de forma independiente.

Fuentes

Abraham Rodríguez actualmente desarrolla funciones de ingeniero backend J2EE en Paradigma donde ya ha realizado diversos proyectos enfocados a arquitecturas de microservicios. Especializado en sistemas Cloud, ha trabajado con AWS y Openshift y es Certified Google Cloud Platform Developer. Cuenta con experiencia en diversos sectores como banca, telefonía, puntocom... Y es un gran defensor de las metodologías ágiles y el software libre."

Ver toda la actividad de José Abraham Rodríguez López