google-site-verification: google61fe8ba583a51912.html
Ensuring Microservice Compatibility: Pact JVM for Contract Testing

Contract testing is a critical practice in microservices architecture that ensures services can communicate reliably without requiring full integration testing environments. Pact JVM is a Java implementation of the Pact contract testing framework that enables consumer-driven contracts between services.


What is Contract Testing?

Contract testing verifies the interactions between service consumers and providers at the boundary, ensuring that:

  • Consumers can correctly use provider APIs
  • Providers don't break existing consumer integrations
  • APIs evolve safely without breaking changes

Pact JVM Architecture

[Consumer Tests] → [Pact File] → [Pact Broker] ← [Provider Tests] ← [Provider API] | | | | | Generate pact JSON contract Share & version Verify against Actual service from tests specification contracts real API implementation

Hands-On Tutorial: Complete Contract Testing Setup with Pact JVM

Let's build a complete contract testing solution for a user management system with multiple consumers and providers.

Step 1: Project Structure Setup

pact-contract-testing/ ├── user-service-consumer/ ├── user-service-provider/ ├── notification-service-consumer/ ├── pact-broker/ ├── shared-contracts/ └── build-scripts/

Step 2: Consumer Project Setup

user-service-consumer/pom.xml:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>user-service-consumer</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- Pact JVM --> <pact.version>4.6.8</pact.version> <junit.version>5.10.1</junit.version> <!-- HTTP Client --> <rest-assured.version>5.3.2</rest-assured.version> <jackson.version>2.16.1</jackson.version> </properties> <dependencies> <!-- Pact Consumer JUnit 5 --> <dependency> <groupId>au.com.dius.pact.consumer</groupId> <artifactId>junit5</artifactId> <version>${pact.version}</version> <scope>test</scope> </dependency> <!-- Pact Provider --> <dependency> <groupId>au.com.dius.pact.provider</groupId> <artifactId>junit5</artifactId> <version>${pact.version}</version> <scope>test</scope> </dependency> <!-- JUnit 5 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- HTTP Client for tests --> <dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <version>${rest-assured.version}</version> <scope>test</scope> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.2</version> </plugin> </plugins> </build> </project>

Step 3: Consumer Test Implementation

user-service-consumer/src/test/java/com/example/consumer/UserServiceConsumerTest.java:

package com.example.consumer; import au.com.dius.pact.consumer.MockServer; import au.com.dius.pact.consumer.dsl.PactDslWithProvider; import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; import au.com.dius.pact.consumer.junit5.PactTestFor; import au.com.dius.pact.core.model.RequestResponsePact; import au.com.dius.pact.core.model.annotations.Pact; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import java.util.List; import java.util.Map; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.*; @Slf4j @ExtendWith(PactConsumerTestExt.class) @PactTestFor(providerName = "UserService", port = "8080") public class UserServiceConsumerTest { private final ObjectMapper objectMapper = new ObjectMapper(); // Pact for getting a user by ID @Pact(consumer = "WebApplication") public RequestResponsePact getUserByIdPact(PactDslWithProvider builder) { return builder .given("user with ID 123 exists") .uponReceiving("a request for user with ID 123") .path("/api/users/123") .method("GET") .headers(Map.of( "Accept", "application/json", "X-Request-ID", "test-request-123" )) .willRespondWith() .status(200) .headers(Map.of( "Content-Type", "application/json", "X-Rate-Limit-Remaining", "99" )) .body(new au.com.dius.pact.consumer.dsl.PactDslJsonBody() .stringType("id", "123") .stringType("email", "[email protected]") .stringType("firstName", "John") .stringType("lastName", "Doe") .stringType("status", "ACTIVE") .datetime("createdAt", "yyyy-MM-dd'T'HH:mm:ss'Z'") .minArrayLike("roles", 1) .stringType("ADMIN") .closeArray() ) .toPact(); } // Pact for creating a new user @Pact(consumer = "WebApplication") public RequestResponsePact createUserPact(PactDslWithProvider builder) { return builder .given("no user with email [email protected] exists") .uponReceiving("a request to create a new user") .path("/api/users") .method("POST") .headers(Map.of( "Content-Type", "application/json", "Accept", "application/json", "X-Request-ID", "test-request-456" )) .body(new au.com.dius.pact.consumer.dsl.PactDslJsonBody() .stringType("email", "[email protected]") .stringType("firstName", "Jane") .stringType("lastName", "Smith") .stringType("password", "securePassword123!") ) .willRespondWith() .status(201) .headers(Map.of( "Content-Type", "application/json", "Location", "/api/users/456" )) .body(new au.com.dius.pact.consumer.dsl.PactDslJsonBody() .stringType("id", "456") .stringType("email", "[email protected]") .stringType("firstName", "Jane") .stringType("lastName", "Smith") .stringType("status", "PENDING_VERIFICATION") .datetime("createdAt", "yyyy-MM-dd'T'HH:mm:ss'Z'") ) .toPact(); } // Pact for getting all users with pagination @Pact(consumer = "WebApplication") public RequestResponsePact getUsersWithPaginationPact(PactDslWithProvider builder) { return builder .given("users exist in the system") .uponReceiving("a request for users with pagination") .path("/api/users") .method("GET") .query("page=0&size=10") .headers(Map.of( "Accept", "application/json" )) .willRespondWith() .status(200) .headers(Map.of("Content-Type", "application/json")) .body(new au.com.dius.pact.consumer.dsl.PactDslJsonBody() .numberType("page", 0) .numberType("size", 10) .numberType("totalPages", 5) .numberType("totalElements", 50) .minArrayLike("content", 2) .stringType("id") .stringType("email") .stringType("firstName") .stringType("lastName") .stringType("status") .datetime("createdAt", "yyyy-MM-dd'T'HH:mm:ss'Z'") .closeArray() ) .toPact(); } // Pact for updating a user @Pact(consumer = "WebApplication") public RequestResponsePact updateUserPact(PactDslWithProvider builder) { return builder .given("user with ID 123 exists") .uponReceiving("a request to update user with ID 123") .path("/api/users/123") .method("PUT") .headers(Map.of( "Content-Type", "application/json", "Accept", "application/json" )) .body(new au.com.dius.pact.consumer.dsl.PactDslJsonBody() .stringType("firstName", "Johnny") .stringType("lastName", "Doe Updated") ) .willRespondWith() .status(200) .headers(Map.of("Content-Type", "application/json")) .body(new au.com.dius.pact.consumer.dsl.PactDslJsonBody() .stringType("id", "123") .stringType("email", "[email protected]") .stringType("firstName", "Johnny") .stringType("lastName", "Doe Updated") .stringType("status", "ACTIVE") .datetime("updatedAt", "yyyy-MM-dd'T'HH:mm:ss'Z'") ) .toPact(); } // Pact for user not found @Pact(consumer = "WebApplication") public RequestResponsePact getUserNotFoundPact(PactDslWithProvider builder) { return builder .given("user with ID 999 does not exist") .uponReceiving("a request for non-existent user with ID 999") .path("/api/users/999") .method("GET") .headers(Map.of("Accept", "application/json")) .willRespondWith() .status(404) .headers(Map.of("Content-Type", "application/json")) .body(new au.com.dius.pact.consumer.dsl.PactDslJsonBody() .stringType("error", "USER_NOT_FOUND") .stringType("message", "User with ID 999 not found") .stringType("path", "/api/users/999") .numberType("status", 404) .datetime("timestamp", "yyyy-MM-dd'T'HH:mm:ss'Z'") ) .toPact(); } @Test @PactTestFor(pactMethod = "getUserByIdPact") void testGetUserById(MockServer mockServer) throws JsonProcessingException { log.info("Testing GET user by ID against mock server: {}", mockServer.getUrl()); User user = given() .baseUri(mockServer.getUrl()) .header("Accept", "application/json") .header("X-Request-ID", "test-request-123") .when() .get("/api/users/123") .then() .statusCode(200) .header("Content-Type", "application/json") .header("X-Rate-Limit-Remaining", not(emptyString())) .body("id", equalTo("123")) .body("email", equalTo("[email protected]")) .body("firstName", equalTo("John")) .body("lastName", equalTo("Doe")) .body("status", equalTo("ACTIVE")) .body("createdAt", matchesPattern("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z")) .body("roles", hasSize(greaterThanOrEqualTo(1))) .extract() .as(User.class); assertNotNull(user); assertEquals("123", user.getId()); assertEquals("[email protected]", user.getEmail()); } @Test @PactTestFor(pactMethod = "createUserPact") void testCreateUser(MockServer mockServer) throws JsonProcessingException { log.info("Testing POST create user against mock server: {}", mockServer.getUrl()); CreateUserRequest createRequest = new CreateUserRequest( "[email protected]", "Jane", "Smith", "securePassword123!" ); User createdUser = given() .baseUri(mockServer.getUrl()) .header("Content-Type", "application/json") .header("Accept", "application/json") .header("X-Request-ID", "test-request-456") .body(objectMapper.writeValueAsString(createRequest)) .when() .post("/api/users") .then() .statusCode(201) .header("Content-Type", "application/json") .header("Location", "/api/users/456") .body("id", equalTo("456")) .body("email", equalTo("[email protected]")) .body("firstName", equalTo("Jane")) .body("lastName", equalTo("Smith")) .body("status", equalTo("PENDING_VERIFICATION")) .extract() .as(User.class); assertNotNull(createdUser); assertEquals("456", createdUser.getId()); assertEquals("[email protected]", createdUser.getEmail()); } @Test @PactTestFor(pactMethod = "getUsersWithPaginationPact") void testGetUsersWithPagination(MockServer mockServer) { log.info("Testing GET users with pagination against mock server: {}", mockServer.getUrl()); UserPage userPage = given() .baseUri(mockServer.getUrl()) .header("Accept", "application/json") .queryParam("page", 0) .queryParam("size", 10) .when() .get("/api/users") .then() .statusCode(200) .header("Content-Type", "application/json") .body("page", equalTo(0)) .body("size", equalTo(10)) .body("totalPages", equalTo(5)) .body("totalElements", equalTo(50)) .body("content", hasSize(greaterThanOrEqualTo(2))) .extract() .as(UserPage.class); assertNotNull(userPage); assertEquals(0, userPage.getPage()); assertEquals(10, userPage.getSize()); assertTrue(userPage.getContent().size() >= 2); } @Test @PactTestFor(pactMethod = "updateUserPact") void testUpdateUser(MockServer mockServer) throws JsonProcessingException { log.info("Testing PUT update user against mock server: {}", mockServer.getUrl()); UpdateUserRequest updateRequest = new UpdateUserRequest( "Johnny", "Doe Updated" ); User updatedUser = given() .baseUri(mockServer.getUrl()) .header("Content-Type", "application/json") .header("Accept", "application/json") .body(objectMapper.writeValueAsString(updateRequest)) .when() .put("/api/users/123") .then() .statusCode(200) .header("Content-Type", "application/json") .body("id", equalTo("123")) .body("firstName", equalTo("Johnny")) .body("lastName", equalTo("Doe Updated")) .body("updatedAt", matchesPattern("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z")) .extract() .as(User.class); assertNotNull(updatedUser); assertEquals("Johnny", updatedUser.getFirstName()); assertEquals("Doe Updated", updatedUser.getLastName()); } @Test @PactTestFor(pactMethod = "getUserNotFoundPact") void testGetUserNotFound(MockServer mockServer) { log.info("Testing GET non-existent user against mock server: {}", mockServer.getUrl()); given() .baseUri(mockServer.getUrl()) .header("Accept", "application/json") .when() .get("/api/users/999") .then() .statusCode(404) .header("Content-Type", "application/json") .body("error", equalTo("USER_NOT_FOUND")) .body("message", equalTo("User with ID 999 not found")) .body("status", equalTo(404)); } // Domain classes for testing public static class User { private String id; private String email; private String firstName; private String lastName; private String status; private String createdAt; private String updatedAt; private List<String> roles; // Getters and setters public String getId() { return id; } public void setId(String id) { this.id = id; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getCreatedAt() { return createdAt; } public void setCreatedAt(String createdAt) { this.createdAt = createdAt; } public String getUpdatedAt() { return updatedAt; } public void setUpdatedAt(String updatedAt) { this.updatedAt = updatedAt; } public List<String> getRoles() { return roles; } public void setRoles(List<String> roles) { this.roles = roles; } } public record CreateUserRequest( String email, String firstName, String lastName, String password ) {} public record UpdateUserRequest( String firstName, String lastName ) {} public static class UserPage { private int page; private int size; private int totalPages; private int totalElements; private List<User> content; // Getters and setters public int getPage() { return page; } public void setPage(int page) { this.page = page; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public int getTotalPages() { return totalPages; } public void setTotalPages(int totalPages) { this.totalPages = totalPages; } public int getTotalElements() { return totalElements; } public void setTotalElements(int totalElements) { this.totalElements = totalElements; } public List<User> getContent() { return content; } public void setContent(List<User> content) { this.content = content; } } }

Step 4: Provider Test Implementation

user-service-provider/pom.xml:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>user-service-provider</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- Spring Boot --> <spring-boot.version>3.2.0</spring-boot.version> <!-- Pact --> <pact.version>4.6.8</pact.version> <junit.version>5.10.1</junit.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- Pact Provider --> <dependency> <groupId>au.com.dius.pact.provider</groupId> <artifactId>junit5</artifactId> <version>${pact.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>au.com.dius.pact.provider</groupId> <artifactId>spring</artifactId> <version>${pact.version}</version> <scope>test</scope> </dependency> <!-- Test Dependencies --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

user-service-provider/src/test/java/com/example/provider/UserServiceProviderTest.java:

package com.example.provider; import au.com.dius.pact.provider.junit5.HttpTestTarget; import au.com.dius.pact.provider.junit5.PactVerificationContext; import au.com.dius.pact.provider.junitsupport.Provider; import au.com.dius.pact.provider.junitsupport.loader.PactBroker; import au.com.dius.pact.provider.junitsupport.loader.PactBrokerAuth; import au.com.dius.pact.provider.spring.junit5.PactVerificationSpringProvider; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.test.context.ActiveProfiles; @Slf4j @Provider("UserService") @PactBroker( host = "${PACT_BROKER_HOST:localhost}", port = "${PACT_BROKER_PORT:9292}", scheme = "${PACT_BROKER_SCHEME:http}", authentication = @PactBrokerAuth( username = "${PACT_BROKER_USERNAME:}", password = "${PACT_BROKER_PASSWORD:}" ) ) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") public class UserServiceProviderTest { @LocalServerPort private int port; @BeforeEach void setUp(PactVerificationContext context) { log.info("Setting up Pact verification context on port: {}", port); context.setTarget(new HttpTestTarget("localhost", port)); } @TestTemplate @ExtendWith(PactVerificationSpringProvider.class) void pactVerificationTestTemplate(PactVerificationContext context) { log.info("Starting Pact verification for provider: UserService"); context.verifyInteraction(); } }

user-service-provider/src/test/java/com/example/provider/UserServiceStateHandler.java:

package com.example.provider; import au.com.dius.pact.provider.junitsupport.State; import com.example.provider.model.User; import com.example.provider.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; @Slf4j @Component @RequiredArgsConstructor public class UserServiceStateHandler { private final UserRepository userRepository; @State("user with ID 123 exists") @Transactional public void setupUserWithId123() { log.info("Setting up state: user with ID 123 exists"); // Clear existing data userRepository.deleteAll(); // Create test user User user = User.builder() .id("123") .email("[email protected]") .firstName("John") .lastName("Doe") .status(User.Status.ACTIVE) .roles(List.of("ADMIN", "USER")) .createdAt(LocalDateTime.now()) .updatedAt(LocalDateTime.now()) .build(); userRepository.save(user); log.info("Created test user with ID 123"); } @State("no user with email [email protected] exists") @Transactional public void setupNoUserWithTestEmail() { log.info("Setting up state: no user with email [email protected] exists"); // Delete any existing user with test email userRepository.findByEmail("[email protected]") .ifPresent(user -> userRepository.delete(user)); log.info("Ensured no user exists with email [email protected]"); } @State("users exist in the system") @Transactional public void setupUsersExist() { log.info("Setting up state: users exist in the system"); // Clear and create multiple users userRepository.deleteAll(); List<User> users = List.of( User.builder() .id("1") .email("[email protected]") .firstName("Alice") .lastName("Johnson") .status(User.Status.ACTIVE) .roles(List.of("USER")) .createdAt(LocalDateTime.now()) .updatedAt(LocalDateTime.now()) .build(), User.builder() .id("2") .email("[email protected]") .firstName("Bob") .lastName("Smith") .status(User.Status.ACTIVE) .roles(List.of("USER")) .createdAt(LocalDateTime.now()) .updatedAt(LocalDateTime.now()) .build(), User.builder() .id("3") .email("[email protected]") .firstName("Carol") .lastName("Williams") .status(User.Status.INACTIVE) .roles(List.of("USER")) .createdAt(LocalDateTime.now()) .updatedAt(LocalDateTime.now()) .build() ); userRepository.saveAll(users); log.info("Created {} test users", users.size()); } @State("user with ID 999 does not exist") @Transactional public void setupUser999DoesNotExist() { log.info("Setting up state: user with ID 999 does not exist"); // Ensure user 999 doesn't exist userRepository.findById("999") .ifPresent(user -> userRepository.delete(user)); log.info("Ensured user with ID 999 does not exist"); } }

Step 5: Spring Boot Provider Implementation

user-service-provider/src/main/java/com/example/provider/UserServiceApplication.java:

package com.example.provider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } }

user-service-provider/src/main/java/com/example/provider/controller/UserController.java:

package com.example.provider.controller; import com.example.provider.model.User; import com.example.provider.service.UserService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.net.URI; import java.util.List; @Slf4j @RestController @RequestMapping("/api/users") @RequiredArgsConstructor public class UserController { private final UserService userService; @GetMapping("/{id}") public ResponseEntity<User> getUserById( @PathVariable String id, @RequestHeader(value = "X-Request-ID", required = false) String requestId) { log.info("Get user by ID: {}, Request-ID: {}", id, requestId); return userService.findById(id) .map(user -> ResponseEntity.ok() .header("X-Rate-Limit-Remaining", "99") .body(user)) .orElse(ResponseEntity.notFound().build()); } @PostMapping public ResponseEntity<User> createUser( @Valid @RequestBody CreateUserRequest request, @RequestHeader(value = "X-Request-ID", required = false) String requestId) { log.info("Create user: {}, Request-ID: {}", request.email(), requestId); User user = userService.createUser(request); return ResponseEntity.status(HttpStatus.CREATED) .header("Location", "/api/users/" + user.getId()) .body(user); } @GetMapping public ResponseEntity<UserPageResponse> getUsers( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { log.info("Get users - page: {}, size: {}", page, size); Page<User> userPage = userService.findAll(PageRequest.of(page, size)); UserPageResponse response = UserPageResponse.builder() .page(userPage.getNumber()) .size(userPage.getSize()) .totalPages(userPage.getTotalPages()) .totalElements((int) userPage.getTotalElements()) .content(userPage.getContent()) .build(); return ResponseEntity.ok(response); } @PutMapping("/{id}") public ResponseEntity<User> updateUser( @PathVariable String id, @Valid @RequestBody UpdateUserRequest request) { log.info("Update user: {}", id); return userService.updateUser(id, request) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } // Request/Response DTOs public record CreateUserRequest( String email, String firstName, String lastName, String password ) {} public record UpdateUserRequest( String firstName, String lastName ) {} @lombok.Builder public static class UserPageResponse { private final int page; private final int size; private final int totalPages; private final int totalElements; private final List<User> content; // Getters public int getPage() { return page; } public int getSize() { return size; } public int getTotalPages() { return totalPages; } public int getTotalElements() { return totalElements; } public List<User> getContent() { return content; } } }

Step 6: Pact Broker Configuration

pact-broker/docker-compose.yml:

version: '3.8' services: postgres: image: postgres:15 environment: POSTGRES_USER: pactbroker POSTGRES_PASSWORD: pactbroker POSTGRES_DB: pactbroker ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U pactbroker"] interval: 10s timeout: 5s retries: 5 pact-broker: image: pactfoundation/pact-broker:2.107.0 ports: - "9292:9292" environment: PACT_BROKER_DATABASE_URL: "postgres://pactbroker:pactbroker@postgres:5432/pactbroker" PACT_BROKER_DATABASE_ADAPTER: "postgres" PACT_BROKER_PORT: "9292" PACT_BROKER_LOG_LEVEL: "INFO" PACT_BROKER_BASE_URL: "http://localhost:9292" PACT_BROKER_WEBHOOK_SCHEME_WHITELIST: "http https" PACT_BROKER_ENABLE_GETTING_STARTED_HTML: "true" depends_on: postgres: condition: service_healthy restart: unless-stopped

Step 7: Build and Deployment Scripts

build-scripts/publish-pacts.sh:
```bash

!/bin/bash

set -e

Configuration

PACT_BROKER_HOST="${PACT_BROKER_HOST:-localhost}"
PACT_BROKER_PORT="${PACT_BROKER_PORT:-9292}"
PACT_BROKER_SCHEME="${PACT_BROKER_SCHEME:-http}"
PACT_BROKER_URL="${PACT_BROKER_SCHEME}://${PACT_BROKER_HOST}:${PACT_BROKER_PORT}"

Colors for output

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}

log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}

log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}

check_broker_health() {
log_info "Checking Pact Broker health…"

if curl -f -s "${PACT_BROKER_URL}/diagnostic/status/heartbeat" > /dev/null; then log_info "Pact Broker is healthy" return 0 else log_error "Pact Broker is not accessible at ${PACT_BROKER_URL}" return 1 fi

}

publish_consumer_pacts() {
local consumer_dir="$1"
local consumer_name="$2"
local version="$3"

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper