In a microservices-based architecture, services often need to communicate with each other over HTTP. One common and robust way to do this is by generating strongly-typed clients based on shared OpenAPI specifications (also known as Swagger specs). This approach ensures that all services strictly adhere to the same contract, reduces manual boilerplate code, and makes changes more manageable through versioned specs.
This use case demonstrates a practical implementation where the payment-service consumes APIs from account-service using OpenAPI-generated clients.
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(buildRestTemplate())
.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");
// 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);
}
}