In a typical microservices architecture, services frequently need to communicate with each other to perform operations or retrieve data. This use case demonstrates how OpenAPI-generated clients can be used to make internal service-to-service calls in a Spring Boot-based system, ensuring:
Strong typing and IDE support
Reduced boilerplate
Consistent request and response handling
Alignment with the contract defined in OpenAPI specs
This setup uses two services:
account-service: Exposes an endpoint to retrieve account details.
payment-service: Calls the account-service using a client generated from account-api-spec.
Both services are aligned on their contracts using shared OpenAPI specifications and generate client code during build time using the OpenAPI Generator with the spring-webclient library.
Project Structure
Specs
account-api-spec Contains the OpenAPI definition (account.yaml) for retrieving account details.
payment-api-spec Contains payment.yaml defining how to retrieve payment details.
Service
account-service Implements the account API (as per spec) using Spring Boot.
payment-service Depends on both API specs and uses OpenAPI-generated clients to call account-service.
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# Exclude unwanted files and directories
**/src/main/AndroidManifest.xml
**/build.sbt
**/pom.xml
**/gradle/
**/git_push.sh
**/.travis.yml
**/api/openapi.yaml
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# Exclude unwanted files and directories
**/src/main/AndroidManifest.xml
**/build.sbt
**/pom.xml
**/gradle/
**/git_push.sh
**/.travis.yml
**/api/openapi.yaml
package com.company.project.client;
import com.company.project.client.account.v1.ApiClient;
import com.company.project.client.account.v1.api.AccountApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AccountServiceRestClientConfig extends ApiClient {
@Value("${services.account-service.base-path}")
private String basePath;
@Bean
public AccountApi accountApi() {
return new AccountApi(createApiClient());
}
private ApiClient createApiClient() {
return new ApiClient(buildWebClient())
.setBasePath(basePath);
}
}
package com.company.project.controller;
import com.company.project.client.payment.v1.api.PaymentApi;
import com.company.project.client.payment.v1.model.PaymentResponse;
import com.company.project.service.PaymentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RequiredArgsConstructor
@RestController
public class PaymentController implements PaymentApi {
private final PaymentService paymentService;
/**
* GET /api/v1/payments/{id}
*
* @param id (required)
* @return Payment found (status code 200)
*/
@Override
public ResponseEntity<PaymentResponse> getPaymentById(String id) {
log.info("Request to get payment details for id: {}", id);
return ResponseEntity.ok(paymentService.fetchPaymentDetails(id));
}
}
package com.company.project.service;
import com.company.project.client.AccountServiceRestClientConfig;
import com.company.project.client.payment.v1.model.PaymentResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor
@Service
public class PaymentService {
private final AccountServiceRestClientConfig accountServiceRestClientConfig;
public PaymentResponse fetchPaymentDetails(String id) {
var accountDetails = accountServiceRestClientConfig.accountApi()
.getAccountById("DUMMY_ACCOUNT_ID")
.block();
// Return dummy details for the sake of this example
return new PaymentResponse()
.id(id)
.amount(new java.math.BigDecimal("100.0"))
.status("COMPLETED")
.accountId(accountDetails.getId())
.accountName(accountDetails.getName());
}
}
package com.company.project;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class PaymentServiceApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentServiceApplication.class, args);
}
}