DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Composite Requests in Salesforce Are a Great Idea
  • Build a REST API With Just 2 Classes in Java and Quarkus
  • Minimizing Latency in Kafka Streaming Applications That Use External API or Database Calls
  • Building a REST Application With Oracle NoSQL Using Helidon

Trending

  • Integration Isn’t a Task — It’s an Architectural Discipline
  • Beyond ChatGPT, AI Reasoning 2.0: Engineering AI Models With Human-Like Reasoning
  • Why High-Performance AI/ML Is Essential in Modern Cybersecurity
  • Top Book Picks for Site Reliability Engineers
  1. DZone
  2. Data Engineering
  3. Databases
  4. A Guide to Enhanced Debugging and Record-Keeping

A Guide to Enhanced Debugging and Record-Keeping

This article demonstrates how to log, store, and retrieve third-party API interactions for improved debugging and communication using Spring Framework.

By 
Amol Gote user avatar
Amol Gote
DZone Core CORE ·
Jul. 12, 23 · Code Snippet
Likes (3)
Comment
Save
Tweet
Share
3.0K Views

Join the DZone community and get the full member experience.

Join For Free

As developers working with third-party REST APIs, it is often necessary to store the request and response details for potential future reference, especially when issues arise. This could serve as invaluable data to liaise with third-party vendors, providing a first-hand look at the raw interaction that occurred. In my role, I am constantly orchestrating API’s with multiple partner services. Therefore, I sought a generic solution to store these third-party API requests and responses seamlessly.

The Spring Framework's RestTemplate, a widely used synchronous HTTP client, proves to be a handy tool for consuming RESTful services. This framework provides an interface called ClientHttpRequestInterceptor, which allows us to take certain actions on the request and response.

This article will guide you through a custom implementation of ClientHttpRequestInterceptor aimed at logging third-party API interactions. Notably, this implementation will store the request and response payloads on S3 and save the file paths to a database. This way, we can easily track and retrieve the raw data for each interaction.

Here is the code for the service, which invokes an API with an external service.

Java
 
@Service
public class ExtSaveEmployeeService {

    private String baseUrl = "http://localhost:8080/api/v1";
    private static final Logger logger = LoggerFactory.getLogger(ExtSaveEmployeeService.class);
    @Autowired
    ExternalApiRestTemplateFactory externalApiRestTemplateFactory;

    public EmployeeResponse addEmployee(EmployeeRequest request, String accessToken) {
        EmployeeResponse result = null;
        try {
            Date dt = new Date();
            RestTemplate restTemplate = this.externalApiRestTemplateFactory.getRestTemplate(accessToken, "ADD_EMPLOYEE", "ADD_EMPLOYEE");
            HttpHeaders headers = this.getHttpHeaders(accessToken);
            HttpEntity<EmployeeRequest> entity = new HttpEntity<>(request, headers);
            long startTime = System.currentTimeMillis();
            String endPoint = this.baseUrl + "/employee";
            ResponseEntity<EmployeeResponse> responseEntity = restTemplate.exchange(endPoint,
                    HttpMethod.POST, entity, EmployeeResponse.class);
            result = responseEntity.getBody();
            long endTime = System.currentTimeMillis();
            long timeDifference = endTime - startTime;
            logger.info("Time taken for API call : " + timeDifference);
        } catch (HttpClientErrorException exception) {
            logger.error("HttpClientErrorException occurred while calling ext API, response string: " + exception.getResponseBodyAsString());
            throw exception;
        } catch (HttpStatusCodeException exception) {
            logger.error("HttpStatusCodeException occurred while calling EXT API, response string: " + exception.getResponseBodyAsString());
            throw exception;
        } catch (Exception ex) {
            logger.error("An Error occurred, Detail error:", ex);
            throw ex;
        }
        return result;
    }

    private HttpHeaders getHttpHeaders(String accessToken){
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        headers.set("Authorization", "Bearer " + accessToken);
        return headers;
    }
}


ExtSaveEmployeeService is spring service class which is responsible for adding an employee by invoking an external API. 

The below code snippet is important from the article's context:

RestTemplate restTemplate = this.externalApiRestTemplateFactory.getRestTemplate(accessToken, "ADD_EMPLOYEE", "ADD_EMPLOYEE");

externalApiRestTemplateFactory is the factory implementation that returns the RestTemplate with a logging interceptor configured. Here is the class definition of the factory:

Java
 
@Component
public class ExternalApiRestTemplateFactory {

    @Autowired
    FilePathRepository filePathRepository;

    @Autowired
    DocumentUploadService documentUploadService;

    public RestTemplate getRestTemplate(String accessToken, String fileNamePrefix, String serviceName) {
        SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
        simpleClientHttpRequestFactory.setOutputStreaming(false);
        ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(simpleClientHttpRequestFactory);
        RestTemplate restTemplate = new RestTemplate(factory);
        ExternalApiLoggingInterceptor loggingInterceptor = new ExternalApiLoggingInterceptor();
        loggingInterceptor.setAccessToken(accessToken);
        loggingInterceptor.setFileNamePrefix(fileNamePrefix);
        loggingInterceptor.setServiceName(serviceName);
        loggingInterceptor.setFilePathRepository(this.filePathRepository);
        loggingInterceptor.setDocumentUploadService(this.documentUploadService);
        restTemplate.setInterceptors(Collections.singletonList(loggingInterceptor));
        return restTemplate;
    }
}


The Factory implementation is responsible for creating RestTemplate instances and configuring the logging interceptor. All the required parameters and dependencies for logging the payloads are passed to the factory.

Here is the implementation of the logging interceptor:

Java
 
public class ExternalApiLoggingInterceptor implements ClientHttpRequestInterceptor {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private String accessToken;
    private String fileNamePrefix;

    private FilePathRepository filePathRepository;

    private DocumentUploadService documentUploadService;

    private String serviceName;

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public String getFileNamePrefix() {
        return fileNamePrefix;
    }

    public void setFileNamePrefix(String fileNamePrefix) {
        this.fileNamePrefix = fileNamePrefix;
    }

    public String getServiceName() {
        return serviceName;
    }

    public void setServiceName(String serviceName) {
        this.serviceName = serviceName;
    }

    public FilePathRepository getFilePathRepository() {
        return filePathRepository;
    }

    public void setFilePathRepository(FilePathRepository filePathRepository) {
        this.filePathRepository = filePathRepository;
    }

    public DocumentUploadService getDocumentUploadService() {
        return documentUploadService;
    }

    public void setDocumentUploadService(DocumentUploadService documentUploadService) {
        this.documentUploadService = documentUploadService;
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        logRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);
        logResponse(response);
        return response;
    }

    private void logRequest(HttpRequest request, byte[] body) throws IOException {
        try {
            String requestBody = new String(body, Charset.defaultCharset());
            this.saveFile(requestBody, "REQUEST");
        } catch (Exception ex) {
            logger.error("An error occurred while saving JSON request ExternalApiLoggingInterceptor::logRequest, detail error:", ex);
        }
    }

    private void logResponse(ClientHttpResponse response) throws IOException {
        try {
            String responseBody = StreamUtils.copyToString(response.getBody(), Charset.defaultCharset());
            this.saveFile(responseBody, "RESPONSE");
        } catch (Exception ex) {
            logger.error("An error occurred while saving JSON response ExternalApiLoggingInterceptor::logResponse, detail error:", ex);
        }
    }

    private void saveFile(String fileContent, String payloadType) {
        try {
            String timeStampSuffix = Long.toString(Instant.now().getEpochSecond());
            String fileName = this.getFileNamePrefix() + "_" + payloadType + "_" + timeStampSuffix + ".json";
            String documentPath = "external-partner/request-response/" + fileName;
            ByteArrayResource resource = this.getDocumentByteArray(fileContent.getBytes(), fileName);
            if (resource != null) {
                //Update the file to Cloud AWS S3 or Azure equivalent.
                DocumentsResponse documentsResponse = this.documentUploadService.updateDocument(resource, documentPath, accessToken);
                if (documentsResponse != null && documentsResponse.getSavedFilePath() != null) {
                    //Save file path in database.
                    boolean result = this.filePathRepository.saveExtPayloadPaths(this.serviceName, documentsResponse.getSavedFilePath());
                    if (!result) {
                        logger.error("An occurred while saving the response file path.");
                    }
                }
            }
        } catch (Exception ex) {
            logger.error("An error occurred while saving req/res for APi call ExternalApiLoggingInterceptor::saveFile, detail error:", ex);
        }
    }

    private ByteArrayResource getDocumentByteArray(byte[] responseContent, String fileName) {
        try {
            final ByteArrayResource byteArrayResource = new ByteArrayResource(responseContent) {
                @Override
                public String getFilename() {
                    return fileName;
                }
            };
            return byteArrayResource;
        } catch (Exception ex) {
            logger.error("Exception - getDocumentByteArray - Error while response body byte array content." + ex.getMessage(), ex);
        }
        return null;
    }
}


The ExternalApiLoggingInterceptor class is an implementation of the ClientHttpRequestInterceptor interface in Spring. The interceptor class intercepts HTTP requests and responses, saves them to a file stream, uploads the file stream to cloud storage, and the cloud storage file path then persists in the database. This can be useful for tracking requests and responses for debugging or record-keeping. 

The code for DocumentUploadService can be found in the GitHub repo. It's primarily storing the files in Amazon S3.

FilePathRepository — this can be database-specific implementation. Depending on what kind of database you are using (e.g., SQL, NoSQL), the specific implementation of the repository might change, but the idea remains the same. 

In conclusion, this article has provided a detailed solution for logging and storing third-party API interactions using the Spring Framework. We've covered the problem scenario necessitating the tracking of requests and responses and the use of Spring's RestTemplate and ClientHttpRequestInterceptor interface. A significant feature of our solution is its capacity to save requests and responses to cloud storage and persist file paths in a database, providing an effective means of record-keeping and access when needed. This solution is particularly valuable when orchestrating multiple partner services, providing an effective tool for debugging and communication. Despite potential scalability limitations, this code can be modified to meet different contexts. As developers, we strive to create dynamic, robust, and scalable solutions like this to meet diverse requirements.

You can find the complete code implementation on this GitHub repository.

API Database Spring Framework Framework REST

Opinions expressed by DZone contributors are their own.

Related

  • Composite Requests in Salesforce Are a Great Idea
  • Build a REST API With Just 2 Classes in Java and Quarkus
  • Minimizing Latency in Kafka Streaming Applications That Use External API or Database Calls
  • Building a REST Application With Oracle NoSQL Using Helidon

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: